From 035523ba7a86773500c053129f65c9a6dc4fd384 Mon Sep 17 00:00:00 2001 From: David Smiley Date: Wed, 20 Sep 2017 23:09:46 -0400 Subject: [PATCH] LUCENE-7951: Spatial4j implementations using Geo3d --- .../lucene/spatial-extras/pom.xml.template | 7 + lucene/CHANGES.txt | 4 + lucene/spatial-extras/build.xml | 9 + .../spatial/spatial4j/Geo3dBinaryCodec.java | 130 ++++++ .../spatial/spatial4j/Geo3dCircleShape.java | 82 ++++ .../spatial4j/Geo3dDistanceCalculator.java | 154 +++++++ .../spatial/spatial4j/Geo3dPointShape.java | 78 ++++ .../spatial4j/Geo3dRectangleShape.java | 163 +++++++ .../lucene/spatial/spatial4j/Geo3dShape.java | 164 ++++---- .../spatial/spatial4j/Geo3dShapeFactory.java | 396 ++++++++++++++++++ .../spatial4j/Geo3dSpatialContextFactory.java | 94 +++++ .../lucene/spatial/SpatialArgsTest.java | 12 +- .../spatial/spatial4j/Geo3dRptTest.java | 193 ++++----- .../Geo3dShapeRectRelationTestCase.java | 20 +- ...Geo3dShapeSphereModelRectRelationTest.java | 29 +- .../Geo3dShapeWGS84ModelRectRelationTest.java | 21 + 16 files changed, 1328 insertions(+), 228 deletions(-) create mode 100644 lucene/spatial-extras/src/java/org/apache/lucene/spatial/spatial4j/Geo3dBinaryCodec.java create mode 100644 lucene/spatial-extras/src/java/org/apache/lucene/spatial/spatial4j/Geo3dCircleShape.java create mode 100644 lucene/spatial-extras/src/java/org/apache/lucene/spatial/spatial4j/Geo3dDistanceCalculator.java create mode 100644 lucene/spatial-extras/src/java/org/apache/lucene/spatial/spatial4j/Geo3dPointShape.java create mode 100644 lucene/spatial-extras/src/java/org/apache/lucene/spatial/spatial4j/Geo3dRectangleShape.java create mode 100644 lucene/spatial-extras/src/java/org/apache/lucene/spatial/spatial4j/Geo3dShapeFactory.java create mode 100644 lucene/spatial-extras/src/java/org/apache/lucene/spatial/spatial4j/Geo3dSpatialContextFactory.java diff --git a/dev-tools/maven/lucene/spatial-extras/pom.xml.template b/dev-tools/maven/lucene/spatial-extras/pom.xml.template index c9f47145898..bae3556ff43 100644 --- a/dev-tools/maven/lucene/spatial-extras/pom.xml.template +++ b/dev-tools/maven/lucene/spatial-extras/pom.xml.template @@ -45,6 +45,13 @@ lucene-test-framework test + + org.apache.lucene + lucene-spatial3d + ${project.version} + test-jar + test + @lucene-spatial-extras.internal.dependencies@ @lucene-spatial-extras.external.dependencies@ @lucene-spatial-extras.internal.test.dependencies@ diff --git a/lucene/CHANGES.txt b/lucene/CHANGES.txt index 72c7fc3b118..fef3f139d75 100644 --- a/lucene/CHANGES.txt +++ b/lucene/CHANGES.txt @@ -28,6 +28,10 @@ New Features * LUCENE-7392: Add point based LatLonBoundingBox as new RangeField Type. (Nick Knize) +* LUCENE-7951: Spatial-extras has much better Geo3d support by implementing Spatial4j + abstractions: SpatialContextFactory, ShapeFactory, BinaryCodec, DistanceCalculator. + (Ignacio Vera, David Smiley) + Optimizations * LUCENE-7905: Optimize how OrdinalMap (used by diff --git a/lucene/spatial-extras/build.xml b/lucene/spatial-extras/build.xml index 4cfa4e53261..d95d935269b 100644 --- a/lucene/spatial-extras/build.xml +++ b/lucene/spatial-extras/build.xml @@ -38,10 +38,19 @@ + + + + + + + + + diff --git a/lucene/spatial-extras/src/java/org/apache/lucene/spatial/spatial4j/Geo3dBinaryCodec.java b/lucene/spatial-extras/src/java/org/apache/lucene/spatial/spatial4j/Geo3dBinaryCodec.java new file mode 100644 index 00000000000..650e5610c9b --- /dev/null +++ b/lucene/spatial-extras/src/java/org/apache/lucene/spatial/spatial4j/Geo3dBinaryCodec.java @@ -0,0 +1,130 @@ +/* + * 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.spatial.spatial4j; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import org.apache.lucene.spatial3d.geom.GeoAreaShape; +import org.apache.lucene.spatial3d.geom.GeoBBox; +import org.apache.lucene.spatial3d.geom.GeoCircle; +import org.apache.lucene.spatial3d.geom.GeoPointShape; +import org.apache.lucene.spatial3d.geom.PlanetModel; +import org.apache.lucene.spatial3d.geom.SerializableObject; +import org.locationtech.spatial4j.context.SpatialContext; +import org.locationtech.spatial4j.context.SpatialContextFactory; +import org.locationtech.spatial4j.io.BinaryCodec; +import org.locationtech.spatial4j.shape.Circle; +import org.locationtech.spatial4j.shape.Point; +import org.locationtech.spatial4j.shape.Rectangle; +import org.locationtech.spatial4j.shape.Shape; +import org.locationtech.spatial4j.shape.ShapeCollection; + +/** + * Geo3d implementation of {@link BinaryCodec} + * + * @lucene.experimental + */ +public class Geo3dBinaryCodec extends BinaryCodec { + + private PlanetModel planetModel; + + @SuppressWarnings("unchecked") + public Geo3dBinaryCodec(SpatialContext ctx, SpatialContextFactory factory) { + super(ctx, factory); + planetModel = ((Geo3dSpatialContextFactory) factory).planetModel; + } + + @Override + public Shape readShape(DataInput dataInput) throws IOException { + SerializableObject serializableObject = SerializableObject.readObject(planetModel, (InputStream) dataInput); + if (serializableObject instanceof GeoAreaShape) { + GeoAreaShape shape = (GeoAreaShape) serializableObject; + return new Geo3dShape<>(shape, ctx); + } + throw new IllegalArgumentException("trying to read a not supported shape: " + serializableObject.getClass()); + } + + @Override + public void writeShape(DataOutput dataOutput, Shape s) throws IOException { + if (s instanceof Geo3dShape) { + Geo3dShape geoAreaShape = (Geo3dShape) s; + SerializableObject.writeObject((OutputStream) dataOutput, geoAreaShape.shape); + } else { + throw new IllegalArgumentException("trying to write a not supported shape: " + s.getClass().getName()); + } + } + + @Override + public Point readPoint(DataInput dataInput) throws IOException { + SerializableObject serializableObject = SerializableObject.readObject(planetModel, (InputStream) dataInput); + if (serializableObject instanceof GeoPointShape) { + GeoPointShape shape = (GeoPointShape) serializableObject; + return new Geo3dPointShape(shape, ctx); + } + throw new IllegalArgumentException("trying to read a not supported point shape: " + serializableObject.getClass()); + } + + @Override + public void writePoint(DataOutput dataOutput, Point pt) throws IOException { + writeShape(dataOutput, pt); + } + + @Override + public Rectangle readRect(DataInput dataInput) throws IOException { + SerializableObject serializableObject = SerializableObject.readObject(planetModel, (InputStream) dataInput); + if (serializableObject instanceof GeoBBox) { + GeoBBox shape = (GeoBBox) serializableObject; + return new Geo3dRectangleShape(shape, ctx); + } + throw new IllegalArgumentException("trying to read a not supported rectangle shape: " + serializableObject.getClass()); + } + + @Override + public void writeRect(DataOutput dataOutput, Rectangle r) throws IOException { + writeShape(dataOutput, r); + } + + @Override + public Circle readCircle(DataInput dataInput) throws IOException { + SerializableObject serializableObject = SerializableObject.readObject(planetModel, (InputStream) dataInput); + if (serializableObject instanceof GeoCircle) { + GeoCircle shape = (GeoCircle) serializableObject; + return new Geo3dCircleShape(shape, ctx); + } + throw new IllegalArgumentException("trying to read a not supported circle shape: " + serializableObject.getClass()); + } + + @Override + public void writeCircle(DataOutput dataOutput, Circle c) throws IOException { + writeShape(dataOutput, c); + } + + @Override + public ShapeCollection readCollection(DataInput dataInput) throws IOException { + throw new UnsupportedOperationException(); + } + + @Override + public void writeCollection(DataOutput dataOutput, ShapeCollection col) throws IOException { + throw new UnsupportedOperationException(); + } +} diff --git a/lucene/spatial-extras/src/java/org/apache/lucene/spatial/spatial4j/Geo3dCircleShape.java b/lucene/spatial-extras/src/java/org/apache/lucene/spatial/spatial4j/Geo3dCircleShape.java new file mode 100644 index 00000000000..ccef92a4bc9 --- /dev/null +++ b/lucene/spatial-extras/src/java/org/apache/lucene/spatial/spatial4j/Geo3dCircleShape.java @@ -0,0 +1,82 @@ +/* + * 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.spatial.spatial4j; + +import org.apache.lucene.spatial3d.geom.GeoCircle; +import org.apache.lucene.spatial3d.geom.GeoCircleFactory; +import org.apache.lucene.spatial3d.geom.GeoPointShapeFactory; +import org.apache.lucene.spatial3d.geom.PlanetModel; +import org.locationtech.spatial4j.context.SpatialContext; +import org.locationtech.spatial4j.distance.DistanceUtils; +import org.locationtech.spatial4j.shape.Circle; +import org.locationtech.spatial4j.shape.Point; +import org.locationtech.spatial4j.shape.Shape; +import org.locationtech.spatial4j.shape.SpatialRelation; + +/** + * Specialization of a {@link Geo3dShape} which represents a {@link Circle}. + * + * @lucene.experimental + */ +public class Geo3dCircleShape extends Geo3dShape implements Circle { + + public Geo3dCircleShape(final GeoCircle shape, final SpatialContext spatialcontext) { + super(shape, spatialcontext); + } + + @Override + public void reset(double x, double y, double radiusDEG) { + shape = GeoCircleFactory.makeGeoCircle(shape.getPlanetModel(), + y * DistanceUtils.DEGREES_TO_RADIANS, + x * DistanceUtils.DEGREES_TO_RADIANS, + radiusDEG * DistanceUtils.DEGREES_TO_RADIANS); + center = null; + boundingBox = null; + } + + @Override + public double getRadius() { + return shape.getRadius() * DistanceUtils.RADIANS_TO_DEGREES; + } + + @Override + public Point getCenter() { + Point center = this.center;//volatile read once + if (center == null) { + center = new Geo3dPointShape( + GeoPointShapeFactory.makeGeoPointShape(shape.getPlanetModel(), + shape.getCenter().getLatitude(), + shape.getCenter().getLongitude()), + spatialcontext); + this.center = center; + } + return center; + } + + //TODO Improve GeoCircle to properly relate a point with WGS84 model -- LUCENE-7970 + @Override + public SpatialRelation relate(Shape other) { + if (shape.getPlanetModel() != PlanetModel.SPHERE && other instanceof Point) { + if (spatialcontext.getDistCalc().distance((Point) other, getCenter()) <= getRadius()) { + return SpatialRelation.CONTAINS; + } + return SpatialRelation.DISJOINT; + } + return super.relate(other); + } +} diff --git a/lucene/spatial-extras/src/java/org/apache/lucene/spatial/spatial4j/Geo3dDistanceCalculator.java b/lucene/spatial-extras/src/java/org/apache/lucene/spatial/spatial4j/Geo3dDistanceCalculator.java new file mode 100644 index 00000000000..5154de431d4 --- /dev/null +++ b/lucene/spatial-extras/src/java/org/apache/lucene/spatial/spatial4j/Geo3dDistanceCalculator.java @@ -0,0 +1,154 @@ +/* + * 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.spatial.spatial4j; + +import org.apache.lucene.spatial3d.geom.GeoPoint; +import org.apache.lucene.spatial3d.geom.GeoPointShape; +import org.apache.lucene.spatial3d.geom.PlanetModel; +import org.locationtech.spatial4j.context.SpatialContext; +import org.locationtech.spatial4j.distance.DistanceCalculator; +import org.locationtech.spatial4j.distance.DistanceUtils; +import org.locationtech.spatial4j.shape.Circle; +import org.locationtech.spatial4j.shape.Point; +import org.locationtech.spatial4j.shape.Rectangle; + +/** + * Geo3d implementation of {@link DistanceCalculator} + * + * @lucene.experimental + */ +public class Geo3dDistanceCalculator implements DistanceCalculator { + + protected final PlanetModel planetModel; + + public Geo3dDistanceCalculator(PlanetModel planetModel) { + this.planetModel = planetModel; + } + + @Override + public double distance(Point from, Point to) { + if (from instanceof Geo3dPointShape && to instanceof Geo3dPointShape) { + GeoPointShape pointShape1 = ((Geo3dPointShape) from).shape; + GeoPointShape pointShape2 = ((Geo3dPointShape) to).shape; + return planetModel.surfaceDistance(pointShape1.getCenter(), pointShape2.getCenter()) * DistanceUtils.RADIANS_TO_DEGREES; + } + return distance(from, to.getX(), to.getY()); + } + + @Override + public double distance(Point from, double toX, double toY) { + GeoPoint fromGeoPoint; + if (from instanceof Geo3dPointShape) { + fromGeoPoint = (((Geo3dPointShape) from).shape).getCenter(); + } else { + fromGeoPoint = new GeoPoint(planetModel, + from.getY() * DistanceUtils.DEGREES_TO_RADIANS, + from.getX() * DistanceUtils.DEGREES_TO_RADIANS); + } + GeoPoint toGeoPoint = new GeoPoint(planetModel, + toY * DistanceUtils.DEGREES_TO_RADIANS, + toX * DistanceUtils.DEGREES_TO_RADIANS); + return planetModel.surfaceDistance(fromGeoPoint, toGeoPoint) * DistanceUtils.RADIANS_TO_DEGREES; + } + + @Override + public boolean within(Point from, double toX, double toY, double distance) { + return (distance < distance(from, toX, toY)); + } + + @Override + public Point pointOnBearing(Point from, double distDEG, double bearingDEG, SpatialContext ctx, Point reuse) { + // Algorithm using Vincenty's formulae (https://en.wikipedia.org/wiki/Vincenty%27s_formulae) + // which takes into account that planets may not be spherical. + //Code adaptation from http://www.movable-type.co.uk/scripts/latlong-vincenty.html + Geo3dPointShape geoFrom = (Geo3dPointShape) from; + GeoPoint point = (GeoPoint) geoFrom.shape; + double lat = point.getLatitude(); + double lon = point.getLongitude(); + double dist = DistanceUtils.DEGREES_TO_RADIANS * distDEG; + double bearing = DistanceUtils.DEGREES_TO_RADIANS * bearingDEG; + + double sinα1 = Math.sin(bearing); + double cosα1 = Math.cos(bearing); + + double tanU1 = (1 - planetModel.flattening) * Math.tan(lat); + double cosU1 = 1 / Math.sqrt((1 + tanU1 * tanU1)); + double sinU1 = tanU1 * cosU1; + + double σ1 = Math.atan2(tanU1, cosα1); + double sinα = cosU1 * sinα1; + double cosSqα = 1 - sinα * sinα; + double uSq = cosSqα * planetModel.squareRatio;// (planetModel.ab* planetModel.ab - planetModel.c*planetModel.c) / (planetModel.c*planetModel.c); + double A = 1 + uSq / 16384 * (4096 + uSq * (-768 + uSq * (320 - 175 * uSq))); + double B = uSq / 1024 * (256 + uSq * (-128 + uSq * (74 - 47 * uSq))); + + double cos2σM; + double sinσ; + double cosσ; + double Δσ; + + double σ = dist / (planetModel.c * A); + double σʹ; + double iterations = 0; + do { + cos2σM = Math.cos(2 * σ1 + σ); + sinσ = Math.sin(σ); + cosσ = Math.cos(σ); + Δσ = B * sinσ * (cos2σM + B / 4 * (cosσ * (-1 + 2 * cos2σM * cos2σM) - + B / 6 * cos2σM * (-3 + 4 * sinσ * sinσ) * (-3 + 4 * cos2σM * cos2σM))); + σʹ = σ; + σ = dist / (planetModel.c * A) + Δσ; + } while (Math.abs(σ - σʹ) > 1e-12 && ++iterations < 200); + + if (iterations >= 200) { + throw new RuntimeException("Formula failed to converge"); + } + + double x = sinU1 * sinσ - cosU1 * cosσ * cosα1; + double φ2 = Math.atan2(sinU1 * cosσ + cosU1 * sinσ * cosα1, (1 - planetModel.flattening) * Math.sqrt(sinα * sinα + x * x)); + double λ = Math.atan2(sinσ * sinα1, cosU1 * cosσ - sinU1 * sinσ * cosα1); + double C = planetModel.flattening / 16 * cosSqα * (4 + planetModel.flattening * (4 - 3 * cosSqα)); + double L = λ - (1 - C) * planetModel.flattening * sinα * + (σ + C * sinσ * (cos2σM + C * cosσ * (-1 + 2 * cos2σM * cos2σM))); + double λ2 = (lon + L + 3 * Math.PI) % (2 * Math.PI) - Math.PI; // normalise to -180..+180 + + return ctx.getShapeFactory().pointXY(λ2 * DistanceUtils.RADIANS_TO_DEGREES, + φ2 * DistanceUtils.RADIANS_TO_DEGREES); + } + + @Override + public Rectangle calcBoxByDistFromPt(Point from, double distDEG, SpatialContext ctx, Rectangle reuse) { + Circle circle = ctx.getShapeFactory().circle(from, distDEG); + return circle.getBoundingBox(); + } + + @Override + public double calcBoxByDistFromPt_yHorizAxisDEG(Point from, double distDEG, SpatialContext ctx) { + throw new UnsupportedOperationException(); + } + + @Override + public double area(Rectangle rect) { + throw new UnsupportedOperationException(); + } + + @Override + public double area(Circle circle) { + throw new UnsupportedOperationException(); + } +} diff --git a/lucene/spatial-extras/src/java/org/apache/lucene/spatial/spatial4j/Geo3dPointShape.java b/lucene/spatial-extras/src/java/org/apache/lucene/spatial/spatial4j/Geo3dPointShape.java new file mode 100644 index 00000000000..c0d127df914 --- /dev/null +++ b/lucene/spatial-extras/src/java/org/apache/lucene/spatial/spatial4j/Geo3dPointShape.java @@ -0,0 +1,78 @@ +/* + * 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.spatial.spatial4j; + +import org.apache.lucene.spatial3d.geom.GeoPointShape; +import org.apache.lucene.spatial3d.geom.GeoPointShapeFactory; +import org.locationtech.spatial4j.context.SpatialContext; +import org.locationtech.spatial4j.distance.DistanceUtils; +import org.locationtech.spatial4j.shape.Point; +import org.locationtech.spatial4j.shape.Rectangle; +import org.locationtech.spatial4j.shape.Shape; + +/** + * Specialization of a {@link Geo3dShape} which represents a {@link Point}. + * + * @lucene.experimental + */ +public class Geo3dPointShape extends Geo3dShape implements Point { + + public Geo3dPointShape(final GeoPointShape shape, final SpatialContext spatialcontext) { + super(shape, spatialcontext); + center = this; + } + + @Override + public void reset(double x, double y) { + shape = GeoPointShapeFactory.makeGeoPointShape(shape.getPlanetModel(), + y * DistanceUtils.DEGREES_TO_RADIANS, + x * DistanceUtils.DEGREES_TO_RADIANS); + center = this; + boundingBox = null; + } + + @Override + public double getX() { + return shape.getCenter().getLongitude() * DistanceUtils.RADIANS_TO_DEGREES; + } + + @Override + public double getY() { + return shape.getCenter().getLatitude() * DistanceUtils.RADIANS_TO_DEGREES; + } + + @Override + public Rectangle getBoundingBox() { + Rectangle bbox = this.boundingBox;//volatile read once + if (bbox == null) { + bbox = new Geo3dRectangleShape(shape, spatialcontext); + this.boundingBox = bbox; + } + return bbox; + } + + @Override + public Shape getBuffered(double distance, SpatialContext spatialContext) { + return spatialContext.getShapeFactory().circle(getX(), getY(), distance); + } + + @Override + public boolean hasArea() { + return false; + } +} diff --git a/lucene/spatial-extras/src/java/org/apache/lucene/spatial/spatial4j/Geo3dRectangleShape.java b/lucene/spatial-extras/src/java/org/apache/lucene/spatial/spatial4j/Geo3dRectangleShape.java new file mode 100644 index 00000000000..d354dcc698a --- /dev/null +++ b/lucene/spatial-extras/src/java/org/apache/lucene/spatial/spatial4j/Geo3dRectangleShape.java @@ -0,0 +1,163 @@ +/* + * 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.spatial.spatial4j; + +import org.apache.lucene.spatial3d.geom.GeoBBox; +import org.apache.lucene.spatial3d.geom.GeoBBoxFactory; +import org.apache.lucene.spatial3d.geom.GeoPoint; +import org.apache.lucene.spatial3d.geom.GeoPointShapeFactory; +import org.apache.lucene.spatial3d.geom.LatLonBounds; +import org.locationtech.spatial4j.context.SpatialContext; +import org.locationtech.spatial4j.distance.DistanceUtils; +import org.locationtech.spatial4j.shape.Point; +import org.locationtech.spatial4j.shape.Rectangle; +import org.locationtech.spatial4j.shape.Shape; +import org.locationtech.spatial4j.shape.SpatialRelation; + +/** + * Specialization of a {@link Geo3dShape} which represents a {@link Rectangle}. + * + * @lucene.experimental + */ +public class Geo3dRectangleShape extends Geo3dShape implements Rectangle { + + private double minX; + private double maxX; + private double minY; + private double maxY; + + public Geo3dRectangleShape(final GeoBBox shape, + final SpatialContext spatialcontext, + double minX, + double maxX, + double minY, + double maxY) { + super(shape, spatialcontext); + this.minX = minX; + this.maxX = maxX; + this.minY = minY; + this.maxY = maxY; + } + + public Geo3dRectangleShape(final GeoBBox shape, final SpatialContext spatialcontext) { + super(shape, spatialcontext); + setBoundsFromshape(); + } + + + /** + * Set the bounds from the wrapped GeoBBox. + */ + private void setBoundsFromshape() { + LatLonBounds bounds = new LatLonBounds(); + shape.getBounds(bounds); + minX = bounds.checkNoLongitudeBound() ? -180.0 : bounds.getLeftLongitude() * DistanceUtils.RADIANS_TO_DEGREES; + minY = bounds.checkNoBottomLatitudeBound() ? -90.0 : bounds.getMinLatitude() * DistanceUtils.RADIANS_TO_DEGREES; + maxX = bounds.checkNoLongitudeBound() ? 180.0 : bounds.getRightLongitude() * DistanceUtils.RADIANS_TO_DEGREES; + maxY = bounds.checkNoTopLatitudeBound() ? 90.0 : bounds.getMaxLatitude() * DistanceUtils.RADIANS_TO_DEGREES; + } + + @Override + public Point getCenter() { + Point center = this.center;//volatile read once + if (center == null) { + GeoPoint point = shape.getCenter(); + center = new Geo3dPointShape( + GeoPointShapeFactory.makeGeoPointShape(shape.getPlanetModel(), + point.getLatitude(), + point.getLongitude()), + spatialcontext); + this.center = center; + } + return center; + } + + @Override + public void reset(double minX, double maxX, double minY, double maxY) { + shape = GeoBBoxFactory.makeGeoBBox(shape.getPlanetModel(), + maxY * DistanceUtils.DEGREES_TO_RADIANS, + minY * DistanceUtils.DEGREES_TO_RADIANS, + minX * DistanceUtils.DEGREES_TO_RADIANS, + maxX * DistanceUtils.DEGREES_TO_RADIANS); + center = null; + boundingBox = null; + } + + @Override + public Rectangle getBoundingBox() { + return this; + } + + @Override + public double getWidth() { + double result = getMaxX() - getMinX(); + if (result < 0) { + result += 360; + } + return result; + } + + @Override + public double getHeight() { + return getMaxY() - getMinY(); + } + + @Override + public double getMinX() { + return minX; + } + + @Override + public double getMinY() { + return minY; + } + + @Override + public double getMaxX() { + return maxX; + } + + @Override + public double getMaxY() { + return maxY; + } + + @Override + public boolean getCrossesDateLine() { + return (getMaxX() > 0 && getMinX() < 0); + + } + + @Override + public SpatialRelation relateYRange(double minY, double maxY) { + Rectangle r = spatialcontext.getShapeFactory().rect(-180, 180, minY, maxY); + return relate(r); + } + + @Override + public SpatialRelation relateXRange(double minX, double maxX) { + Rectangle r = spatialcontext.getShapeFactory().rect(minX, maxX, -90, 90); + return relate(r); + } + + @Override + public Shape getBuffered(double distance, SpatialContext spatialContext) { + GeoBBox bBox = shape.expand(distance * DistanceUtils.DEGREES_TO_RADIANS); + return new Geo3dRectangleShape(bBox, spatialContext); + } +} diff --git a/lucene/spatial-extras/src/java/org/apache/lucene/spatial/spatial4j/Geo3dShape.java b/lucene/spatial-extras/src/java/org/apache/lucene/spatial/spatial4j/Geo3dShape.java index 9fa6d8e5d5f..bbe1f3a45d6 100644 --- a/lucene/spatial-extras/src/java/org/apache/lucene/spatial/spatial4j/Geo3dShape.java +++ b/lucene/spatial-extras/src/java/org/apache/lucene/spatial/spatial4j/Geo3dShape.java @@ -14,127 +14,110 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.apache.lucene.spatial.spatial4j; +import org.apache.lucene.spatial3d.geom.GeoArea; +import org.apache.lucene.spatial3d.geom.GeoAreaFactory; +import org.apache.lucene.spatial3d.geom.GeoAreaShape; +import org.apache.lucene.spatial3d.geom.GeoBBox; +import org.apache.lucene.spatial3d.geom.GeoBBoxFactory; +import org.apache.lucene.spatial3d.geom.GeoPoint; +import org.apache.lucene.spatial3d.geom.LatLonBounds; import org.locationtech.spatial4j.context.SpatialContext; import org.locationtech.spatial4j.distance.DistanceUtils; import org.locationtech.spatial4j.shape.Point; import org.locationtech.spatial4j.shape.Rectangle; import org.locationtech.spatial4j.shape.Shape; import org.locationtech.spatial4j.shape.SpatialRelation; -import org.locationtech.spatial4j.shape.impl.RectangleImpl; -import org.apache.lucene.spatial3d.geom.LatLonBounds; -import org.apache.lucene.spatial3d.geom.GeoArea; -import org.apache.lucene.spatial3d.geom.GeoAreaFactory; -import org.apache.lucene.spatial3d.geom.GeoPoint; -import org.apache.lucene.spatial3d.geom.GeoShape; -import org.apache.lucene.spatial3d.geom.PlanetModel; /** - * A Spatial4j Shape wrapping a {@link GeoShape} ("Geo3D") -- a 3D planar geometry based Spatial4j Shape implementation. + * A Spatial4j Shape wrapping a {@link GeoAreaShape} ("Geo3D") -- a 3D planar geometry + * based Spatial4j Shape implementation. * Geo3D implements shapes on the surface of a sphere or ellipsoid. * + * @param is the type of {@link GeoAreaShape} * @lucene.experimental */ -public class Geo3dShape implements Shape { - /** The required size of this adjustment depends on the actual planetary model chosen. - * This value is big enough to account for WGS84. */ - protected static final double ROUNDOFF_ADJUSTMENT = 0.05; - public final SpatialContext ctx; - public final GeoShape shape; - public final PlanetModel planetModel; +public class Geo3dShape implements Shape { - private volatile Rectangle boundingBox = null; // lazy initialized + protected final SpatialContext spatialcontext; - public Geo3dShape(final GeoShape shape, final SpatialContext ctx) { - this(PlanetModel.SPHERE, shape, ctx); - } + protected T shape; + protected volatile Rectangle boundingBox = null; // lazy initialized + protected volatile Point center = null; // lazy initialized - public Geo3dShape(final PlanetModel planetModel, final GeoShape shape, final SpatialContext ctx) { - if (!ctx.isGeo()) { - throw new IllegalArgumentException("SpatialContext.isGeo() must be true"); - } - this.ctx = ctx; - this.planetModel = planetModel; + public Geo3dShape(final T shape, final SpatialContext spatialcontext) { + this.spatialcontext = spatialcontext; this.shape = shape; } - @Override - public SpatialContext getContext() { - return ctx; - } - @Override public SpatialRelation relate(Shape other) { - if (other instanceof Rectangle) - return relate((Rectangle)other); - else if (other instanceof Point) - return relate((Point)other); - else + int relationship; + if (other instanceof Geo3dShape) { + relationship = relate((Geo3dShape) other); + } else if (other instanceof Rectangle) { + relationship = relate((Rectangle) other); + } else if (other instanceof Point) { + relationship = relate((Point) other); + } else { throw new RuntimeException("Unimplemented shape relationship determination: " + other.getClass()); + } + + switch (relationship) { + case GeoArea.DISJOINT: + return SpatialRelation.DISJOINT; + case GeoArea.OVERLAPS: + return (other instanceof Point ? SpatialRelation.CONTAINS : SpatialRelation.INTERSECTS); + case GeoArea.CONTAINS: + return (other instanceof Point ? SpatialRelation.CONTAINS : SpatialRelation.WITHIN); + case GeoArea.WITHIN: + return SpatialRelation.CONTAINS; + } + + throw new RuntimeException("Undetermined shape relationship: " + relationship); } - protected SpatialRelation relate(Rectangle r) { + private int relate(Geo3dShape s) { + return shape.getRelationship(s.shape); + } + + private int relate(Rectangle r) { // Construct the right kind of GeoArea first - GeoArea geoArea = GeoAreaFactory.makeGeoArea(planetModel, + GeoArea geoArea = GeoAreaFactory.makeGeoArea(shape.getPlanetModel(), r.getMaxY() * DistanceUtils.DEGREES_TO_RADIANS, r.getMinY() * DistanceUtils.DEGREES_TO_RADIANS, r.getMinX() * DistanceUtils.DEGREES_TO_RADIANS, r.getMaxX() * DistanceUtils.DEGREES_TO_RADIANS); - int relationship = geoArea.getRelationship(shape); - if (relationship == GeoArea.WITHIN) - return SpatialRelation.WITHIN; - else if (relationship == GeoArea.CONTAINS) - return SpatialRelation.CONTAINS; - else if (relationship == GeoArea.OVERLAPS) - return SpatialRelation.INTERSECTS; - else if (relationship == GeoArea.DISJOINT) - return SpatialRelation.DISJOINT; - else - throw new RuntimeException("Unknown relationship returned: "+relationship); + + return geoArea.getRelationship(shape); } - protected SpatialRelation relate(Point p) { - // Create a GeoPoint - GeoPoint point = new GeoPoint(planetModel, p.getY()* DistanceUtils.DEGREES_TO_RADIANS, p.getX()* DistanceUtils.DEGREES_TO_RADIANS); + private int relate(Point p) { + GeoPoint point = new GeoPoint(shape.getPlanetModel(), + p.getY() * DistanceUtils.DEGREES_TO_RADIANS, + p.getX() * DistanceUtils.DEGREES_TO_RADIANS); + if (shape.isWithin(point)) { - // Point within shape - return SpatialRelation.CONTAINS; + return GeoArea.WITHIN; } - return SpatialRelation.DISJOINT; + return GeoArea.DISJOINT; } - - @Override public Rectangle getBoundingBox() { Rectangle bbox = this.boundingBox;//volatile read once if (bbox == null) { LatLonBounds bounds = new LatLonBounds(); shape.getBounds(bounds); - double leftLon; - double rightLon; - if (bounds.checkNoLongitudeBound()) { - leftLon = -180.0; - rightLon = 180.0; - } else { - leftLon = bounds.getLeftLongitude().doubleValue() * DistanceUtils.RADIANS_TO_DEGREES; - rightLon = bounds.getRightLongitude().doubleValue() * DistanceUtils.RADIANS_TO_DEGREES; - } - double minLat; - if (bounds.checkNoBottomLatitudeBound()) { - minLat = -90.0; - } else { - minLat = bounds.getMinLatitude().doubleValue() * DistanceUtils.RADIANS_TO_DEGREES; - } - double maxLat; - if (bounds.checkNoTopLatitudeBound()) { - maxLat = 90.0; - } else { - maxLat = bounds.getMaxLatitude().doubleValue() * DistanceUtils.RADIANS_TO_DEGREES; - } - bbox = new RectangleImpl(leftLon, rightLon, minLat, maxLat, ctx).getBuffered(ROUNDOFF_ADJUSTMENT, ctx); + double leftLon = bounds.checkNoLongitudeBound() ? -Math.PI : bounds.getLeftLongitude(); + double rightLon = bounds.checkNoLongitudeBound() ? Math.PI : bounds.getRightLongitude(); + double minLat = bounds.checkNoBottomLatitudeBound() ? -Math.PI * 0.5 : bounds.getMinLatitude(); + double maxLat = bounds.checkNoTopLatitudeBound() ? Math.PI * 0.5 : bounds.getMaxLatitude(); + GeoBBox geoBBox = GeoBBoxFactory.makeGeoBBox(shape.getPlanetModel(), maxLat, minLat, leftLon, rightLon); + bbox = new Geo3dRectangleShape(geoBBox, spatialcontext); this.boundingBox = bbox; } return bbox; @@ -146,17 +129,22 @@ public class Geo3dShape implements Shape { } @Override - public double getArea(SpatialContext ctx) { + public double getArea(SpatialContext spatialContext) { throw new UnsupportedOperationException(); } @Override public Point getCenter() { - throw new UnsupportedOperationException(); + Point center = this.center;//volatile read once + if (center == null) { + center = getBoundingBox().getCenter(); + this.center = center; + } + return center; } @Override - public Shape getBuffered(double distance, SpatialContext ctx) { + public Shape getBuffered(double distance, SpatialContext spatialContext) { throw new UnsupportedOperationException(); } @@ -166,20 +154,20 @@ public class Geo3dShape implements Shape { } @Override - public String toString() { - return "Geo3dShape{planetmodel=" + planetModel + ", shape=" + shape + '}'; + public SpatialContext getContext() { + return spatialcontext; } @Override - public boolean equals(Object other) { - if (!(other instanceof Geo3dShape)) + public boolean equals(Object o) { + if (!(o instanceof Geo3dShape)) return false; - Geo3dShape tr = (Geo3dShape)other; - return tr.ctx.equals(ctx) && tr.planetModel.equals(planetModel) && tr.shape.equals(shape); + final Geo3dShape other = (Geo3dShape) o; + return (other.spatialcontext.equals(spatialcontext) && other.shape.equals(shape)); } @Override public int hashCode() { - return planetModel.hashCode() + shape.hashCode(); + return spatialcontext.hashCode() + shape.hashCode(); } } diff --git a/lucene/spatial-extras/src/java/org/apache/lucene/spatial/spatial4j/Geo3dShapeFactory.java b/lucene/spatial-extras/src/java/org/apache/lucene/spatial/spatial4j/Geo3dShapeFactory.java new file mode 100644 index 00000000000..a80a0439b29 --- /dev/null +++ b/lucene/spatial-extras/src/java/org/apache/lucene/spatial/spatial4j/Geo3dShapeFactory.java @@ -0,0 +1,396 @@ +/* + * 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.spatial.spatial4j; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.lucene.spatial3d.geom.GeoBBox; +import org.apache.lucene.spatial3d.geom.GeoBBoxFactory; +import org.apache.lucene.spatial3d.geom.GeoCircle; +import org.apache.lucene.spatial3d.geom.GeoCircleFactory; +import org.apache.lucene.spatial3d.geom.GeoCompositeAreaShape; +import org.apache.lucene.spatial3d.geom.GeoPath; +import org.apache.lucene.spatial3d.geom.GeoPathFactory; +import org.apache.lucene.spatial3d.geom.GeoPoint; +import org.apache.lucene.spatial3d.geom.GeoPointShape; +import org.apache.lucene.spatial3d.geom.GeoPointShapeFactory; +import org.apache.lucene.spatial3d.geom.GeoPolygon; +import org.apache.lucene.spatial3d.geom.GeoPolygonFactory; +import org.apache.lucene.spatial3d.geom.PlanetModel; +import org.locationtech.spatial4j.context.SpatialContext; +import org.locationtech.spatial4j.context.SpatialContextFactory; +import org.locationtech.spatial4j.distance.DistanceUtils; +import org.locationtech.spatial4j.exception.InvalidShapeException; +import org.locationtech.spatial4j.shape.Circle; +import org.locationtech.spatial4j.shape.Point; +import org.locationtech.spatial4j.shape.Rectangle; +import org.locationtech.spatial4j.shape.Shape; +import org.locationtech.spatial4j.shape.ShapeCollection; +import org.locationtech.spatial4j.shape.ShapeFactory; + +/** + * Geo3d implementation of {@link ShapeFactory} + * + * @lucene.experimental + */ +public class Geo3dShapeFactory implements ShapeFactory { + + private final boolean normWrapLongitude; + private SpatialContext context; + private PlanetModel planetModel; + + @SuppressWarnings("unchecked") + public Geo3dShapeFactory(SpatialContext context, SpatialContextFactory factory) { + this.context = context; + this.planetModel = ((Geo3dSpatialContextFactory) factory).planetModel; + this.normWrapLongitude = context.isGeo() && factory.normWrapLongitude; + } + + @Override + public SpatialContext getSpatialContext() { + return context; + } + + @Override + public boolean isNormWrapLongitude() { + return normWrapLongitude; + } + + @Override + public double normX(double x) { + if (this.normWrapLongitude) { + x = DistanceUtils.normLonDEG(x); + } + return x; + } + + @Override + public double normY(double y) { + return y; + } + + @Override + public double normZ(double z) { + return z; + } + + @Override + public double normDist(double distance) { + return distance; + } + + @Override + public void verifyX(double x) { + Rectangle bounds = this.context.getWorldBounds(); + if (x < bounds.getMinX() || x > bounds.getMaxX()) { + throw new InvalidShapeException("Bad X value " + x + " is not in boundary " + bounds); + } + } + + @Override + public void verifyY(double y) { + Rectangle bounds = this.context.getWorldBounds(); + if (y < bounds.getMinY() || y > bounds.getMaxY()) { + throw new InvalidShapeException("Bad Y value " + y + " is not in boundary " + bounds); + } + } + + @Override + public void verifyZ(double v) { + } + + @Override + public Point pointXY(double x, double y) { + GeoPointShape point = GeoPointShapeFactory.makeGeoPointShape(planetModel, + y * DistanceUtils.DEGREES_TO_RADIANS, + x * DistanceUtils.DEGREES_TO_RADIANS); + return new Geo3dPointShape(point, context); + } + + @Override + public Point pointXYZ(double x, double y, double z) { + GeoPoint point = new GeoPoint(x, y, z); + GeoPointShape pointShape = GeoPointShapeFactory.makeGeoPointShape(planetModel, + point.getLatitude(), + point.getLongitude()); + return new Geo3dPointShape(pointShape, context); + //throw new UnsupportedOperationException(); + } + + @Override + public Rectangle rect(Point point, Point point1) { + return rect(point.getX(), point1.getX(), point.getY(), point1.getY()); + } + + @Override + public Rectangle rect(double minX, double maxX, double minY, double maxY) { + GeoBBox bBox = GeoBBoxFactory.makeGeoBBox(planetModel, + maxY * DistanceUtils.DEGREES_TO_RADIANS, + minY * DistanceUtils.DEGREES_TO_RADIANS, + minX * DistanceUtils.DEGREES_TO_RADIANS, + maxX * DistanceUtils.DEGREES_TO_RADIANS); + return new Geo3dRectangleShape(bBox, context, minX, maxX, minY, maxY); + } + + @Override + public Circle circle(double x, double y, double distance) { + GeoCircle circle = GeoCircleFactory.makeGeoCircle(planetModel, + y * DistanceUtils.DEGREES_TO_RADIANS, + x * DistanceUtils.DEGREES_TO_RADIANS, + distance * DistanceUtils.DEGREES_TO_RADIANS); + return new Geo3dCircleShape(circle, context); + } + + @Override + public Circle circle(Point point, double distance) { + return circle(point.getX(), point.getY(), distance); + } + + @Override + public Shape lineString(List list, double distance) { + LineStringBuilder builder = lineString(); + for (Point point : list) { + builder.pointXY(point.getX(), point.getY()); + } + builder.buffer(distance); + return builder.build(); + } + + @Override + public ShapeCollection multiShape(List list) { + throw new UnsupportedOperationException(); + } + + @Override + public LineStringBuilder lineString() { + return new Geo3dLineStringBuilder(); + } + + @Override + public PolygonBuilder polygon() { + return new Geo3dPolygonBuilder(); + } + + @Override + public MultiShapeBuilder multiShape(Class aClass) { + return new Geo3dMultiShapeBuilder<>(); + } + + @Override + public MultiPointBuilder multiPoint() { + return new Geo3dMultiPointBuilder(); + } + + @Override + public MultiLineStringBuilder multiLineString() { + return new Geo3dMultiLineBuilder(); + } + + @Override + public MultiPolygonBuilder multiPolygon() { + return new Geo3dMultiPolygonBuilder(); + } + + /** + * Geo3d implementation of {@link org.locationtech.spatial4j.shape.ShapeFactory.PointsBuilder} interface to + * generate {@link GeoPoint}. + * + * @param is normally this object + */ + private class Geo3dPointBuilder implements PointsBuilder { + + List points = new ArrayList<>(); + + @SuppressWarnings("unchecked") + @Override + public T pointXY(double x, double y) { + GeoPoint point = new GeoPoint(planetModel, y * DistanceUtils.DEGREES_TO_RADIANS, x * DistanceUtils.DEGREES_TO_RADIANS); + points.add(point); + return (T) this; + } + + @SuppressWarnings("unchecked") + @Override + public T pointXYZ(double x, double y, double z) { + GeoPoint point = new GeoPoint(x, y, z); + if (!points.contains(point)) { + points.add(point); + } + return (T) this; + } + } + + /** + * Geo3d implementation of {@link org.locationtech.spatial4j.shape.ShapeFactory.LineStringBuilder} to generate + * nine Strings. Note that GeoPath needs a buffer so we set the + * buffer to 1e-10. + */ + private class Geo3dLineStringBuilder extends Geo3dPointBuilder implements LineStringBuilder { + + double distance = 0; + + @Override + public LineStringBuilder buffer(double distance) { + this.distance = distance; + return this; + } + + @Override + public Shape build() { + GeoPath path = GeoPathFactory.makeGeoPath(planetModel, distance, points.toArray(new GeoPoint[points.size()])); + return new Geo3dShape<>(path, context); + } + } + + /** + * Geo3d implementation of {@link org.locationtech.spatial4j.shape.ShapeFactory.PolygonBuilder} to generate + * polygons. + */ + private class Geo3dPolygonBuilder extends Geo3dPointBuilder implements PolygonBuilder { + + List polyHoles; + + @Override + public HoleBuilder hole() { + return new Geo3dHoleBuilder(); + } + + class Geo3dHoleBuilder extends Geo3dPointBuilder implements PolygonBuilder.HoleBuilder { + @Override + public PolygonBuilder endHole() { + if (polyHoles == null) { + polyHoles = new ArrayList<>(); + } + polyHoles.add(GeoPolygonFactory.makeGeoPolygon(planetModel, points)); + return Geo3dPolygonBuilder.this; + } + } + + @SuppressWarnings("unchecked") + @Override + public Shape build() { + GeoPolygon polygon = GeoPolygonFactory.makeGeoPolygon(planetModel, points, polyHoles); + return new Geo3dShape<>(polygon, context); + } + + @Override + public Shape buildOrRect() { + return build(); + } + } + + private class Geo3dMultiPointBuilder extends Geo3dPointBuilder implements MultiPointBuilder { + + @Override + public Shape build() { + GeoCompositeAreaShape areaShape = new GeoCompositeAreaShape(planetModel); + for (GeoPoint point : points) { + GeoPointShape pointShape = GeoPointShapeFactory.makeGeoPointShape(planetModel, point.getLatitude(), point.getLongitude()); + areaShape.addShape(pointShape); + } + return new Geo3dShape<>(areaShape, context); + } + } + + /** + * Geo3d implementation of {@link org.locationtech.spatial4j.shape.ShapeFactory.MultiLineStringBuilder} to generate + * multi-lines + */ + private class Geo3dMultiLineBuilder implements MultiLineStringBuilder { + + List builders = new ArrayList<>(); + + @Override + public LineStringBuilder lineString() { + return new Geo3dLineStringBuilder(); + } + + @Override + public MultiLineStringBuilder add(LineStringBuilder lineStringBuilder) { + builders.add(lineStringBuilder); + return this; + } + + @SuppressWarnings("unchecked") + @Override + public Shape build() { + GeoCompositeAreaShape areaShape = new GeoCompositeAreaShape(planetModel); + for (LineStringBuilder builder : builders) { + Geo3dShape shape = (Geo3dShape) builder.build(); + areaShape.addShape(shape.shape); + } + return new Geo3dShape<>(areaShape, context); + } + } + + /** + * Geo3d implementation of {@link org.locationtech.spatial4j.shape.ShapeFactory.MultiPolygonBuilder} to generate + * multi-polygons. We have chosen to use a composite shape but + * it might be possible to use GeoComplexPolygon. + */ + private class Geo3dMultiPolygonBuilder implements MultiPolygonBuilder { + + List builders = new ArrayList<>(); + + @Override + public PolygonBuilder polygon() { + return new Geo3dPolygonBuilder(); + } + + @Override + public MultiPolygonBuilder add(PolygonBuilder polygonBuilder) { + builders.add(polygonBuilder); + return this; + } + + @SuppressWarnings("unchecked") + @Override + public Shape build() { + GeoCompositeAreaShape areaShape = new GeoCompositeAreaShape(planetModel); + for (PolygonBuilder builder : builders) { + Geo3dShape shape = (Geo3dShape) builder.build(); + areaShape.addShape(shape.shape); + } + return new Geo3dShape<>(areaShape, context); + } + } + + /** + * Geo3d implementation of {@link org.locationtech.spatial4j.shape.ShapeFactory.MultiShapeBuilder} to generate + * geometry collections + * + * @param is the type of shapes. + */ + private class Geo3dMultiShapeBuilder implements MultiShapeBuilder { + + GeoCompositeAreaShape composite = new GeoCompositeAreaShape(planetModel); + + @Override + public MultiShapeBuilder add(T shape) { + Geo3dShape areaShape = (Geo3dShape) shape; + composite.addShape(areaShape.shape); + return this; + } + + @Override + public Shape build() { + return new Geo3dShape<>(composite, context); + } + } +} diff --git a/lucene/spatial-extras/src/java/org/apache/lucene/spatial/spatial4j/Geo3dSpatialContextFactory.java b/lucene/spatial-extras/src/java/org/apache/lucene/spatial/spatial4j/Geo3dSpatialContextFactory.java new file mode 100644 index 00000000000..1ce4ebce67a --- /dev/null +++ b/lucene/spatial-extras/src/java/org/apache/lucene/spatial/spatial4j/Geo3dSpatialContextFactory.java @@ -0,0 +1,94 @@ +/* + * 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.spatial.spatial4j; + +import java.util.Map; + +import org.apache.lucene.spatial3d.geom.PlanetModel; +import org.locationtech.spatial4j.context.SpatialContext; +import org.locationtech.spatial4j.context.SpatialContextFactory; + +/** + * Geo3d implementation of {@link SpatialContextFactory} + * + * @lucene.experimental + */ +public class Geo3dSpatialContextFactory extends SpatialContextFactory { + + /** + * The default planet model + */ + private static final PlanetModel DEFAULT_PLANET_MODEL = PlanetModel.SPHERE; + + /** + * The planet model + */ + public PlanetModel planetModel; + + /** + * Empty Constructor. + */ + public Geo3dSpatialContextFactory() { + this.binaryCodecClass = Geo3dBinaryCodec.class; + this.shapeFactoryClass = Geo3dShapeFactory.class; + } + + @Override + public SpatialContext newSpatialContext() { + if (planetModel == null) { + planetModel = DEFAULT_PLANET_MODEL; + } + if (distCalc == null) { + this.distCalc = new Geo3dDistanceCalculator(planetModel); + } + return new SpatialContext(this); + } + + @Override + protected void init(Map args, ClassLoader classLoader) { + initPlanetModel(args); + super.init(args, classLoader); + } + + protected void initPlanetModel(Map args) { + String planetModel = args.get("planetModel"); + if (planetModel != null) { + if (planetModel.equalsIgnoreCase("sphere")) { + this.planetModel = PlanetModel.SPHERE; + } else if (planetModel.equalsIgnoreCase("wgs84")) { + this.planetModel = PlanetModel.WGS84; + } else { + throw new RuntimeException("Unknown planet model: " + planetModel); + } + } else { + this.planetModel = DEFAULT_PLANET_MODEL; + } + } + + @Override + protected void initCalculator() { + String calcStr = this.args.get("distCalculator"); + if (calcStr == null) { + return; + } else if (calcStr.equals("geo3d")) { + this.distCalc = new Geo3dDistanceCalculator(planetModel); + } else { + super.initCalculator(); // some other distance calculator + } + } +} diff --git a/lucene/spatial-extras/src/test/org/apache/lucene/spatial/SpatialArgsTest.java b/lucene/spatial-extras/src/test/org/apache/lucene/spatial/SpatialArgsTest.java index 094953a95db..da351fc857e 100644 --- a/lucene/spatial-extras/src/test/org/apache/lucene/spatial/SpatialArgsTest.java +++ b/lucene/spatial-extras/src/test/org/apache/lucene/spatial/SpatialArgsTest.java @@ -16,18 +16,18 @@ */ package org.apache.lucene.spatial; +import org.apache.lucene.spatial.query.SpatialArgs; +import org.apache.lucene.spatial.spatial4j.Geo3dSpatialContextFactory; +import org.apache.lucene.util.LuceneTestCase; +import org.junit.Test; import org.locationtech.spatial4j.context.SpatialContext; import org.locationtech.spatial4j.shape.Shape; -import org.apache.lucene.spatial.query.SpatialArgs; -import org.junit.Test; -import static org.junit.Assert.assertEquals; - -public class SpatialArgsTest { +public class SpatialArgsTest extends LuceneTestCase { @Test public void calcDistanceFromErrPct() { - final SpatialContext ctx = SpatialContext.GEO; + final SpatialContext ctx = usually() ? SpatialContext.GEO : new Geo3dSpatialContextFactory().newSpatialContext(); final double DEP = 0.5;//distErrPct //the result is the diagonal distance from the center to the closest corner, diff --git a/lucene/spatial-extras/src/test/org/apache/lucene/spatial/spatial4j/Geo3dRptTest.java b/lucene/spatial-extras/src/test/org/apache/lucene/spatial/spatial4j/Geo3dRptTest.java index 26448b6bf09..0b2968465f7 100644 --- a/lucene/spatial-extras/src/test/org/apache/lucene/spatial/spatial4j/Geo3dRptTest.java +++ b/lucene/spatial-extras/src/test/org/apache/lucene/spatial/spatial4j/Geo3dRptTest.java @@ -28,16 +28,15 @@ import org.apache.lucene.spatial.prefix.tree.GeohashPrefixTree; import org.apache.lucene.spatial.prefix.tree.SpatialPrefixTree; import org.apache.lucene.spatial.query.SpatialOperation; import org.apache.lucene.spatial.serialized.SerializedDVStrategy; -import org.apache.lucene.spatial3d.geom.GeoBBoxFactory; -import org.apache.lucene.spatial3d.geom.GeoCircleFactory; +import org.apache.lucene.spatial3d.geom.GeoAreaShape; +import org.apache.lucene.spatial3d.geom.GeoPath; import org.apache.lucene.spatial3d.geom.GeoPathFactory; import org.apache.lucene.spatial3d.geom.GeoPoint; import org.apache.lucene.spatial3d.geom.GeoPolygonFactory; -import org.apache.lucene.spatial3d.geom.GeoShape; import org.apache.lucene.spatial3d.geom.PlanetModel; +import org.apache.lucene.spatial3d.geom.RandomGeo3dShapeGenerator; import org.junit.Test; import org.locationtech.spatial4j.context.SpatialContext; -import org.locationtech.spatial4j.shape.Point; import org.locationtech.spatial4j.shape.Rectangle; import org.locationtech.spatial4j.shape.Shape; @@ -45,11 +44,10 @@ import static org.locationtech.spatial4j.distance.DistanceUtils.DEGREES_TO_RADIA public class Geo3dRptTest extends RandomSpatialOpStrategyTestCase { + private PlanetModel planetModel; + private RandomGeo3dShapeGenerator shapeGenerator; private SpatialPrefixTree grid; private RecursivePrefixTreeStrategy rptStrategy; - { - this.ctx = SpatialContext.GEO; - } private void setupGeohashGrid() { this.grid = new GeohashPrefixTree(ctx, 2);//A fairly shallow grid @@ -64,7 +62,12 @@ public class Geo3dRptTest extends RandomSpatialOpStrategyTestCase { } private void setupStrategy() { - //setup + shapeGenerator = new RandomGeo3dShapeGenerator(); + planetModel = shapeGenerator.randomPlanetModel(); + Geo3dSpatialContextFactory factory = new Geo3dSpatialContextFactory(); + factory.planetModel = planetModel; + ctx = factory.newSpatialContext(); + setupGeohashGrid(); SerializedDVStrategy serializedDVStrategy = new SerializedDVStrategy(ctx, getClass().getSimpleName() + "_sdv"); @@ -76,12 +79,12 @@ public class Geo3dRptTest extends RandomSpatialOpStrategyTestCase { public void testFailure1() throws IOException { setupStrategy(); final List points = new ArrayList(); - points.add(new GeoPoint(PlanetModel.SPHERE, 18 * DEGREES_TO_RADIANS, -27 * DEGREES_TO_RADIANS)); - points.add(new GeoPoint(PlanetModel.SPHERE, -57 * DEGREES_TO_RADIANS, 146 * DEGREES_TO_RADIANS)); - points.add(new GeoPoint(PlanetModel.SPHERE, 14 * DEGREES_TO_RADIANS, -180 * DEGREES_TO_RADIANS)); - points.add(new GeoPoint(PlanetModel.SPHERE, -15 * DEGREES_TO_RADIANS, 153 * DEGREES_TO_RADIANS)); + points.add(new GeoPoint(planetModel, 18 * DEGREES_TO_RADIANS, -27 * DEGREES_TO_RADIANS)); + points.add(new GeoPoint(planetModel, -57 * DEGREES_TO_RADIANS, 146 * DEGREES_TO_RADIANS)); + points.add(new GeoPoint(planetModel, 14 * DEGREES_TO_RADIANS, -180 * DEGREES_TO_RADIANS)); + points.add(new GeoPoint(planetModel, -15 * DEGREES_TO_RADIANS, 153 * DEGREES_TO_RADIANS)); - final Shape triangle = new Geo3dShape(GeoPolygonFactory.makeGeoPolygon(PlanetModel.SPHERE, points),ctx); + final Shape triangle = new Geo3dShape(GeoPolygonFactory.makeGeoPolygon(planetModel, points),ctx); final Rectangle rect = ctx.makeRectangle(-49, -45, 73, 86); testOperation(rect,SpatialOperation.Intersects,triangle, false); } @@ -91,16 +94,16 @@ public class Geo3dRptTest extends RandomSpatialOpStrategyTestCase { setupStrategy(); final List points = new ArrayList<>(); - points.add(new GeoPoint(PlanetModel.SPHERE, 18 * DEGREES_TO_RADIANS, -27 * DEGREES_TO_RADIANS)); - points.add(new GeoPoint(PlanetModel.SPHERE, -57 * DEGREES_TO_RADIANS, 146 * DEGREES_TO_RADIANS)); - points.add(new GeoPoint(PlanetModel.SPHERE, 14 * DEGREES_TO_RADIANS, -180 * DEGREES_TO_RADIANS)); - points.add(new GeoPoint(PlanetModel.SPHERE, -15 * DEGREES_TO_RADIANS, 153 * DEGREES_TO_RADIANS)); + points.add(new GeoPoint(planetModel, 18 * DEGREES_TO_RADIANS, -27 * DEGREES_TO_RADIANS)); + points.add(new GeoPoint(planetModel, -57 * DEGREES_TO_RADIANS, 146 * DEGREES_TO_RADIANS)); + points.add(new GeoPoint(planetModel, 14 * DEGREES_TO_RADIANS, -180 * DEGREES_TO_RADIANS)); + points.add(new GeoPoint(planetModel, -15 * DEGREES_TO_RADIANS, 153 * DEGREES_TO_RADIANS)); final GeoPoint[] pathPoints = new GeoPoint[] { - new GeoPoint(PlanetModel.SPHERE, 55.0 * DEGREES_TO_RADIANS, -26.0 * DEGREES_TO_RADIANS), - new GeoPoint(PlanetModel.SPHERE, -90.0 * DEGREES_TO_RADIANS, 0.0), - new GeoPoint(PlanetModel.SPHERE, 54.0 * DEGREES_TO_RADIANS, 165.0 * DEGREES_TO_RADIANS), - new GeoPoint(PlanetModel.SPHERE, -90.0 * DEGREES_TO_RADIANS, 0.0)}; - final GeoShape path = GeoPathFactory.makeGeoPath(PlanetModel.SPHERE, 29 * DEGREES_TO_RADIANS, pathPoints); + new GeoPoint(planetModel, 55.0 * DEGREES_TO_RADIANS, -26.0 * DEGREES_TO_RADIANS), + new GeoPoint(planetModel, -90.0 * DEGREES_TO_RADIANS, 0.0), + new GeoPoint(planetModel, 54.0 * DEGREES_TO_RADIANS, 165.0 * DEGREES_TO_RADIANS), + new GeoPoint(planetModel, -90.0 * DEGREES_TO_RADIANS, 0.0)}; + final GeoPath path = GeoPathFactory.makeGeoPath(planetModel, 29 * DEGREES_TO_RADIANS, pathPoints); final Shape shape = new Geo3dShape(path,ctx); final Rectangle rect = ctx.makeRectangle(131, 143, 39, 54); testOperation(rect,SpatialOperation.Intersects,shape,true); @@ -114,111 +117,57 @@ public class Geo3dRptTest extends RandomSpatialOpStrategyTestCase { testOperationRandomShapes(SpatialOperation.Intersects); } - private Shape makeTriangle(double x1, double y1, double x2, double y2, double x3, double y3) { - final List geoPoints = new ArrayList<>(); - geoPoints.add(new GeoPoint(PlanetModel.SPHERE, y1 * DEGREES_TO_RADIANS, x1 * DEGREES_TO_RADIANS)); - geoPoints.add(new GeoPoint(PlanetModel.SPHERE, y2 * DEGREES_TO_RADIANS, x2 * DEGREES_TO_RADIANS)); - geoPoints.add(new GeoPoint(PlanetModel.SPHERE, y3 * DEGREES_TO_RADIANS, x3 * DEGREES_TO_RADIANS)); - final GeoShape shape = GeoPolygonFactory.makeGeoPolygon(PlanetModel.SPHERE, geoPoints); - return new Geo3dShape(shape, ctx); - } - @Override protected Shape randomIndexedShape() { - return randomRectangle(); + int type = shapeGenerator.randomShapeType(); + GeoAreaShape areaShape = shapeGenerator.randomGeoAreaShape(type, planetModel); + return new Geo3dShape<>(areaShape, ctx); } @Override protected Shape randomQueryShape() { - final int shapeType = random().nextInt(4); - switch (shapeType) { - case 0: { - // Polygons - final int vertexCount = random().nextInt(3) + 3; - while (true) { - final List geoPoints = new ArrayList<>(); - while (geoPoints.size() < vertexCount) { - final Point point = randomPoint(); - final GeoPoint gPt = new GeoPoint(PlanetModel.SPHERE, point.getY() * DEGREES_TO_RADIANS, point.getX() * DEGREES_TO_RADIANS); - geoPoints.add(gPt); - } - final int convexPointIndex = random().nextInt(vertexCount); //If we get this wrong, hopefully we get IllegalArgumentException - try { - final GeoShape shape = GeoPolygonFactory.makeGeoPolygon(PlanetModel.SPHERE, geoPoints); - if (shape == null) { - continue; - } - return new Geo3dShape(shape, ctx); - } catch (IllegalArgumentException e) { - // This is what happens when we create a shape that is invalid. Although it is conceivable that there are cases where - // the exception is thrown incorrectly, we aren't going to be able to do that in this random test. - continue; - } - } - } - case 1: { - // Circles - while (true) { - final int circleRadius = random().nextInt(179) + 1; - final Point point = randomPoint(); - try { - final GeoShape shape = GeoCircleFactory.makeGeoCircle(PlanetModel.SPHERE, point.getY() * DEGREES_TO_RADIANS, point.getX() * DEGREES_TO_RADIANS, - circleRadius * DEGREES_TO_RADIANS); - return new Geo3dShape(shape, ctx); - } catch (IllegalArgumentException e) { - // This is what happens when we create a shape that is invalid. Although it is conceivable that there are cases where - // the exception is thrown incorrectly, we aren't going to be able to do that in this random test. - continue; - } - } - } - case 2: { - // Rectangles - while (true) { - Point ulhcPoint = randomPoint(); - Point lrhcPoint = randomPoint(); - if (ulhcPoint.getY() < lrhcPoint.getY()) { - //swap - Point temp = ulhcPoint; - ulhcPoint = lrhcPoint; - lrhcPoint = temp; - } - try { - final GeoShape shape = GeoBBoxFactory.makeGeoBBox(PlanetModel.SPHERE, ulhcPoint.getY() * DEGREES_TO_RADIANS, - lrhcPoint.getY() * DEGREES_TO_RADIANS, - ulhcPoint.getX() * DEGREES_TO_RADIANS, - lrhcPoint.getX() * DEGREES_TO_RADIANS); - //System.err.println("Trial rectangle shape: "+shape); - return new Geo3dShape(shape, ctx); - } catch (IllegalArgumentException e) { - // This is what happens when we create a shape that is invalid. Although it is conceivable that there are cases where - // the exception is thrown incorrectly, we aren't going to be able to do that in this random test. - continue; - } - } - } - case 3: { - // Paths - final int pointCount = random().nextInt(5) + 1; - final double width = (random().nextInt(89)+1) * DEGREES_TO_RADIANS; - final GeoPoint[] points = new GeoPoint[pointCount]; - while (true) { - for (int i = 0; i < pointCount; i++) { - final Point nextPoint = randomPoint(); - points[i] = new GeoPoint(PlanetModel.SPHERE, nextPoint.getY() * DEGREES_TO_RADIANS, nextPoint.getX() * DEGREES_TO_RADIANS); - } - try { - final GeoShape path = GeoPathFactory.makeGeoPath(PlanetModel.SPHERE, width, points); - return new Geo3dShape(path, ctx); - } catch (IllegalArgumentException e) { - // This is what happens when we create a shape that is invalid. Although it is conceivable that there are cases where - // the exception is thrown incorrectly, we aren't going to be able to do that in this random test. - continue; - } - } - } - default: - throw new IllegalStateException("Unexpected shape type"); - } + int type = shapeGenerator.randomShapeType(); + GeoAreaShape areaShape = shapeGenerator.randomGeoAreaShape(type, planetModel); + return new Geo3dShape<>(areaShape, ctx); + } + + //TODO move to a new test class? + @Test + public void testWKT() throws Exception { + Geo3dSpatialContextFactory factory = new Geo3dSpatialContextFactory(); + SpatialContext ctx = factory.newSpatialContext(); + String wkt = "POLYGON ((20.0 -60.4, 20.1 -60.4, 20.1 -60.3, 20.0 -60.3,20.0 -60.4))"; + Shape s = ctx.getFormats().getWktReader().read(wkt); + assertTrue(s instanceof Geo3dShape); + wkt = "POINT (30 10)"; + s = ctx.getFormats().getWktReader().read(wkt); + assertTrue(s instanceof Geo3dShape); + wkt = "LINESTRING (30 10, 10 30, 40 40)"; + s = ctx.getFormats().getWktReader().read(wkt); + assertTrue(s instanceof Geo3dShape); + wkt = "POLYGON ((35 10, 45 45, 15 40, 10 20, 35 10), (20 30, 35 35, 30 20, 20 30))"; + s = ctx.getFormats().getWktReader().read(wkt); + assertTrue(s instanceof Geo3dShape); + wkt = "MULTIPOINT ((10 40), (40 30), (20 20), (30 10))"; + s = ctx.getFormats().getWktReader().read(wkt); + assertTrue(s instanceof Geo3dShape); + wkt = "MULTILINESTRING ((10 10, 20 20, 10 40),(40 40, 30 30, 40 20, 30 10))"; + s = ctx.getFormats().getWktReader().read(wkt); + assertTrue(s instanceof Geo3dShape); + wkt = "MULTIPOLYGON (((40 40, 20 45, 45 30, 40 40)), ((20 35, 10 30, 10 10, 30 5, 45 20, 20 35),(30 20, 20 15, 20 25, 30 20)))"; + s = ctx.getFormats().getWktReader().read(wkt); + assertTrue(s instanceof Geo3dShape); + wkt = "GEOMETRYCOLLECTION(POINT(4 6),LINESTRING(4 6,7 10))"; + s = ctx.getFormats().getWktReader().read(wkt); + assertTrue(s instanceof Geo3dShape); + wkt = "ENVELOPE(1, 2, 4, 3)"; + s = ctx.getFormats().getWktReader().read(wkt); + assertTrue(s instanceof Geo3dShape); + wkt = "BUFFER(POINT(-10 30), 5.2)"; + s = ctx.getFormats().getWktReader().read(wkt); + assertTrue(s instanceof Geo3dShape); + //wkt = "BUFFER(LINESTRING(1 2, 3 4), 0.5)"; + //s = ctx.getFormats().getWktReader().read(wkt); + //assertTrue(s instanceof Geo3dShape); } } diff --git a/lucene/spatial-extras/src/test/org/apache/lucene/spatial/spatial4j/Geo3dShapeRectRelationTestCase.java b/lucene/spatial-extras/src/test/org/apache/lucene/spatial/spatial4j/Geo3dShapeRectRelationTestCase.java index af31120356b..9873012f6b0 100644 --- a/lucene/spatial-extras/src/test/org/apache/lucene/spatial/spatial4j/Geo3dShapeRectRelationTestCase.java +++ b/lucene/spatial-extras/src/test/org/apache/lucene/spatial/spatial4j/Geo3dShapeRectRelationTestCase.java @@ -19,6 +19,8 @@ package org.apache.lucene.spatial.spatial4j; import java.util.ArrayList; import java.util.List; +import org.apache.lucene.spatial3d.geom.GeoPath; +import org.apache.lucene.spatial3d.geom.GeoPolygon; import org.locationtech.spatial4j.TestLog; import org.locationtech.spatial4j.context.SpatialContext; import org.locationtech.spatial4j.distance.DistanceUtils; @@ -119,9 +121,9 @@ public abstract class Geo3dShapeRectRelationTestCase extends RandomizedShapeTest protected Geo3dShape generateRandomShape(Point nearP) { final int circleRadius = 180 - random().nextInt(180);//no 0-radius final Point point = nearP; - final GeoShape shape = GeoCircleFactory.makeGeoCircle(planetModel, point.getY() * DEGREES_TO_RADIANS, point.getX() * DEGREES_TO_RADIANS, + final GeoCircle shape = GeoCircleFactory.makeGeoCircle(planetModel, point.getY() * DEGREES_TO_RADIANS, point.getX() * DEGREES_TO_RADIANS, circleRadius * DEGREES_TO_RADIANS); - return new Geo3dShape(planetModel, shape, ctx); + return new Geo3dShape(shape, ctx); } @Override @@ -153,11 +155,11 @@ public abstract class Geo3dShapeRectRelationTestCase extends RandomizedShapeTest ulhcPoint = lrhcPoint; lrhcPoint = temp; } - final GeoShape shape = GeoBBoxFactory.makeGeoBBox(planetModel, ulhcPoint.getY() * DEGREES_TO_RADIANS, + final GeoBBox shape = GeoBBoxFactory.makeGeoBBox(planetModel, ulhcPoint.getY() * DEGREES_TO_RADIANS, lrhcPoint.getY() * DEGREES_TO_RADIANS, ulhcPoint.getX() * DEGREES_TO_RADIANS, lrhcPoint.getX() * DEGREES_TO_RADIANS); - return new Geo3dShape(planetModel, shape, ctx); + return new Geo3dShape(shape, ctx); } @Override @@ -185,11 +187,11 @@ public abstract class Geo3dShapeRectRelationTestCase extends RandomizedShapeTest geoPoints.add(gPt); } try { - final GeoShape shape = GeoPolygonFactory.makeGeoPolygon(planetModel, geoPoints); + final GeoPolygon shape = GeoPolygonFactory.makeGeoPolygon(planetModel, geoPoints); if (shape == null) { continue; } - return new Geo3dShape(planetModel, shape, ctx); + return new Geo3dShape(shape, ctx); } catch (IllegalArgumentException e) { // This is what happens when we create a shape that is invalid. Although it is conceivable that there are cases where // the exception is thrown incorrectly, we aren't going to be able to do that in this random test. @@ -231,8 +233,8 @@ public abstract class Geo3dShapeRectRelationTestCase extends RandomizedShapeTest } try { - final GeoShape path = GeoPathFactory.makeGeoPath(planetModel, width, points); - return new Geo3dShape(planetModel, path, ctx); + final GeoPath path = GeoPathFactory.makeGeoPath(planetModel, width, points); + return new Geo3dShape(path, ctx); } catch (IllegalArgumentException e) { // This is what happens when we create a shape that is invalid. Although it is conceivable that there are cases where // the exception is thrown incorrectly, we aren't going to be able to do that in this random test. @@ -257,6 +259,6 @@ public abstract class Geo3dShapeRectRelationTestCase extends RandomizedShapeTest private Point geoPointToSpatial4jPoint(GeoPoint geoPoint) { return ctx.makePoint(geoPoint.getLongitude() * DistanceUtils.RADIANS_TO_DEGREES, - geoPoint.getLongitude() * DistanceUtils.RADIANS_TO_DEGREES); + geoPoint.getLatitude() * DistanceUtils.RADIANS_TO_DEGREES); } } diff --git a/lucene/spatial-extras/src/test/org/apache/lucene/spatial/spatial4j/Geo3dShapeSphereModelRectRelationTest.java b/lucene/spatial-extras/src/test/org/apache/lucene/spatial/spatial4j/Geo3dShapeSphereModelRectRelationTest.java index 19e3912a833..20db21c61c3 100644 --- a/lucene/spatial-extras/src/test/org/apache/lucene/spatial/spatial4j/Geo3dShapeSphereModelRectRelationTest.java +++ b/lucene/spatial-extras/src/test/org/apache/lucene/spatial/spatial4j/Geo3dShapeSphereModelRectRelationTest.java @@ -19,21 +19,29 @@ package org.apache.lucene.spatial.spatial4j; import java.util.ArrayList; import java.util.List; -import org.locationtech.spatial4j.shape.Rectangle; import org.apache.lucene.spatial3d.geom.GeoArea; import org.apache.lucene.spatial3d.geom.GeoBBox; import org.apache.lucene.spatial3d.geom.GeoBBoxFactory; +import org.apache.lucene.spatial3d.geom.GeoCircle; import org.apache.lucene.spatial3d.geom.GeoCircleFactory; import org.apache.lucene.spatial3d.geom.GeoPoint; import org.apache.lucene.spatial3d.geom.GeoPolygonFactory; import org.apache.lucene.spatial3d.geom.GeoShape; import org.apache.lucene.spatial3d.geom.PlanetModel; import org.junit.Test; +import org.locationtech.spatial4j.shape.Circle; +import org.locationtech.spatial4j.shape.Point; +import org.locationtech.spatial4j.shape.Rectangle; +import org.locationtech.spatial4j.shape.SpatialRelation; public class Geo3dShapeSphereModelRectRelationTest extends Geo3dShapeRectRelationTestCase { public Geo3dShapeSphereModelRectRelationTest() { super(PlanetModel.SPHERE); + Geo3dSpatialContextFactory factory = new Geo3dSpatialContextFactory(); + factory.planetModel = PlanetModel.SPHERE; + //factory.distCalc = new GeodesicSphereDistCalc.Haversine(); + this.ctx = factory.newSpatialContext(); } @Test @@ -60,13 +68,28 @@ public class Geo3dShapeSphereModelRectRelationTest extends Geo3dShapeRectRelatio @Test public void testFailure2_LUCENE6475() { - GeoShape geo3dCircle = GeoCircleFactory.makeGeoCircle(planetModel, 1.6282053147165243E-4 * RADIANS_PER_DEGREE, + GeoCircle geo3dCircle = GeoCircleFactory.makeGeoCircle(planetModel, 1.6282053147165243E-4 * RADIANS_PER_DEGREE, -70.1600629789353 * RADIANS_PER_DEGREE, 86 * RADIANS_PER_DEGREE); - Geo3dShape geo3dShape = new Geo3dShape(planetModel, geo3dCircle, ctx); + Geo3dShape geo3dShape = new Geo3dShape(geo3dCircle, ctx); Rectangle rect = ctx.makeRectangle(-118, -114, -2.0, 32.0); assertTrue(geo3dShape.relate(rect).intersects()); // thus the bounding box must intersect too assertTrue(geo3dShape.getBoundingBox().relate(rect).intersects()); } + + @Test + public void pointBearingTest(){ + double radius = 136; + double distance = 135.97; + double bearing = 188; + Point p = ctx.getShapeFactory().pointXY(35, 85); + Circle circle = ctx.getShapeFactory().circle(p, radius); + Point bPoint = ctx.getDistCalc().pointOnBearing(p, distance, bearing, ctx, (Point) null); + + double d = ctx.getDistCalc().distance(p, bPoint); + assertEquals(d, distance, 10-8); + + assertEquals(circle.relate(bPoint), SpatialRelation.CONTAINS); + } } diff --git a/lucene/spatial-extras/src/test/org/apache/lucene/spatial/spatial4j/Geo3dShapeWGS84ModelRectRelationTest.java b/lucene/spatial-extras/src/test/org/apache/lucene/spatial/spatial4j/Geo3dShapeWGS84ModelRectRelationTest.java index 43b88ca5912..22d7bd4cbdb 100644 --- a/lucene/spatial-extras/src/test/org/apache/lucene/spatial/spatial4j/Geo3dShapeWGS84ModelRectRelationTest.java +++ b/lucene/spatial-extras/src/test/org/apache/lucene/spatial/spatial4j/Geo3dShapeWGS84ModelRectRelationTest.java @@ -26,11 +26,18 @@ import org.apache.lucene.spatial3d.geom.GeoPath; import org.apache.lucene.spatial3d.geom.GeoPoint; import org.apache.lucene.spatial3d.geom.PlanetModel; import org.junit.Test; +import org.locationtech.spatial4j.shape.Circle; +import org.locationtech.spatial4j.shape.Point; +import org.locationtech.spatial4j.shape.SpatialRelation; public class Geo3dShapeWGS84ModelRectRelationTest extends Geo3dShapeRectRelationTestCase { public Geo3dShapeWGS84ModelRectRelationTest() { super(PlanetModel.WGS84); + Geo3dSpatialContextFactory factory = new Geo3dSpatialContextFactory(); + factory.planetModel = PlanetModel.WGS84; + //factory.distCalc = new GeodesicSphereDistCalc.Haversine(); + this.ctx = factory.newSpatialContext(); } @Test @@ -92,4 +99,18 @@ public class Geo3dShapeWGS84ModelRectRelationTest extends Geo3dShapeRectRelation // (3) The point mentioned is NOT inside the path segment, either. (I think it should be...) } + @Test + public void pointBearingTest(){ + double radius = 136; + double distance = 135.97; + double bearing = 188; + Point p = ctx.getShapeFactory().pointXY(35, 85); + Circle circle = ctx.getShapeFactory().circle(p, radius); + Point bPoint = ctx.getDistCalc().pointOnBearing(p, distance, bearing, ctx, (Point) null); + + double d = ctx.getDistCalc().distance(p, bPoint); + assertEquals(d, distance, 10-8); + + assertEquals(circle.relate(bPoint), SpatialRelation.CONTAINS); + } }