# GeoShape Precision
The `geo_shape` precision could be only set via `tree_levels` so far. A new option `precision` now allows to set the levels of the underlying tree structures to be set by distances like `50m`. The def ## Example ```json curl -XPUT 'http://127.0.0.1:9200/myindex/' -d '{ "mappings" : { "type1": { "dynamic": "false", "properties": { "location" : { "type" : "geo_shape", "geohash" : "true", "store" : "yes", "precision":"50m" } } } } }' ``` ## Changes - GeoUtils defines the [WGS84](http://en.wikipedia.org/wiki/WGS84) reference ellipsoid of earth - DistanceUnits refer to a more precise definition of earth circumference - DistanceUnits for inch, yard and meter have been defined - Set default levels in GeoShapeFieldMapper to 50m precision Closes #2803
This commit is contained in:
parent
4705eb2959
commit
f08d458545
|
@ -19,10 +19,157 @@
|
|||
|
||||
package org.elasticsearch.common.geo;
|
||||
|
||||
import org.apache.lucene.spatial.prefix.tree.GeohashPrefixTree;
|
||||
import org.apache.lucene.spatial.prefix.tree.QuadPrefixTree;
|
||||
import org.elasticsearch.common.unit.DistanceUnit;
|
||||
|
||||
/**
|
||||
*/
|
||||
public class GeoUtils {
|
||||
|
||||
/** Earth ellipsoid major axis defined by WGS 84 in meters */
|
||||
public static final double EARTH_SEMI_MAJOR_AXIS = 6378137.0; // meters (WGS 84)
|
||||
|
||||
/** Earth ellipsoid minor axis defined by WGS 84 in meters */
|
||||
public static final double EARTH_SEMI_MINOR_AXIS = 6356752.314245; // meters (WGS 84)
|
||||
|
||||
/** Earth ellipsoid equator length in meters */
|
||||
public static final double EARTH_EQUATOR = 2*Math.PI * EARTH_SEMI_MAJOR_AXIS;
|
||||
|
||||
/** Earth ellipsoid polar distance in meters */
|
||||
public static final double EARTH_POLAR_DISTANCE = Math.PI * EARTH_SEMI_MINOR_AXIS;
|
||||
|
||||
/**
|
||||
* Calculate the width (in meters) of geohash cells at a specific level
|
||||
* @param level geohash level must be greater or equal to zero
|
||||
* @return the width of cells at level in meters
|
||||
*/
|
||||
public static double geoHashCellWidth(int level) {
|
||||
assert level>=0;
|
||||
// Geohash cells are split into 32 cells at each level. the grid
|
||||
// alternates at each level between a 8x4 and a 4x8 grid
|
||||
return EARTH_EQUATOR / (1L<<((((level+1)/2)*3) + ((level/2)*2)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the width (in meters) of quadtree cells at a specific level
|
||||
* @param level quadtree level must be greater or equal to zero
|
||||
* @return the width of cells at level in meters
|
||||
*/
|
||||
public static double quadTreeCellWidth(int level) {
|
||||
assert level >=0;
|
||||
return EARTH_EQUATOR / (1L<<level);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the height (in meters) of geohash cells at a specific level
|
||||
* @param level geohash level must be greater or equal to zero
|
||||
* @return the height of cells at level in meters
|
||||
*/
|
||||
public static double geoHashCellHeight(int level) {
|
||||
assert level>=0;
|
||||
// Geohash cells are split into 32 cells at each level. the grid
|
||||
// alternates at each level between a 8x4 and a 4x8 grid
|
||||
return EARTH_POLAR_DISTANCE / (1L<<((((level+1)/2)*2) + ((level/2)*3)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the height (in meters) of quadtree cells at a specific level
|
||||
* @param level quadtree level must be greater or equal to zero
|
||||
* @return the height of cells at level in meters
|
||||
*/
|
||||
public static double quadTreeCellHeight(int level) {
|
||||
assert level>=0;
|
||||
return EARTH_POLAR_DISTANCE / (1L<<level);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the size (in meters) of geohash cells at a specific level
|
||||
* @param level geohash level must be greater or equal to zero
|
||||
* @return the size of cells at level in meters
|
||||
*/
|
||||
public static double geoHashCellSize(int level) {
|
||||
assert level>=0;
|
||||
final double w = geoHashCellWidth(level);
|
||||
final double h = geoHashCellHeight(level);
|
||||
return Math.sqrt(w*w + h*h);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the size (in meters) of quadtree cells at a specific level
|
||||
* @param level quadtree level must be greater or equal to zero
|
||||
* @return the size of cells at level in meters
|
||||
*/
|
||||
public static double quadTreeCellSize(int level) {
|
||||
assert level>=0;
|
||||
return Math.sqrt(EARTH_POLAR_DISTANCE*EARTH_POLAR_DISTANCE + EARTH_EQUATOR*EARTH_EQUATOR) / (1L<<level);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the number of levels needed for a specific precision. Quadtree
|
||||
* cells will not exceed the specified size (diagonal) of the precision.
|
||||
* @param meters Maximum size of cells in meters (must greater than zero)
|
||||
* @return levels need to achieve precision
|
||||
*/
|
||||
public static int quadTreeLevelsForPrecision(double meters) {
|
||||
assert meters >= 0;
|
||||
if(meters == 0) {
|
||||
return QuadPrefixTree.MAX_LEVELS_POSSIBLE;
|
||||
} else {
|
||||
final double ratio = 1+(EARTH_POLAR_DISTANCE / EARTH_EQUATOR); // cell ratio
|
||||
final double width = Math.sqrt((meters*meters)/(ratio*ratio)); // convert to cell width
|
||||
final long part = Math.round(Math.ceil(EARTH_EQUATOR / width));
|
||||
final int level = Long.SIZE - Long.numberOfLeadingZeros(part)-1; // (log_2)
|
||||
return (part<=(1l<<level)) ?level :(level+1); // adjust level
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the number of levels needed for a specific precision. QuadTree
|
||||
* cells will not exceed the specified size (diagonal) of the precision.
|
||||
* @param distance Maximum size of cells as unit string (must greater or equal to zero)
|
||||
* @return levels need to achieve precision
|
||||
*/
|
||||
public static int quadTreeLevelsForPrecision(String distance) {
|
||||
return quadTreeLevelsForPrecision(DistanceUnit.parse(distance, DistanceUnit.METERS, DistanceUnit.METERS));
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the number of levels needed for a specific precision. GeoHash
|
||||
* cells will not exceed the specified size (diagonal) of the precision.
|
||||
* @param meters Maximum size of cells in meters (must greater or equal to zero)
|
||||
* @return levels need to achieve precision
|
||||
*/
|
||||
public static int geoHashLevelsForPrecision(double meters) {
|
||||
assert meters >= 0;
|
||||
|
||||
if(meters == 0) {
|
||||
return GeohashPrefixTree.getMaxLevelsPossible();
|
||||
} else {
|
||||
final double ratio = 1+(EARTH_POLAR_DISTANCE / EARTH_EQUATOR); // cell ratio
|
||||
final double width = Math.sqrt((meters*meters)/(ratio*ratio)); // convert to cell width
|
||||
final double part = Math.ceil(EARTH_EQUATOR / width);
|
||||
if(part == 1)
|
||||
return 1;
|
||||
final int bits = (int)Math.round(Math.ceil(Math.log(part) / Math.log(2)));
|
||||
final int full = bits / 5; // number of 5 bit subdivisions
|
||||
final int left = bits - full*5; // bit representing the last level
|
||||
final int even = full + (left>0?1:0); // number of even levels
|
||||
final int odd = full + (left>3?1:0); // number of odd levels
|
||||
return even+odd;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the number of levels needed for a specific precision. GeoHash
|
||||
* cells will not exceed the specified size (diagonal) of the precision.
|
||||
* @param distance Maximum size of cells as unit string (must greater or equal to zero)
|
||||
* @return levels need to achieve precision
|
||||
*/
|
||||
public static int geoHashLevelsForPrecision(String distance) {
|
||||
return geoHashLevelsForPrecision(DistanceUnit.parse(distance, DistanceUnit.METERS, DistanceUnit.METERS));
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize longitude to lie within the -180 (exclusive) to 180 (inclusive) range.
|
||||
*
|
||||
|
|
|
@ -20,59 +20,132 @@
|
|||
package org.elasticsearch.common.unit;
|
||||
|
||||
import org.elasticsearch.ElasticSearchIllegalArgumentException;
|
||||
import org.elasticsearch.common.geo.GeoUtils;
|
||||
import org.elasticsearch.common.io.stream.StreamInput;
|
||||
import org.elasticsearch.common.io.stream.StreamOutput;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
*
|
||||
* The DistanceUnit enumerates several units for measuring distances. These units
|
||||
* provide methods for converting strings and methods to convert units among each
|
||||
* others. Some methods like {@link DistanceUnit#getEarthCircumference} refer to
|
||||
* the earth ellipsoid defined in {@link GeoUtils}.
|
||||
*/
|
||||
public enum DistanceUnit {
|
||||
MILES(3959, 24902) {
|
||||
@Override
|
||||
public String toString() {
|
||||
return "miles";
|
||||
}
|
||||
INCH(0.0254, "in", "inch"),
|
||||
YARD(0.9144, "yd", "yards"),
|
||||
MILES(1609.344, "mi", "miles"),
|
||||
KILOMETERS(1000.0, "km", "kilometers"),
|
||||
MILLIMETERS(0.001, "mm", "millimeters"),
|
||||
CENTIMETERS(0.01, "cm", "centimeters"),
|
||||
|
||||
// since 'm' is suffix of other unit
|
||||
// it must be the last entry of unit
|
||||
// names ending with 'm'. otherwise
|
||||
// parsing would fail
|
||||
METERS(1, "m", "meters");
|
||||
|
||||
@Override
|
||||
public double toMiles(double distance) {
|
||||
return distance;
|
||||
}
|
||||
private double meters;
|
||||
private final String[] names;
|
||||
|
||||
DistanceUnit(double meters, String...names) {
|
||||
this.meters = meters;
|
||||
this.names = names;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double toKilometers(double distance) {
|
||||
return distance * MILES_KILOMETRES_RATIO;
|
||||
}
|
||||
/**
|
||||
* Measures the circumference of earth in this unit
|
||||
*
|
||||
* @return length of earth circumference in this unit
|
||||
*/
|
||||
public double getEarthCircumference() {
|
||||
return GeoUtils.EARTH_EQUATOR / meters;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString(double distance) {
|
||||
return distance + "mi";
|
||||
}
|
||||
},
|
||||
KILOMETERS(6371, 40076) {
|
||||
@Override
|
||||
public String toString() {
|
||||
return "km";
|
||||
}
|
||||
/**
|
||||
* Measures the radius of earth in this unit
|
||||
*
|
||||
* @return length of earth radius in this unit
|
||||
*/
|
||||
public double getEarthRadius() {
|
||||
return GeoUtils.EARTH_SEMI_MAJOR_AXIS / meters;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double toMiles(double distance) {
|
||||
return distance / MILES_KILOMETRES_RATIO;
|
||||
}
|
||||
/**
|
||||
* Measures a longitude in this unit
|
||||
*
|
||||
* @return length of a longitude degree in this unit
|
||||
*/
|
||||
public double getDistancePerDegree() {
|
||||
return GeoUtils.EARTH_EQUATOR / (360.0 * meters);
|
||||
}
|
||||
|
||||
@Override
|
||||
public double toKilometers(double distance) {
|
||||
return distance;
|
||||
}
|
||||
/**
|
||||
* Convert a value into miles
|
||||
*
|
||||
* @param distance distance in this unit
|
||||
* @return value in miles
|
||||
*/
|
||||
public double toMiles(double distance) {
|
||||
return convert(distance, this, DistanceUnit.MILES);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString(double distance) {
|
||||
return distance + "km";
|
||||
}
|
||||
};
|
||||
/**
|
||||
* Convert a value into kilometers
|
||||
*
|
||||
* @param distance distance in this unit
|
||||
* @return value in kilometers
|
||||
*/
|
||||
public double toKilometers(double distance) {
|
||||
return convert(distance, this, DistanceUnit.KILOMETERS);
|
||||
}
|
||||
|
||||
static final double MILES_KILOMETRES_RATIO = 1.609344;
|
||||
/**
|
||||
* Convert a value into meters
|
||||
*
|
||||
* @param distance distance in this unit
|
||||
* @return value in meters
|
||||
*/
|
||||
public double toMeters(double distance) {
|
||||
return convert(distance, this, DistanceUnit.METERS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a value given in meters to a value of this unit
|
||||
*
|
||||
* @param distance distance in meters
|
||||
* @return value in this unit
|
||||
*/
|
||||
public double fromMeters(double distance) {
|
||||
return convert(distance, DistanceUnit.METERS, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a given value into another unit
|
||||
*
|
||||
* @param distance value in this unit
|
||||
* @param unit target unit
|
||||
* @return value of the target unit
|
||||
*/
|
||||
public double convert(double distance, DistanceUnit unit) {
|
||||
return convert(distance, this, unit);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a value to a distance string
|
||||
*
|
||||
* @param distance value to convert
|
||||
* @return String representation of the distance
|
||||
*/
|
||||
public String toString(double distance) {
|
||||
return distance + toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return names[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the given distance from the given DistanceUnit, to the given DistanceUnit
|
||||
|
@ -80,94 +153,166 @@ public enum DistanceUnit {
|
|||
* @param distance Distance to convert
|
||||
* @param from Unit to convert the distance from
|
||||
* @param to Unit of distance to convert to
|
||||
* @return Given distance converted to the distance in the given uni
|
||||
* @return Given distance converted to the distance in the given unit
|
||||
*/
|
||||
public static double convert(double distance, DistanceUnit from, DistanceUnit to) {
|
||||
if (from == to) {
|
||||
return distance;
|
||||
} else {
|
||||
return distance * from.meters / to.meters;
|
||||
}
|
||||
return (to == MILES) ? distance / MILES_KILOMETRES_RATIO : distance * MILES_KILOMETRES_RATIO;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a given distance and converts it to the specified unit.
|
||||
*
|
||||
* @param distance String defining a distance (value and unit)
|
||||
* @param defaultUnit unit assumed if none is defined
|
||||
* @param to unit of result
|
||||
* @return parsed distance
|
||||
*/
|
||||
public static double parse(String distance, DistanceUnit defaultUnit, DistanceUnit to) {
|
||||
if (distance.endsWith("mi")) {
|
||||
return convert(Double.parseDouble(distance.substring(0, distance.length() - "mi".length())), MILES, to);
|
||||
} else if (distance.endsWith("miles")) {
|
||||
return convert(Double.parseDouble(distance.substring(0, distance.length() - "miles".length())), MILES, to);
|
||||
} else if (distance.endsWith("km")) {
|
||||
return convert(Double.parseDouble(distance.substring(0, distance.length() - "km".length())), KILOMETERS, to);
|
||||
} else {
|
||||
return convert(Double.parseDouble(distance), defaultUnit, to);
|
||||
}
|
||||
Distance dist = Distance.parseDistance(distance, defaultUnit);
|
||||
return convert(dist.value, dist.unit, to);
|
||||
}
|
||||
|
||||
public static DistanceUnit parseUnit(String distance, DistanceUnit defaultUnit) {
|
||||
if (distance.endsWith("mi")) {
|
||||
return MILES;
|
||||
} else if (distance.endsWith("miles")) {
|
||||
return MILES;
|
||||
} else if (distance.endsWith("km")) {
|
||||
return KILOMETERS;
|
||||
} else {
|
||||
return defaultUnit;
|
||||
}
|
||||
}
|
||||
|
||||
protected final double earthCircumference;
|
||||
protected final double earthRadius;
|
||||
protected final double distancePerDegree;
|
||||
|
||||
DistanceUnit(double earthRadius, double earthCircumference) {
|
||||
this.earthCircumference = earthCircumference;
|
||||
this.earthRadius = earthRadius;
|
||||
this.distancePerDegree = earthCircumference / 360;
|
||||
}
|
||||
|
||||
public double getEarthCircumference() {
|
||||
return earthCircumference;
|
||||
}
|
||||
|
||||
public double getEarthRadius() {
|
||||
return earthRadius;
|
||||
}
|
||||
|
||||
public double getDistancePerDegree() {
|
||||
return distancePerDegree;
|
||||
}
|
||||
|
||||
public abstract double toMiles(double distance);
|
||||
|
||||
public abstract double toKilometers(double distance);
|
||||
|
||||
public abstract String toString(double distance);
|
||||
|
||||
/**
|
||||
* Convert a String to a {@link DistanceUnit}
|
||||
*
|
||||
* @param unit name of the unit
|
||||
* @return unit matching the given name
|
||||
* @throws ElasticSearchIllegalArgumentException if no unit matches the given name
|
||||
*/
|
||||
public static DistanceUnit fromString(String unit) {
|
||||
if ("km".equals(unit)) {
|
||||
return KILOMETERS;
|
||||
} else if ("mi".equals(unit)) {
|
||||
return MILES;
|
||||
} else if ("miles".equals(unit)) {
|
||||
return MILES;
|
||||
for (DistanceUnit dunit : values()) {
|
||||
for (String name : dunit.names) {
|
||||
if(name.equals(unit)) {
|
||||
return dunit;
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new ElasticSearchIllegalArgumentException("No distance unit match [" + unit + "]");
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the suffix of a given distance string and return the corresponding {@link DistanceUnit}
|
||||
*
|
||||
* @param distance string representing a distance
|
||||
* @param defaultUnit default unit to use, if no unit is provided by the string
|
||||
* @return unit of the given distance
|
||||
*/
|
||||
public static DistanceUnit parseUnit(String distance, DistanceUnit defaultUnit) {
|
||||
for (DistanceUnit unit : values()) {
|
||||
for (String name : unit.names) {
|
||||
if(distance.endsWith(name)) {
|
||||
return unit;
|
||||
}
|
||||
}
|
||||
}
|
||||
return defaultUnit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a {@link DistanceUnit} to a {@link StreamOutput}
|
||||
*
|
||||
* @param out {@link StreamOutput} to write to
|
||||
* @param unit {@link DistanceUnit} to write
|
||||
* @throws IOException
|
||||
*/
|
||||
public static void writeDistanceUnit(StreamOutput out, DistanceUnit unit) throws IOException {
|
||||
if (unit == MILES) {
|
||||
out.writeByte((byte) 0);
|
||||
} else if (unit == KILOMETERS) {
|
||||
out.writeByte((byte) 1);
|
||||
out.writeByte((byte) unit.ordinal());
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a {@link DistanceUnit} from a {@link StreamInput}
|
||||
*
|
||||
* @param in {@link StreamInput} to read the {@link DistanceUnit} from
|
||||
* @return {@link DistanceUnit} read from the {@link StreamInput}
|
||||
* @throws IOException if no unit can be read from the {@link StreamInput}
|
||||
* @thrown ElasticSearchIllegalArgumentException if no matching {@link DistanceUnit} can be found
|
||||
*/
|
||||
public static DistanceUnit readDistanceUnit(StreamInput in) throws IOException {
|
||||
byte b = in.readByte();
|
||||
|
||||
if(b<0 || b>=values().length) {
|
||||
throw new ElasticSearchIllegalArgumentException("No type for distance unit matching [" + b + "]");
|
||||
} else {
|
||||
return values()[b];
|
||||
}
|
||||
}
|
||||
|
||||
public static DistanceUnit readDistanceUnit(StreamInput in) throws IOException {
|
||||
byte b = in.readByte();
|
||||
if (b == 0) {
|
||||
return MILES;
|
||||
} else if (b == 1) {
|
||||
return KILOMETERS;
|
||||
} else {
|
||||
throw new ElasticSearchIllegalArgumentException("No type for distance unit matching [" + b + "]");
|
||||
/**
|
||||
* This class implements a value+unit tuple.
|
||||
*/
|
||||
public static class Distance implements Comparable<Distance> {
|
||||
public final double value;
|
||||
public final DistanceUnit unit;
|
||||
|
||||
private Distance(double value, DistanceUnit unit) {
|
||||
super();
|
||||
this.value = value;
|
||||
this.unit = unit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a {@link Distance} value given in a specific {@link DistanceUnit} into
|
||||
* a value equal to the specified value but in a other {@link DistanceUnit}.
|
||||
*
|
||||
* @param unit unit of the result
|
||||
* @return converted distance
|
||||
*/
|
||||
public Distance convert(DistanceUnit unit) {
|
||||
if(this.unit == unit) {
|
||||
return this;
|
||||
} else {
|
||||
return new Distance(DistanceUnit.convert(value, this.unit, unit), unit);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if(obj == null) {
|
||||
return false;
|
||||
} else if (obj instanceof Distance) {
|
||||
Distance other = (Distance) obj;
|
||||
return DistanceUnit.convert(value, unit, other.unit) == other.value;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Double.valueOf(value * unit.meters).hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(Distance o) {
|
||||
return Double.compare(value, DistanceUnit.convert(o.value, o.unit, unit));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return unit.toString(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a {@link Distance} from a given String
|
||||
*
|
||||
* @param distance String defining a {@link Distance}
|
||||
* @param defaultUnit {@link DistanceUnit} to be assumed
|
||||
* if not unit is provided in the first argument
|
||||
* @return parsed {@link Distance}
|
||||
*/
|
||||
public static Distance parseDistance(String distance, DistanceUnit defaultUnit) {
|
||||
for (DistanceUnit unit : values()) {
|
||||
for (String name : unit.names) {
|
||||
if(distance.endsWith(name)) {
|
||||
return new Distance(Double.parseDouble(distance.substring(0, distance.length() - name.length())), unit);
|
||||
}
|
||||
}
|
||||
}
|
||||
return new Distance(Double.parseDouble(distance), defaultUnit);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,21 @@
|
|||
/*
|
||||
* Licensed to ElasticSearch and Shay Banon under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. ElasticSearch licenses this
|
||||
* file to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.elasticsearch.index.mapper.geo;
|
||||
|
||||
import org.apache.lucene.document.Field;
|
||||
|
@ -13,7 +31,9 @@ import org.elasticsearch.ElasticSearchIllegalArgumentException;
|
|||
import org.elasticsearch.common.Strings;
|
||||
import org.elasticsearch.common.geo.GeoJSONShapeParser;
|
||||
import org.elasticsearch.common.geo.GeoShapeConstants;
|
||||
import org.elasticsearch.common.geo.GeoUtils;
|
||||
import org.elasticsearch.common.geo.SpatialStrategy;
|
||||
import org.elasticsearch.common.unit.DistanceUnit;
|
||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||
import org.elasticsearch.index.codec.postingsformat.PostingsFormatProvider;
|
||||
import org.elasticsearch.index.fielddata.FieldDataType;
|
||||
|
@ -51,6 +71,7 @@ public class GeoShapeFieldMapper extends AbstractFieldMapper<String> {
|
|||
public static final String TREE_GEOHASH = "geohash";
|
||||
public static final String TREE_QUADTREE = "quadtree";
|
||||
public static final String TREE_LEVELS = "tree_levels";
|
||||
public static final String TREE_PRESISION = "precision";
|
||||
public static final String DISTANCE_ERROR_PCT = "distance_error_pct";
|
||||
public static final String STRATEGY = "strategy";
|
||||
}
|
||||
|
@ -58,8 +79,8 @@ public class GeoShapeFieldMapper extends AbstractFieldMapper<String> {
|
|||
public static class Defaults {
|
||||
public static final String TREE = Names.TREE_GEOHASH;
|
||||
public static final String STRATEGY = SpatialStrategy.RECURSIVE.getStrategyName();
|
||||
public static final int GEOHASH_LEVELS = GeohashPrefixTree.getMaxLevelsPossible();
|
||||
public static final int QUADTREE_LEVELS = QuadPrefixTree.DEFAULT_MAX_LEVELS;
|
||||
public static final int GEOHASH_LEVELS = GeoUtils.geoHashLevelsForPrecision("50m");
|
||||
public static final int QUADTREE_LEVELS = GeoUtils.quadTreeLevelsForPrecision("50m");
|
||||
public static final double DISTANCE_ERROR_PCT = 0.025d;
|
||||
|
||||
public static final FieldType FIELD_TYPE = new FieldType();
|
||||
|
@ -80,7 +101,8 @@ public class GeoShapeFieldMapper extends AbstractFieldMapper<String> {
|
|||
|
||||
private String tree = Defaults.TREE;
|
||||
private String strategyName = Defaults.STRATEGY;
|
||||
private int treeLevels;
|
||||
private int treeLevels = 0;
|
||||
private double precisionInMeters = -1;
|
||||
private double distanceErrorPct = Defaults.DISTANCE_ERROR_PCT;
|
||||
|
||||
private SpatialPrefixTree prefixTree;
|
||||
|
@ -99,6 +121,11 @@ public class GeoShapeFieldMapper extends AbstractFieldMapper<String> {
|
|||
return this;
|
||||
}
|
||||
|
||||
public Builder treeLevelsByDistance(double meters) {
|
||||
this.precisionInMeters = meters;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder treeLevels(int treeLevels) {
|
||||
this.treeLevels = treeLevels;
|
||||
return this;
|
||||
|
@ -112,14 +139,11 @@ public class GeoShapeFieldMapper extends AbstractFieldMapper<String> {
|
|||
@Override
|
||||
public GeoShapeFieldMapper build(BuilderContext context) {
|
||||
|
||||
FieldMapper.Names names = buildNames(context);
|
||||
|
||||
if (tree.equals(Names.TREE_GEOHASH)) {
|
||||
int levels = treeLevels != 0 ? treeLevels : Defaults.GEOHASH_LEVELS;
|
||||
prefixTree = new GeohashPrefixTree(GeoShapeConstants.SPATIAL_CONTEXT, levels);
|
||||
} else if (tree.equals(Names.TREE_QUADTREE)) {
|
||||
int levels = treeLevels != 0 ? treeLevels : Defaults.QUADTREE_LEVELS;
|
||||
prefixTree = new QuadPrefixTree(GeoShapeConstants.SPATIAL_CONTEXT, levels);
|
||||
final FieldMapper.Names names = buildNames(context);
|
||||
if (Names.TREE_GEOHASH.equals(tree)) {
|
||||
prefixTree = new GeohashPrefixTree(GeoShapeConstants.SPATIAL_CONTEXT, getLevels(treeLevels, precisionInMeters, Defaults.GEOHASH_LEVELS, true));
|
||||
} else if (Names.TREE_QUADTREE.equals(tree)) {
|
||||
prefixTree = new QuadPrefixTree(GeoShapeConstants.SPATIAL_CONTEXT, getLevels(treeLevels, precisionInMeters, Defaults.QUADTREE_LEVELS, false));
|
||||
} else {
|
||||
throw new ElasticSearchIllegalArgumentException("Unknown prefix tree type [" + tree + "]");
|
||||
}
|
||||
|
@ -127,7 +151,16 @@ public class GeoShapeFieldMapper extends AbstractFieldMapper<String> {
|
|||
return new GeoShapeFieldMapper(names, prefixTree, strategyName, distanceErrorPct, fieldType, provider);
|
||||
}
|
||||
}
|
||||
|
||||
private static final int getLevels(int treeLevels, double precisionInMeters, int defaultLevels, boolean geoHash) {
|
||||
if (treeLevels > 0 || precisionInMeters >= 0) {
|
||||
return Math.max(treeLevels, precisionInMeters >= 0 ? (geoHash ? GeoUtils.geoHashLevelsForPrecision(precisionInMeters)
|
||||
: GeoUtils.quadTreeLevelsForPrecision(precisionInMeters)) : 0);
|
||||
}
|
||||
return defaultLevels;
|
||||
}
|
||||
|
||||
|
||||
public static class TypeParser implements Mapper.TypeParser {
|
||||
|
||||
@Override
|
||||
|
@ -141,6 +174,8 @@ public class GeoShapeFieldMapper extends AbstractFieldMapper<String> {
|
|||
builder.tree(fieldNode.toString());
|
||||
} else if (Names.TREE_LEVELS.equals(fieldName)) {
|
||||
builder.treeLevels(Integer.parseInt(fieldNode.toString()));
|
||||
} else if (Names.TREE_PRESISION.equals(fieldName)) {
|
||||
builder.treeLevelsByDistance(DistanceUnit.parse(fieldNode.toString(), DistanceUnit.METERS, DistanceUnit.METERS));
|
||||
} else if (Names.DISTANCE_ERROR_PCT.equals(fieldName)) {
|
||||
builder.distanceErrorPct(Double.parseDouble(fieldNode.toString()));
|
||||
} else if (Names.STRATEGY.equals(fieldName)) {
|
||||
|
|
|
@ -20,11 +20,10 @@
|
|||
package org.elasticsearch.test.unit.common.unit;
|
||||
|
||||
import org.elasticsearch.common.unit.DistanceUnit;
|
||||
import org.hamcrest.MatcherAssert;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.closeTo;
|
||||
import static org.hamcrest.Matchers.*;
|
||||
|
||||
/**
|
||||
*
|
||||
|
@ -34,9 +33,31 @@ public class DistanceUnitTests {
|
|||
|
||||
@Test
|
||||
void testSimpleDistanceUnit() {
|
||||
MatcherAssert.assertThat(DistanceUnit.MILES.toKilometers(10), closeTo(16.09344, 0.001));
|
||||
assertThat(DistanceUnit.MILES.toKilometers(10), closeTo(16.09344, 0.001));
|
||||
assertThat(DistanceUnit.MILES.toMiles(10), closeTo(10, 0.001));
|
||||
assertThat(DistanceUnit.KILOMETERS.toMiles(10), closeTo(6.21371192, 0.001));
|
||||
assertThat(DistanceUnit.KILOMETERS.toKilometers(10), closeTo(10, 0.001));
|
||||
assertThat(DistanceUnit.METERS.toKilometers(10), closeTo(0.01, 0.00001));
|
||||
assertThat(DistanceUnit.METERS.toKilometers(1000), closeTo(1, 0.001));
|
||||
assertThat(DistanceUnit.KILOMETERS.toMeters(1), closeTo(1000, 0.001));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDistanceUnitParsing() {
|
||||
assertThat(DistanceUnit.Distance.parseDistance("50km", null).unit, equalTo(DistanceUnit.KILOMETERS));
|
||||
assertThat(DistanceUnit.Distance.parseDistance("500m", null).unit, equalTo(DistanceUnit.METERS));
|
||||
assertThat(DistanceUnit.Distance.parseDistance("51mi", null).unit, equalTo(DistanceUnit.MILES));
|
||||
assertThat(DistanceUnit.Distance.parseDistance("52yd", null).unit, equalTo(DistanceUnit.YARD));
|
||||
assertThat(DistanceUnit.Distance.parseDistance("12in", null).unit, equalTo(DistanceUnit.INCH));
|
||||
assertThat(DistanceUnit.Distance.parseDistance("23mm", null).unit, equalTo(DistanceUnit.MILLIMETERS));
|
||||
assertThat(DistanceUnit.Distance.parseDistance("23cm", null).unit, equalTo(DistanceUnit.CENTIMETERS));
|
||||
|
||||
double testValue = 12345.678;
|
||||
for (DistanceUnit unit : DistanceUnit.values()) {
|
||||
assertThat("Unit can be parsed from '" + unit.toString() + "'", DistanceUnit.fromString(unit.toString()), equalTo(unit));
|
||||
assertThat("Unit can be parsed from '" + testValue + unit.toString() + "'", DistanceUnit.fromString(unit.toString()), equalTo(unit));
|
||||
assertThat("Value can be parsed from '" + testValue + unit.toString() + "'", DistanceUnit.Distance.parseDistance(unit.toString(testValue), null).value, equalTo(testValue));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,3 +1,21 @@
|
|||
/*
|
||||
* Licensed to ElasticSearch and Shay Banon under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. ElasticSearch licenses this
|
||||
* file to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.elasticsearch.test.unit.index.mapper.geo;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
|
@ -9,6 +27,7 @@ import java.io.IOException;
|
|||
import org.apache.lucene.spatial.prefix.PrefixTreeStrategy;
|
||||
import org.apache.lucene.spatial.prefix.tree.GeohashPrefixTree;
|
||||
import org.apache.lucene.spatial.prefix.tree.QuadPrefixTree;
|
||||
import org.elasticsearch.common.geo.GeoUtils;
|
||||
import org.elasticsearch.common.xcontent.XContentFactory;
|
||||
import org.elasticsearch.index.mapper.DocumentMapper;
|
||||
import org.elasticsearch.index.mapper.FieldMapper;
|
||||
|
@ -83,4 +102,148 @@ public class GeoShapeFieldMapperTests {
|
|||
assertThat(strategy.getGrid(), instanceOf(QuadPrefixTree.class));
|
||||
assertThat(strategy.getGrid().getMaxLevels(), equalTo(6));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLevelPrecisionConfiguration() throws IOException {
|
||||
{
|
||||
String mapping = XContentFactory.jsonBuilder().startObject().startObject("type1")
|
||||
.startObject("properties").startObject("location")
|
||||
.field("type", "geo_shape")
|
||||
.field("tree", "quadtree")
|
||||
.field("tree_levels", "6")
|
||||
.field("precision", "70m")
|
||||
.field("distance_error_pct", "0.5")
|
||||
.endObject().endObject()
|
||||
.endObject().endObject().string();
|
||||
|
||||
DocumentMapper defaultMapper = MapperTests.newParser().parse(mapping);
|
||||
FieldMapper fieldMapper = defaultMapper.mappers().name("location").mapper();
|
||||
assertThat(fieldMapper, instanceOf(GeoShapeFieldMapper.class));
|
||||
|
||||
GeoShapeFieldMapper geoShapeFieldMapper = (GeoShapeFieldMapper) fieldMapper;
|
||||
PrefixTreeStrategy strategy = geoShapeFieldMapper.defaultStrategy();
|
||||
|
||||
assertThat(strategy.getDistErrPct(), equalTo(0.5));
|
||||
assertThat(strategy.getGrid(), instanceOf(QuadPrefixTree.class));
|
||||
/* 70m is more precise so it wins */
|
||||
assertThat(strategy.getGrid().getMaxLevels(), equalTo(GeoUtils.quadTreeLevelsForPrecision(70d)));
|
||||
}
|
||||
|
||||
{
|
||||
String mapping = XContentFactory.jsonBuilder().startObject().startObject("type1")
|
||||
.startObject("properties").startObject("location")
|
||||
.field("type", "geo_shape")
|
||||
.field("tree", "geohash")
|
||||
.field("tree_levels", "6")
|
||||
.field("precision", "70m")
|
||||
.field("distance_error_pct", "0.5")
|
||||
.endObject().endObject()
|
||||
.endObject().endObject().string();
|
||||
|
||||
DocumentMapper defaultMapper = MapperTests.newParser().parse(mapping);
|
||||
FieldMapper fieldMapper = defaultMapper.mappers().name("location").mapper();
|
||||
assertThat(fieldMapper, instanceOf(GeoShapeFieldMapper.class));
|
||||
|
||||
GeoShapeFieldMapper geoShapeFieldMapper = (GeoShapeFieldMapper) fieldMapper;
|
||||
PrefixTreeStrategy strategy = geoShapeFieldMapper.defaultStrategy();
|
||||
|
||||
assertThat(strategy.getDistErrPct(), equalTo(0.5));
|
||||
assertThat(strategy.getGrid(), instanceOf(GeohashPrefixTree.class));
|
||||
/* 70m is more precise so it wins */
|
||||
assertThat(strategy.getGrid().getMaxLevels(), equalTo(GeoUtils.geoHashLevelsForPrecision(70d)));
|
||||
}
|
||||
|
||||
{
|
||||
String mapping = XContentFactory.jsonBuilder().startObject().startObject("type1")
|
||||
.startObject("properties").startObject("location")
|
||||
.field("type", "geo_shape")
|
||||
.field("tree", "geohash")
|
||||
.field("tree_levels", GeoUtils.geoHashLevelsForPrecision(70d)+1)
|
||||
.field("precision", "70m")
|
||||
.field("distance_error_pct", "0.5")
|
||||
.endObject().endObject()
|
||||
.endObject().endObject().string();
|
||||
|
||||
DocumentMapper defaultMapper = MapperTests.newParser().parse(mapping);
|
||||
FieldMapper fieldMapper = defaultMapper.mappers().name("location").mapper();
|
||||
assertThat(fieldMapper, instanceOf(GeoShapeFieldMapper.class));
|
||||
|
||||
GeoShapeFieldMapper geoShapeFieldMapper = (GeoShapeFieldMapper) fieldMapper;
|
||||
PrefixTreeStrategy strategy = geoShapeFieldMapper.defaultStrategy();
|
||||
|
||||
assertThat(strategy.getDistErrPct(), equalTo(0.5));
|
||||
assertThat(strategy.getGrid(), instanceOf(GeohashPrefixTree.class));
|
||||
assertThat(strategy.getGrid().getMaxLevels(), equalTo(GeoUtils.geoHashLevelsForPrecision(70d)+1));
|
||||
}
|
||||
|
||||
{
|
||||
String mapping = XContentFactory.jsonBuilder().startObject().startObject("type1")
|
||||
.startObject("properties").startObject("location")
|
||||
.field("type", "geo_shape")
|
||||
.field("tree", "quadtree")
|
||||
.field("tree_levels", GeoUtils.quadTreeLevelsForPrecision(70d)+1)
|
||||
.field("precision", "70m")
|
||||
.field("distance_error_pct", "0.5")
|
||||
.endObject().endObject()
|
||||
.endObject().endObject().string();
|
||||
|
||||
DocumentMapper defaultMapper = MapperTests.newParser().parse(mapping);
|
||||
FieldMapper fieldMapper = defaultMapper.mappers().name("location").mapper();
|
||||
assertThat(fieldMapper, instanceOf(GeoShapeFieldMapper.class));
|
||||
|
||||
GeoShapeFieldMapper geoShapeFieldMapper = (GeoShapeFieldMapper) fieldMapper;
|
||||
PrefixTreeStrategy strategy = geoShapeFieldMapper.defaultStrategy();
|
||||
|
||||
assertThat(strategy.getDistErrPct(), equalTo(0.5));
|
||||
assertThat(strategy.getGrid(), instanceOf(QuadPrefixTree.class));
|
||||
assertThat(strategy.getGrid().getMaxLevels(), equalTo(GeoUtils.quadTreeLevelsForPrecision(70d)+1));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLevelDefaults() throws IOException {
|
||||
{
|
||||
String mapping = XContentFactory.jsonBuilder().startObject().startObject("type1")
|
||||
.startObject("properties").startObject("location")
|
||||
.field("type", "geo_shape")
|
||||
.field("tree", "quadtree")
|
||||
.field("distance_error_pct", "0.5")
|
||||
.endObject().endObject()
|
||||
.endObject().endObject().string();
|
||||
|
||||
DocumentMapper defaultMapper = MapperTests.newParser().parse(mapping);
|
||||
FieldMapper fieldMapper = defaultMapper.mappers().name("location").mapper();
|
||||
assertThat(fieldMapper, instanceOf(GeoShapeFieldMapper.class));
|
||||
|
||||
GeoShapeFieldMapper geoShapeFieldMapper = (GeoShapeFieldMapper) fieldMapper;
|
||||
PrefixTreeStrategy strategy = geoShapeFieldMapper.defaultStrategy();
|
||||
|
||||
assertThat(strategy.getDistErrPct(), equalTo(0.5));
|
||||
assertThat(strategy.getGrid(), instanceOf(QuadPrefixTree.class));
|
||||
/* 50m is default */
|
||||
assertThat(strategy.getGrid().getMaxLevels(), equalTo(GeoUtils.quadTreeLevelsForPrecision(50d)));
|
||||
}
|
||||
|
||||
{
|
||||
String mapping = XContentFactory.jsonBuilder().startObject().startObject("type1")
|
||||
.startObject("properties").startObject("location")
|
||||
.field("type", "geo_shape")
|
||||
.field("tree", "geohash")
|
||||
.field("distance_error_pct", "0.5")
|
||||
.endObject().endObject()
|
||||
.endObject().endObject().string();
|
||||
|
||||
DocumentMapper defaultMapper = MapperTests.newParser().parse(mapping);
|
||||
FieldMapper fieldMapper = defaultMapper.mappers().name("location").mapper();
|
||||
assertThat(fieldMapper, instanceOf(GeoShapeFieldMapper.class));
|
||||
|
||||
GeoShapeFieldMapper geoShapeFieldMapper = (GeoShapeFieldMapper) fieldMapper;
|
||||
PrefixTreeStrategy strategy = geoShapeFieldMapper.defaultStrategy();
|
||||
|
||||
assertThat(strategy.getDistErrPct(), equalTo(0.5));
|
||||
assertThat(strategy.getGrid(), instanceOf(GeohashPrefixTree.class));
|
||||
/* 50m is default */
|
||||
assertThat(strategy.getGrid().getMaxLevels(), equalTo(GeoUtils.geoHashLevelsForPrecision(50d)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,12 +19,19 @@
|
|||
|
||||
package org.elasticsearch.test.unit.index.search.geo;
|
||||
|
||||
import org.apache.lucene.spatial.prefix.tree.GeohashPrefixTree;
|
||||
import org.apache.lucene.spatial.prefix.tree.Node;
|
||||
import org.apache.lucene.spatial.prefix.tree.QuadPrefixTree;
|
||||
import org.elasticsearch.common.geo.GeoPoint;
|
||||
import org.elasticsearch.common.geo.GeoUtils;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
import com.spatial4j.core.context.SpatialContext;
|
||||
import com.spatial4j.core.distance.DistanceUtils;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.lessThanOrEqualTo;
|
||||
import static org.hamcrest.Matchers.not;
|
||||
|
||||
/**
|
||||
|
@ -199,6 +206,54 @@ public class GeoUtilsTests {
|
|||
assertThat(GeoUtils.normalizeLat(+18000000000091.0), equalTo(GeoUtils.normalizeLat(+091.0)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPrefixTreeCellSizes() {
|
||||
assertThat(GeoUtils.EARTH_SEMI_MAJOR_AXIS, equalTo(DistanceUtils.EARTH_EQUATORIAL_RADIUS_KM * 1000));
|
||||
assertThat(GeoUtils.quadTreeCellWidth(0), lessThanOrEqualTo(GeoUtils.EARTH_EQUATOR));
|
||||
|
||||
SpatialContext spatialContext = new SpatialContext(true);
|
||||
|
||||
GeohashPrefixTree geohashPrefixTree = new GeohashPrefixTree(spatialContext, GeohashPrefixTree.getMaxLevelsPossible()/2);
|
||||
Node gNode = geohashPrefixTree.getWorldNode();
|
||||
|
||||
for(int i = 0; i<geohashPrefixTree.getMaxLevels(); i++) {
|
||||
double width = GeoUtils.geoHashCellWidth(i);
|
||||
double height = GeoUtils.geoHashCellHeight(i);
|
||||
double size = GeoUtils.geoHashCellSize(i);
|
||||
double degrees = 360.0 * width / GeoUtils.EARTH_EQUATOR;
|
||||
int level = GeoUtils.quadTreeLevelsForPrecision(size);
|
||||
|
||||
assertThat(GeoUtils.quadTreeCellWidth(level), lessThanOrEqualTo(width));
|
||||
assertThat(GeoUtils.quadTreeCellHeight(level), lessThanOrEqualTo(height));
|
||||
assertThat(GeoUtils.geoHashLevelsForPrecision(size), equalTo(geohashPrefixTree.getLevelForDistance(degrees)));
|
||||
|
||||
assertThat("width at level "+i, gNode.getShape().getBoundingBox().getWidth(), equalTo(360.d * width / GeoUtils.EARTH_EQUATOR));
|
||||
assertThat("height at level "+i, gNode.getShape().getBoundingBox().getHeight(), equalTo(180.d * height / GeoUtils.EARTH_POLAR_DISTANCE));
|
||||
|
||||
gNode = gNode.getSubCells(null).iterator().next();
|
||||
}
|
||||
|
||||
QuadPrefixTree quadPrefixTree = new QuadPrefixTree(spatialContext);
|
||||
Node qNode = quadPrefixTree.getWorldNode();
|
||||
for (int i = 0; i < QuadPrefixTree.DEFAULT_MAX_LEVELS; i++) {
|
||||
|
||||
double degrees = 360.0/(1L<<i);
|
||||
double width = GeoUtils.quadTreeCellWidth(i);
|
||||
double height = GeoUtils.quadTreeCellHeight(i);
|
||||
double size = GeoUtils.quadTreeCellSize(i);
|
||||
int level = GeoUtils.quadTreeLevelsForPrecision(size);
|
||||
|
||||
assertThat(GeoUtils.quadTreeCellWidth(level), lessThanOrEqualTo(width));
|
||||
assertThat(GeoUtils.quadTreeCellHeight(level), lessThanOrEqualTo(height));
|
||||
assertThat(GeoUtils.quadTreeLevelsForPrecision(size), equalTo(quadPrefixTree.getLevelForDistance(degrees)));
|
||||
|
||||
assertThat("width at level "+i, qNode.getShape().getBoundingBox().getWidth(), equalTo(360.d * width / GeoUtils.EARTH_EQUATOR));
|
||||
assertThat("height at level "+i, qNode.getShape().getBoundingBox().getHeight(), equalTo(180.d * height / GeoUtils.EARTH_POLAR_DISTANCE));
|
||||
|
||||
qNode = qNode.getSubCells(null).iterator().next();
|
||||
}
|
||||
}
|
||||
|
||||
private static void assertNormalizedPoint(GeoPoint input, GeoPoint expected) {
|
||||
GeoUtils.normalizePoint(input);
|
||||
assertThat(input, equalTo(expected));
|
||||
|
|
Loading…
Reference in New Issue