org.codelibs.elasticsearch.common.geo.GeoDistance Maven / Gradle / Ivy
/*
* Licensed to Elasticsearch 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.codelibs.elasticsearch.common.geo;
import org.apache.lucene.util.Bits;
import org.apache.lucene.util.SloppyMath;
import org.codelibs.elasticsearch.common.io.stream.StreamInput;
import org.codelibs.elasticsearch.common.io.stream.StreamOutput;
import org.codelibs.elasticsearch.common.io.stream.Writeable;
import org.codelibs.elasticsearch.common.unit.DistanceUnit;
import org.codelibs.elasticsearch.index.fielddata.FieldData;
import org.codelibs.elasticsearch.index.fielddata.GeoPointValues;
import org.codelibs.elasticsearch.index.fielddata.MultiGeoPointValues;
import org.codelibs.elasticsearch.index.fielddata.NumericDoubleValues;
import org.codelibs.elasticsearch.index.fielddata.SortedNumericDoubleValues;
import org.codelibs.elasticsearch.index.fielddata.SortingNumericDoubleValues;
import java.io.IOException;
import java.util.Locale;
/**
* Geo distance calculation.
*/
public enum GeoDistance implements Writeable {
/**
* Calculates distance as points on a plane. Faster, but less accurate than {#ARC}.
* @deprecated use {GeoUtils#planeDistance}
*/
@Deprecated
PLANE {
@Override
public double calculate(double sourceLatitude, double sourceLongitude, double targetLatitude, double targetLongitude, DistanceUnit unit) {
double px = targetLongitude - sourceLongitude;
double py = targetLatitude - sourceLatitude;
return Math.sqrt(px * px + py * py) * unit.getDistancePerDegree();
}
@Override
public double normalize(double distance, DistanceUnit unit) {
return distance;
}
@Override
public FixedSourceDistance fixedSourceDistance(double sourceLatitude, double sourceLongitude, DistanceUnit unit) {
return new PlaneFixedSourceDistance(sourceLatitude, sourceLongitude, unit);
}
},
/**
* Calculates distance factor.
* Note: {@code calculate} is simply returning the RHS of the spherical law of cosines from 2 lat,lon points.
* {@code normalize} also returns the RHS of the spherical law of cosines for a given distance
* @deprecated use {SloppyMath#haversinMeters} to get distance in meters, law of cosines is being removed
*/
@Deprecated
FACTOR {
@Override
public double calculate(double sourceLatitude, double sourceLongitude, double targetLatitude, double targetLongitude, DistanceUnit unit) {
double longitudeDifference = targetLongitude - sourceLongitude;
double a = Math.toRadians(90D - sourceLatitude);
double c = Math.toRadians(90D - targetLatitude);
return (Math.cos(a) * Math.cos(c)) + (Math.sin(a) * Math.sin(c) * Math.cos(Math.toRadians(longitudeDifference)));
}
@Override
public double normalize(double distance, DistanceUnit unit) {
return Math.cos(distance / unit.getEarthRadius());
}
@Override
public FixedSourceDistance fixedSourceDistance(double sourceLatitude, double sourceLongitude, DistanceUnit unit) {
return new FactorFixedSourceDistance(sourceLatitude, sourceLongitude);
}
},
/**
* Calculates distance as points on a globe.
* @deprecated use {GeoUtils#arcDistance}
*/
@Deprecated
ARC {
@Override
public double calculate(double sourceLatitude, double sourceLongitude, double targetLatitude, double targetLongitude, DistanceUnit unit) {
double result = SloppyMath.haversinMeters(sourceLatitude, sourceLongitude, targetLatitude, targetLongitude);
return unit.fromMeters(result);
}
@Override
public double normalize(double distance, DistanceUnit unit) {
return distance;
}
@Override
public FixedSourceDistance fixedSourceDistance(double sourceLatitude, double sourceLongitude, DistanceUnit unit) {
return new ArcFixedSourceDistance(sourceLatitude, sourceLongitude, unit);
}
},
/**
* Calculates distance as points on a globe in a sloppy way. Close to the pole areas the accuracy
* of this function decreases.
*/
@Deprecated
SLOPPY_ARC {
@Override
public double normalize(double distance, DistanceUnit unit) {
return distance;
}
@Override
public double calculate(double sourceLatitude, double sourceLongitude, double targetLatitude, double targetLongitude, DistanceUnit unit) {
return unit.fromMeters(SloppyMath.haversinMeters(sourceLatitude, sourceLongitude, targetLatitude, targetLongitude));
}
@Override
public FixedSourceDistance fixedSourceDistance(double sourceLatitude, double sourceLongitude, DistanceUnit unit) {
return new SloppyArcFixedSourceDistance(sourceLatitude, sourceLongitude, unit);
}
};
public static GeoDistance readFromStream(StreamInput in) throws IOException {
int ord = in.readVInt();
if (ord < 0 || ord >= values().length) {
throw new IOException("Unknown GeoDistance ordinal [" + ord + "]");
}
return GeoDistance.values()[ord];
}
@Override
public void writeTo(StreamOutput out) throws IOException {
out.writeVInt(this.ordinal());
}
/**
* Default {GeoDistance} function. This method should be used, If no specific function has been selected.
* This is an alias for SLOPPY_ARC
*/
@Deprecated
public static final GeoDistance DEFAULT = SLOPPY_ARC;
public abstract double normalize(double distance, DistanceUnit unit);
public abstract double calculate(double sourceLatitude, double sourceLongitude, double targetLatitude, double targetLongitude, DistanceUnit unit);
public abstract FixedSourceDistance fixedSourceDistance(double sourceLatitude, double sourceLongitude, DistanceUnit unit);
private static final double MIN_LAT = Math.toRadians(-90d); // -PI/2
private static final double MAX_LAT = Math.toRadians(90d); // PI/2
private static final double MIN_LON = Math.toRadians(-180d); // -PI
private static final double MAX_LON = Math.toRadians(180d); // PI
public static DistanceBoundingCheck distanceBoundingCheck(double sourceLatitude, double sourceLongitude, double distance, DistanceUnit unit) {
// angular distance in radians on a great circle
// assume worst-case: use the minor axis
double radDist = unit.toMeters(distance) / GeoUtils.EARTH_SEMI_MINOR_AXIS;
double radLat = Math.toRadians(sourceLatitude);
double radLon = Math.toRadians(sourceLongitude);
double minLat = radLat - radDist;
double maxLat = radLat + radDist;
double minLon, maxLon;
if (minLat > MIN_LAT && maxLat < MAX_LAT) {
double deltaLon = Math.asin(Math.sin(radDist) / Math.cos(radLat));
minLon = radLon - deltaLon;
if (minLon < MIN_LON) {
minLon += 2d * Math.PI;
}
maxLon = radLon + deltaLon;
if (maxLon > MAX_LON) {
maxLon -= 2d * Math.PI;
}
} else {
// a pole is within the distance
minLat = Math.max(minLat, MIN_LAT);
maxLat = Math.min(maxLat, MAX_LAT);
minLon = MIN_LON;
maxLon = MAX_LON;
}
GeoPoint topLeft = new GeoPoint(Math.toDegrees(maxLat), Math.toDegrees(minLon));
GeoPoint bottomRight = new GeoPoint(Math.toDegrees(minLat), Math.toDegrees(maxLon));
if (minLon > maxLon) {
return new Meridian180DistanceBoundingCheck(topLeft, bottomRight);
}
return new SimpleDistanceBoundingCheck(topLeft, bottomRight);
}
/**
* Get a {GeoDistance} according to a given name. Valid values are
*
*
* - plane for
GeoDistance.PLANE
* - sloppy_arc for
GeoDistance.SLOPPY_ARC
* - factor for
GeoDistance.FACTOR
* - arc for
GeoDistance.ARC
*
*
* @param name name of the {GeoDistance}
* @return a {GeoDistance}
*/
public static GeoDistance fromString(String name) {
name = name.toLowerCase(Locale.ROOT);
if ("plane".equals(name)) {
return PLANE;
} else if ("arc".equals(name)) {
return ARC;
} else if ("sloppy_arc".equals(name)) {
return SLOPPY_ARC;
} else if ("factor".equals(name)) {
return FACTOR;
}
throw new IllegalArgumentException("No geo distance for [" + name + "]");
}
public interface FixedSourceDistance {
double calculate(double targetLatitude, double targetLongitude);
}
public interface DistanceBoundingCheck {
boolean isWithin(double targetLatitude, double targetLongitude);
GeoPoint topLeft();
GeoPoint bottomRight();
}
public static final AlwaysDistanceBoundingCheck ALWAYS_INSTANCE = new AlwaysDistanceBoundingCheck();
private static class AlwaysDistanceBoundingCheck implements DistanceBoundingCheck {
@Override
public boolean isWithin(double targetLatitude, double targetLongitude) {
return true;
}
@Override
public GeoPoint topLeft() {
return null;
}
@Override
public GeoPoint bottomRight() {
return null;
}
}
public static class Meridian180DistanceBoundingCheck implements DistanceBoundingCheck {
private final GeoPoint topLeft;
private final GeoPoint bottomRight;
public Meridian180DistanceBoundingCheck(GeoPoint topLeft, GeoPoint bottomRight) {
this.topLeft = topLeft;
this.bottomRight = bottomRight;
}
@Override
public boolean isWithin(double targetLatitude, double targetLongitude) {
return (targetLatitude >= bottomRight.lat() && targetLatitude <= topLeft.lat()) &&
(targetLongitude >= topLeft.lon() || targetLongitude <= bottomRight.lon());
}
@Override
public GeoPoint topLeft() {
return topLeft;
}
@Override
public GeoPoint bottomRight() {
return bottomRight;
}
}
public static class SimpleDistanceBoundingCheck implements DistanceBoundingCheck {
private final GeoPoint topLeft;
private final GeoPoint bottomRight;
public SimpleDistanceBoundingCheck(GeoPoint topLeft, GeoPoint bottomRight) {
this.topLeft = topLeft;
this.bottomRight = bottomRight;
}
@Override
public boolean isWithin(double targetLatitude, double targetLongitude) {
return (targetLatitude >= bottomRight.lat() && targetLatitude <= topLeft.lat()) &&
(targetLongitude >= topLeft.lon() && targetLongitude <= bottomRight.lon());
}
@Override
public GeoPoint topLeft() {
return topLeft;
}
@Override
public GeoPoint bottomRight() {
return bottomRight;
}
}
public static class PlaneFixedSourceDistance implements FixedSourceDistance {
private final double sourceLatitude;
private final double sourceLongitude;
private final double distancePerDegree;
public PlaneFixedSourceDistance(double sourceLatitude, double sourceLongitude, DistanceUnit unit) {
this.sourceLatitude = sourceLatitude;
this.sourceLongitude = sourceLongitude;
this.distancePerDegree = unit.getDistancePerDegree();
}
@Override
public double calculate(double targetLatitude, double targetLongitude) {
double px = targetLongitude - sourceLongitude;
double py = targetLatitude - sourceLatitude;
return Math.sqrt(px * px + py * py) * distancePerDegree;
}
}
public static class FactorFixedSourceDistance implements FixedSourceDistance {
private final double sourceLongitude;
private final double a;
private final double sinA;
private final double cosA;
public FactorFixedSourceDistance(double sourceLatitude, double sourceLongitude) {
this.sourceLongitude = sourceLongitude;
this.a = Math.toRadians(90D - sourceLatitude);
this.sinA = Math.sin(a);
this.cosA = Math.cos(a);
}
@Override
public double calculate(double targetLatitude, double targetLongitude) {
double longitudeDifference = targetLongitude - sourceLongitude;
double c = Math.toRadians(90D - targetLatitude);
return (cosA * Math.cos(c)) + (sinA * Math.sin(c) * Math.cos(Math.toRadians(longitudeDifference)));
}
}
/**
* Basic implementation of {FixedSourceDistance}. This class keeps the basic parameters for a distance
* functions based on a fixed source. Namely latitude, longitude and unit.
*/
public abstract static class FixedSourceDistanceBase implements FixedSourceDistance {
protected final double sourceLatitude;
protected final double sourceLongitude;
protected final DistanceUnit unit;
public FixedSourceDistanceBase(double sourceLatitude, double sourceLongitude, DistanceUnit unit) {
this.sourceLatitude = sourceLatitude;
this.sourceLongitude = sourceLongitude;
this.unit = unit;
}
}
public static class ArcFixedSourceDistance extends FixedSourceDistanceBase {
public ArcFixedSourceDistance(double sourceLatitude, double sourceLongitude, DistanceUnit unit) {
super(sourceLatitude, sourceLongitude, unit);
}
@Override
public double calculate(double targetLatitude, double targetLongitude) {
return ARC.calculate(sourceLatitude, sourceLongitude, targetLatitude, targetLongitude, unit);
}
}
public static class SloppyArcFixedSourceDistance extends FixedSourceDistanceBase {
public SloppyArcFixedSourceDistance(double sourceLatitude, double sourceLongitude, DistanceUnit unit) {
super(sourceLatitude, sourceLongitude, unit);
}
@Override
public double calculate(double targetLatitude, double targetLongitude) {
return SLOPPY_ARC.calculate(sourceLatitude, sourceLongitude, targetLatitude, targetLongitude, unit);
}
}
/**
* Return a {SortedNumericDoubleValues} instance that returns the distances to a list of geo-points for each document.
*/
public static SortedNumericDoubleValues distanceValues(final MultiGeoPointValues geoPointValues, final FixedSourceDistance... distances) {
final GeoPointValues singleValues = FieldData.unwrapSingleton(geoPointValues);
if (singleValues != null && distances.length == 1) {
final Bits docsWithField = FieldData.unwrapSingletonBits(geoPointValues);
return FieldData.singleton(new NumericDoubleValues() {
@Override
public double get(int docID) {
if (docsWithField != null && !docsWithField.get(docID)) {
return 0d;
}
final GeoPoint point = singleValues.get(docID);
return distances[0].calculate(point.lat(), point.lon());
}
}, docsWithField);
} else {
return new SortingNumericDoubleValues() {
@Override
public void setDocument(int doc) {
geoPointValues.setDocument(doc);
resize(geoPointValues.count() * distances.length);
int valueCounter = 0;
for (FixedSourceDistance distance : distances) {
for (int i = 0; i < geoPointValues.count(); ++i) {
final GeoPoint point = geoPointValues.valueAt(i);
values[valueCounter] = distance.calculate(point.lat(), point.lon());
valueCounter++;
}
}
sort();
}
};
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy