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
|
disk. This change adds an expert setting to opt ouf of this behavior unless
|
||||||
flusing is falling behind. (Simon Willnauer)
|
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 =======================
|
======================= Lucene 7.2.0 =======================
|
||||||
|
|
||||||
API Changes
|
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.GeoCircle;
|
||||||
import org.apache.lucene.spatial3d.geom.GeoCircleFactory;
|
import org.apache.lucene.spatial3d.geom.GeoCircleFactory;
|
||||||
import org.apache.lucene.spatial3d.geom.GeoPointShapeFactory;
|
import org.apache.lucene.spatial3d.geom.GeoPointShapeFactory;
|
||||||
import org.apache.lucene.spatial3d.geom.PlanetModel;
|
|
||||||
import org.locationtech.spatial4j.context.SpatialContext;
|
import org.locationtech.spatial4j.context.SpatialContext;
|
||||||
import org.locationtech.spatial4j.distance.DistanceUtils;
|
import org.locationtech.spatial4j.distance.DistanceUtils;
|
||||||
import org.locationtech.spatial4j.shape.Circle;
|
import org.locationtech.spatial4j.shape.Circle;
|
||||||
import org.locationtech.spatial4j.shape.Point;
|
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}.
|
* Specialization of a {@link Geo3dShape} which represents a {@link Circle}.
|
||||||
|
@ -67,16 +64,4 @@ public class Geo3dCircleShape extends Geo3dShape<GeoCircle> implements Circle {
|
||||||
}
|
}
|
||||||
return 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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -73,62 +73,20 @@ public class Geo3dDistanceCalculator implements DistanceCalculator {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Point pointOnBearing(Point from, double distDEG, double bearingDEG, SpatialContext ctx, Point reuse) {
|
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;
|
Geo3dPointShape geoFrom = (Geo3dPointShape) from;
|
||||||
GeoPoint point = (GeoPoint) geoFrom.shape;
|
GeoPoint point = (GeoPoint) geoFrom.shape;
|
||||||
double lat = point.getLatitude();
|
|
||||||
double lon = point.getLongitude();
|
|
||||||
double dist = DistanceUtils.DEGREES_TO_RADIANS * distDEG;
|
double dist = DistanceUtils.DEGREES_TO_RADIANS * distDEG;
|
||||||
double bearing = DistanceUtils.DEGREES_TO_RADIANS * bearingDEG;
|
double bearing = DistanceUtils.DEGREES_TO_RADIANS * bearingDEG;
|
||||||
|
GeoPoint newPoint = planetModel.surfacePointOnBearing(point, dist, bearing);
|
||||||
double sinα1 = Math.sin(bearing);
|
double newLat = newPoint.getLatitude() * DistanceUtils.RADIANS_TO_DEGREES;
|
||||||
double cosα1 = Math.cos(bearing);
|
double newLon = newPoint.getLongitude() * DistanceUtils.RADIANS_TO_DEGREES;
|
||||||
|
if (reuse != null) {
|
||||||
double tanU1 = (1 - planetModel.flattening) * Math.tan(lat);
|
reuse.reset(newLon, newLat);
|
||||||
double cosU1 = 1 / Math.sqrt((1 + tanU1 * tanU1));
|
return reuse;
|
||||||
double sinU1 = tanU1 * cosU1;
|
}
|
||||||
|
else {
|
||||||
double σ1 = Math.atan2(tanU1, cosα1);
|
return ctx.getShapeFactory().pointXY(newLon, newLat);
|
||||||
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
|
@Override
|
||||||
|
|
|
@ -112,11 +112,7 @@ public class Geo3dShape<T extends GeoAreaShape> implements Shape {
|
||||||
if (bbox == null) {
|
if (bbox == null) {
|
||||||
LatLonBounds bounds = new LatLonBounds();
|
LatLonBounds bounds = new LatLonBounds();
|
||||||
shape.getBounds(bounds);
|
shape.getBounds(bounds);
|
||||||
double leftLon = bounds.checkNoLongitudeBound() ? -Math.PI : bounds.getLeftLongitude();
|
GeoBBox geoBBox = GeoBBoxFactory.makeGeoBBox(shape.getPlanetModel(), bounds);
|
||||||
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);
|
bbox = new Geo3dRectangleShape(geoBBox, spatialcontext);
|
||||||
this.boundingBox = bbox;
|
this.boundingBox = bbox;
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,6 +55,13 @@ public class Geo3dShapeFactory implements ShapeFactory {
|
||||||
private SpatialContext context;
|
private SpatialContext context;
|
||||||
private PlanetModel planetModel;
|
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")
|
@SuppressWarnings("unchecked")
|
||||||
public Geo3dShapeFactory(SpatialContext context, SpatialContextFactory factory) {
|
public Geo3dShapeFactory(SpatialContext context, SpatialContextFactory factory) {
|
||||||
this.context = context;
|
this.context = context;
|
||||||
|
@ -67,6 +74,16 @@ public class Geo3dShapeFactory implements ShapeFactory {
|
||||||
return context;
|
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
|
@Override
|
||||||
public boolean isNormWrapLongitude() {
|
public boolean isNormWrapLongitude() {
|
||||||
return normWrapLongitude;
|
return normWrapLongitude;
|
||||||
|
@ -150,10 +167,23 @@ public class Geo3dShapeFactory implements ShapeFactory {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Circle circle(double x, double y, double distance) {
|
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,
|
y * DistanceUtils.DEGREES_TO_RADIANS,
|
||||||
x * DistanceUtils.DEGREES_TO_RADIANS,
|
x * DistanceUtils.DEGREES_TO_RADIANS,
|
||||||
distance * 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);
|
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
|
* Geo3d implementation of {@link org.locationtech.spatial4j.shape.ShapeFactory.LineStringBuilder} to generate
|
||||||
* nine Strings. Note that GeoPath needs a buffer so we set the
|
* line strings.
|
||||||
* buffer to 1e-10.
|
|
||||||
*/
|
*/
|
||||||
private class Geo3dLineStringBuilder extends Geo3dPointBuilder<LineStringBuilder> implements LineStringBuilder {
|
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
|
* Geo3d implementation of {@link org.locationtech.spatial4j.shape.ShapeFactory.MultiShapeBuilder} to generate
|
||||||
* geometry collections
|
* geometry collections.
|
||||||
*
|
*
|
||||||
* @param <T> is the type of shapes.
|
* @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.Rectangle;
|
||||||
import org.locationtech.spatial4j.shape.SpatialRelation;
|
import org.locationtech.spatial4j.shape.SpatialRelation;
|
||||||
|
|
||||||
public class Geo3dShapeSphereModelRectRelationTest extends Geo3dShapeRectRelationTestCase {
|
public class Geo3dShapeSphereModelRectRelationTest extends ShapeRectRelationTestCase {
|
||||||
|
|
||||||
|
PlanetModel planetModel = PlanetModel.SPHERE;
|
||||||
|
|
||||||
public Geo3dShapeSphereModelRectRelationTest() {
|
public Geo3dShapeSphereModelRectRelationTest() {
|
||||||
super(PlanetModel.SPHERE);
|
|
||||||
Geo3dSpatialContextFactory factory = new Geo3dSpatialContextFactory();
|
Geo3dSpatialContextFactory factory = new Geo3dSpatialContextFactory();
|
||||||
factory.planetModel = PlanetModel.SPHERE;
|
factory.planetModel = planetModel;
|
||||||
//factory.distCalc = new GeodesicSphereDistCalc.Haversine();
|
|
||||||
this.ctx = factory.newSpatialContext();
|
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.Point;
|
||||||
import org.locationtech.spatial4j.shape.SpatialRelation;
|
import org.locationtech.spatial4j.shape.SpatialRelation;
|
||||||
|
|
||||||
public class Geo3dShapeWGS84ModelRectRelationTest extends Geo3dShapeRectRelationTestCase {
|
public class Geo3dShapeWGS84ModelRectRelationTest extends ShapeRectRelationTestCase {
|
||||||
|
|
||||||
|
PlanetModel planetModel = PlanetModel.WGS84;
|
||||||
|
|
||||||
public Geo3dShapeWGS84ModelRectRelationTest() {
|
public Geo3dShapeWGS84ModelRectRelationTest() {
|
||||||
super(PlanetModel.WGS84);
|
|
||||||
Geo3dSpatialContextFactory factory = new Geo3dSpatialContextFactory();
|
Geo3dSpatialContextFactory factory = new Geo3dSpatialContextFactory();
|
||||||
factory.planetModel = PlanetModel.WGS84;
|
factory.planetModel = planetModel;
|
||||||
//factory.distCalc = new GeodesicSphereDistCalc.Haversine();
|
|
||||||
this.ctx = factory.newSpatialContext();
|
this.ctx = factory.newSpatialContext();
|
||||||
|
this.maxRadius = 178;
|
||||||
|
((Geo3dShapeFactory)ctx.getShapeFactory()).setCircleAccuracy(1e-6);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@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