Geo: Automatically normalize lat/lon on search components, closes #1264.

This commit is contained in:
Shay Banon 2011-08-19 20:19:54 +03:00
parent ec6fa83856
commit 779dc4309b
9 changed files with 143 additions and 173 deletions

View File

@ -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();

View File

@ -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);

View File

@ -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) {

View File

@ -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) {

View File

@ -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) {

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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);

View File

@ -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);
}
}