Geo: Automatically normalize lat/lon on search components, closes #1264.
This commit is contained in:
parent
ec6fa83856
commit
779dc4309b
|
@ -40,6 +40,7 @@ import org.elasticsearch.index.mapper.core.NumberFieldMapper;
|
|||
import org.elasticsearch.index.mapper.core.StringFieldMapper;
|
||||
import org.elasticsearch.index.mapper.object.ArrayValueMapperParser;
|
||||
import org.elasticsearch.index.search.geo.GeoHashUtils;
|
||||
import org.elasticsearch.index.search.geo.GeoUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
|
@ -358,10 +359,10 @@ public class GeoPointFieldMapper implements Mapper, ArrayValueMapperParser {
|
|||
|
||||
private void parseLatLon(ParseContext context, double lat, double lon) throws IOException {
|
||||
if (normalizeLon) {
|
||||
lon = normalizeLon(lon);
|
||||
lon = GeoUtils.normalizeLon(lon);
|
||||
}
|
||||
if (normalizeLat) {
|
||||
lat = normalizeLat(lat);
|
||||
lat = GeoUtils.normalizeLat(lat);
|
||||
}
|
||||
|
||||
if (validateLat) {
|
||||
|
@ -395,10 +396,10 @@ public class GeoPointFieldMapper implements Mapper, ArrayValueMapperParser {
|
|||
double lon = values[1];
|
||||
|
||||
if (normalizeLon) {
|
||||
lon = normalizeLon(lon);
|
||||
lon = GeoUtils.normalizeLon(lon);
|
||||
}
|
||||
if (normalizeLat) {
|
||||
lat = normalizeLat(lat);
|
||||
lat = GeoUtils.normalizeLat(lat);
|
||||
}
|
||||
|
||||
if (validateLat) {
|
||||
|
@ -426,36 +427,6 @@ public class GeoPointFieldMapper implements Mapper, ArrayValueMapperParser {
|
|||
}
|
||||
}
|
||||
|
||||
private double normalizeLon(double lon) {
|
||||
double delta = 0;
|
||||
if (lon < 0) {
|
||||
delta = 360;
|
||||
} else if (lon >= 0) {
|
||||
delta = -360;
|
||||
}
|
||||
|
||||
double newLng = lon;
|
||||
while (newLng < -180 || newLng > 180) {
|
||||
newLng += delta;
|
||||
}
|
||||
return newLng;
|
||||
}
|
||||
|
||||
private double normalizeLat(double lat) {
|
||||
double delta = 0;
|
||||
if (lat < 0) {
|
||||
delta = 180;
|
||||
} else if (lat >= 0) {
|
||||
delta = -180;
|
||||
}
|
||||
|
||||
double newLat = lat;
|
||||
while (newLat < -90 || newLat > 90) {
|
||||
newLat += delta;
|
||||
}
|
||||
return newLat;
|
||||
}
|
||||
|
||||
@Override public void close() {
|
||||
if (latMapper != null) {
|
||||
latMapper.close();
|
||||
|
|
|
@ -29,6 +29,7 @@ import org.elasticsearch.index.mapper.geo.GeoPointFieldDataType;
|
|||
import org.elasticsearch.index.mapper.geo.GeoPointFieldMapper;
|
||||
import org.elasticsearch.index.search.geo.GeoBoundingBoxFilter;
|
||||
import org.elasticsearch.index.search.geo.GeoHashUtils;
|
||||
import org.elasticsearch.index.search.geo.GeoUtils;
|
||||
import org.elasticsearch.index.search.geo.Point;
|
||||
|
||||
import java.io.IOException;
|
||||
|
@ -61,6 +62,8 @@ public class GeoBoundingBoxFilterParser implements FilterParser {
|
|||
String filterName = null;
|
||||
String currentFieldName = null;
|
||||
XContentParser.Token token;
|
||||
boolean normalizeLon = true;
|
||||
boolean normalizeLat = true;
|
||||
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
|
||||
if (token == XContentParser.Token.FIELD_NAME) {
|
||||
currentFieldName = parser.currentName();
|
||||
|
@ -145,10 +148,22 @@ public class GeoBoundingBoxFilterParser implements FilterParser {
|
|||
cache = parser.booleanValue();
|
||||
} else if ("_cache_key".equals(currentFieldName) || "_cacheKey".equals(currentFieldName)) {
|
||||
cacheKey = new CacheKeyFilter.Key(parser.text());
|
||||
} else if ("normalize".equals(currentFieldName)) {
|
||||
normalizeLat = parser.booleanValue();
|
||||
normalizeLon = parser.booleanValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (normalizeLat) {
|
||||
topLeft.lat = GeoUtils.normalizeLat(topLeft.lat);
|
||||
bottomRight.lat = GeoUtils.normalizeLat(bottomRight.lat);
|
||||
}
|
||||
if (normalizeLon) {
|
||||
topLeft.lon = GeoUtils.normalizeLon(topLeft.lon);
|
||||
bottomRight.lon = GeoUtils.normalizeLon(bottomRight.lon);
|
||||
}
|
||||
|
||||
MapperService mapperService = parseContext.mapperService();
|
||||
|
||||
FieldMapper mapper = mapperService.smartNameFieldMapper(fieldName);
|
||||
|
|
|
@ -31,6 +31,7 @@ import org.elasticsearch.index.mapper.geo.GeoPointFieldMapper;
|
|||
import org.elasticsearch.index.search.geo.GeoDistance;
|
||||
import org.elasticsearch.index.search.geo.GeoDistanceFilter;
|
||||
import org.elasticsearch.index.search.geo.GeoHashUtils;
|
||||
import org.elasticsearch.index.search.geo.GeoUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
|
@ -74,6 +75,8 @@ public class GeoDistanceFilterParser implements FilterParser {
|
|||
DistanceUnit unit = DistanceUnit.KILOMETERS; // default unit
|
||||
GeoDistance geoDistance = GeoDistance.ARC;
|
||||
boolean optimizeBbox = true;
|
||||
boolean normalizeLon = true;
|
||||
boolean normalizeLat = true;
|
||||
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
|
||||
if (token == XContentParser.Token.FIELD_NAME) {
|
||||
currentFieldName = parser.currentName();
|
||||
|
@ -135,6 +138,9 @@ public class GeoDistanceFilterParser implements FilterParser {
|
|||
cacheKey = new CacheKeyFilter.Key(parser.text());
|
||||
} else if ("optimize_bbox".equals(currentFieldName) || "optimizeBbox".equals(currentFieldName)) {
|
||||
optimizeBbox = parser.booleanValue();
|
||||
} else if ("normalize".equals(currentFieldName)) {
|
||||
normalizeLat = parser.booleanValue();
|
||||
normalizeLon = parser.booleanValue();
|
||||
} else {
|
||||
// assume the value is the actual value
|
||||
String value = parser.text();
|
||||
|
@ -159,6 +165,13 @@ public class GeoDistanceFilterParser implements FilterParser {
|
|||
}
|
||||
distance = geoDistance.normalize(distance, DistanceUnit.MILES);
|
||||
|
||||
if (normalizeLat) {
|
||||
lat = GeoUtils.normalizeLat(lat);
|
||||
}
|
||||
if (normalizeLon) {
|
||||
lon = GeoUtils.normalizeLon(lon);
|
||||
}
|
||||
|
||||
MapperService mapperService = parseContext.mapperService();
|
||||
FieldMapper mapper = mapperService.smartNameFieldMapper(fieldName);
|
||||
if (mapper == null) {
|
||||
|
|
|
@ -31,6 +31,7 @@ import org.elasticsearch.index.mapper.geo.GeoPointFieldMapper;
|
|||
import org.elasticsearch.index.search.geo.GeoDistance;
|
||||
import org.elasticsearch.index.search.geo.GeoDistanceRangeFilter;
|
||||
import org.elasticsearch.index.search.geo.GeoHashUtils;
|
||||
import org.elasticsearch.index.search.geo.GeoUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
|
@ -76,6 +77,8 @@ public class GeoDistanceRangeFilterParser implements FilterParser {
|
|||
DistanceUnit unit = DistanceUnit.KILOMETERS; // default unit
|
||||
GeoDistance geoDistance = GeoDistance.ARC;
|
||||
boolean optimizeBbox = true;
|
||||
boolean normalizeLon = true;
|
||||
boolean normalizeLat = true;
|
||||
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
|
||||
if (token == XContentParser.Token.FIELD_NAME) {
|
||||
currentFieldName = parser.currentName();
|
||||
|
@ -184,6 +187,9 @@ public class GeoDistanceRangeFilterParser implements FilterParser {
|
|||
cacheKey = new CacheKeyFilter.Key(parser.text());
|
||||
} else if ("optimize_bbox".equals(currentFieldName) || "optimizeBbox".equals(currentFieldName)) {
|
||||
optimizeBbox = parser.booleanValue();
|
||||
} else if ("normalize".equals(currentFieldName)) {
|
||||
normalizeLat = parser.booleanValue();
|
||||
normalizeLon = parser.booleanValue();
|
||||
} else {
|
||||
// assume the value is the actual value
|
||||
String value = parser.text();
|
||||
|
@ -216,6 +222,13 @@ public class GeoDistanceRangeFilterParser implements FilterParser {
|
|||
}
|
||||
to = geoDistance.normalize(to, DistanceUnit.MILES);
|
||||
|
||||
if (normalizeLat) {
|
||||
lat = GeoUtils.normalizeLat(lat);
|
||||
}
|
||||
if (normalizeLon) {
|
||||
lon = GeoUtils.normalizeLon(lon);
|
||||
}
|
||||
|
||||
MapperService mapperService = parseContext.mapperService();
|
||||
FieldMapper mapper = mapperService.smartNameFieldMapper(fieldName);
|
||||
if (mapper == null) {
|
||||
|
|
|
@ -30,6 +30,7 @@ import org.elasticsearch.index.mapper.geo.GeoPointFieldDataType;
|
|||
import org.elasticsearch.index.mapper.geo.GeoPointFieldMapper;
|
||||
import org.elasticsearch.index.search.geo.GeoHashUtils;
|
||||
import org.elasticsearch.index.search.geo.GeoPolygonFilter;
|
||||
import org.elasticsearch.index.search.geo.GeoUtils;
|
||||
import org.elasticsearch.index.search.geo.Point;
|
||||
|
||||
import java.io.IOException;
|
||||
|
@ -70,6 +71,8 @@ public class GeoPolygonFilterParser implements FilterParser {
|
|||
String fieldName = null;
|
||||
List<Point> points = Lists.newArrayList();
|
||||
|
||||
boolean normalizeLon = true;
|
||||
boolean normalizeLat = true;
|
||||
|
||||
String filterName = null;
|
||||
String currentFieldName = null;
|
||||
|
@ -142,6 +145,9 @@ public class GeoPolygonFilterParser implements FilterParser {
|
|||
cache = parser.booleanValue();
|
||||
} else if ("_cache_key".equals(currentFieldName) || "_cacheKey".equals(currentFieldName)) {
|
||||
cacheKey = new CacheKeyFilter.Key(parser.text());
|
||||
} else if ("normalize".equals(currentFieldName)) {
|
||||
normalizeLat = parser.booleanValue();
|
||||
normalizeLon = parser.booleanValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -150,6 +156,15 @@ public class GeoPolygonFilterParser implements FilterParser {
|
|||
throw new QueryParsingException(parseContext.index(), "no points defined for geo_polygon filter");
|
||||
}
|
||||
|
||||
for (Point point : points) {
|
||||
if (normalizeLat) {
|
||||
point.lat = GeoUtils.normalizeLat(point.lat);
|
||||
}
|
||||
if (normalizeLon) {
|
||||
point.lon = GeoUtils.normalizeLon(point.lon);
|
||||
}
|
||||
}
|
||||
|
||||
MapperService mapperService = parseContext.mapperService();
|
||||
FieldMapper mapper = mapperService.smartNameFieldMapper(fieldName);
|
||||
if (mapper == null) {
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* Licensed to Elastic Search and Shay Banon under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. Elastic Search 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.search.geo;
|
||||
|
||||
/**
|
||||
*/
|
||||
public class GeoUtils {
|
||||
|
||||
public static double normalizeLon(double lon) {
|
||||
double delta = 0;
|
||||
if (lon < 0) {
|
||||
delta = 360;
|
||||
} else if (lon >= 0) {
|
||||
delta = -360;
|
||||
}
|
||||
|
||||
double newLng = lon;
|
||||
while (newLng < -180 || newLng > 180) {
|
||||
newLng += delta;
|
||||
}
|
||||
return newLng;
|
||||
}
|
||||
|
||||
public static double normalizeLat(double lat) {
|
||||
double delta = 0;
|
||||
if (lat < 0) {
|
||||
delta = 180;
|
||||
} else if (lat >= 0) {
|
||||
delta = -180;
|
||||
}
|
||||
|
||||
double newLat = lat;
|
||||
while (newLat < -90 || newLat > 90) {
|
||||
newLat += delta;
|
||||
}
|
||||
return newLat;
|
||||
}
|
||||
}
|
|
@ -1,139 +0,0 @@
|
|||
/*
|
||||
* Licensed to Elastic Search and Shay Banon under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. Elastic Search 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.search.geo;
|
||||
|
||||
import org.elasticsearch.common.unit.DistanceUnit;
|
||||
|
||||
/**
|
||||
* @author kimchy (shay.banon)
|
||||
*/
|
||||
public class LatLng {
|
||||
|
||||
private double lat;
|
||||
private double lng;
|
||||
private boolean normalized;
|
||||
|
||||
public LatLng(double lat, double lng) {
|
||||
if (lat > 90.0 || lat < -90.0) {
|
||||
throw new IllegalArgumentException("Illegal latitude value " + lat);
|
||||
}
|
||||
this.lat = lat;
|
||||
this.lng = lng;
|
||||
}
|
||||
|
||||
public boolean isNormalized() {
|
||||
return normalized || ((lng >= -180) && (lng <= 180));
|
||||
}
|
||||
|
||||
public LatLng normalize() {
|
||||
if (isNormalized()) {
|
||||
return this;
|
||||
}
|
||||
|
||||
double delta = 0;
|
||||
if (lng < 0) {
|
||||
delta = 360;
|
||||
} else if (lng >= 0) {
|
||||
delta = -360;
|
||||
}
|
||||
|
||||
double newLng = lng;
|
||||
while (newLng <= -180 || newLng >= 180) {
|
||||
newLng += delta;
|
||||
}
|
||||
|
||||
LatLng ret = new LatLng(lat, newLng);
|
||||
ret.normalized = true;
|
||||
return ret;
|
||||
}
|
||||
|
||||
public double getLat() {
|
||||
return this.lat;
|
||||
}
|
||||
|
||||
public double getLng() {
|
||||
return this.lng;
|
||||
}
|
||||
|
||||
public boolean equals(LatLng other) {
|
||||
return lat == other.getLat() && lng == other.getLng();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
return other instanceof LatLng && equals((LatLng) other);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the distance between two lat/lng's in miles.
|
||||
* Imported from mq java client.
|
||||
*
|
||||
* @param ll2 Second lat,lng position to calculate distance to.
|
||||
* @return Returns the distance in miles.
|
||||
*/
|
||||
public double arcDistance(LatLng ll2) {
|
||||
return arcDistance(ll2, DistanceUnit.MILES);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the distance between two lat/lng's in miles or meters.
|
||||
* Imported from mq java client. Variable references changed to match.
|
||||
*
|
||||
* @param ll2 Second lat,lng position to calculate distance to.
|
||||
* @param lUnits Units to calculate distace, defaults to miles
|
||||
* @return Returns the distance in meters or miles.
|
||||
*/
|
||||
public double arcDistance(LatLng ll2, DistanceUnit lUnits) {
|
||||
LatLng ll1 = normalize();
|
||||
ll2 = ll2.normalize();
|
||||
|
||||
double lat1 = ll1.getLat(), lng1 = ll1.getLng();
|
||||
double lat2 = ll2.getLat(), lng2 = ll2.getLng();
|
||||
|
||||
// Check for same position
|
||||
if (lat1 == lat2 && lng1 == lng2)
|
||||
return 0.0;
|
||||
|
||||
// Get the m_dLongitude diffeernce. Don't need to worry about
|
||||
// crossing 180 since cos(x) = cos(-x)
|
||||
double dLon = lng2 - lng1;
|
||||
|
||||
double a = radians(90.0 - lat1);
|
||||
double c = radians(90.0 - lat2);
|
||||
double cosB = (Math.cos(a) * Math.cos(c))
|
||||
+ (Math.sin(a) * Math.sin(c) * Math.cos(radians(dLon)));
|
||||
|
||||
double radius = (lUnits == DistanceUnit.MILES) ? 3963.205/* MILERADIUSOFEARTH */
|
||||
: 6378.160187/* KMRADIUSOFEARTH */;
|
||||
|
||||
// Find angle subtended (with some bounds checking) in radians and
|
||||
// multiply by earth radius to find the arc distance
|
||||
if (cosB < -1.0)
|
||||
return 3.14159265358979323846/* PI */ * radius;
|
||||
else if (cosB >= 1.0)
|
||||
return 0;
|
||||
else
|
||||
return Math.acos(cosB) * radius;
|
||||
}
|
||||
|
||||
private double radians(double a) {
|
||||
return a * 0.01745329251994;
|
||||
}
|
||||
}
|
|
@ -28,6 +28,7 @@ import org.elasticsearch.common.xcontent.XContentParser;
|
|||
import org.elasticsearch.index.mapper.geo.GeoPointFieldMapper;
|
||||
import org.elasticsearch.index.search.geo.GeoDistance;
|
||||
import org.elasticsearch.index.search.geo.GeoHashUtils;
|
||||
import org.elasticsearch.index.search.geo.GeoUtils;
|
||||
import org.elasticsearch.search.facet.Facet;
|
||||
import org.elasticsearch.search.facet.FacetCollector;
|
||||
import org.elasticsearch.search.facet.FacetPhaseExecutionException;
|
||||
|
@ -64,6 +65,9 @@ public class GeoDistanceFacetProcessor extends AbstractComponent implements Face
|
|||
GeoDistance geoDistance = GeoDistance.ARC;
|
||||
List<GeoDistanceFacet.Entry> entries = Lists.newArrayList();
|
||||
|
||||
boolean normalizeLon = true;
|
||||
boolean normalizeLat = true;
|
||||
|
||||
XContentParser.Token token;
|
||||
String currentName = parser.currentName();
|
||||
|
||||
|
@ -135,6 +139,9 @@ public class GeoDistanceFacetProcessor extends AbstractComponent implements Face
|
|||
valueScript = parser.text();
|
||||
} else if ("lang".equals(currentName)) {
|
||||
scriptLang = parser.text();
|
||||
} else if ("normalize".equals(currentName)) {
|
||||
normalizeLat = parser.booleanValue();
|
||||
normalizeLon = parser.booleanValue();
|
||||
} else {
|
||||
// assume the value is the actual value
|
||||
String value = parser.text();
|
||||
|
@ -161,6 +168,13 @@ public class GeoDistanceFacetProcessor extends AbstractComponent implements Face
|
|||
throw new FacetPhaseExecutionException(facetName, "no ranges defined for geo_distance facet");
|
||||
}
|
||||
|
||||
if (normalizeLat) {
|
||||
lat = GeoUtils.normalizeLat(lat);
|
||||
}
|
||||
if (normalizeLon) {
|
||||
lon = GeoUtils.normalizeLon(lon);
|
||||
}
|
||||
|
||||
if (valueFieldName != null) {
|
||||
return new ValueGeoDistanceFacetCollector(facetName, fieldName, lat, lon, unit, geoDistance, entries.toArray(new GeoDistanceFacet.Entry[entries.size()]),
|
||||
context, valueFieldName);
|
||||
|
|
|
@ -26,6 +26,7 @@ import org.elasticsearch.index.mapper.geo.GeoPointFieldMapper;
|
|||
import org.elasticsearch.index.search.geo.GeoDistance;
|
||||
import org.elasticsearch.index.search.geo.GeoDistanceDataComparator;
|
||||
import org.elasticsearch.index.search.geo.GeoHashUtils;
|
||||
import org.elasticsearch.index.search.geo.GeoUtils;
|
||||
import org.elasticsearch.search.internal.SearchContext;
|
||||
|
||||
/**
|
||||
|
@ -45,6 +46,8 @@ public class GeoDistanceSortParser implements SortParser {
|
|||
GeoDistance geoDistance = GeoDistance.ARC;
|
||||
boolean reverse = false;
|
||||
|
||||
boolean normalizeLon = true;
|
||||
boolean normalizeLat = true;
|
||||
|
||||
XContentParser.Token token;
|
||||
String currentName = parser.currentName();
|
||||
|
@ -87,6 +90,9 @@ public class GeoDistanceSortParser implements SortParser {
|
|||
unit = DistanceUnit.fromString(parser.text());
|
||||
} else if (currentName.equals("distance_type") || currentName.equals("distanceType")) {
|
||||
geoDistance = GeoDistance.fromString(parser.text());
|
||||
} else if ("normalize".equals(currentName)) {
|
||||
normalizeLat = parser.booleanValue();
|
||||
normalizeLon = parser.booleanValue();
|
||||
} else {
|
||||
// assume the value is the actual value
|
||||
String value = parser.text();
|
||||
|
@ -105,6 +111,13 @@ public class GeoDistanceSortParser implements SortParser {
|
|||
}
|
||||
}
|
||||
|
||||
if (normalizeLat) {
|
||||
lat = GeoUtils.normalizeLat(lat);
|
||||
}
|
||||
if (normalizeLon) {
|
||||
lon = GeoUtils.normalizeLon(lon);
|
||||
}
|
||||
|
||||
return new SortField(fieldName, GeoDistanceDataComparator.comparatorSource(fieldName, lat, lon, unit, geoDistance, context.fieldDataCache(), context.mapperService()), reverse);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue