LUCENE-7212: Add outside distance classes and methods.

This commit is contained in:
Karl Wright 2016-05-18 09:03:52 -04:00
parent 2a810938ba
commit cc26341345
5 changed files with 363 additions and 8 deletions

View File

@ -22,10 +22,12 @@ import org.apache.lucene.search.FieldDoc;
import org.apache.lucene.search.SortField;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.FieldType;
import org.apache.lucene.geo.Polygon;
import org.apache.lucene.spatial3d.geom.PlanetModel;
import org.apache.lucene.spatial3d.geom.GeoPoint;
import org.apache.lucene.spatial3d.geom.GeoDistanceShape;
import org.apache.lucene.spatial3d.geom.GeoOutsideDistance;
/**
* An per-document 3D location field.
@ -256,7 +258,7 @@ public class Geo3DDocValuesField extends Field {
}
/**
* Creates a SortField for sorting by distance from a point.
* Creates a SortField for sorting by distance within a circle.
* <p>
* This sort orders documents by ascending distance from the location. The value returned in {@link FieldDoc} for
* the hits contains a Double instance with the distance in meters.
@ -264,14 +266,14 @@ public class Geo3DDocValuesField extends Field {
* If a document is missing the field, then by default it is treated as having {@link Double#POSITIVE_INFINITY} distance
* (missing values sort last).
* <p>
* If a document contains multiple values for the field, the <i>closest</i> distance to the location is used.
* If a document contains multiple values for the field, the <i>closest</i> distance from the circle center is used.
*
* @param field field name. must not be null.
* @param latitude latitude at the center: must be within standard +/-90 coordinate bounds.
* @param longitude longitude at the center: must be within standard +/-180 coordinate bounds.
* @param maxRadiusMeters is the maximum radius in meters.
* @return SortField ordering documents by distance
* @throws IllegalArgumentException if {@code field} is null or location has invalid coordinates.
* @throws IllegalArgumentException if {@code field} is null or circle has invalid coordinates.
*/
public static SortField newDistanceSort(final String field, final double latitude, final double longitude, final double maxRadiusMeters) {
final GeoDistanceShape shape = Geo3DUtil.fromDistance(latitude, longitude, maxRadiusMeters);
@ -287,18 +289,138 @@ public class Geo3DDocValuesField extends Field {
* If a document is missing the field, then by default it is treated as having {@link Double#POSITIVE_INFINITY} distance
* (missing values sort last).
* <p>
* If a document contains multiple values for the field, the <i>closest</i> distance to the location is used.
* If a document contains multiple values for the field, the <i>closest</i> distance along the path is used.
*
* @param field field name. must not be null.
* @param pathLatitudes latitude values for points of the path: must be within standard +/-90 coordinate bounds.
* @param pathLongitudes longitude values for points of the path: must be within standard +/-180 coordinate bounds.
* @param pathWidthMeters width of the path in meters.
* @return SortField ordering documents by distance
* @throws IllegalArgumentException if {@code field} is null or location has invalid coordinates.
* @throws IllegalArgumentException if {@code field} is null or path has invalid coordinates.
*/
public static SortField newPathSort(final String field, final double[] pathLatitudes, final double[] pathLongitudes, final double pathWidthMeters) {
final GeoDistanceShape shape = Geo3DUtil.fromPath(pathLatitudes, pathLongitudes, pathWidthMeters);
return new Geo3DPointSortField(field, shape);
}
// Outside distances
/**
* Creates a SortField for sorting by outside distance from a circle.
* <p>
* This sort orders documents by ascending outside distance from the circle. Points within the circle have distance 0.0.
* The value returned in {@link FieldDoc} for
* the hits contains a Double instance with the distance in meters.
* <p>
* If a document is missing the field, then by default it is treated as having {@link Double#POSITIVE_INFINITY} distance
* (missing values sort last).
* <p>
* If a document contains multiple values for the field, the <i>closest</i> distance to the circle is used.
*
* @param field field name. must not be null.
* @param latitude latitude at the center: must be within standard +/-90 coordinate bounds.
* @param longitude longitude at the center: must be within standard +/-180 coordinate bounds.
* @param maxRadiusMeters is the maximum radius in meters.
* @return SortField ordering documents by distance
* @throws IllegalArgumentException if {@code field} is null or location has invalid coordinates.
*/
public static SortField newOutsideDistanceSort(final String field, final double latitude, final double longitude, final double maxRadiusMeters) {
final GeoOutsideDistance shape = Geo3DUtil.fromDistance(latitude, longitude, maxRadiusMeters);
return new Geo3DPointOutsideSortField(field, shape);
}
/**
* Creates a SortField for sorting by outside distance from a box.
* <p>
* This sort orders documents by ascending outside distance from the box. Points within the box have distance 0.0.
* The value returned in {@link FieldDoc} for
* the hits contains a Double instance with the distance in meters.
* <p>
* If a document is missing the field, then by default it is treated as having {@link Double#POSITIVE_INFINITY} distance
* (missing values sort last).
* <p>
* If a document contains multiple values for the field, the <i>closest</i> distance to the box is used.
*
* @param field field name. must not be null.
* @param minLatitude latitude lower bound: must be within standard +/-90 coordinate bounds.
* @param maxLatitude latitude upper bound: must be within standard +/-90 coordinate bounds.
* @param minLongitude longitude lower bound: must be within standard +/-180 coordinate bounds.
* @param maxLongitude longitude upper bound: must be within standard +/-180 coordinate bounds.
* @return SortField ordering documents by distance
* @throws IllegalArgumentException if {@code field} is null or box has invalid coordinates.
*/
public static SortField newOutsideBoxSort(final String field, final double minLatitude, final double maxLatitude, final double minLongitude, final double maxLongitude) {
final GeoOutsideDistance shape = Geo3DUtil.fromBox(minLatitude, maxLatitude, minLongitude, maxLongitude);
return new Geo3DPointOutsideSortField(field, shape);
}
/**
* Creates a SortField for sorting by outside distance from a polygon.
* <p>
* This sort orders documents by ascending outside distance from the polygon. Points within the polygon have distance 0.0.
* The value returned in {@link FieldDoc} for
* the hits contains a Double instance with the distance in meters.
* <p>
* If a document is missing the field, then by default it is treated as having {@link Double#POSITIVE_INFINITY} distance
* (missing values sort last).
* <p>
* If a document contains multiple values for the field, the <i>closest</i> distance to the polygon is used.
*
* @param field field name. must not be null.
* @param polygons is the list of polygons to use to construct the query; must be at least one.
* @return SortField ordering documents by distance
* @throws IllegalArgumentException if {@code field} is null or polygon has invalid coordinates.
*/
public static SortField newOutsidePolygonSort(final String field, final Polygon... polygons) {
final GeoOutsideDistance shape = Geo3DUtil.fromPolygon(polygons);
return new Geo3DPointOutsideSortField(field, shape);
}
/**
* Creates a SortField for sorting by outside distance from a large polygon. This differs from the related newOutsideLargePolygonSort in that it
* does little or no legality checking and is optimized for very large numbers of polygon edges.
* <p>
* This sort orders documents by ascending outside distance from the polygon. Points within the polygon have distance 0.0.
* The value returned in {@link FieldDoc} for
* the hits contains a Double instance with the distance in meters.
* <p>
* If a document is missing the field, then by default it is treated as having {@link Double#POSITIVE_INFINITY} distance
* (missing values sort last).
* <p>
* If a document contains multiple values for the field, the <i>closest</i> distance to the polygon is used.
*
* @param field field name. must not be null.
* @param polygons is the list of polygons to use to construct the query; must be at least one.
* @return SortField ordering documents by distance
* @throws IllegalArgumentException if {@code field} is null or polygon has invalid coordinates.
*/
public static SortField newOutsideLargePolygonSort(final String field, final Polygon... polygons) {
final GeoOutsideDistance shape = Geo3DUtil.fromLargePolygon(polygons);
return new Geo3DPointOutsideSortField(field, shape);
}
/**
* Creates a SortField for sorting by outside distance from a path.
* <p>
* This sort orders documents by ascending outside distance from the described path. Points within the path
* are given the distance of 0.0. The value returned in {@link FieldDoc} for
* the hits contains a Double instance with the distance in meters.
* <p>
* If a document is missing the field, then by default it is treated as having {@link Double#POSITIVE_INFINITY} distance
* (missing values sort last).
* <p>
* If a document contains multiple values for the field, the <i>closest</i> distance from the path is used.
*
* @param field field name. must not be null.
* @param pathLatitudes latitude values for points of the path: must be within standard +/-90 coordinate bounds.
* @param pathLongitudes longitude values for points of the path: must be within standard +/-180 coordinate bounds.
* @param pathWidthMeters width of the path in meters.
* @return SortField ordering documents by distance
* @throws IllegalArgumentException if {@code field} is null or path has invalid coordinates.
*/
public static SortField newOutsidePathSort(final String field, final double[] pathLatitudes, final double[] pathLongitudes, final double pathWidthMeters) {
final GeoOutsideDistance shape = Geo3DUtil.fromPath(pathLatitudes, pathLongitudes, pathWidthMeters);
return new Geo3DPointOutsideSortField(field, shape);
}
}

View File

@ -129,9 +129,6 @@ public final class Geo3DPoint extends Field {
* @return query matching points within this polygon
*/
public static Query newLargePolygonQuery(final String field, final Polygon... polygons) {
if (polygons.length < 1) {
throw new IllegalArgumentException("need at least one polygon");
}
final GeoShape shape = Geo3DUtil.fromLargePolygon(polygons);
return newShapeQuery(field, shape);
}

View File

@ -0,0 +1,137 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF 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.apache.lucene.spatial3d;
import java.io.IOException;
import org.apache.lucene.index.DocValues;
import org.apache.lucene.index.FieldInfo;
import org.apache.lucene.index.LeafReader;
import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.index.SortedNumericDocValues;
import org.apache.lucene.search.FieldComparator;
import org.apache.lucene.search.LeafFieldComparator;
import org.apache.lucene.search.Scorer;
import org.apache.lucene.spatial3d.geom.GeoOutsideDistance;
import org.apache.lucene.spatial3d.geom.DistanceStyle;
import org.apache.lucene.spatial3d.geom.PlanetModel;
/**
* Compares documents by outside distance, using a GeoOutsideDistance to compute the distance
*/
class Geo3DPointOutsideDistanceComparator extends FieldComparator<Double> implements LeafFieldComparator {
final String field;
final GeoOutsideDistance distanceShape;
final double[] values;
double bottomDistance;
double topValue;
SortedNumericDocValues currentDocs;
public Geo3DPointOutsideDistanceComparator(String field, final GeoOutsideDistance distanceShape, int numHits) {
this.field = field;
this.distanceShape = distanceShape;
this.values = new double[numHits];
}
@Override
public void setScorer(Scorer scorer) {}
@Override
public int compare(int slot1, int slot2) {
return Double.compare(values[slot1], values[slot2]);
}
@Override
public void setBottom(int slot) {
bottomDistance = values[slot];
}
@Override
public void setTopValue(Double value) {
topValue = value.doubleValue();
}
@Override
public int compareBottom(int doc) throws IOException {
currentDocs.setDocument(doc);
int numValues = currentDocs.count();
if (numValues == 0) {
return Double.compare(bottomDistance, Double.POSITIVE_INFINITY);
}
int cmp = -1;
for (int i = 0; i < numValues; i++) {
long encoded = currentDocs.valueAt(i);
// Test against bounds.
// First we need to decode...
final double x = Geo3DDocValuesField.decodeXValue(encoded);
final double y = Geo3DDocValuesField.decodeYValue(encoded);
final double z = Geo3DDocValuesField.decodeZValue(encoded);
cmp = Math.max(cmp, Double.compare(bottomDistance, distanceShape.computeOutsideDistance(DistanceStyle.ARC, x, y, z)));
}
return cmp;
}
@Override
public void copy(int slot, int doc) throws IOException {
values[slot] = computeMinimumDistance(doc);
}
@Override
public LeafFieldComparator getLeafComparator(LeafReaderContext context) throws IOException {
LeafReader reader = context.reader();
FieldInfo info = reader.getFieldInfos().fieldInfo(field);
if (info != null) {
Geo3DDocValuesField.checkCompatible(info);
}
currentDocs = DocValues.getSortedNumeric(reader, field);
return this;
}
@Override
public Double value(int slot) {
// Return the arc distance
return Double.valueOf(values[slot] * PlanetModel.WGS84_MEAN);
}
@Override
public int compareTop(int doc) throws IOException {
return Double.compare(topValue, computeMinimumDistance(doc));
}
double computeMinimumDistance(final int doc) {
currentDocs.setDocument(doc);
double minValue = Double.POSITIVE_INFINITY;
final int numValues = currentDocs.count();
for (int i = 0; i < numValues; i++) {
final long encoded = currentDocs.valueAt(i);
final double distance = distanceShape.computeOutsideDistance(DistanceStyle.ARC,
Geo3DDocValuesField.decodeXValue(encoded),
Geo3DDocValuesField.decodeYValue(encoded),
Geo3DDocValuesField.decodeZValue(encoded));
minValue = Math.min(minValue, distance);
}
return minValue;
}
}

View File

@ -0,0 +1,96 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF 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.apache.lucene.spatial3d;
import java.io.IOException;
import org.apache.lucene.search.FieldComparator;
import org.apache.lucene.search.SortField;
import org.apache.lucene.spatial3d.geom.GeoOutsideDistance;
/**
* Sorts by outside distance from an origin location.
*/
final class Geo3DPointOutsideSortField extends SortField {
final GeoOutsideDistance distanceShape;
Geo3DPointOutsideSortField(final String field, final GeoOutsideDistance distanceShape) {
super(field, SortField.Type.CUSTOM);
if (field == null) {
throw new IllegalArgumentException("field must not be null");
}
if (distanceShape == null) {
throw new IllegalArgumentException("distanceShape must not be null");
}
this.distanceShape = distanceShape;
setMissingValue(Double.POSITIVE_INFINITY);
}
@Override
public FieldComparator<?> getComparator(int numHits, int sortPos) throws IOException {
return new Geo3DPointOutsideDistanceComparator(getField(), distanceShape, numHits);
}
@Override
public Double getMissingValue() {
return (Double) super.getMissingValue();
}
@Override
public void setMissingValue(Object missingValue) {
if (Double.valueOf(Double.POSITIVE_INFINITY).equals(missingValue) == false) {
throw new IllegalArgumentException("Missing value can only be Double.POSITIVE_INFINITY (missing values last), but got " + missingValue);
}
this.missingValue = missingValue;
}
@Override
public int hashCode() {
final int prime = 31;
int result = super.hashCode();
long temp;
temp = distanceShape.hashCode();
result = prime * result + (int) (temp ^ (temp >>> 32));
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (!super.equals(obj)) return false;
if (getClass() != obj.getClass()) return false;
final Geo3DPointSortField other = (Geo3DPointSortField) obj;
return distanceShape.equals(other.distanceShape);
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("<outsideDistanceShape:");
builder.append('"');
builder.append(getField());
builder.append('"');
builder.append(" shape=");
builder.append(distanceShape);
if (Double.POSITIVE_INFINITY != getMissingValue()) {
builder.append(" missingValue=" + getMissingValue());
}
builder.append('>');
return builder.toString();
}
}

View File

@ -136,6 +136,9 @@ class Geo3DUtil {
* @return the large GeoPolygon.
*/
static GeoPolygon fromLargePolygon(final Polygon... polygons) {
if (polygons.length < 1) {
throw new IllegalArgumentException("need at least one polygon");
}
return GeoPolygonFactory.makeLargeGeoPolygon(PlanetModel.WGS84, convertToDescription(polygons));
}