mirror of https://github.com/apache/lucene.git
Merge remote-tracking branch 'origin/master' into gradle-master
This commit is contained in:
commit
c51a4a030b
|
@ -93,7 +93,8 @@ API Changes
|
|||
|
||||
New Features
|
||||
---------------------
|
||||
(No changes)
|
||||
|
||||
* LUCENE-8903: Add LatLonShape point query. (Ignacio Vera)
|
||||
|
||||
Improvements
|
||||
---------------------
|
||||
|
|
|
@ -132,4 +132,17 @@ public class LatLonShape {
|
|||
}
|
||||
return new LatLonShapePolygonQuery(field, queryRelation, polygons);
|
||||
}
|
||||
|
||||
/** create a query to find all indexed shapes that comply the {@link QueryRelation} with the provided point
|
||||
**/
|
||||
public static Query newPointQuery(String field, QueryRelation queryRelation, double[]... points) {
|
||||
if (queryRelation == QueryRelation.CONTAINS && points.length > 1) {
|
||||
BooleanQuery.Builder builder = new BooleanQuery.Builder();
|
||||
for (int i =0; i < points.length; i++) {
|
||||
builder.add(newPointQuery(field, queryRelation, points[i]), BooleanClause.Occur.MUST);
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
return new LatLonShapePointQuery(field, queryRelation, points);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,125 @@
|
|||
/*
|
||||
* 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.document;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.apache.lucene.geo.Component2D;
|
||||
import org.apache.lucene.geo.GeoEncodingUtils;
|
||||
import org.apache.lucene.geo.Point2D;
|
||||
import org.apache.lucene.index.PointValues.Relation;
|
||||
import org.apache.lucene.util.NumericUtils;
|
||||
|
||||
/**
|
||||
* Finds all previously indexed shapes that intersect the specified bounding box.
|
||||
*
|
||||
* <p>The field must be indexed using
|
||||
* {@link LatLonShape#createIndexableFields} added per document.
|
||||
*
|
||||
* @lucene.experimental
|
||||
**/
|
||||
final class LatLonShapePointQuery extends ShapeQuery {
|
||||
final Component2D point2D;
|
||||
final double[][] points;
|
||||
|
||||
public LatLonShapePointQuery(String field, ShapeField.QueryRelation queryRelation, double[][] points) {
|
||||
super(field, queryRelation);
|
||||
this.points = points;
|
||||
this.point2D = Point2D.create(points);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Relation relateRangeBBoxToQuery(int minXOffset, int minYOffset, byte[] minTriangle,
|
||||
int maxXOffset, int maxYOffset, byte[] maxTriangle) {
|
||||
double minLat = GeoEncodingUtils.decodeLatitude(NumericUtils.sortableBytesToInt(minTriangle, minYOffset));
|
||||
double minLon = GeoEncodingUtils.decodeLongitude(NumericUtils.sortableBytesToInt(minTriangle, minXOffset));
|
||||
double maxLat = GeoEncodingUtils.decodeLatitude(NumericUtils.sortableBytesToInt(maxTriangle, maxYOffset));
|
||||
double maxLon = GeoEncodingUtils.decodeLongitude(NumericUtils.sortableBytesToInt(maxTriangle, maxXOffset));
|
||||
|
||||
// check internal node against query
|
||||
return point2D.relate(minLon, maxLon, minLat, maxLat);
|
||||
}
|
||||
|
||||
/** returns true if the query matches the encoded triangle */
|
||||
@Override
|
||||
protected boolean queryMatches(byte[] t, ShapeField.DecodedTriangle scratchTriangle, ShapeField.QueryRelation queryRelation) {
|
||||
ShapeField.decodeTriangle(t, scratchTriangle);
|
||||
|
||||
double alat = GeoEncodingUtils.decodeLatitude(scratchTriangle.aY);
|
||||
double alon = GeoEncodingUtils.decodeLongitude(scratchTriangle.aX);
|
||||
double blat = GeoEncodingUtils.decodeLatitude(scratchTriangle.bY);
|
||||
double blon = GeoEncodingUtils.decodeLongitude(scratchTriangle.bX);
|
||||
double clat = GeoEncodingUtils.decodeLatitude(scratchTriangle.cY);
|
||||
double clon = GeoEncodingUtils.decodeLongitude(scratchTriangle.cX);
|
||||
|
||||
switch (queryRelation) {
|
||||
case INTERSECTS:
|
||||
return point2D.relateTriangle(alon, alat, blon, blat, clon, clat) != Relation.CELL_OUTSIDE_QUERY;
|
||||
case WITHIN:
|
||||
return point2D.relateTriangle(alon, alat, blon, blat, clon, clat) == Relation.CELL_INSIDE_QUERY;
|
||||
case DISJOINT:
|
||||
return point2D.relateTriangle(alon, alat, blon, blat, clon, clat) == Relation.CELL_OUTSIDE_QUERY;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unsupported query type :[" + queryRelation + "]");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Component2D.WithinRelation queryWithin(byte[] t, ShapeField.DecodedTriangle scratchTriangle) {
|
||||
ShapeField.decodeTriangle(t, scratchTriangle);
|
||||
|
||||
double alat = GeoEncodingUtils.decodeLatitude(scratchTriangle.aY);
|
||||
double alon = GeoEncodingUtils.decodeLongitude(scratchTriangle.aX);
|
||||
double blat = GeoEncodingUtils.decodeLatitude(scratchTriangle.bY);
|
||||
double blon = GeoEncodingUtils.decodeLongitude(scratchTriangle.bX);
|
||||
double clat = GeoEncodingUtils.decodeLatitude(scratchTriangle.cY);
|
||||
double clon = GeoEncodingUtils.decodeLongitude(scratchTriangle.cX);
|
||||
|
||||
return point2D.withinTriangle(alon, alat, scratchTriangle.ab, blon, blat, scratchTriangle.bc, clon, clat, scratchTriangle.ca);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
return sameClassAs(o) && equalsTo(getClass().cast(o));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean equalsTo(Object o) {
|
||||
return super.equalsTo(o) && Arrays.equals(points, ((LatLonShapePointQuery)o).points);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int hash = super.hashCode();
|
||||
hash = 31 * hash + Arrays.hashCode(points);
|
||||
return hash;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString(String field) {
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
sb.append(getClass().getSimpleName());
|
||||
sb.append(':');
|
||||
if (this.field.equals(field) == false) {
|
||||
sb.append(" field=");
|
||||
sb.append(this.field);
|
||||
sb.append(':');
|
||||
}
|
||||
sb.append("lat = " + points[0][0] + " , lon = " + points[0][1]);
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
|
@ -116,4 +116,17 @@ public class XYShape {
|
|||
}
|
||||
return new XYShapePolygonQuery(field, queryRelation, polygons);
|
||||
}
|
||||
|
||||
/** create a query to find all indexed shapes that comply the {@link QueryRelation} with the provided point
|
||||
**/
|
||||
public static Query newPointQuery(String field, QueryRelation queryRelation, float[]... points) {
|
||||
if (queryRelation == QueryRelation.CONTAINS && points.length > 1) {
|
||||
BooleanQuery.Builder builder = new BooleanQuery.Builder();
|
||||
for (int i =0; i < points.length; i++) {
|
||||
builder.add(newPointQuery(field, queryRelation, points[i]), BooleanClause.Occur.MUST);
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
return new XYShapePointQuery(field, queryRelation, points);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,127 @@
|
|||
/*
|
||||
* 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.document;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.apache.lucene.geo.Component2D;
|
||||
import org.apache.lucene.geo.Point2D;
|
||||
import org.apache.lucene.geo.XYEncodingUtils;
|
||||
import org.apache.lucene.index.PointValues.Relation;
|
||||
import org.apache.lucene.util.NumericUtils;
|
||||
|
||||
import static org.apache.lucene.geo.XYEncodingUtils.decode;
|
||||
|
||||
/**
|
||||
* Finds all previously indexed shapes that intersect the specified bounding box.
|
||||
*
|
||||
* <p>The field must be indexed using
|
||||
* {@link XYShape#createIndexableFields} added per document.
|
||||
*
|
||||
* @lucene.experimental
|
||||
**/
|
||||
final class XYShapePointQuery extends ShapeQuery {
|
||||
final Component2D point2D;
|
||||
final float[][] points;
|
||||
|
||||
public XYShapePointQuery(String field, ShapeField.QueryRelation queryRelation, float[][] points) {
|
||||
super(field, queryRelation);
|
||||
this.points = points;
|
||||
this.point2D = Point2D.create(points);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Relation relateRangeBBoxToQuery(int minXOffset, int minYOffset, byte[] minTriangle,
|
||||
int maxXOffset, int maxYOffset, byte[] maxTriangle) {
|
||||
double minY = XYEncodingUtils.decode(NumericUtils.sortableBytesToInt(minTriangle, minYOffset));
|
||||
double minX = XYEncodingUtils.decode(NumericUtils.sortableBytesToInt(minTriangle, minXOffset));
|
||||
double maxY = XYEncodingUtils.decode(NumericUtils.sortableBytesToInt(maxTriangle, maxYOffset));
|
||||
double maxX = XYEncodingUtils.decode(NumericUtils.sortableBytesToInt(maxTriangle, maxXOffset));
|
||||
|
||||
// check internal node against query
|
||||
return point2D.relate(minX, maxX, minY, maxY);
|
||||
}
|
||||
|
||||
/** returns true if the query matches the encoded triangle */
|
||||
@Override
|
||||
protected boolean queryMatches(byte[] t, ShapeField.DecodedTriangle scratchTriangle, ShapeField.QueryRelation queryRelation) {
|
||||
ShapeField.decodeTriangle(t, scratchTriangle);
|
||||
|
||||
double aY = decode(scratchTriangle.aY);
|
||||
double aX = decode(scratchTriangle.aX);
|
||||
double bY = decode(scratchTriangle.bY);
|
||||
double bX = decode(scratchTriangle.bX);
|
||||
double cY = decode(scratchTriangle.cY);
|
||||
double cX = decode(scratchTriangle.cX);
|
||||
|
||||
switch (queryRelation) {
|
||||
case INTERSECTS:
|
||||
return point2D.relateTriangle(aX, aY, bX, bY, cX, cY) != Relation.CELL_OUTSIDE_QUERY;
|
||||
case WITHIN:
|
||||
return point2D.relateTriangle(aX, aY, bX, bY, cX, cY) == Relation.CELL_INSIDE_QUERY;
|
||||
case DISJOINT:
|
||||
return point2D.relateTriangle(aX, aY, bX, bY, cX, cY) == Relation.CELL_OUTSIDE_QUERY;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unsupported query type :[" + queryRelation + "]");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Component2D.WithinRelation queryWithin(byte[] t, ShapeField.DecodedTriangle scratchTriangle) {
|
||||
ShapeField.decodeTriangle(t, scratchTriangle);
|
||||
|
||||
double aY = decode(scratchTriangle.aY);
|
||||
double aX = decode(scratchTriangle.aX);
|
||||
double bY = decode(scratchTriangle.bY);
|
||||
double bX = decode(scratchTriangle.bX);
|
||||
double cY = decode(scratchTriangle.cY);
|
||||
double cX = decode(scratchTriangle.cX);
|
||||
|
||||
return point2D.withinTriangle(aX, aY, scratchTriangle.ab, bX, bY, scratchTriangle.bc, cX, cY, scratchTriangle.ca);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
return sameClassAs(o) && equalsTo(getClass().cast(o));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean equalsTo(Object o) {
|
||||
return super.equalsTo(o) && Arrays.equals(points, ((XYShapePointQuery)o).points);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int hash = super.hashCode();
|
||||
hash = 31 * hash + Arrays.hashCode(points);
|
||||
return hash;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString(String field) {
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
sb.append(getClass().getSimpleName());
|
||||
sb.append(':');
|
||||
if (this.field.equals(field) == false) {
|
||||
sb.append(" field=");
|
||||
sb.append(this.field);
|
||||
sb.append(':');
|
||||
}
|
||||
sb.append("lat = " + points[0][0] + " , lon = " + points[0][1]);
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,134 @@
|
|||
/*
|
||||
* 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.geo;
|
||||
|
||||
import org.apache.lucene.index.PointValues;
|
||||
|
||||
import static org.apache.lucene.geo.GeoUtils.orient;
|
||||
|
||||
/**
|
||||
* 2D point implementation containing geo spatial logic.
|
||||
*
|
||||
* @lucene.internal
|
||||
*/
|
||||
public class Point2D implements Component2D {
|
||||
|
||||
final private double x;
|
||||
final private double y;
|
||||
|
||||
Point2D(double x, double y) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getMinX() {
|
||||
return x;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getMaxX() {
|
||||
return x;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getMinY() {
|
||||
return y;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getMaxY() {
|
||||
return y;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(double x, double y) {
|
||||
return x == this.x && y == this.y;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PointValues.Relation relate(double minX, double maxX, double minY, double maxY) {
|
||||
if (Component2D.containsPoint(x, y, minX, maxX, minY, maxY)) {
|
||||
return PointValues.Relation.CELL_CROSSES_QUERY;
|
||||
}
|
||||
return PointValues.Relation.CELL_OUTSIDE_QUERY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PointValues.Relation relateTriangle(double minX, double maxX, double minY, double maxY,
|
||||
double ax, double ay, double bx, double by, double cx, double cy) {
|
||||
if (Component2D.containsPoint(x, y, minX, maxX, minY, maxY) == false) {
|
||||
return PointValues.Relation.CELL_OUTSIDE_QUERY;
|
||||
}
|
||||
if (ax == bx && bx == cx && ay == by && by == cy) {
|
||||
return PointValues.Relation.CELL_INSIDE_QUERY;
|
||||
} else if (ax == cx && ay == cy) {
|
||||
// indexed "triangle" is a line:
|
||||
if (orient(ax, ay, bx, by, x, y) == 0) {
|
||||
return PointValues.Relation.CELL_INSIDE_QUERY;
|
||||
}
|
||||
return PointValues.Relation.CELL_OUTSIDE_QUERY;
|
||||
} else if (ax == bx && ay == by) {
|
||||
// indexed "triangle" is a line:
|
||||
if (orient(bx, by, cx, cy, x, y) == 0) {
|
||||
return PointValues.Relation.CELL_INSIDE_QUERY;
|
||||
}
|
||||
return PointValues.Relation.CELL_OUTSIDE_QUERY;
|
||||
} else if (bx == cx && by == cy) {
|
||||
// indexed "triangle" is a line:
|
||||
if (orient(cx, cy, ax, ay, x, y) == 0) {
|
||||
return PointValues.Relation.CELL_INSIDE_QUERY;
|
||||
}
|
||||
return PointValues.Relation.CELL_OUTSIDE_QUERY;
|
||||
} else if (Component2D.pointInTriangle(minX, maxX, minY, maxY, x, y, ax, ay, bx, by, cx, cy) == true) {
|
||||
// indexed "triangle" is a triangle:
|
||||
return PointValues.Relation.CELL_INSIDE_QUERY;
|
||||
}
|
||||
return PointValues.Relation.CELL_OUTSIDE_QUERY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public WithinRelation withinTriangle(double minX, double maxX, double minY, double maxY,
|
||||
double aX, double aY, boolean ab, double bX, double bY, boolean bc, double cX, double cY, boolean ca) {
|
||||
if (aX == bX && aY == bY && aX == cX && aY == cY) {
|
||||
if (contains(aX, aY)) {
|
||||
return WithinRelation.CANDIDATE;
|
||||
}
|
||||
}
|
||||
return WithinRelation.DISJOINT;
|
||||
}
|
||||
|
||||
/** create a Point2D component tree from provided array of LatLon points. */
|
||||
public static Component2D create(double[]... points) {
|
||||
Point2D components[] = new Point2D[points.length];
|
||||
for (int i = 0; i < components.length; ++i) {
|
||||
components[i] = new Point2D(GeoEncodingUtils.decodeLongitude(GeoEncodingUtils.encodeLongitude(points[i][1]))
|
||||
, GeoEncodingUtils.decodeLatitude(GeoEncodingUtils.encodeLatitude(points[i][0])));
|
||||
}
|
||||
return ComponentTree.create(components);
|
||||
}
|
||||
|
||||
/** create a Point2D component tree from provided array of XY points. */
|
||||
public static Component2D create(float[]... xyPoints) {
|
||||
Point2D components[] = new Point2D[xyPoints.length];
|
||||
for (int i = 0; i < components.length; ++i) {
|
||||
components[i] = new Point2D(xyPoints[i][0], xyPoints[i][1]);
|
||||
}
|
||||
return ComponentTree.create(components);
|
||||
}
|
||||
}
|
|
@ -24,11 +24,13 @@ import org.apache.lucene.geo.Component2D;
|
|||
import org.apache.lucene.geo.GeoTestUtil;
|
||||
import org.apache.lucene.geo.Line;
|
||||
import org.apache.lucene.geo.Line2D;
|
||||
import org.apache.lucene.geo.Point2D;
|
||||
import org.apache.lucene.geo.Polygon;
|
||||
import org.apache.lucene.geo.Polygon2D;
|
||||
import org.apache.lucene.geo.Rectangle;
|
||||
import org.apache.lucene.search.Query;
|
||||
import org.apache.lucene.search.QueryUtils;
|
||||
import org.apache.lucene.util.TestUtil;
|
||||
|
||||
import static org.apache.lucene.geo.GeoEncodingUtils.decodeLatitude;
|
||||
import static org.apache.lucene.geo.GeoEncodingUtils.decodeLongitude;
|
||||
|
@ -66,6 +68,11 @@ public abstract class BaseLatLonShapeTestCase extends BaseShapeTestCase {
|
|||
return LatLonShape.newPolygonQuery(field, queryRelation, Arrays.stream(polygons).toArray(Polygon[]::new));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Query newPointsQuery(String field, QueryRelation queryRelation, Object... points) {
|
||||
return LatLonShape.newPointQuery(field, queryRelation, Arrays.stream(points).toArray(double[][]::new));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Component2D toLine2D(Object... lines) {
|
||||
return Line2D.create(Arrays.stream(lines).toArray(Line[]::new));
|
||||
|
@ -76,11 +83,27 @@ public abstract class BaseLatLonShapeTestCase extends BaseShapeTestCase {
|
|||
return Polygon2D.create(Arrays.stream(polygons).toArray(Polygon[]::new));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Component2D toPoint2D(Object... points) {
|
||||
return Point2D.create(Arrays.stream(points).toArray(double[][]::new));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Rectangle randomQueryBox() {
|
||||
return GeoTestUtil.nextBox();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object[] nextPoints() {
|
||||
int numPoints = TestUtil.nextInt(random(), 1, 20);
|
||||
double[][] points = new double[numPoints][2];
|
||||
for (int i = 0; i < numPoints; i++) {
|
||||
points[i][0] = nextLatitude();
|
||||
points[i][1] = nextLongitude();
|
||||
}
|
||||
return points;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected double rectMinX(Object rect) {
|
||||
return ((Rectangle)rect).minLon;
|
||||
|
@ -162,6 +185,11 @@ public abstract class BaseLatLonShapeTestCase extends BaseShapeTestCase {
|
|||
return LatLonShape.newPolygonQuery(field, queryRelation, polygons);
|
||||
}
|
||||
|
||||
/** factory method to create a new point query */
|
||||
protected Query newPointQuery(String field, QueryRelation queryRelation, double[]... points) {
|
||||
return LatLonShape.newPointQuery(field, queryRelation, points);
|
||||
}
|
||||
|
||||
public void testPolygonQueryEqualsAndHashcode() {
|
||||
Polygon polygon = GeoTestUtil.nextPolygon();
|
||||
QueryRelation queryRelation = RandomPicks.randomFrom(random(), QueryRelation.values());
|
||||
|
|
|
@ -161,6 +161,8 @@ public abstract class BaseShapeTestCase extends LuceneTestCase {
|
|||
|
||||
protected abstract Object randomQueryBox();
|
||||
|
||||
protected abstract Object[] nextPoints();
|
||||
|
||||
protected abstract double rectMinX(Object rect);
|
||||
protected abstract double rectMaxX(Object rect);
|
||||
protected abstract double rectMinY(Object rect);
|
||||
|
@ -189,10 +191,15 @@ public abstract class BaseShapeTestCase extends LuceneTestCase {
|
|||
/** factory method to create a new polygon query */
|
||||
protected abstract Query newPolygonQuery(String field, QueryRelation queryRelation, Object... polygons);
|
||||
|
||||
/** factory method to create a new polygon query */
|
||||
protected abstract Query newPointsQuery(String field, QueryRelation queryRelation, Object... points);
|
||||
|
||||
protected abstract Component2D toLine2D(Object... line);
|
||||
|
||||
protected abstract Component2D toPolygon2D(Object... polygon);
|
||||
|
||||
protected abstract Component2D toPoint2D(Object... points);
|
||||
|
||||
private void verify(Object... shapes) throws Exception {
|
||||
IndexWriterConfig iwc = newIndexWriterConfig();
|
||||
iwc.setMergeScheduler(new SerialMergeScheduler());
|
||||
|
@ -251,6 +258,8 @@ public abstract class BaseShapeTestCase extends LuceneTestCase {
|
|||
verifyRandomLineQueries(reader, shapes);
|
||||
// test random polygon queries
|
||||
verifyRandomPolygonQueries(reader, shapes);
|
||||
// test random point queries
|
||||
verifyRandomPointQueries(reader, shapes);
|
||||
}
|
||||
|
||||
/** test random generated bounding boxes */
|
||||
|
@ -547,6 +556,105 @@ public abstract class BaseShapeTestCase extends LuceneTestCase {
|
|||
}
|
||||
}
|
||||
|
||||
/** test random generated point queries */
|
||||
protected void verifyRandomPointQueries(IndexReader reader, Object... shapes) throws Exception {
|
||||
IndexSearcher s = newSearcher(reader);
|
||||
|
||||
final int iters = scaledIterationCount(shapes.length);
|
||||
|
||||
Bits liveDocs = MultiBits.getLiveDocs(s.getIndexReader());
|
||||
int maxDoc = s.getIndexReader().maxDoc();
|
||||
|
||||
for (int iter = 0; iter < iters; ++iter) {
|
||||
if (VERBOSE) {
|
||||
System.out.println("\nTEST: iter=" + (iter+1) + " of " + iters + " s=" + s);
|
||||
}
|
||||
|
||||
Object[] queryPoints = nextPoints();
|
||||
QueryRelation queryRelation = RandomPicks.randomFrom(random(), QueryRelation.values());
|
||||
Component2D queryPoly2D;
|
||||
Query query;
|
||||
if (queryRelation == QueryRelation.CONTAINS) {
|
||||
queryPoly2D = toPoint2D(queryPoints[0]);
|
||||
query = newPointsQuery(FIELD_NAME, queryRelation, queryPoints[0]);
|
||||
} else {
|
||||
queryPoly2D = toPoint2D(queryPoints);
|
||||
query = newPointsQuery(FIELD_NAME, queryRelation, queryPoints);
|
||||
}
|
||||
|
||||
if (VERBOSE) {
|
||||
System.out.println(" query=" + query + ", relation=" + queryRelation);
|
||||
}
|
||||
|
||||
final FixedBitSet hits = new FixedBitSet(maxDoc);
|
||||
s.search(query, new SimpleCollector() {
|
||||
|
||||
private int docBase;
|
||||
|
||||
@Override
|
||||
public ScoreMode scoreMode() {
|
||||
return ScoreMode.COMPLETE_NO_SCORES;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doSetNextReader(LeafReaderContext context) throws IOException {
|
||||
docBase = context.docBase;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void collect(int doc) throws IOException {
|
||||
hits.set(docBase+doc);
|
||||
}
|
||||
});
|
||||
|
||||
boolean fail = false;
|
||||
NumericDocValues docIDToID = MultiDocValues.getNumericValues(reader, "id");
|
||||
for (int docID = 0; docID < maxDoc; ++docID) {
|
||||
assertEquals(docID, docIDToID.nextDoc());
|
||||
int id = (int) docIDToID.longValue();
|
||||
boolean expected;
|
||||
|
||||
if (liveDocs != null && liveDocs.get(docID) == false) {
|
||||
// document is deleted
|
||||
expected = false;
|
||||
} else if (shapes[id] == null) {
|
||||
expected = false;
|
||||
} else {
|
||||
expected = VALIDATOR.setRelation(queryRelation).testComponentQuery(queryPoly2D, shapes[id]);
|
||||
}
|
||||
|
||||
if (hits.get(docID) != expected) {
|
||||
StringBuilder b = new StringBuilder();
|
||||
|
||||
if (expected) {
|
||||
b.append("FAIL: id=" + id + " should match but did not\n");
|
||||
} else {
|
||||
b.append("FAIL: id=" + id + " should not match but did\n");
|
||||
}
|
||||
b.append(" relation=" + queryRelation + "\n");
|
||||
b.append(" query=" + query + " docID=" + docID + "\n");
|
||||
if (shapes[id] instanceof Object[]) {
|
||||
b.append(" shape=" + Arrays.toString((Object[]) shapes[id]) + "\n");
|
||||
} else {
|
||||
b.append(" shape=" + shapes[id] + "\n");
|
||||
}
|
||||
b.append(" deleted?=" + (liveDocs != null && liveDocs.get(docID) == false));
|
||||
b.append(" rect=Points(" + Arrays.toString(queryPoints) + ")\n");
|
||||
if (true) {
|
||||
fail("wrong hit (first of possibly more):\n\n" + b);
|
||||
} else {
|
||||
System.out.println(b.toString());
|
||||
fail = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (fail) {
|
||||
fail("some hits were wrong");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected abstract Validator getValidator();
|
||||
|
||||
protected static abstract class Encoder {
|
||||
|
|
|
@ -22,12 +22,14 @@ import com.carrotsearch.randomizedtesting.generators.RandomPicks;
|
|||
import org.apache.lucene.document.ShapeField.QueryRelation;
|
||||
import org.apache.lucene.geo.Component2D;
|
||||
import org.apache.lucene.geo.Line2D;
|
||||
import org.apache.lucene.geo.Point2D;
|
||||
import org.apache.lucene.geo.ShapeTestUtil;
|
||||
import org.apache.lucene.geo.XYLine;
|
||||
import org.apache.lucene.geo.XYPolygon;
|
||||
import org.apache.lucene.geo.XYPolygon2D;
|
||||
import org.apache.lucene.geo.XYRectangle;
|
||||
import org.apache.lucene.search.Query;
|
||||
import org.apache.lucene.util.TestUtil;
|
||||
|
||||
import static org.apache.lucene.geo.XYEncodingUtils.decode;
|
||||
import static org.apache.lucene.geo.XYEncodingUtils.encode;
|
||||
|
@ -58,6 +60,16 @@ public abstract class BaseXYShapeTestCase extends BaseShapeTestCase {
|
|||
return XYShape.newPolygonQuery(field, queryRelation, Arrays.stream(polygons).toArray(XYPolygon[]::new));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Query newPointsQuery(String field, QueryRelation queryRelation, Object... points) {
|
||||
return XYShape.newPointQuery(field, queryRelation, Arrays.stream(points).toArray(float[][]::new));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Component2D toPoint2D(Object... points) {
|
||||
return Point2D.create(Arrays.stream(points).toArray(float[][]::new));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Component2D toLine2D(Object... lines) {
|
||||
return Line2D.create(Arrays.stream(lines).toArray(XYLine[]::new));
|
||||
|
@ -121,6 +133,17 @@ public abstract class BaseXYShapeTestCase extends BaseShapeTestCase {
|
|||
return ShapeTestUtil.nextPolygon();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object[] nextPoints() {
|
||||
int numPoints = TestUtil.nextInt(random(), 1, 20);
|
||||
float[][] points = new float[numPoints][2];
|
||||
for (int i = 0; i < numPoints; i++) {
|
||||
points[i][0] = (float) ShapeTestUtil.nextDouble();
|
||||
points[i][1] = (float) ShapeTestUtil.nextDouble();
|
||||
}
|
||||
return points;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Encoder getEncoder() {
|
||||
return new Encoder() {
|
||||
|
|
|
@ -93,12 +93,12 @@ public class TestLatLonPointShapeQueries extends BaseLatLonShapeTestCase {
|
|||
|
||||
@Override
|
||||
public boolean testComponentQuery(Component2D query, Object shape) {
|
||||
if (queryRelation == QueryRelation.CONTAINS) {
|
||||
return false;
|
||||
}
|
||||
Point p = (Point) shape;
|
||||
Point p = (Point) shape;
|
||||
double lat = encoder.quantizeY(p.lat);
|
||||
double lon = encoder.quantizeX(p.lon);
|
||||
if (queryRelation == QueryRelation.CONTAINS) {
|
||||
return query.withinTriangle(lon, lat, true, lon, lat, true, lon, lat, true) == Component2D.WithinRelation.CANDIDATE;
|
||||
}
|
||||
// for consistency w/ the query we test the point as a triangle
|
||||
Relation r = query.relateTriangle(lon, lat, lon, lat, lon, lat);
|
||||
if (queryRelation == QueryRelation.WITHIN) {
|
||||
|
|
|
@ -82,12 +82,12 @@ public class TestXYPointShapeQueries extends BaseXYShapeTestCase {
|
|||
|
||||
@Override
|
||||
public boolean testComponentQuery(Component2D query, Object shape) {
|
||||
if (queryRelation == QueryRelation.CONTAINS) {
|
||||
return false;
|
||||
}
|
||||
Point p = (Point) shape;
|
||||
double lat = encoder.quantizeY(p.y);
|
||||
double lon = encoder.quantizeX(p.x);
|
||||
if (queryRelation == QueryRelation.CONTAINS) {
|
||||
return query.withinTriangle(lon, lat, true, lon, lat, true, lon, lat, true) == Component2D.WithinRelation.CANDIDATE;
|
||||
}
|
||||
// for consistency w/ the query we test the point as a triangle
|
||||
Relation r = query.relateTriangle(lon, lat, lon, lat, lon, lat);
|
||||
if (queryRelation == QueryRelation.WITHIN) {
|
||||
|
|
|
@ -0,0 +1,80 @@
|
|||
/*
|
||||
* 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.geo;
|
||||
|
||||
import org.apache.lucene.index.PointValues.Relation;
|
||||
import org.apache.lucene.util.LuceneTestCase;
|
||||
|
||||
public class TestPoint2D extends LuceneTestCase {
|
||||
|
||||
public void testTriangleDisjoint() {
|
||||
Component2D point2D = Point2D.create(new double[] {0, 0});
|
||||
double ax = 4;
|
||||
double ay = 4;
|
||||
double bx = 5;
|
||||
double by = 5;
|
||||
double cx = 5;
|
||||
double cy = 4;
|
||||
assertEquals(Relation.CELL_OUTSIDE_QUERY, point2D.relateTriangle(ax, ay, bx, by , cx, cy));
|
||||
}
|
||||
|
||||
public void testTriangleIntersects() {
|
||||
Component2D point2D = Point2D.create(new double[] {0, 0});
|
||||
double ax = 0.0;
|
||||
double ay = 0.0;
|
||||
double bx = 1;
|
||||
double by = 0;
|
||||
double cx = 0;
|
||||
double cy = 1;
|
||||
assertEquals(Relation.CELL_INSIDE_QUERY, point2D.relateTriangle(ax, ay, bx, by , cx, cy));
|
||||
}
|
||||
|
||||
public void testTriangleContains() {
|
||||
Component2D point2D = Point2D.create(new double[] {0, 0});
|
||||
double ax = 0.0;
|
||||
double ay = 0.0;
|
||||
double bx = 0;
|
||||
double by = 0;
|
||||
double cx = 0;
|
||||
double cy = 0;
|
||||
assertEquals(Relation.CELL_INSIDE_QUERY, point2D.relateTriangle(ax, ay, bx, by , cx, cy));
|
||||
}
|
||||
|
||||
public void testRandomTriangles() {
|
||||
Component2D point2D = Point2D.create(new double[] {GeoTestUtil.nextLatitude(), GeoTestUtil.nextLongitude()});
|
||||
|
||||
for (int i =0; i < 100; i++) {
|
||||
double ax = GeoTestUtil.nextLongitude();
|
||||
double ay = GeoTestUtil.nextLatitude();
|
||||
double bx = GeoTestUtil.nextLongitude();
|
||||
double by = GeoTestUtil.nextLatitude();
|
||||
double cx = GeoTestUtil.nextLongitude();
|
||||
double cy = GeoTestUtil.nextLatitude();
|
||||
|
||||
double tMinX = StrictMath.min(StrictMath.min(ax, bx), cx);
|
||||
double tMaxX = StrictMath.max(StrictMath.max(ax, bx), cx);
|
||||
double tMinY = StrictMath.min(StrictMath.min(ay, by), cy);
|
||||
double tMaxY = StrictMath.max(StrictMath.max(ay, by), cy);
|
||||
|
||||
Relation r = point2D.relate(tMinX, tMaxX, tMinY, tMaxY);
|
||||
if (r == Relation.CELL_OUTSIDE_QUERY) {
|
||||
assertEquals(Relation.CELL_OUTSIDE_QUERY, point2D.relateTriangle(ax, ay, bx, by, cx, cy));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -102,7 +102,7 @@ fq=+popularity:[10 TO *] +section:0
|
|||
----
|
||||
|
||||
* The document sets from each filter query are cached independently. Thus, concerning the previous examples: use a single `fq` containing two mandatory clauses if those clauses appear together often, and use two separate `fq` parameters if they are relatively independent. (To learn about tuning cache sizes and making sure a filter cache actually exists, see <<the-well-configured-solr-instance.adoc#the-well-configured-solr-instance,The Well-Configured Solr Instance>>.)
|
||||
* It is also possible to use <<the-standard-query-parser.adoc#differences-between-lucene-s-classic-query-parser-and-solr-s-standard-query-parser,filter(condition) syntax>> inside the `fq` to cache clauses individually and - among other things - to achieve union of cached filter queries.
|
||||
* It is also possible to use <<the-standard-query-parser.adoc#differences-between-lucenes-classic-query-parser-and-solrs-standard-query-parser,filter(condition) syntax>> inside the `fq` to cache clauses individually and - among other things - to achieve union of cached filter queries.
|
||||
|
||||
* As with all parameters: special characters in an URL need to be properly escaped and encoded as hex values. Online tools are available to help you with URL-encoding. For example: http://meyerweb.com/eric/tools/dencoder/.
|
||||
|
||||
|
|
Loading…
Reference in New Issue