LUCENE-9141: Simplify LatLonShapeXQuery API by adding a new abstract class called LatLonGeometry. (#1170)

This commit is contained in:
Ignacio Vera 2020-01-30 08:03:22 +01:00 committed by GitHub
parent 29469b454f
commit a9482911a8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 248 additions and 328 deletions

View File

@ -134,6 +134,9 @@ Improvements
* LUCENE-9152: Improve line intersections with polygons when they are touching from the outside. (Ignacio Vera)
* LUCENE-9141: Simplify LatLonShapeXQuery API by adding a new abstract class called LatLonGeometry. Queries are
executed with input objects that extend such interface. (Ignacio Vera)
Optimizations
---------------------

View File

@ -22,7 +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.GeoUtils;
import org.apache.lucene.geo.LatLonGeometry;
import org.apache.lucene.geo.Line;
import org.apache.lucene.geo.Point;
import org.apache.lucene.geo.Polygon;
import org.apache.lucene.geo.Tessellator;
import org.apache.lucene.index.PointValues; // javadoc
@ -37,7 +39,7 @@ import static org.apache.lucene.geo.GeoEncodingUtils.encodeLongitude;
* An geo shape utility class for indexing and searching gis geometries
* whose vertices are latitude, longitude values (in decimal degrees).
* <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, Polygon)} for indexing a geo polygon.
* <li>{@link #createIndexableFields(String, Line)} for indexing a geo linestring.
@ -45,6 +47,8 @@ import static org.apache.lucene.geo.GeoEncodingUtils.encodeLongitude;
* <li>{@link #newBoxQuery newBoxQuery()} for matching geo shapes that have some {@link QueryRelation} with a bounding box.
* <li>{@link #newLineQuery newLineQuery()} for matching geo shapes that have some {@link QueryRelation} with a linestring.
* <li>{@link #newPolygonQuery newPolygonQuery()} for matching geo shapes that have some {@link QueryRelation} with a polygon.
* <li>{@link #newGeometryQuery newGeometryQuery()} for matching geo shapes that have some {@link QueryRelation}
* with one or more {@link LatLonGeometry}.
* </ul>
* <b>WARNING</b>: Like {@link LatLonPoint}, vertex values are indexed with some loss of precision from the
@ -107,40 +111,36 @@ public class LatLonShape {
* note: does not support dateline crossing
**/
public static Query newLineQuery(String field, QueryRelation queryRelation, Line... 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 LatLonShapeLineQuery(field, queryRelation, lines);
return newGeometryQuery(field, queryRelation, lines);
}
/** create a query to find all indexed geo shapes that intersect a provided polygon (or array of polygons)
* note: does not support dateline crossing
**/
public static Query newPolygonQuery(String field, QueryRelation queryRelation, Polygon... 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 LatLonShapePolygonQuery(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
/** create a query to find all indexed shapes that comply the {@link QueryRelation} with the provided points
**/
public static Query newPointQuery(String field, QueryRelation queryRelation, double[]... points) {
if (queryRelation == QueryRelation.CONTAINS && points.length > 1) {
Point[] pointArray = new Point[points.length];
for (int i =0; i < points.length; i++) {
pointArray[i] = new Point(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 (or array of geometries).
**/
public static Query newGeometryQuery(String field, QueryRelation queryRelation, LatLonGeometry... latLonGeometries) {
if (queryRelation == QueryRelation.CONTAINS && latLonGeometries.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 < latLonGeometries.length; i++) {
builder.add(newGeometryQuery(field, queryRelation, latLonGeometries[i]), BooleanClause.Occur.MUST);
}
return builder.build();
}
return new LatLonShapePointQuery(field, queryRelation, points);
return new LatLonShapeQuery(field, queryRelation, latLonGeometries);
}
}

View File

@ -1,143 +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.GeoEncodingUtils;
import org.apache.lucene.geo.Line;
import org.apache.lucene.geo.Line2D;
import org.apache.lucene.index.PointValues.Relation;
import org.apache.lucene.util.NumericUtils;
/**
* Finds all previously indexed geo shapes that intersect the specified arbitrary {@code Line}.
* <p>
* Note:
* <ul>
* <li>{@code QueryRelation.WITHIN} queries are not yet supported</li>
* <li>Dateline crossing is 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.LatLonShape#createIndexableFields} added per document.
**/
final class LatLonShapeLineQuery extends ShapeQuery {
final Line[] lines;
final private Component2D line2D;
public LatLonShapeLineQuery(String field, QueryRelation queryRelation, Line... lines) {
super(field, queryRelation);
/** line queries do not support within relations, only intersects and disjoint */
if (queryRelation == QueryRelation.WITHIN) {
throw new IllegalArgumentException("LatLonShapeLineQuery 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].minLon > lines[i].maxLon) {
throw new IllegalArgumentException("LatLonShapeLineQuery does not currently support querying across dateline.");
}
}
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 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 line2D.relate(minLon, maxLon, minLat, maxLat);
}
@Override
protected boolean queryMatches(byte[] t, ShapeField.DecodedTriangle scratchTriangle, 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 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 = 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 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("Line(").append(lines[0].toGeoJSON()).append(')');
return sb.toString();
}
@Override
protected boolean equalsTo(Object o) {
return super.equalsTo(o) && Arrays.equals(lines, ((LatLonShapeLineQuery)o).lines);
}
@Override
public int hashCode() {
int hash = super.hashCode();
hash = 31 * hash + Arrays.hashCode(lines);
return hash;
}
}

View File

@ -1,123 +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.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.
**/
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();
}
}

View File

@ -21,46 +21,38 @@ import java.util.Arrays;
import org.apache.lucene.document.ShapeField.QueryRelation;
import org.apache.lucene.geo.Component2D;
import org.apache.lucene.geo.GeoEncodingUtils;
import org.apache.lucene.geo.Polygon;
import org.apache.lucene.geo.Polygon2D;
import org.apache.lucene.geo.LatLonGeometry;
import org.apache.lucene.geo.Line;
import org.apache.lucene.index.PointValues.Relation;
import org.apache.lucene.util.NumericUtils;
/**
* Finds all previously indexed geo shapes that intersect the specified arbitrary.
* <p>
* Note:
* <ul>
* <li>Dateline crossing is not yet supported. Polygons should be cut at the dateline and provided as a multipolygon query</li>
* </ul>
* Finds all previously indexed cartesian shapes that comply the given {@link QueryRelation} with
* the specified array of {@link LatLonGeometry}.
*
* <p>The field must be indexed using {@link LatLonShape#createIndexableFields} added per document.
*
* <p>The field must be indexed using
* {@link org.apache.lucene.document.LatLonShape#createIndexableFields} added per document.
**/
final class LatLonShapePolygonQuery extends ShapeQuery {
final Polygon[] polygons;
final private Component2D poly2D;
final class LatLonShapeQuery extends ShapeQuery {
final private LatLonGeometry[] geometries;
final private Component2D component2D;
/**
* Creates a query that matches all indexed shapes to the provided polygons
* Creates a query that matches all indexed shapes to the provided array of {@link LatLonGeometry}
*/
public LatLonShapePolygonQuery(String field, QueryRelation queryRelation, Polygon... polygons) {
LatLonShapeQuery(String field, QueryRelation queryRelation, LatLonGeometry[] 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].minLon > polygons[i].maxLon) {
throw new IllegalArgumentException("LatLonShapePolygonQuery does not currently support querying across dateline.");
if (queryRelation == QueryRelation.WITHIN) {
for (LatLonGeometry geometry : geometries) {
if (geometry instanceof Line) {
// TODO: line queries do not support within relations
throw new IllegalArgumentException("LatLonShapeQuery does not support " + QueryRelation.WITHIN + " queries with line geometries");
}
}
}
this.polygons = polygons.clone();
this.poly2D = Polygon2D.create(polygons);
this.component2D = LatLonGeometry.create(geometries);
this.geometries = geometries.clone();
}
@Override
@ -73,7 +65,7 @@ final class LatLonShapePolygonQuery extends ShapeQuery {
double maxLon = GeoEncodingUtils.decodeLongitude(NumericUtils.sortableBytesToInt(maxTriangle, maxXOffset));
// check internal node against query
return poly2D.relate(minLon, maxLon, minLat, maxLat);
return component2D.relate(minLon, maxLon, minLat, maxLat);
}
@Override
@ -88,9 +80,9 @@ final class LatLonShapePolygonQuery extends ShapeQuery {
double clon = GeoEncodingUtils.decodeLongitude(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 + "]");
}
}
@ -106,7 +98,7 @@ final class LatLonShapePolygonQuery extends ShapeQuery {
double clat = GeoEncodingUtils.decodeLatitude(scratchTriangle.cY);
double clon = GeoEncodingUtils.decodeLongitude(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
@ -119,19 +111,24 @@ final class LatLonShapePolygonQuery extends ShapeQuery {
sb.append(this.field);
sb.append(':');
}
sb.append("Polygon(").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, ((LatLonShapePolygonQuery)o).polygons);
return super.equalsTo(o) && Arrays.equals(geometries, ((LatLonShapeQuery)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

@ -79,7 +79,7 @@ abstract class ShapeQuery extends Query {
/**
* relates an internal node (bounding box of a range of triangles) to the target query
* Note: logic is specific to query type
* see {@link LatLonShapeBoundingBoxQuery#relateRangeToQuery} and {@link LatLonShapePolygonQuery#relateRangeToQuery}
* see {@link LatLonShapeBoundingBoxQuery#relateRangeToQuery} and {@link LatLonShapeQuery#relateRangeToQuery}
*/
protected abstract Relation relateRangeBBoxToQuery(int minXOffset, int minYOffset, byte[] minTriangle,
int maxXOffset, int maxYOffset, byte[] maxTriangle);

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

View File

@ -28,7 +28,7 @@ import java.util.Arrays;
* <li>For more advanced GeoSpatial indexing and query operations see the {@code spatial-extras} module
* </ol>
*/
public class Line {
public class Line extends LatLonGeometry {
/** array of latitude coordinates */
private final double[] lats;
/** array of longitude coordinates */
@ -107,6 +107,11 @@ public class Line {
return lons.clone();
}
@Override
protected Component2D toComponent2D() {
return Line2D.create(this);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
@ -137,7 +142,7 @@ public class Line {
return sb.toString();
}
/** prints polygons as geojson */
/** prints lines as geojson */
public String toGeoJSON() {
StringBuilder sb = new StringBuilder();
sb.append("[");

View File

@ -0,0 +1,89 @@
/*
* 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 Point extends LatLonGeometry {
/** latitude coordinate */
private final double lat;
/** longitude coordinate */
private final double lon;
/**
* Creates a new Point from the supplied latitude/longitude.
*/
public Point(double lat, double lon) {
GeoUtils.checkLatitude(lat);
GeoUtils.checkLongitude(lon);
this.lat = lat;
this.lon = lon;
}
/** Returns latitude value at given index */
public double getLat() {
return lat;
}
/** Returns longitude value at given index */
public double getLon() {
return lon;
}
@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});
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Point)) return false;
Point point = (Point) o;
return point.lat == lat && point.lon == lon;
}
@Override
public int hashCode() {
int result = Double.hashCode(lat);
result = 31 * result + Double.hashCode(lon);
return result;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("Point(");
sb.append(lon);
sb.append(",");
sb.append(lat);
sb.append(')');
return sb.toString();
}
}

View File

@ -37,7 +37,7 @@ import org.apache.lucene.geo.GeoUtils.WindingOrder;
* </ol>
* @lucene.experimental
*/
public final class Polygon {
public final class Polygon extends LatLonGeometry {
private final double[] polyLats;
private final double[] polyLons;
private final Polygon[] holes;
@ -163,6 +163,11 @@ public final class Polygon {
return holes.length;
}
@Override
protected Component2D toComponent2D() {
return Polygon2D.create(this);
}
@Override
public int hashCode() {
final int prime = 31;

View File

@ -0,0 +1,36 @@
/*
* 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.util.LuceneTestCase;
public class TestPoint extends LuceneTestCase {
public void testInvalidLat() {
IllegalArgumentException expected = expectThrows(IllegalArgumentException.class, () -> {
new Point(134.14, 45.23);
});
assertTrue(expected.getMessage().contains("invalid latitude 134.14; must be between -90.0 and 90.0"));
}
public void testInvalidLon() {
IllegalArgumentException expected = expectThrows(IllegalArgumentException.class, () -> {
new Point(43.5, 180.5);
});
assertTrue(expected.getMessage().contains("invalid longitude 180.5; must be between -180.0 and 180.0"));
}
}