mirror of https://github.com/apache/lucene.git
LUCENE-8086: spatial-extras Geo3dFactory: Use GeoExactCircle with configurable precision for non-spherical planet models.
Some internal refactorings as well.
This commit is contained in:
parent
44cc0defe8
commit
d66d9549d7
|
@ -72,6 +72,10 @@ Improvements
|
|||
disk. This change adds an expert setting to opt ouf of this behavior unless
|
||||
flusing is falling behind. (Simon Willnauer)
|
||||
|
||||
* LUCENE-8086: spatial-extras Geo3dFactory: Use GeoExactCircle with
|
||||
configurable precision for non-spherical planet models.
|
||||
(Ignacio Vera via David Smiley)
|
||||
|
||||
======================= Lucene 7.2.0 =======================
|
||||
|
||||
API Changes
|
||||
|
|
|
@ -20,13 +20,10 @@ 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}.
|
||||
|
@ -67,16 +64,4 @@ public class Geo3dCircleShape extends Geo3dShape<GeoCircle> implements Circle {
|
|||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -73,62 +73,20 @@ public class Geo3dDistanceCalculator implements DistanceCalculator {
|
|||
|
||||
@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");
|
||||
GeoPoint newPoint = planetModel.surfacePointOnBearing(point, dist, bearing);
|
||||
double newLat = newPoint.getLatitude() * DistanceUtils.RADIANS_TO_DEGREES;
|
||||
double newLon = newPoint.getLongitude() * DistanceUtils.RADIANS_TO_DEGREES;
|
||||
if (reuse != null) {
|
||||
reuse.reset(newLon, newLat);
|
||||
return reuse;
|
||||
}
|
||||
else {
|
||||
return ctx.getShapeFactory().pointXY(newLon, newLat);
|
||||
}
|
||||
|
||||
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
|
||||
|
|
|
@ -112,11 +112,7 @@ public class Geo3dShape<T extends GeoAreaShape> implements Shape {
|
|||
if (bbox == null) {
|
||||
LatLonBounds bounds = new LatLonBounds();
|
||||
shape.getBounds(bounds);
|
||||
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);
|
||||
GeoBBox geoBBox = GeoBBoxFactory.makeGeoBBox(shape.getPlanetModel(), bounds);
|
||||
bbox = new Geo3dRectangleShape(geoBBox, spatialcontext);
|
||||
this.boundingBox = bbox;
|
||||
}
|
||||
|
|
|
@ -55,6 +55,13 @@ public class Geo3dShapeFactory implements ShapeFactory {
|
|||
private SpatialContext context;
|
||||
private PlanetModel planetModel;
|
||||
|
||||
/**
|
||||
* Default accuracy for circles when not using the unit sphere.
|
||||
* It is equivalent to ~10m on the surface of the earth.
|
||||
*/
|
||||
private static final double DEFAULT_CIRCLE_ACCURACY = 1e-4;
|
||||
private double circleAccuracy = DEFAULT_CIRCLE_ACCURACY;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public Geo3dShapeFactory(SpatialContext context, SpatialContextFactory factory) {
|
||||
this.context = context;
|
||||
|
@ -67,6 +74,16 @@ public class Geo3dShapeFactory implements ShapeFactory {
|
|||
return context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the accuracy for circles in decimal degrees. Note that accuracy has no effect
|
||||
* when the planet model is a sphere. In that case, circles are always fully precise.
|
||||
*
|
||||
* @param circleAccuracy the provided accuracy in decimal degrees.
|
||||
*/
|
||||
public void setCircleAccuracy(double circleAccuracy) {
|
||||
this.circleAccuracy = circleAccuracy;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isNormWrapLongitude() {
|
||||
return normWrapLongitude;
|
||||
|
@ -150,10 +167,23 @@ public class Geo3dShapeFactory implements ShapeFactory {
|
|||
|
||||
@Override
|
||||
public Circle circle(double x, double y, double distance) {
|
||||
GeoCircle circle = GeoCircleFactory.makeGeoCircle(planetModel,
|
||||
GeoCircle circle;
|
||||
if (planetModel.isSphere()) {
|
||||
circle = GeoCircleFactory.makeGeoCircle(planetModel,
|
||||
y * DistanceUtils.DEGREES_TO_RADIANS,
|
||||
x * DistanceUtils.DEGREES_TO_RADIANS,
|
||||
distance * DistanceUtils.DEGREES_TO_RADIANS);
|
||||
}
|
||||
else {
|
||||
//accuracy is defined as a linear distance in this class. At tiny distances, linear distance
|
||||
//can be approximated to surface distance in radians.
|
||||
circle = GeoCircleFactory.makeExactGeoCircle(planetModel,
|
||||
y * DistanceUtils.DEGREES_TO_RADIANS,
|
||||
x * DistanceUtils.DEGREES_TO_RADIANS,
|
||||
distance * DistanceUtils.DEGREES_TO_RADIANS,
|
||||
circleAccuracy * DistanceUtils.DEGREES_TO_RADIANS);
|
||||
|
||||
}
|
||||
return new Geo3dCircleShape(circle, context);
|
||||
}
|
||||
|
||||
|
@ -238,8 +268,7 @@ public class Geo3dShapeFactory implements ShapeFactory {
|
|||
|
||||
/**
|
||||
* 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.
|
||||
* line strings.
|
||||
*/
|
||||
private class Geo3dLineStringBuilder extends Geo3dPointBuilder<LineStringBuilder> implements LineStringBuilder {
|
||||
|
||||
|
@ -373,7 +402,7 @@ public class Geo3dShapeFactory implements ShapeFactory {
|
|||
|
||||
/**
|
||||
* Geo3d implementation of {@link org.locationtech.spatial4j.shape.ShapeFactory.MultiShapeBuilder} to generate
|
||||
* geometry collections
|
||||
* geometry collections.
|
||||
*
|
||||
* @param <T> is the type of shapes.
|
||||
*/
|
||||
|
|
|
@ -1,264 +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.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;
|
||||
import org.locationtech.spatial4j.shape.Circle;
|
||||
import org.locationtech.spatial4j.shape.Point;
|
||||
import org.locationtech.spatial4j.shape.RectIntersectionTestHelper;
|
||||
import org.apache.lucene.spatial3d.geom.LatLonBounds;
|
||||
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.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.junit.Rule;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.locationtech.spatial4j.distance.DistanceUtils.DEGREES_TO_RADIANS;
|
||||
|
||||
public abstract class Geo3dShapeRectRelationTestCase extends RandomizedShapeTestCase {
|
||||
protected final static double RADIANS_PER_DEGREE = Math.PI/180.0;
|
||||
|
||||
@Rule
|
||||
public final TestLog testLog = TestLog.instance;
|
||||
|
||||
protected final PlanetModel planetModel;
|
||||
|
||||
public Geo3dShapeRectRelationTestCase(PlanetModel planetModel) {
|
||||
super(SpatialContext.GEO);
|
||||
this.planetModel = planetModel;
|
||||
}
|
||||
|
||||
protected GeoBBox getBoundingBox(final GeoShape path) {
|
||||
LatLonBounds bounds = new LatLonBounds();
|
||||
path.getBounds(bounds);
|
||||
|
||||
double leftLon;
|
||||
double rightLon;
|
||||
if (bounds.checkNoLongitudeBound()) {
|
||||
leftLon = -Math.PI;
|
||||
rightLon = Math.PI;
|
||||
} else {
|
||||
leftLon = bounds.getLeftLongitude().doubleValue();
|
||||
rightLon = bounds.getRightLongitude().doubleValue();
|
||||
}
|
||||
double minLat;
|
||||
if (bounds.checkNoBottomLatitudeBound()) {
|
||||
minLat = -Math.PI * 0.5;
|
||||
} else {
|
||||
minLat = bounds.getMinLatitude().doubleValue();
|
||||
}
|
||||
double maxLat;
|
||||
if (bounds.checkNoTopLatitudeBound()) {
|
||||
maxLat = Math.PI * 0.5;
|
||||
} else {
|
||||
maxLat = bounds.getMaxLatitude().doubleValue();
|
||||
}
|
||||
return GeoBBoxFactory.makeGeoBBox(planetModel, maxLat, minLat, leftLon, rightLon);
|
||||
}
|
||||
|
||||
public abstract class Geo3dRectIntersectionTestHelper extends RectIntersectionTestHelper<Geo3dShape> {
|
||||
|
||||
public Geo3dRectIntersectionTestHelper(SpatialContext ctx) {
|
||||
super(ctx);
|
||||
}
|
||||
|
||||
//20 times each -- should be plenty
|
||||
|
||||
protected int getContainsMinimum(int laps) {
|
||||
return 20;
|
||||
}
|
||||
|
||||
protected int getIntersectsMinimum(int laps) {
|
||||
return 20;
|
||||
}
|
||||
|
||||
// producing "within" cases in Geo3D based on our random shapes doesn't happen often. It'd be nice to increase this.
|
||||
protected int getWithinMinimum(int laps) {
|
||||
return 2;
|
||||
}
|
||||
|
||||
protected int getDisjointMinimum(int laps) {
|
||||
return 20;
|
||||
}
|
||||
|
||||
protected int getBoundingMinimum(int laps) {
|
||||
return 20;
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGeoCircleRect() {
|
||||
new Geo3dRectIntersectionTestHelper(ctx) {
|
||||
|
||||
@Override
|
||||
protected Geo3dShape generateRandomShape(Point nearP) {
|
||||
final int circleRadius = 180 - random().nextInt(180);//no 0-radius
|
||||
final Point point = nearP;
|
||||
final GeoCircle shape = GeoCircleFactory.makeGeoCircle(planetModel, point.getY() * DEGREES_TO_RADIANS, point.getX() * DEGREES_TO_RADIANS,
|
||||
circleRadius * DEGREES_TO_RADIANS);
|
||||
return new Geo3dShape(shape, ctx);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Point randomPointInEmptyShape(Geo3dShape shape) {
|
||||
GeoPoint geoPoint = ((GeoCircle)shape.shape).getCenter();
|
||||
return geoPointToSpatial4jPoint(geoPoint);
|
||||
}
|
||||
|
||||
}.testRelateWithRectangle();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGeoBBoxRect() {
|
||||
new Geo3dRectIntersectionTestHelper(ctx) {
|
||||
|
||||
@Override
|
||||
protected boolean isRandomShapeRectangular() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Geo3dShape generateRandomShape(Point nearP) {
|
||||
// (ignoring nearP)
|
||||
Point ulhcPoint = randomPoint();
|
||||
Point lrhcPoint = randomPoint();
|
||||
if (ulhcPoint.getY() < lrhcPoint.getY()) {
|
||||
//swap
|
||||
Point temp = ulhcPoint;
|
||||
ulhcPoint = lrhcPoint;
|
||||
lrhcPoint = temp;
|
||||
}
|
||||
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(shape, ctx);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Point randomPointInEmptyShape(Geo3dShape shape) {
|
||||
return shape.getBoundingBox().getCenter();
|
||||
}
|
||||
}.testRelateWithRectangle();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGeoPolygonRect() {
|
||||
new Geo3dRectIntersectionTestHelper(ctx) {
|
||||
|
||||
@Override
|
||||
protected Geo3dShape generateRandomShape(Point nearP) {
|
||||
final Point centerPoint = randomPoint();
|
||||
final int maxDistance = random().nextInt(160) + 20;
|
||||
final Circle pointZone = ctx.makeCircle(centerPoint, maxDistance);
|
||||
final int vertexCount = random().nextInt(3) + 3;
|
||||
while (true) {
|
||||
final List<GeoPoint> geoPoints = new ArrayList<>();
|
||||
while (geoPoints.size() < vertexCount) {
|
||||
final Point point = randomPointIn(pointZone);
|
||||
final GeoPoint gPt = new GeoPoint(planetModel, point.getY() * DEGREES_TO_RADIANS, point.getX() * DEGREES_TO_RADIANS);
|
||||
geoPoints.add(gPt);
|
||||
}
|
||||
try {
|
||||
final GeoPolygon shape = GeoPolygonFactory.makeGeoPolygon(planetModel, 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Point randomPointInEmptyShape(Geo3dShape shape) {
|
||||
throw new IllegalStateException("unexpected; need to finish test code");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getWithinMinimum(int laps) {
|
||||
// Long/thin so lets just find 1.
|
||||
return 1;
|
||||
}
|
||||
|
||||
}.testRelateWithRectangle();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGeoPathRect() {
|
||||
new Geo3dRectIntersectionTestHelper(ctx) {
|
||||
|
||||
@Override
|
||||
protected Geo3dShape generateRandomShape(Point nearP) {
|
||||
final Point centerPoint = randomPoint();
|
||||
final int maxDistance = random().nextInt(160) + 20;
|
||||
final Circle pointZone = ctx.makeCircle(centerPoint, maxDistance);
|
||||
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 = randomPointIn(pointZone);
|
||||
points[i] = new GeoPoint(planetModel, nextPoint.getY() * DEGREES_TO_RADIANS, nextPoint.getX() * DEGREES_TO_RADIANS);
|
||||
}
|
||||
|
||||
try {
|
||||
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.
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Point randomPointInEmptyShape(Geo3dShape shape) {
|
||||
throw new IllegalStateException("unexpected; need to finish test code");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getWithinMinimum(int laps) {
|
||||
// Long/thin so lets just find 1.
|
||||
return 1;
|
||||
}
|
||||
|
||||
}.testRelateWithRectangle();
|
||||
}
|
||||
|
||||
private Point geoPointToSpatial4jPoint(GeoPoint geoPoint) {
|
||||
return ctx.makePoint(geoPoint.getLongitude() * DistanceUtils.RADIANS_TO_DEGREES,
|
||||
geoPoint.getLatitude() * DistanceUtils.RADIANS_TO_DEGREES);
|
||||
}
|
||||
}
|
|
@ -34,13 +34,13 @@ import org.locationtech.spatial4j.shape.Point;
|
|||
import org.locationtech.spatial4j.shape.Rectangle;
|
||||
import org.locationtech.spatial4j.shape.SpatialRelation;
|
||||
|
||||
public class Geo3dShapeSphereModelRectRelationTest extends Geo3dShapeRectRelationTestCase {
|
||||
public class Geo3dShapeSphereModelRectRelationTest extends ShapeRectRelationTestCase {
|
||||
|
||||
PlanetModel planetModel = PlanetModel.SPHERE;
|
||||
|
||||
public Geo3dShapeSphereModelRectRelationTest() {
|
||||
super(PlanetModel.SPHERE);
|
||||
Geo3dSpatialContextFactory factory = new Geo3dSpatialContextFactory();
|
||||
factory.planetModel = PlanetModel.SPHERE;
|
||||
//factory.distCalc = new GeodesicSphereDistCalc.Haversine();
|
||||
factory.planetModel = planetModel;
|
||||
this.ctx = factory.newSpatialContext();
|
||||
}
|
||||
|
||||
|
|
|
@ -30,14 +30,16 @@ import org.locationtech.spatial4j.shape.Circle;
|
|||
import org.locationtech.spatial4j.shape.Point;
|
||||
import org.locationtech.spatial4j.shape.SpatialRelation;
|
||||
|
||||
public class Geo3dShapeWGS84ModelRectRelationTest extends Geo3dShapeRectRelationTestCase {
|
||||
public class Geo3dShapeWGS84ModelRectRelationTest extends ShapeRectRelationTestCase {
|
||||
|
||||
PlanetModel planetModel = PlanetModel.WGS84;
|
||||
|
||||
public Geo3dShapeWGS84ModelRectRelationTest() {
|
||||
super(PlanetModel.WGS84);
|
||||
Geo3dSpatialContextFactory factory = new Geo3dSpatialContextFactory();
|
||||
factory.planetModel = PlanetModel.WGS84;
|
||||
//factory.distCalc = new GeodesicSphereDistCalc.Haversine();
|
||||
factory.planetModel = planetModel;
|
||||
this.ctx = factory.newSpatialContext();
|
||||
this.maxRadius = 178;
|
||||
((Geo3dShapeFactory)ctx.getShapeFactory()).setCircleAccuracy(1e-6);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -0,0 +1,201 @@
|
|||
/*
|
||||
* 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.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.locationtech.spatial4j.TestLog;
|
||||
import org.locationtech.spatial4j.context.SpatialContext;
|
||||
import org.locationtech.spatial4j.shape.Circle;
|
||||
import org.locationtech.spatial4j.shape.Point;
|
||||
import org.locationtech.spatial4j.shape.RectIntersectionTestHelper;
|
||||
import org.locationtech.spatial4j.shape.Shape;
|
||||
import org.locationtech.spatial4j.shape.ShapeFactory;
|
||||
|
||||
import static org.locationtech.spatial4j.distance.DistanceUtils.DEGREES_TO_RADIANS;
|
||||
|
||||
public abstract class ShapeRectRelationTestCase extends RandomizedShapeTestCase {
|
||||
protected final static double RADIANS_PER_DEGREE = Math.PI/180.0;
|
||||
|
||||
@Rule
|
||||
public final TestLog testLog = TestLog.instance;
|
||||
|
||||
protected int maxRadius = 180;
|
||||
|
||||
public ShapeRectRelationTestCase() {
|
||||
super(SpatialContext.GEO);
|
||||
}
|
||||
|
||||
public abstract class AbstractRectIntersectionTestHelper extends RectIntersectionTestHelper<Shape> {
|
||||
|
||||
public AbstractRectIntersectionTestHelper(SpatialContext ctx) {
|
||||
super(ctx);
|
||||
}
|
||||
|
||||
//20 times each -- should be plenty
|
||||
|
||||
protected int getContainsMinimum(int laps) {
|
||||
return 20;
|
||||
}
|
||||
|
||||
protected int getIntersectsMinimum(int laps) {
|
||||
return 20;
|
||||
}
|
||||
|
||||
// producing "within" cases in Geo3D based on our random shapes doesn't happen often. It'd be nice to increase this.
|
||||
protected int getWithinMinimum(int laps) {
|
||||
return 2;
|
||||
}
|
||||
|
||||
protected int getDisjointMinimum(int laps) {
|
||||
return 20;
|
||||
}
|
||||
|
||||
protected int getBoundingMinimum(int laps) {
|
||||
return 20;
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGeoCircleRect() {
|
||||
new AbstractRectIntersectionTestHelper(ctx) {
|
||||
|
||||
@Override
|
||||
protected Shape generateRandomShape(Point nearP) {
|
||||
final int circleRadius = maxRadius - random().nextInt(maxRadius);//no 0-radius
|
||||
return ctx.getShapeFactory().circle(nearP, circleRadius);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Point randomPointInEmptyShape(Shape shape) {
|
||||
return shape.getCenter();
|
||||
}
|
||||
|
||||
}.testRelateWithRectangle();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGeoBBoxRect() {
|
||||
new AbstractRectIntersectionTestHelper(ctx) {
|
||||
|
||||
@Override
|
||||
protected boolean isRandomShapeRectangular() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Shape generateRandomShape(Point nearP) {
|
||||
Point upperRight = randomPoint();
|
||||
Point lowerLeft = randomPoint();
|
||||
if (upperRight.getY() < lowerLeft.getY()) {
|
||||
//swap
|
||||
Point temp = upperRight;
|
||||
upperRight = lowerLeft;
|
||||
lowerLeft = temp;
|
||||
}
|
||||
return ctx.getShapeFactory().rect(lowerLeft, upperRight);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Point randomPointInEmptyShape(Shape shape) {
|
||||
return shape.getCenter();
|
||||
}
|
||||
}.testRelateWithRectangle();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGeoPolygonRect() {
|
||||
new AbstractRectIntersectionTestHelper(ctx) {
|
||||
|
||||
@Override
|
||||
protected Shape generateRandomShape(Point nearP) {
|
||||
final Point centerPoint = randomPoint();
|
||||
final int maxDistance = random().nextInt(maxRadius -20) + 20;
|
||||
final Circle pointZone = ctx.getShapeFactory().circle(centerPoint, maxDistance);
|
||||
final int vertexCount = random().nextInt(3) + 3;
|
||||
while (true) {
|
||||
ShapeFactory.PolygonBuilder builder = ctx.getShapeFactory().polygon();
|
||||
for (int i = 0; i < vertexCount; i++) {
|
||||
final Point point = randomPointIn(pointZone);
|
||||
builder.pointXY(point.getX(), point.getY());
|
||||
}
|
||||
try {
|
||||
return builder.build();
|
||||
} 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Point randomPointInEmptyShape(Shape shape) {
|
||||
throw new IllegalStateException("unexpected; need to finish test code");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getWithinMinimum(int laps) {
|
||||
// Long/thin so lets just find 1.
|
||||
return 1;
|
||||
}
|
||||
|
||||
}.testRelateWithRectangle();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGeoPathRect() {
|
||||
new AbstractRectIntersectionTestHelper(ctx) {
|
||||
|
||||
@Override
|
||||
protected Shape generateRandomShape(Point nearP) {
|
||||
final Point centerPoint = randomPoint();
|
||||
final int maxDistance = random().nextInt(maxRadius -20) + 20;
|
||||
final Circle pointZone = ctx.getShapeFactory().circle(centerPoint, maxDistance);
|
||||
final int pointCount = random().nextInt(5) + 1;
|
||||
final double width = (random().nextInt(89)+1) * DEGREES_TO_RADIANS;
|
||||
final ShapeFactory.LineStringBuilder builder = ctx.getShapeFactory().lineString();
|
||||
while (true) {
|
||||
for (int i = 0; i < pointCount; i++) {
|
||||
final Point nextPoint = randomPointIn(pointZone);
|
||||
builder.pointXY(nextPoint.getX(), nextPoint.getY());
|
||||
}
|
||||
builder.buffer(width);
|
||||
try {
|
||||
return builder.build();
|
||||
} 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Point randomPointInEmptyShape(Shape shape) {
|
||||
throw new IllegalStateException("unexpected; need to finish test code");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getWithinMinimum(int laps) {
|
||||
// Long/thin so lets just find 1.
|
||||
return 1;
|
||||
}
|
||||
|
||||
}.testRelateWithRectangle();
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue