UCENE-9194: Simplify XYShapeXQuery API by adding a new abstract class called XYGeometry

This commit is contained in:
Ignacio Vera 2020-02-07 19:29:13 +01:00 committed by GitHub
parent 9a19093586
commit 73dbf6d061
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
34 changed files with 375 additions and 712 deletions

View File

@ -147,6 +147,9 @@ Improvements
* LUCENE-9123: Add new JapaneseTokenizer constructors with discardCompoundToken option that controls whether
the tokenizer emits original (compound) tokens when the mode is not NORMAL. (Kazuaki Hiraga via Tomoko Uchida)
* LUCENE-9194: Simplify XYShapeXQuery API by adding a new abstract class called XYGeometry. Queries are
executed with input objects that extend such interface. (Ignacio Vera)
Optimizations
---------------------

View File

@ -22,8 +22,8 @@ import java.util.Arrays;
import org.apache.lucene.geo.Component2D;
import org.apache.lucene.geo.GeoEncodingUtils;
import org.apache.lucene.geo.LatLonGeometry;
import org.apache.lucene.geo.Polygon;
import org.apache.lucene.geo.Polygon2D;
import org.apache.lucene.index.DocValues;
import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.index.SortedNumericDocValues;
@ -104,7 +104,7 @@ public class LatLonDocValuesPointInPolygonQuery extends Query {
return new ConstantScoreWeight(this, boost) {
final Component2D tree = Polygon2D.create(polygons);
final Component2D tree = LatLonGeometry.create(polygons);
final GeoEncodingUtils.PolygonPredicate polygonPredicate = GeoEncodingUtils.createComponentPredicate(tree);
@Override

View File

@ -21,8 +21,8 @@ import java.util.Arrays;
import org.apache.lucene.geo.Component2D;
import org.apache.lucene.geo.GeoEncodingUtils;
import org.apache.lucene.geo.LatLonGeometry;
import org.apache.lucene.geo.Polygon;
import org.apache.lucene.geo.Polygon2D;
import org.apache.lucene.index.FieldInfo;
import org.apache.lucene.index.LeafReader;
import org.apache.lucene.index.LeafReaderContext;
@ -141,7 +141,7 @@ final class LatLonPointInPolygonQuery extends Query {
@Override
public Weight createWeight(IndexSearcher searcher, ScoreMode scoreMode, float boost) throws IOException {
final Component2D tree = Polygon2D.create(polygons);
final Component2D tree = LatLonGeometry.create(polygons);
final GeoEncodingUtils.PolygonPredicate polygonPredicate = GeoEncodingUtils.createComponentPredicate(tree);
// bounding box over all polygons, this can speed up tree intersection/cheaply improve approximation for complex multi-polygons
final byte minLat[] = new byte[Integer.BYTES];

View File

@ -22,6 +22,9 @@ import java.util.List;
import org.apache.lucene.document.ShapeField.QueryRelation; // javadoc
import org.apache.lucene.document.ShapeField.Triangle;
import org.apache.lucene.geo.Tessellator;
import org.apache.lucene.geo.XYGeometry;
import org.apache.lucene.geo.XYPoint;
import org.apache.lucene.geo.XYRectangle;
import org.apache.lucene.index.PointValues; // javadoc
import org.apache.lucene.geo.XYLine;
import org.apache.lucene.geo.XYPolygon;
@ -34,7 +37,7 @@ import static org.apache.lucene.geo.XYEncodingUtils.encode;
/**
* A cartesian shape utility class for indexing and searching geometries whose vertices are unitless x, y values.
* <p>
* This class defines six static factory methods for common indexing and search operations:
* This class defines seven static factory methods for common indexing and search operations:
* <ul>
* <li>{@link #createIndexableFields(String, XYPolygon)} for indexing a cartesian polygon.
* <li>{@link #createIndexableFields(String, XYLine)} for indexing a cartesian linestring.
@ -42,6 +45,8 @@ import static org.apache.lucene.geo.XYEncodingUtils.encode;
* <li>{@link #newBoxQuery newBoxQuery()} for matching cartesian shapes that have some {@link QueryRelation} with a bounding box.
* <li>{@link #newBoxQuery newLineQuery()} for matching cartesian shapes that have some {@link QueryRelation} with a linestring.
* <li>{@link #newBoxQuery newPolygonQuery()} for matching cartesian shapes that have some {@link QueryRelation} with a polygon.
* <li>{@link #newGeometryQuery newGeometryQuery()} for matching cartesian shapes that have some {@link QueryRelation}
* with one or more {@link XYGeometry}.
* </ul>
* <b>WARNING</b>: Like {@link LatLonPoint}, vertex values are indexed with some loss of precision from the
@ -88,43 +93,41 @@ public class XYShape {
/** create a query to find all cartesian shapes that intersect a defined bounding box **/
public static Query newBoxQuery(String field, QueryRelation queryRelation, float minX, float maxX, float minY, float maxY) {
return new XYShapeBoundingBoxQuery(field, queryRelation, minX, maxX, minY, maxY);
XYRectangle rectangle = new XYRectangle(minX, maxX, minY, maxY);
return newGeometryQuery(field, queryRelation, rectangle);
}
/** create a query to find all cartesian shapes that intersect a provided linestring (or array of linestrings) **/
public static Query newLineQuery(String field, QueryRelation queryRelation, XYLine... lines) {
if (queryRelation == QueryRelation.CONTAINS && lines.length > 1) {
BooleanQuery.Builder builder = new BooleanQuery.Builder();
for (int i =0; i < lines.length; i++) {
builder.add(newLineQuery(field, queryRelation, lines[i]), BooleanClause.Occur.MUST);
}
return builder.build();
}
return new XYShapeLineQuery(field, queryRelation, lines);
return newGeometryQuery(field, queryRelation, lines);
}
/** create a query to find all cartesian shapes that intersect a provided polygon (or array of polygons) **/
public static Query newPolygonQuery(String field, QueryRelation queryRelation, XYPolygon... polygons) {
if (queryRelation == QueryRelation.CONTAINS && polygons.length > 1) {
BooleanQuery.Builder builder = new BooleanQuery.Builder();
for (int i =0; i < polygons.length; i++) {
builder.add(newPolygonQuery(field, queryRelation, polygons[i]), BooleanClause.Occur.MUST);
}
return builder.build();
}
return new XYShapePolygonQuery(field, queryRelation, polygons);
return newGeometryQuery(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) {
XYPoint[] pointArray = new XYPoint[points.length];
for (int i =0; i < points.length; i++) {
pointArray[i] = new XYPoint(points[i][0], points[i][1]);
}
return newGeometryQuery(field, queryRelation, pointArray);
}
/** create a query to find all indexed geo shapes that intersect a provided geometry collection
* note: Components do not support dateline crossing
**/
public static Query newGeometryQuery(String field, QueryRelation queryRelation, XYGeometry... xyGeometries) {
if (queryRelation == QueryRelation.CONTAINS && xyGeometries.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);
for (int i = 0; i < xyGeometries.length; i++) {
builder.add(newGeometryQuery(field, queryRelation, xyGeometries[i]), BooleanClause.Occur.MUST);
}
return builder.build();
}
return new XYShapePointQuery(field, queryRelation, points);
return new XYShapeQuery(field, queryRelation, xyGeometries);
}
}

View File

@ -1,142 +0,0 @@
/*
* 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 org.apache.lucene.document.ShapeField.QueryRelation;
import org.apache.lucene.geo.Component2D;
import org.apache.lucene.geo.XYRectangle;
import org.apache.lucene.geo.XYRectangle2D;
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 cartesian shapes that intersect the specified bounding box.
*
* <p>The field must be indexed using
* {@link org.apache.lucene.document.XYShape#createIndexableFields} added per document.
**/
public class XYShapeBoundingBoxQuery extends ShapeQuery {
final Component2D rectangle2D;
final private XYRectangle rectangle;
/** construct a Bounding Box Query over cartesian geometries from the given ranges */
public XYShapeBoundingBoxQuery(String field, QueryRelation queryRelation, double minX, double maxX, double minY, double maxY) {
super(field, queryRelation);
this.rectangle = new XYRectangle(minX, maxX, minY, maxY);
this.rectangle2D = XYRectangle2D.create(this.rectangle);
}
@Override
protected Relation relateRangeBBoxToQuery(int minXOffset, int minYOffset, byte[] minTriangle,
int maxXOffset, int maxYOffset, byte[] maxTriangle) {
double minY = decode(NumericUtils.sortableBytesToInt(minTriangle, minYOffset));
double minX = decode(NumericUtils.sortableBytesToInt(minTriangle, minXOffset));
double maxY = decode(NumericUtils.sortableBytesToInt(maxTriangle, maxYOffset));
double maxX = decode(NumericUtils.sortableBytesToInt(maxTriangle, maxXOffset));
// check internal node against query
Relation rel = rectangle2D.relate(minX, maxX, minY, maxY);
// TODO: Check if this really helps
if (queryRelation == QueryRelation.INTERSECTS && rel == Relation.CELL_CROSSES_QUERY) {
// for intersects we can restrict the conditions by using the inner box
double innerMaxY = decode(NumericUtils.sortableBytesToInt(maxTriangle, minYOffset));
if (rectangle2D.relate(minX, maxX, minY, innerMaxY) == Relation.CELL_INSIDE_QUERY) {
return Relation.CELL_INSIDE_QUERY;
}
double innerMaX = decode(NumericUtils.sortableBytesToInt(maxTriangle, minXOffset));
if (rectangle2D.relate(minX, innerMaX, minY, maxY) == Relation.CELL_INSIDE_QUERY) {
return Relation.CELL_INSIDE_QUERY;
}
double innerMinY = decode(NumericUtils.sortableBytesToInt(minTriangle, maxYOffset));
if (rectangle2D.relate(minX, maxX, innerMinY, maxY) == Relation.CELL_INSIDE_QUERY) {
return Relation.CELL_INSIDE_QUERY;
}
double innerMinX = decode(NumericUtils.sortableBytesToInt(minTriangle, maxXOffset));
if (rectangle2D.relate(innerMinX, maxX, minY, maxY) == Relation.CELL_INSIDE_QUERY) {
return Relation.CELL_INSIDE_QUERY;
}
}
return rel;
}
/** returns true if the query matches the encoded triangle */
@Override
protected boolean queryMatches(byte[] t, ShapeField.DecodedTriangle scratchTriangle, QueryRelation queryRelation) {
// decode indexed triangle
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 rectangle2D.relateTriangle(aX, aY, bX, bY, cX, cY) != Relation.CELL_OUTSIDE_QUERY;
case WITHIN: return rectangle2D.contains(aX, aY) && rectangle2D.contains(bX, bY) && rectangle2D.contains(cX, cY);
case DISJOINT: return rectangle2D.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 rectangle2D.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) && rectangle.equals(((XYShapeBoundingBoxQuery)o).rectangle);
}
@Override
public int hashCode() {
int hash = super.hashCode();
hash = 31 * hash + rectangle.hashCode();
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(rectangle.toString());
return sb.toString();
}
}

View File

@ -1,146 +0,0 @@
/*
* 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.document.ShapeField.QueryRelation;
import org.apache.lucene.geo.Component2D;
import org.apache.lucene.geo.Line2D;
import org.apache.lucene.geo.XYLine;
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 cartesian shapes that intersect the specified arbitrary {@code XYLine}.
* <p>
* Note:
* <ul>
* <li>{@code QueryRelation.WITHIN} queries are not yet supported</li>
* </ul>
* <p>
* todo:
* <ul>
* <li>Add distance support for buffered queries</li>
* </ul>
* <p>The field must be indexed using
* {@link org.apache.lucene.document.XYShape#createIndexableFields} added per document.
**/
final class XYShapeLineQuery extends ShapeQuery {
final XYLine[] lines;
final private Component2D line2D;
/** construct a Line Query over cartesian geometries from the given line objects */
public XYShapeLineQuery(String field, QueryRelation queryRelation, XYLine... lines) {
super(field, queryRelation);
/** line queries do not support within relations, only intersects and disjoint */
if (queryRelation == QueryRelation.WITHIN) {
throw new IllegalArgumentException("XYShapeLineQuery does not support " + QueryRelation.WITHIN + " queries");
}
if (lines == null) {
throw new IllegalArgumentException("lines must not be null");
}
if (lines.length == 0) {
throw new IllegalArgumentException("lines must not be empty");
}
for (int i = 0; i < lines.length; ++i) {
if (lines[i] == null) {
throw new IllegalArgumentException("line[" + i + "] must not be null");
} else if (lines[i].minX > lines[i].maxX) {
throw new IllegalArgumentException("XYShapeLineQuery: minX cannot be greater than maxX.");
} else if (lines[i].minY > lines[i].maxY) {
throw new IllegalArgumentException("XYShapeLineQuery: minY cannot be greater than maxY.");
}
}
this.lines = lines.clone();
this.line2D = Line2D.create(lines);
}
@Override
protected Relation relateRangeBBoxToQuery(int minXOffset, int minYOffset, byte[] minTriangle,
int maxXOffset, int maxYOffset, byte[] maxTriangle) {
double minY = decode(NumericUtils.sortableBytesToInt(minTriangle, minYOffset));
double minX = decode(NumericUtils.sortableBytesToInt(minTriangle, minXOffset));
double maxY = decode(NumericUtils.sortableBytesToInt(maxTriangle, maxYOffset));
double maxX = decode(NumericUtils.sortableBytesToInt(maxTriangle, maxXOffset));
// check internal node against query
return line2D.relate(minX, maxX, minY, maxY);
}
@Override
protected boolean queryMatches(byte[] t, ShapeField.DecodedTriangle scratchTriangle, QueryRelation queryRelation) {
ShapeField.decodeTriangle(t, scratchTriangle);
double alat = decode(scratchTriangle.aY);
double alon = decode(scratchTriangle.aX);
double blat = decode(scratchTriangle.bY);
double blon = decode(scratchTriangle.bX);
double clat = decode(scratchTriangle.cY);
double clon = decode(scratchTriangle.cX);
switch (queryRelation) {
case INTERSECTS: return line2D.relateTriangle(alon, alat, blon, blat, clon, clat) != Relation.CELL_OUTSIDE_QUERY;
case WITHIN: return line2D.relateTriangle(alon, alat, blon, blat, clon, clat) == Relation.CELL_INSIDE_QUERY;
case DISJOINT: return line2D.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 = decode(scratchTriangle.aY);
double alon = decode(scratchTriangle.aX);
double blat = decode(scratchTriangle.bY);
double blon = decode(scratchTriangle.bX);
double clat = decode(scratchTriangle.cY);
double clon = decode(scratchTriangle.cX);
return line2D.withinTriangle(alon, alat, scratchTriangle.ab, blon, blat, scratchTriangle.bc, clon, clat, scratchTriangle.ca);
}
@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("XYLine(").append(lines[0].toGeoJSON()).append(")");
return sb.toString();
}
@Override
protected boolean equalsTo(Object o) {
return super.equalsTo(o) && Arrays.equals(lines, ((XYShapeLineQuery)o).lines);
}
@Override
public int hashCode() {
int hash = super.hashCode();
hash = 31 * hash + Arrays.hashCode(lines);
return hash;
}
}

View File

@ -1,126 +0,0 @@
/*
* 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.
**/
final class XYShapePointQuery extends ShapeQuery {
final Component2D point2D;
final float[][] points;
/** construct a Point or MultiPoint Query over cartesian geometries from the given point values */
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();
}
}

View File

@ -21,45 +21,29 @@ import java.util.Arrays;
import org.apache.lucene.document.ShapeField.QueryRelation;
import org.apache.lucene.geo.Component2D;
import org.apache.lucene.geo.XYEncodingUtils;
import org.apache.lucene.geo.XYPolygon;
import org.apache.lucene.geo.XYPolygon2D;
import org.apache.lucene.geo.XYGeometry;
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 cartesian shapes that intersect the specified arbitrary cartesian {@link XYPolygon}.
* Finds all previously indexed cartesian shapes that comply the given {@link QueryRelation} with
* the specified array of {@link XYGeometry}.
*
* <p>The field must be indexed using
* {@link org.apache.lucene.document.XYShape#createIndexableFields} added per document.
* <p>The field must be indexed using {@link XYShape#createIndexableFields} added per document.
**/
final class XYShapePolygonQuery extends ShapeQuery {
final XYPolygon[] polygons;
final private Component2D poly2D;
final class XYShapeQuery extends ShapeQuery {
final XYGeometry[] geometries;
final private Component2D component2D;
/**
* Creates a query that matches all indexed shapes to the provided polygons
*/
public XYShapePolygonQuery(String field, QueryRelation queryRelation, XYPolygon... polygons) {
XYShapeQuery(String field, QueryRelation queryRelation, XYGeometry... geometries) {
super(field, queryRelation);
if (polygons == null) {
throw new IllegalArgumentException("polygons must not be null");
}
if (polygons.length == 0) {
throw new IllegalArgumentException("polygons must not be empty");
}
for (int i = 0; i < polygons.length; i++) {
if (polygons[i] == null) {
throw new IllegalArgumentException("polygon[" + i + "] must not be null");
} else if (polygons[i].minX > polygons[i].maxX) {
throw new IllegalArgumentException("XYShapePolygonQuery: minX cannot be greater than maxX.");
} else if (polygons[i].minY > polygons[i].maxY) {
throw new IllegalArgumentException("XYShapePolygonQuery: minY cannot be greater than maxY.");
}
}
this.polygons = polygons.clone();
this.poly2D = XYPolygon2D.create(polygons);
this.component2D = XYGeometry.create(geometries);
this.geometries = geometries.clone();
}
@Override
@ -72,7 +56,7 @@ final class XYShapePolygonQuery extends ShapeQuery {
double maxLon = XYEncodingUtils.decode(NumericUtils.sortableBytesToInt(maxTriangle, maxXOffset));
// check internal node against query
return poly2D.relate(minLon, maxLon, minLat, maxLat);
return component2D.relate(minLon, maxLon, minLat, maxLat);
}
@Override
@ -87,9 +71,9 @@ final class XYShapePolygonQuery extends ShapeQuery {
double clon = decode(scratchTriangle.cX);
switch (queryRelation) {
case INTERSECTS: return poly2D.relateTriangle(alon, alat, blon, blat, clon, clat) != Relation.CELL_OUTSIDE_QUERY;
case WITHIN: return poly2D.relateTriangle(alon, alat, blon, blat, clon, clat) == Relation.CELL_INSIDE_QUERY;
case DISJOINT: return poly2D.relateTriangle(alon, alat, blon, blat, clon, clat) == Relation.CELL_OUTSIDE_QUERY;
case INTERSECTS: return component2D.relateTriangle(alon, alat, blon, blat, clon, clat) != Relation.CELL_OUTSIDE_QUERY;
case WITHIN: return component2D.relateTriangle(alon, alat, blon, blat, clon, clat) == Relation.CELL_INSIDE_QUERY;
case DISJOINT: return component2D.relateTriangle(alon, alat, blon, blat, clon, clat) == Relation.CELL_OUTSIDE_QUERY;
default: throw new IllegalArgumentException("Unsupported query type :[" + queryRelation + "]");
}
}
@ -105,7 +89,7 @@ final class XYShapePolygonQuery extends ShapeQuery {
double clat = decode(scratchTriangle.cY);
double clon = decode(scratchTriangle.cX);
return poly2D.withinTriangle(alon, alat, scratchTriangle.ab, blon, blat, scratchTriangle.bc, clon, clat, scratchTriangle.ca);
return component2D.withinTriangle(alon, alat, scratchTriangle.ab, blon, blat, scratchTriangle.bc, clon, clat, scratchTriangle.ca);
}
@Override
@ -118,19 +102,24 @@ final class XYShapePolygonQuery extends ShapeQuery {
sb.append(this.field);
sb.append(':');
}
sb.append("XYPolygon(").append(polygons[0].toGeoJSON()).append(")");
sb.append("[");
for (int i = 0; i < geometries.length; i++) {
sb.append(geometries[i].toString());
sb.append(',');
}
sb.append(']');
return sb.toString();
}
@Override
protected boolean equalsTo(Object o) {
return super.equalsTo(o) && Arrays.equals(polygons, ((XYShapePolygonQuery)o).polygons);
return super.equalsTo(o) && Arrays.equals(geometries, ((XYShapeQuery)o).geometries);
}
@Override
public int hashCode() {
int hash = super.hashCode();
hash = 31 * hash + Arrays.hashCode(polygons);
hash = 31 * hash + Arrays.hashCode(geometries);
return hash;
}
}

View File

@ -25,17 +25,15 @@ import org.apache.lucene.util.ArrayUtil;
* 2D multi-component geometry implementation represented as an interval tree of components.
* <p>
* Construction takes {@code O(n log n)} time for sorting and tree construction.
*
* @lucene.internal
*/
final class ComponentTree implements Component2D {
/** minimum latitude of this geometry's bounding box area */
/** minimum Y of this geometry's bounding box area */
private double minY;
/** maximum latitude of this geometry's bounding box area */
/** maximum Y of this geometry's bounding box area */
private double maxY;
/** minimum longitude of this geometry's bounding box area */
/** minimum X of this geometry's bounding box area */
private double minX;
/** maximum longitude of this geometry's bounding box area */
/** maximum X of this geometry's bounding box area */
private double maxX;
// child components, or null. Note internal nodes might mot have
// a consistent bounding box. Internal nodes should not be accessed
@ -48,7 +46,7 @@ final class ComponentTree implements Component2D {
/** root node of edge tree */
final private Component2D component;
protected ComponentTree(Component2D component, boolean splitX) {
private ComponentTree(Component2D component, boolean splitX) {
this.minY = component.getMinY();
this.maxY = component.getMaxY();
this.minX = component.getMinX();
@ -97,7 +95,6 @@ final class ComponentTree implements Component2D {
return false;
}
/** Returns relation to the provided triangle */
@Override
public Relation relateTriangle(double minX, double maxX, double minY, double maxY,
double ax, double ay, double bx, double by, double cx, double cy) {
@ -131,7 +128,6 @@ final class ComponentTree implements Component2D {
return component.withinTriangle(minX, maxX, minY, maxY, aX, aY, ab, bX, bY, bc, cX, cY, ca);
}
/** Returns relation to the provided rectangle */
@Override
public Relation relate(double minX, double maxX, double minY, double maxY) {
if (minY <= this.maxY && minX <= this.maxX) {
@ -156,7 +152,7 @@ final class ComponentTree implements Component2D {
}
/** Creates tree from provided components */
public static Component2D create(Component2D[] components) {
static Component2D create(Component2D[] components) {
if (components.length == 1) {
return components[0];
}

View File

@ -23,45 +23,39 @@ import static org.apache.lucene.geo.GeoUtils.lineCrossesLineWithBoundary;
import static org.apache.lucene.geo.GeoUtils.orient;
/**
* 2D line/polygon geometry implementation represented as a balanced interval tree of edges.
* Internal tree node: represents geometry edge from [x1, y1] to [x2, y2].
* The sort value is {@code low}, which is the minimum y of the edge.
* {@code max} stores the maximum y of this edge or any children.
* <p>
* Construction takes {@code O(n log n)} time for sorting and tree construction.
* {@link #relate relate()} are {@code O(n)}, but for most
* Methods are {@code O(n)}, but for most
* practical lines and polygons are much faster than brute force.
* @lucene.internal
*/
/**
* Internal tree node: represents geometry edge from lat1,lon1 to lat2,lon2.
* The sort value is {@code low}, which is the minimum latitude of the edge.
* {@code max} stores the maximum latitude of this edge or any children.
*
* @lucene.internal
*/
public class EdgeTree {
// lat-lon pair (in original order) of the two vertices
final double y1, y2;
final double x1, x2;
/** min of this edge */
final double low;
/** max latitude of this edge or any children */
double max;
/** left child edge, or null */
EdgeTree left;
/** right child edge, or null */
EdgeTree right;
/** helper bytes to signal if a point is on an edge, it is within the edge tree or disjoint */
final private static byte FALSE = 0x00;
final private static byte TRUE = 0x01;
final private static byte ON_EDGE = 0x02;
final class EdgeTree {
// X-Y pair (in original order) of the two vertices
final double y1, y2;
final double x1, x2;
/** min Y of this edge */
final double low;
/** max Y of this edge or any children */
double max;
/** left child edge, or null */
EdgeTree left;
/** right child edge, or null */
EdgeTree right;
/** helper bytes to signal if a point is on an edge, it is within the edge tree or disjoint */
final private static byte FALSE = 0x00;
final private static byte TRUE = 0x01;
final private static byte ON_EDGE = 0x02;
EdgeTree(double x1, double y1, double x2, double y2, double low, double max) {
this.y1 = y1;
this.x1 = x1;
this.y2 = y2;
this.x2 = x2;
this.low = low;
this.max = max;
}
private EdgeTree(double x1, double y1, double x2, double y2, double low, double max) {
this.y1 = y1;
this.x1 = x1;
this.y2 = y2;
this.x2 = x2;
this.low = low;
this.max = max;
}
/**
* Returns true if the point is on an edge or crosses the edge subtree an odd number
@ -135,7 +129,7 @@ public class EdgeTree {
}
/** returns true if the provided x, y point lies on the line */
protected boolean isPointOnLine(double x, double y) {
boolean isPointOnLine(double x, double y) {
if (y <= max) {
double a1x = x1;
double a1y = y1;
@ -160,7 +154,7 @@ public class EdgeTree {
/** Returns true if the triangle crosses any edge in this edge subtree */
protected boolean crossesTriangle(double minX, double maxX, double minY, double maxY,
boolean crossesTriangle(double minX, double maxX, double minY, double maxY,
double ax, double ay, double bx, double by, double cx, double cy, boolean includeBoundary) {
if (minY <= max) {
double dy = y1;
@ -204,7 +198,7 @@ public class EdgeTree {
}
/** Returns true if the box crosses any edge in this edge subtree */
protected boolean crossesBox(double minX, double maxX, double minY, double maxY, boolean includeBoundary) {
boolean crossesBox(double minX, double maxX, double minY, double maxY, boolean includeBoundary) {
// we just have to cross one edge to answer the question, so we descend the tree and return when we do.
if (minY <= max) {
// we compute line intersections of every polygon edge with every box line.
@ -261,7 +255,7 @@ public class EdgeTree {
}
/** Returns true if the line crosses any edge in this edge subtree */
protected boolean crossesLine(double minX, double maxX, double minY, double maxY, double a2x, double a2y, double b2x, double b2y, boolean includeBoundary) {
boolean crossesLine(double minX, double maxX, double minY, double maxY, double a2x, double a2y, double b2x, double b2y, boolean includeBoundary) {
if (minY <= max) {
double a1x = x1;
double a1y = y1;
@ -297,7 +291,7 @@ public class EdgeTree {
* Creates an edge interval tree from a set of geometry vertices.
* @return root node of the tree.
*/
protected static EdgeTree createTree(double[] x, double[] y) {
static EdgeTree createTree(double[] x, double[] y) {
EdgeTree edges[] = new EdgeTree[x.length - 1];
for (int i = 1; i < x.length; i++) {
double x1 = x[i-1];

View File

@ -23,17 +23,16 @@ import org.apache.lucene.index.PointValues.Relation;
* <p>
* Line {@code Line2D} Construction takes {@code O(n log n)} time for sorting and tree construction.
* {@link #relate relate()} are {@code O(n)}, but for most practical lines are much faster than brute force.
* @lucene.internal
*/
public final class Line2D implements Component2D {
final class Line2D implements Component2D {
/** minimum latitude of this geometry's bounding box area */
/** minimum Y of this geometry's bounding box area */
final private double minY;
/** maximum latitude of this geometry's bounding box area */
/** maximum Y of this geometry's bounding box area */
final private double maxY;
/** minimum longitude of this geometry's bounding box area */
/** minimum X of this geometry's bounding box area */
final private double minX;
/** maximum longitude of this geometry's bounding box area */
/** maximum X of this geometry's bounding box area */
final private double maxX;
/** lines represented as a 2-d interval tree.*/
final private EdgeTree tree;
@ -184,21 +183,13 @@ public final class Line2D implements Component2D {
return relation;
}
/** create a Line2D edge tree from provided array of Linestrings */
public static Component2D create(Line... lines) {
Component2D components[] = new Component2D[lines.length];
for (int i = 0; i < components.length; ++i) {
components[i] = new Line2D(lines[i]);
}
return ComponentTree.create(components);
/** create a Line2D from the provided LatLon Linestring */
static Component2D create(Line line) {
return new Line2D(line);
}
/** create a Line2D edge tree from provided array of Linestrings */
public static Component2D create(XYLine... lines) {
Line2D components[] = new Line2D[lines.length];
for (int i = 0; i < components.length; ++i) {
components[i] = new Line2D(lines[i]);
}
return ComponentTree.create(components);
/** create a Line2D from the provided XY Linestring */
static Component2D create(XYLine line) {
return new Line2D(line);
}
}

View File

@ -56,9 +56,7 @@ public final class Point extends LatLonGeometry {
@Override
protected Component2D toComponent2D() {
double qLat = GeoEncodingUtils.decodeLatitude(GeoEncodingUtils.encodeLatitude(lat));
double qLon = GeoEncodingUtils.decodeLongitude(GeoEncodingUtils.encodeLongitude(lon));
return Point2D.create(new double[] {qLat, qLon});
return Point2D.create(this);
}
@Override

View File

@ -21,15 +21,13 @@ import org.apache.lucene.index.PointValues;
/**
* 2D point implementation containing geo spatial logic.
*
* @lucene.internal
*/
public class Point2D implements Component2D {
final class Point2D implements Component2D {
final private double x;
final private double y;
Point2D(double x, double y) {
private Point2D(double x, double y) {
this.x = x;
this.y = y;
}
@ -88,22 +86,14 @@ public class Point2D implements Component2D {
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 a LatLon point */
static Component2D create(Point point) {
return new Point2D(GeoEncodingUtils.decodeLongitude(GeoEncodingUtils.encodeLongitude(point.getLon())),
GeoEncodingUtils.decodeLatitude(GeoEncodingUtils.encodeLatitude(point.getLat())));
}
/** 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);
/** create a Point2D component tree from a XY point */
static Component2D create(XYPoint xyPoint) {
return new Point2D(xyPoint.getX(), xyPoint.getY());
}
}

View File

@ -23,24 +23,23 @@ import org.apache.lucene.index.PointValues.Relation;
* <p>
* Loosely based on the algorithm described in <a href="http://www-ma2.upc.es/geoc/Schirra-pointPolygon.pdf">
* http://www-ma2.upc.es/geoc/Schirra-pointPolygon.pdf</a>.
* @lucene.internal
*/
public class Polygon2D implements Component2D {
/** minimum latitude of this geometry's bounding box area */
final class Polygon2D implements Component2D {
/** minimum Y of this geometry's bounding box area */
final private double minY;
/** maximum latitude of this geometry's bounding box area */
/** maximum Y of this geometry's bounding box area */
final private double maxY;
/** minimum longitude of this geometry's bounding box area */
/** minimum X of this geometry's bounding box area */
final private double minX;
/** maximum longitude of this geometry's bounding box area */
/** maximum X of this geometry's bounding box area */
final private double maxX;
/** tree of holes, or null */
final protected Component2D holes;
/** Edges of the polygon represented as a 2-d interval tree.*/
final EdgeTree tree;
protected Polygon2D(final double minX, final double maxX, final double minY, final double maxY, double[] x, double[] y, Component2D holes) {
private Polygon2D(final double minX, final double maxX, final double minY, final double maxY, double[] x, double[] y, Component2D holes) {
this.minY = minY;
this.maxY = maxY;
this.minX = minX;
@ -49,6 +48,10 @@ public class Polygon2D implements Component2D {
this.tree = EdgeTree.createTree(x, y);
}
private Polygon2D(XYPolygon polygon, Component2D holes) {
this(polygon.minX, polygon.maxX, polygon.minY, polygon.maxY, polygon.getPolyX(), polygon.getPolyY(), holes);
}
protected Polygon2D(Polygon polygon, Component2D holes) {
this(polygon.minLon, polygon.maxLon, polygon.minLat, polygon.maxLat, polygon.getPolyLons(), polygon.getPolyLats(), holes);
}
@ -312,18 +315,24 @@ public class Polygon2D implements Component2D {
return containsCount;
}
/** Builds a Polygon2D from multipolygon */
public static Component2D create(Polygon... polygons) {
Component2D components[] = new Component2D[polygons.length];
for (int i = 0; i < components.length; i++) {
Polygon gon = polygons[i];
Polygon gonHoles[] = gon.getHoles();
Component2D holes = null;
if (gonHoles.length > 0) {
holes = create(gonHoles);
}
components[i] = new Polygon2D(gon, holes);
/** Builds a Polygon2D from LatLon polygon */
static Component2D create(Polygon polygon) {
Polygon gonHoles[] = polygon.getHoles();
Component2D holes = null;
if (gonHoles.length > 0) {
holes = LatLonGeometry.create(gonHoles);
}
return ComponentTree.create(components);
return new Polygon2D(polygon, holes);
}
/** Builds a Polygon2D from XY polygon */
static Component2D create(XYPolygon polygon) {
XYPolygon gonHoles[] = polygon.getHoles();
Component2D holes = null;
if (gonHoles.length > 0) {
holes = XYGeometry.create(gonHoles);
}
return new Polygon2D(polygon, holes);
}
}

View File

@ -0,0 +1,51 @@
/*
* 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;
/**
* Cartesian Geometry object.
**/
public abstract class XYGeometry {
/** get a Component2D from this object */
protected abstract Component2D toComponent2D();
/** Creates a Component2D from the provided XYGeometries array */
public static Component2D create(XYGeometry... xyGeometries) {
if (xyGeometries == null) {
throw new IllegalArgumentException("geometries must not be null");
}
if (xyGeometries.length == 0) {
throw new IllegalArgumentException("geometries must not be empty");
}
if (xyGeometries.length == 1) {
if (xyGeometries[0] == null) {
throw new IllegalArgumentException("geometries[0] must not be null");
}
return xyGeometries[0].toComponent2D();
}
Component2D[] components = new Component2D[xyGeometries.length];
for (int i = 0; i < xyGeometries.length; i++) {
if (xyGeometries[i] == null) {
throw new IllegalArgumentException("geometries[" + i + "] must not be null");
}
components[i] = xyGeometries[i].toComponent2D();
}
return ComponentTree.create(components);
}
}

View File

@ -19,10 +19,10 @@ package org.apache.lucene.geo;
import java.util.Arrays;
/**
* Represents a line in cartesian space. You can construct the Line directly with {@code double[]}, {@code double[]} x, y arrays
* Represents a line in cartesian space. You can construct the Line directly with {@code float[]}, {@code float[]} x, y arrays
* coordinates.
*/
public class XYLine {
public class XYLine extends XYGeometry {
/** array of x coordinates */
private final double[] x;
/** array of y coordinates */
@ -30,7 +30,7 @@ public class XYLine {
/** minimum x of this line's bounding box */
public final double minX;
/** maximum x of this line's bounding box */
/** maximum y of this line's bounding box */
public final double maxX;
/** minimum y of this line's bounding box */
public final double minY;
@ -38,7 +38,7 @@ public class XYLine {
public final double maxY;
/**
* Creates a new Line from the supplied x/y array.
* Creates a new Line from the supplied X/Y array.
*/
public XYLine(float[] x, float[] y) {
if (x == null) {
@ -103,6 +103,19 @@ public class XYLine {
return y.clone();
}
@Override
protected Component2D toComponent2D() {
return Line2D.create(this);
}
public String toGeoJSON() {
StringBuilder sb = new StringBuilder();
sb.append("[");
sb.append(Polygon.verticesToGeoJSON(x, y));
sb.append("]");
return sb.toString();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
@ -132,13 +145,4 @@ public class XYLine {
sb.append(')');
return sb.toString();
}
/** prints polygons as geojson */
public String toGeoJSON() {
StringBuilder sb = new StringBuilder();
sb.append("[");
sb.append(Polygon.verticesToGeoJSON(x, y));
sb.append("]");
return sb.toString();
}
}

View File

@ -0,0 +1,85 @@
/*
* 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;
/**
* Represents a point on the earth's surface. You can construct the point directly with {@code double}
* coordinates.
* <p>
* NOTES:
* <ol>
* <li>latitude/longitude values must be in decimal degrees.
* <li>For more advanced GeoSpatial indexing and query operations see the {@code spatial-extras} module
* </ol>
*/
public final class XYPoint extends XYGeometry {
/** latitude coordinate */
private final double x;
/** longitude coordinate */
private final double y;
/**
* Creates a new Point from the supplied latitude/longitude.
*/
public XYPoint(float x, float y) {
this.x = x;
this.y = y;
}
/** Returns latitude value at given index */
public double getX() {
return x;
}
/** Returns longitude value at given index */
public double getY() {
return y;
}
@Override
protected Component2D toComponent2D() {
return Point2D.create(this);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof XYPoint)) return false;
XYPoint point = (XYPoint) o;
return point.x == x && point.y == y;
}
@Override
public int hashCode() {
int result = Double.hashCode(x);
result = 31 * result + Double.hashCode(y);
return result;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("Point(");
sb.append(x);
sb.append(",");
sb.append(y);
sb.append(')');
return sb.toString();
}
}

View File

@ -19,10 +19,10 @@ package org.apache.lucene.geo;
import java.util.Arrays;
/**
* Represents a polygon in cartesian space. You can construct the Polygon directly with {@code double[]}, {@code double[]} x, y arrays
* Represents a polygon in cartesian space. You can construct the Polygon directly with {@code float[]}, {@code float[]} x, y arrays
* coordinates.
*/
public class XYPolygon {
public final class XYPolygon extends XYGeometry {
private final double[] x;
private final double[] y;
private final XYPolygon[] holes;
@ -145,6 +145,23 @@ public class XYPolygon {
return holes.length;
}
@Override
protected Component2D toComponent2D() {
return Polygon2D.create(this);
}
public String toGeoJSON() {
StringBuilder sb = new StringBuilder();
sb.append("[");
sb.append(Polygon.verticesToGeoJSON(y, x));
for (XYPolygon hole : holes) {
sb.append(",");
sb.append(Polygon.verticesToGeoJSON(hole.y, hole.x));
}
sb.append("]");
return sb.toString();
}
@Override
public int hashCode() {
final int prime = 31;
@ -183,17 +200,4 @@ public class XYPolygon {
}
return sb.toString();
}
/** prints polygons as geojson */
public String toGeoJSON() {
StringBuilder sb = new StringBuilder();
sb.append("[");
sb.append(Polygon.verticesToGeoJSON(y, x));
for (XYPolygon hole : holes) {
sb.append(",");
sb.append(Polygon.verticesToGeoJSON(hole.y, hole.x));
}
sb.append("]");
return sb.toString();
}
}

View File

@ -1,44 +0,0 @@
/*
* 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;
/**
* 2D cartesian polygon implementation represented as a balanced interval tree of edges.
*
* @lucene.internal
*/
public class XYPolygon2D extends Polygon2D {
protected XYPolygon2D(XYPolygon polygon, Component2D holes) {
super(polygon.minX, polygon.maxX, polygon.minY, polygon.maxY, polygon.getPolyX(), polygon.getPolyY(), holes);
}
/** Builds a Polygon2D from multipolygon */
public static Component2D create(XYPolygon... polygons) {
XYPolygon2D components[] = new XYPolygon2D[polygons.length];
for (int i = 0; i < components.length; i++) {
XYPolygon gon = polygons[i];
XYPolygon gonHoles[] = gon.getHoles();
Component2D holes = null;
if (gonHoles.length > 0) {
holes = create(gonHoles);
}
components[i] = new XYPolygon2D(gon, holes);
}
return ComponentTree.create(components);
}
}

View File

@ -17,7 +17,7 @@
package org.apache.lucene.geo;
/** Represents a x/y cartesian rectangle. */
public class XYRectangle {
public final class XYRectangle extends XYGeometry {
/** minimum x value */
public final double minX;
/** minimum y value */
@ -37,6 +37,11 @@ public class XYRectangle {
assert minY <= maxY;
}
@Override
protected Component2D toComponent2D() {
return XYRectangle2D.create(this);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;

View File

@ -24,17 +24,15 @@ import static org.apache.lucene.geo.GeoUtils.orient;
/**
* 2D rectangle implementation containing cartesian spatial logic.
*
* @lucene.internal
*/
public class XYRectangle2D implements Component2D {
final class XYRectangle2D implements Component2D {
private final double minX;
private final double maxX;
private final double minY;
private final double maxY;
protected XYRectangle2D(double minX, double maxX, double minY, double maxY) {
private XYRectangle2D(double minX, double maxX, double minY, double maxY) {
this.minX = minX;
this.maxX = maxX;
this.minY = minY;
@ -235,15 +233,11 @@ public class XYRectangle2D implements Component2D {
return sb.toString();
}
/** create a component2D from provided array of rectangles */
public static Component2D create(XYRectangle... rectangles) {
XYRectangle2D[] components = new XYRectangle2D[rectangles.length];
for (int i = 0; i < components.length; ++i) {
components[i] = new XYRectangle2D(XYEncodingUtils.decode(XYEncodingUtils.encode(rectangles[i].minX)),
XYEncodingUtils.decode(XYEncodingUtils.encode(rectangles[i].maxX)),
XYEncodingUtils.decode(XYEncodingUtils.encode(rectangles[i].minY)),
XYEncodingUtils.decode(XYEncodingUtils.encode(rectangles[i].maxY)));
}
return ComponentTree.create(components);
/** create a component2D from the provided XY rectangle */
static Component2D create(XYRectangle rectangle) {
return new XYRectangle2D(XYEncodingUtils.decode(XYEncodingUtils.encode(rectangle.minX)),
XYEncodingUtils.decode(XYEncodingUtils.encode(rectangle.maxX)),
XYEncodingUtils.decode(XYEncodingUtils.encode(rectangle.minY)),
XYEncodingUtils.decode(XYEncodingUtils.encode(rectangle.maxY)));
}
}

View File

@ -22,11 +22,9 @@ import com.carrotsearch.randomizedtesting.generators.RandomPicks;
import org.apache.lucene.document.ShapeField.QueryRelation;
import org.apache.lucene.geo.Component2D;
import org.apache.lucene.geo.GeoTestUtil;
import org.apache.lucene.geo.LatLonGeometry;
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;
@ -75,17 +73,22 @@ public abstract class BaseLatLonShapeTestCase extends BaseShapeTestCase {
@Override
protected Component2D toLine2D(Object... lines) {
return Line2D.create(Arrays.stream(lines).toArray(Line[]::new));
return LatLonGeometry.create(Arrays.stream(lines).toArray(Line[]::new));
}
@Override
protected Component2D toPolygon2D(Object... polygons) {
return Polygon2D.create(Arrays.stream(polygons).toArray(Polygon[]::new));
return LatLonGeometry.create(Arrays.stream(polygons).toArray(Polygon[]::new));
}
@Override
protected Component2D toPoint2D(Object... points) {
return Point2D.create(Arrays.stream(points).toArray(double[][]::new));
double[][] p = Arrays.stream(points).toArray(double[][]::new);
org.apache.lucene.geo.Point[] pointArray = new org.apache.lucene.geo.Point[points.length];
for (int i =0; i < points.length; i++) {
pointArray[i] = new org.apache.lucene.geo.Point(p[i][0], p[i][1]);
}
return LatLonGeometry.create(pointArray);
}
@Override
@ -388,5 +391,4 @@ public abstract class BaseLatLonShapeTestCase extends BaseShapeTestCase {
return sb.toString();
}
}
}

View File

@ -22,12 +22,11 @@ import java.util.Random;
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.XYGeometry;
import org.apache.lucene.geo.XYLine;
import org.apache.lucene.geo.XYPoint;
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;
@ -68,17 +67,22 @@ public abstract class BaseXYShapeTestCase extends BaseShapeTestCase {
@Override
protected Component2D toPoint2D(Object... points) {
return Point2D.create(Arrays.stream(points).toArray(float[][]::new));
float[][] p = Arrays.stream(points).toArray(float[][]::new);
XYPoint[] pointArray = new XYPoint[points.length];
for (int i =0; i < points.length; i++) {
pointArray[i] = new XYPoint(p[i][0], p[i][1]);
}
return XYGeometry.create(pointArray);
}
@Override
protected Component2D toLine2D(Object... lines) {
return Line2D.create(Arrays.stream(lines).toArray(XYLine[]::new));
return XYGeometry.create(Arrays.stream(lines).toArray(XYLine[]::new));
}
@Override
protected Component2D toPolygon2D(Object... polygons) {
return XYPolygon2D.create(Arrays.stream(polygons).toArray(XYPolygon[]::new));
return XYGeometry.create(Arrays.stream(polygons).toArray(XYPolygon[]::new));
}
@Override

View File

@ -21,10 +21,9 @@ import org.apache.lucene.document.ShapeField.QueryRelation;
import org.apache.lucene.geo.Component2D;
import org.apache.lucene.geo.GeoEncodingUtils;
import org.apache.lucene.geo.GeoTestUtil;
import org.apache.lucene.geo.LatLonGeometry;
import org.apache.lucene.geo.Line;
import org.apache.lucene.geo.Line2D;
import org.apache.lucene.geo.Polygon;
import org.apache.lucene.geo.Polygon2D;
import org.apache.lucene.geo.Rectangle;
import org.apache.lucene.geo.Rectangle2D;
import org.apache.lucene.geo.Tessellator;
@ -515,7 +514,7 @@ public class TestLatLonShape extends LuceneTestCase {
double blon = -52.67048754768767;
Polygon polygon = new Polygon(new double[] {-14.448264200949083, 0, 0, -14.448264200949083, -14.448264200949083},
new double[] {0.9999999403953552, 0.9999999403953552, 124.50086371762484, 124.50086371762484, 0.9999999403953552});
Component2D polygon2D = Polygon2D.create(polygon);
Component2D polygon2D = LatLonGeometry.create(polygon);
PointValues.Relation rel = polygon2D.relateTriangle(
quantizeLon(alon), quantizeLat(blat),
quantizeLon(blon), quantizeLat(blat),
@ -533,7 +532,7 @@ public class TestLatLonShape extends LuceneTestCase {
public void testTriangleTouchingEdges() {
Polygon p = new Polygon(new double[] {0, 0, 1, 1, 0}, new double[] {0, 1, 1, 0, 0});
Component2D polygon2D = Polygon2D.create(p);
Component2D polygon2D = LatLonGeometry.create(p);
//3 shared points
PointValues.Relation rel = polygon2D.relateTriangle(
quantizeLon(0.5), quantizeLat(0),
@ -633,7 +632,7 @@ public class TestLatLonShape extends LuceneTestCase {
public void testTriangleCrossingPolygonVertices() {
Polygon p = new Polygon(new double[] {0, 0, -5, -10, -5, 0}, new double[] {-1, 1, 5, 0, -5, -1});
Component2D polygon2D = Polygon2D.create(p);
Component2D polygon2D = LatLonGeometry.create(p);
PointValues.Relation rel = polygon2D.relateTriangle(
quantizeLon(-5), quantizeLat(0),
quantizeLon(10), quantizeLat(0),
@ -643,7 +642,7 @@ public class TestLatLonShape extends LuceneTestCase {
public void testLineCrossingPolygonVertices() {
Polygon p = new Polygon(new double[] {0, -1, 0, 1, 0}, new double[] {-1, 0, 1, 0, -1});
Component2D polygon2D = Polygon2D.create(p);
Component2D polygon2D = LatLonGeometry.create(p);
PointValues.Relation rel = polygon2D.relateTriangle(
quantizeLon(-1.5), quantizeLat(0),
quantizeLon(1.5), quantizeLat(0),
@ -653,7 +652,7 @@ public class TestLatLonShape extends LuceneTestCase {
public void testLineSharedLine() {
Line l = new Line(new double[] {0, 0, 0, 0}, new double[] {-2, -1, 0, 1});
Component2D l2d = Line2D.create(l);
Component2D l2d = LatLonGeometry.create(l);
PointValues.Relation r = l2d.relateTriangle(
quantizeLon(-5), quantizeLat(0),
quantizeLon(5), quantizeLat(0),

View File

@ -19,8 +19,8 @@ package org.apache.lucene.document;
import org.apache.lucene.geo.Component2D;
import org.apache.lucene.geo.GeoEncodingUtils;
import org.apache.lucene.geo.GeoTestUtil;
import org.apache.lucene.geo.LatLonGeometry;
import org.apache.lucene.geo.Polygon;
import org.apache.lucene.geo.Polygon2D;
/** Test case for LatLonShape encoding */
public class TestLatLonShapeEncoding extends BaseShapeEncodingTestCase {
@ -62,6 +62,6 @@ public class TestLatLonShapeEncoding extends BaseShapeEncodingTestCase {
@Override
protected Component2D createPolygon2D(Object polygon) {
return Polygon2D.create((Polygon)polygon);
return LatLonGeometry.create((Polygon)polygon);
}
}

View File

@ -23,9 +23,9 @@ import java.util.Random;
import org.apache.lucene.document.ShapeField.QueryRelation;
import org.apache.lucene.geo.Component2D;
import org.apache.lucene.geo.ShapeTestUtil;
import org.apache.lucene.geo.XYGeometry;
import org.apache.lucene.geo.XYLine;
import org.apache.lucene.geo.XYRectangle;
import org.apache.lucene.geo.XYRectangle2D;
import org.apache.lucene.index.PointValues.Relation;
/** random cartesian bounding box, line, and polygon query tests for random generated cartesian {@link XYLine} types */
@ -80,7 +80,7 @@ public class TestXYLineShapeQueries extends BaseXYShapeTestCase {
@Override
public boolean testBBoxQuery(double minY, double maxY, double minX, double maxX, Object shape) {
Component2D rectangle2D = XYRectangle2D.create(new XYRectangle(minX, maxX, minY, maxY));
Component2D rectangle2D = XYGeometry.create(new XYRectangle(minX, maxX, minY, maxY));
return testComponentQuery(rectangle2D, shape);
}

View File

@ -21,9 +21,9 @@ import java.util.List;
import org.apache.lucene.document.ShapeField.QueryRelation;
import org.apache.lucene.geo.Component2D;
import org.apache.lucene.geo.XYGeometry;
import org.apache.lucene.geo.XYLine;
import org.apache.lucene.geo.XYRectangle;
import org.apache.lucene.geo.XYRectangle2D;
/** random cartesian bounding box, line, and polygon query tests for random indexed arrays of cartesian {@link XYLine} types */
public class TestXYMultiLineShapeQueries extends BaseXYShapeTestCase {
@ -76,7 +76,7 @@ public class TestXYMultiLineShapeQueries extends BaseXYShapeTestCase {
@Override
public boolean testBBoxQuery(double minY, double maxY, double minX, double maxX, Object shape) {
Component2D rectangle2D = XYRectangle2D.create(new XYRectangle(minX, maxX, minY, maxY));
Component2D rectangle2D = XYGeometry.create(new XYRectangle(minX, maxX, minY, maxY));
return testComponentQuery(rectangle2D, shape);
}

View File

@ -21,8 +21,8 @@ import java.util.List;
import org.apache.lucene.document.ShapeField.QueryRelation;
import org.apache.lucene.geo.Component2D;
import org.apache.lucene.geo.XYGeometry;
import org.apache.lucene.geo.XYRectangle;
import org.apache.lucene.geo.XYRectangle2D;
/** random cartesian bounding box, line, and polygon query tests for random indexed arrays of {@code x, y} points */
public class TestXYMultiPointShapeQueries extends BaseXYShapeTestCase {
@ -75,7 +75,7 @@ public class TestXYMultiPointShapeQueries extends BaseXYShapeTestCase {
@Override
public boolean testBBoxQuery(double minY, double maxY, double minX, double maxX, Object shape) {
Component2D rectangle2D = XYRectangle2D.create(new XYRectangle(minX, maxX, minY, maxY));
Component2D rectangle2D = XYGeometry.create(new XYRectangle(minX, maxX, minY, maxY));
return testComponentQuery(rectangle2D, shape);
}

View File

@ -24,7 +24,7 @@ import org.apache.lucene.geo.Component2D;
import org.apache.lucene.geo.Tessellator;
import org.apache.lucene.geo.XYPolygon;
import org.apache.lucene.geo.XYRectangle;
import org.apache.lucene.geo.XYRectangle2D;
import org.apache.lucene.geo.XYGeometry;
import org.apache.lucene.util.LuceneTestCase;
/** random cartesian bounding box, line, and polygon query tests for random indexed arrays of cartesian {@link XYPolygon} types */
@ -115,7 +115,7 @@ public class TestXYMultiPolygonShapeQueries extends BaseXYShapeTestCase {
@Override
public boolean testBBoxQuery(double minY, double maxY, double minX, double maxX, Object shape) {
Component2D rectangle2D = XYRectangle2D.create(new XYRectangle(minX, maxX, minY, maxY));
Component2D rectangle2D = XYGeometry.create(new XYRectangle(minX, maxX, minY, maxY));
return testComponentQuery(rectangle2D, shape);
}

View File

@ -23,9 +23,9 @@ import java.util.Random;
import org.apache.lucene.document.ShapeField.QueryRelation;
import org.apache.lucene.geo.Component2D;
import org.apache.lucene.geo.ShapeTestUtil;
import org.apache.lucene.geo.XYGeometry;
import org.apache.lucene.geo.XYLine;
import org.apache.lucene.geo.XYRectangle;
import org.apache.lucene.geo.XYRectangle2D;
import org.apache.lucene.index.PointValues.Relation;
/** random cartesian bounding box, line, and polygon query tests for random generated {@code x, y} points */
@ -80,7 +80,7 @@ public class TestXYPointShapeQueries extends BaseXYShapeTestCase {
@Override
public boolean testBBoxQuery(double minY, double maxY, double minX, double maxX, Object shape) {
Component2D rectangle2D = XYRectangle2D.create(new XYRectangle(minX, maxX, minY, maxY));
Component2D rectangle2D = XYGeometry.create(new XYRectangle(minX, maxX, minY, maxY));
return testComponentQuery(rectangle2D, shape);
}

View File

@ -21,9 +21,9 @@ import java.util.List;
import org.apache.lucene.document.ShapeField.QueryRelation;
import org.apache.lucene.geo.Component2D;
import org.apache.lucene.geo.Tessellator;
import org.apache.lucene.geo.XYGeometry;
import org.apache.lucene.geo.XYPolygon;
import org.apache.lucene.geo.XYRectangle;
import org.apache.lucene.geo.XYRectangle2D;
import org.apache.lucene.index.PointValues.Relation;
/** random cartesian bounding box, line, and polygon query tests for random indexed {@link XYPolygon} types */
@ -66,7 +66,7 @@ public class TestXYPolygonShapeQueries extends BaseXYShapeTestCase {
@Override
public boolean testBBoxQuery(double minY, double maxY, double minX, double maxX, Object shape) {
Component2D rectangle2D = XYRectangle2D.create(new XYRectangle(minX, maxX, minY, maxY));
Component2D rectangle2D = XYGeometry.create(new XYRectangle(minX, maxX, minY, maxY));
return testComponentQuery(rectangle2D, shape);
}

View File

@ -19,8 +19,8 @@ package org.apache.lucene.document;
import org.apache.lucene.geo.Component2D;
import org.apache.lucene.geo.ShapeTestUtil;
import org.apache.lucene.geo.XYEncodingUtils;
import org.apache.lucene.geo.XYGeometry;
import org.apache.lucene.geo.XYPolygon;
import org.apache.lucene.geo.XYPolygon2D;
/** tests XYShape encoding */
public class TestXYShapeEncoding extends BaseShapeEncodingTestCase {
@ -61,6 +61,6 @@ public class TestXYShapeEncoding extends BaseShapeEncodingTestCase {
@Override
protected Component2D createPolygon2D(Object polygon) {
return XYPolygon2D.create((XYPolygon)polygon);
return XYGeometry.create((XYPolygon)polygon);
}
}

View File

@ -23,7 +23,7 @@ import org.apache.lucene.util.LuceneTestCase;
public class TestPoint2D extends LuceneTestCase {
public void testTriangleDisjoint() {
Component2D point2D = Point2D.create(new double[] {0, 0});
Component2D point2D = Point2D.create(new Point(0, 0));
double ax = 4;
double ay = 4;
double bx = 5;
@ -36,7 +36,7 @@ public class TestPoint2D extends LuceneTestCase {
}
public void testTriangleIntersects() {
Component2D point2D = Point2D.create(new double[] {0, 0});
Component2D point2D = Point2D.create(new Point(0, 0));
double ax = 0.0;
double ay = 0.0;
double bx = 1;
@ -49,7 +49,7 @@ public class TestPoint2D extends LuceneTestCase {
}
public void testTriangleContains() {
Component2D point2D = Point2D.create(new double[] {0, 0});
Component2D point2D = Point2D.create(new Point(0, 0));
double ax = 0.0;
double ay = 0.0;
double bx = 0;
@ -63,7 +63,7 @@ public class TestPoint2D extends LuceneTestCase {
public void testRandomTriangles() {
Component2D point2D = Point2D.create(new double[] {GeoTestUtil.nextLatitude(), GeoTestUtil.nextLongitude()});
Component2D point2D = Point2D.create(new Point(GeoTestUtil.nextLatitude(), GeoTestUtil.nextLongitude()));
for (int i =0; i < 100; i++) {
double ax = GeoTestUtil.nextLongitude();

View File

@ -35,7 +35,7 @@ public class TestPolygon2D extends LuceneTestCase {
Polygon hole = new Polygon(new double[] { -10, -10, 10, 10, -10 }, new double[] { -10, 10, 10, -10, -10 });
Polygon outer = new Polygon(new double[] { -50, -50, 50, 50, -50 }, new double[] { -50, 50, 50, -50, -50 }, hole);
Polygon island = new Polygon(new double[] { -5, -5, 5, 5, -5 }, new double[] { -5, 5, 5, -5, -5 } );
Component2D polygon = Polygon2D.create(outer, island);
Component2D polygon = LatLonGeometry.create(outer, island);
// contains(point)
assertTrue(polygon.contains(-2, 2)); // on the island