mirror of https://github.com/apache/lucene.git
LUCENE-8220: Add general makeGeoPolygon variant that decides the best technology for you.
This commit is contained in:
parent
273a829c46
commit
85c182607b
|
@ -36,19 +36,7 @@ public class GeoPolygonFactory {
|
|||
private GeoPolygonFactory() {
|
||||
}
|
||||
|
||||
/** Create a GeoPolygon using the specified points and holes, using order to determine
|
||||
* siding of the polygon. Much like ESRI, this method uses clockwise to indicate the space
|
||||
* on the same side of the shape as being inside, and counter-clockwise to indicate the
|
||||
* space on the opposite side as being inside.
|
||||
* @param pointList is a list of the GeoPoints to build an arbitrary polygon out of. If points go
|
||||
* clockwise from a given pole, then that pole should be within the polygon. If points go
|
||||
* counter-clockwise, then that pole should be outside the polygon.
|
||||
* @return a GeoPolygon corresponding to what was specified.
|
||||
*/
|
||||
public static GeoPolygon makeGeoPolygon(final PlanetModel planetModel,
|
||||
final List<GeoPoint> pointList) {
|
||||
return makeGeoPolygon(planetModel, pointList, null);
|
||||
}
|
||||
private static final int SMALL_POLYGON_CUTOFF_EDGES = 100;
|
||||
|
||||
/** Create a GeoConcavePolygon using the specified points. The polygon must have
|
||||
* a maximum extent larger than PI. The siding of the polygon is chosen so that any
|
||||
|
@ -78,24 +66,6 @@ public class GeoPolygonFactory {
|
|||
return new GeoConvexPolygon(planetModel, pointList);
|
||||
}
|
||||
|
||||
/** Create a GeoPolygon using the specified points and holes, using order to determine
|
||||
* siding of the polygon. Much like ESRI, this method uses clockwise to indicate the space
|
||||
* on the same side of the shape as being inside, and counter-clockwise to indicate the
|
||||
* space on the opposite side as being inside.
|
||||
* @param pointList is a list of the GeoPoints to build an arbitrary polygon out of. If points go
|
||||
* clockwise from a given pole, then that pole should be within the polygon. If points go
|
||||
* counter-clockwise, then that pole should be outside the polygon.
|
||||
* @param holes is a list of polygons representing "holes" in the outside polygon. Holes describe the area outside
|
||||
* each hole as being "in set". Null == none.
|
||||
* @return a GeoPolygon corresponding to what was specified, or null if a valid polygon cannot be generated
|
||||
* from this input.
|
||||
*/
|
||||
public static GeoPolygon makeGeoPolygon(final PlanetModel planetModel,
|
||||
final List<GeoPoint> pointList,
|
||||
final List<GeoPolygon> holes) {
|
||||
return makeGeoPolygon(planetModel, pointList, holes, 0.0);
|
||||
}
|
||||
|
||||
|
||||
/** Create a GeoConcavePolygon using the specified points and holes. The polygon must have
|
||||
* a maximum extent larger than PI. The siding of the polygon is chosen so that any adjacent
|
||||
|
@ -131,6 +101,163 @@ public class GeoPolygonFactory {
|
|||
return new GeoConvexPolygon(planetModel,pointList, holes);
|
||||
}
|
||||
|
||||
/** Use this class to specify a polygon with associated holes.
|
||||
*/
|
||||
public static class PolygonDescription {
|
||||
/** The list of points */
|
||||
public final List<? extends GeoPoint> points;
|
||||
/** The list of holes */
|
||||
public final List<? extends PolygonDescription> holes;
|
||||
|
||||
/** Instantiate the polygon description.
|
||||
* @param points is the list of points.
|
||||
*/
|
||||
public PolygonDescription(final List<? extends GeoPoint> points) {
|
||||
this(points, new ArrayList<>());
|
||||
}
|
||||
|
||||
/** Instantiate the polygon description.
|
||||
* @param points is the list of points.
|
||||
* @param holes is the list of holes.
|
||||
*/
|
||||
public PolygonDescription(final List<? extends GeoPoint> points, final List<? extends PolygonDescription> holes) {
|
||||
this.points = points;
|
||||
this.holes = holes;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/** Create a GeoPolygon using the specified points and holes, using order to determine
|
||||
* siding of the polygon. Much like ESRI, this method uses clockwise to indicate the space
|
||||
* on the same side of the shape as being inside, and counter-clockwise to indicate the
|
||||
* space on the opposite side as being inside.
|
||||
* @param description describes the polygon and its associated holes. If points go
|
||||
* clockwise from a given pole, then that pole should be within the polygon. If points go
|
||||
* counter-clockwise, then that pole should be outside the polygon.
|
||||
* @return a GeoPolygon corresponding to what was specified, or null if a valid polygon cannot be generated
|
||||
* from this input.
|
||||
*/
|
||||
public static GeoPolygon makeGeoPolygon(final PlanetModel planetModel,
|
||||
final PolygonDescription description) {
|
||||
return makeGeoPolygon(planetModel, description, 0.0);
|
||||
}
|
||||
|
||||
/** Create a GeoPolygon using the specified points and holes, using order to determine
|
||||
* siding of the polygon. Much like ESRI, this method uses clockwise to indicate the space
|
||||
* on the same side of the shape as being inside, and counter-clockwise to indicate the
|
||||
* space on the opposite side as being inside.
|
||||
* @param description describes the polygon and its associated holes. If points go
|
||||
* clockwise from a given pole, then that pole should be within the polygon. If points go
|
||||
* counter-clockwise, then that pole should be outside the polygon.
|
||||
* @param leniencyValue is the maximum distance (in units) that a point can be from the plane and still be considered as
|
||||
* belonging to the plane. Any value greater than zero may cause some of the provided points that are in fact outside
|
||||
* the strict definition of co-planarity, but are within this distance, to be discarded for the purposes of creating a
|
||||
* "safe" polygon.
|
||||
* @return a GeoPolygon corresponding to what was specified, or null if a valid polygon cannot be generated
|
||||
* from this input.
|
||||
*/
|
||||
public static GeoPolygon makeGeoPolygon(final PlanetModel planetModel,
|
||||
final PolygonDescription description,
|
||||
final double leniencyValue) {
|
||||
|
||||
// First, convert the holes to polygons in their own right.
|
||||
final List<GeoPolygon> holes;
|
||||
if (description.holes != null && description.holes.size() > 0) {
|
||||
holes = new ArrayList<>(description.holes.size());
|
||||
for (final PolygonDescription holeDescription : description.holes) {
|
||||
final GeoPolygon gp = makeGeoPolygon(planetModel, holeDescription, leniencyValue);
|
||||
if (gp == null) {
|
||||
return null;
|
||||
}
|
||||
holes.add(gp);
|
||||
}
|
||||
} else {
|
||||
holes = null;
|
||||
}
|
||||
|
||||
// First, exercise a sanity filter on the provided pointList, and remove identical points, linear points, and backtracks
|
||||
//System.err.println(" filtering "+pointList.size()+" points...");
|
||||
//final long startTime = System.currentTimeMillis();
|
||||
final List<GeoPoint> firstFilteredPointList = filterPoints(description.points);
|
||||
if (firstFilteredPointList == null) {
|
||||
return null;
|
||||
}
|
||||
final List<GeoPoint> filteredPointList = filterEdges(firstFilteredPointList, leniencyValue);
|
||||
//System.err.println(" ...done in "+(System.currentTimeMillis()-startTime)+"ms ("+((filteredPointList==null)?"degenerate":(filteredPointList.size()+" points"))+")");
|
||||
if (filteredPointList == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (filteredPointList.size() <= SMALL_POLYGON_CUTOFF_EDGES) {
|
||||
try {
|
||||
//First approximation to find a point
|
||||
final GeoPoint centerOfMass = getCenterOfMass(planetModel, filteredPointList);
|
||||
final Boolean isCenterOfMassInside = isInsidePolygon(centerOfMass, filteredPointList);
|
||||
if (isCenterOfMassInside != null) {
|
||||
return generateGeoPolygon(planetModel, filteredPointList, holes, centerOfMass, isCenterOfMassInside);
|
||||
}
|
||||
|
||||
//System.err.println("points="+pointList);
|
||||
// Create a random number generator. Effectively this furnishes us with a repeatable sequence
|
||||
// of points to use for poles.
|
||||
final Random generator = new Random(1234);
|
||||
for (int counter = 0; counter < 1000000; counter++) {
|
||||
//counter++;
|
||||
// Pick the next random pole
|
||||
final GeoPoint pole = pickPole(generator, planetModel, filteredPointList);
|
||||
// Is it inside or outside?
|
||||
final Boolean isPoleInside = isInsidePolygon(pole, filteredPointList);
|
||||
if (isPoleInside != null) {
|
||||
// Legal pole
|
||||
//System.out.println("Took "+counter+" iterations to find pole");
|
||||
//System.out.println("Pole = "+pole+"; isInside="+isPoleInside+"; pointList = "+pointList);
|
||||
return generateGeoPolygon(planetModel, filteredPointList, holes, pole, isPoleInside);
|
||||
}
|
||||
// If pole choice was illegal, try another one
|
||||
}
|
||||
throw new IllegalArgumentException("cannot find a point that is inside the polygon "+filteredPointList);
|
||||
} catch (TileException e) {
|
||||
// Couldn't tile the polygon; use GeoComplexPolygon instead, if we can.
|
||||
}
|
||||
}
|
||||
// Fallback: create large geo polygon, using complex polygon logic.
|
||||
final List<PolygonDescription> pd = new ArrayList<>(1);
|
||||
pd.add(description);
|
||||
return makeLargeGeoPolygon(planetModel, pd);
|
||||
}
|
||||
|
||||
/** Create a GeoPolygon using the specified points and holes, using order to determine
|
||||
* siding of the polygon. Much like ESRI, this method uses clockwise to indicate the space
|
||||
* on the same side of the shape as being inside, and counter-clockwise to indicate the
|
||||
* space on the opposite side as being inside.
|
||||
* @param pointList is a list of the GeoPoints to build an arbitrary polygon out of. If points go
|
||||
* clockwise from a given pole, then that pole should be within the polygon. If points go
|
||||
* counter-clockwise, then that pole should be outside the polygon.
|
||||
* @return a GeoPolygon corresponding to what was specified.
|
||||
*/
|
||||
public static GeoPolygon makeGeoPolygon(final PlanetModel planetModel,
|
||||
final List<GeoPoint> pointList) {
|
||||
return makeGeoPolygon(planetModel, pointList, null);
|
||||
}
|
||||
|
||||
/** Create a GeoPolygon using the specified points and holes, using order to determine
|
||||
* siding of the polygon. Much like ESRI, this method uses clockwise to indicate the space
|
||||
* on the same side of the shape as being inside, and counter-clockwise to indicate the
|
||||
* space on the opposite side as being inside.
|
||||
* @param pointList is a list of the GeoPoints to build an arbitrary polygon out of. If points go
|
||||
* clockwise from a given pole, then that pole should be within the polygon. If points go
|
||||
* counter-clockwise, then that pole should be outside the polygon.
|
||||
* @param holes is a list of polygons representing "holes" in the outside polygon. Holes describe the area outside
|
||||
* each hole as being "in set". Null == none.
|
||||
* @return a GeoPolygon corresponding to what was specified, or null if a valid polygon cannot be generated
|
||||
* from this input.
|
||||
*/
|
||||
public static GeoPolygon makeGeoPolygon(final PlanetModel planetModel,
|
||||
final List<GeoPoint> pointList,
|
||||
final List<GeoPolygon> holes) {
|
||||
return makeGeoPolygon(planetModel, pointList, holes, 0.0);
|
||||
}
|
||||
|
||||
/** Create a GeoPolygon using the specified points and holes, using order to determine
|
||||
* siding of the polygon. Much like ESRI, this method uses clockwise to indicate the space
|
||||
* on the same side of the shape as being inside, and counter-clockwise to indicate the
|
||||
|
@ -220,32 +347,6 @@ public class GeoPolygonFactory {
|
|||
return planetModel.createSurfacePoint(x, y, z);
|
||||
}
|
||||
|
||||
/** Use this class to specify a polygon with associated holes.
|
||||
*/
|
||||
public static class PolygonDescription {
|
||||
/** The list of points */
|
||||
public final List<? extends GeoPoint> points;
|
||||
/** The list of holes */
|
||||
public final List<? extends PolygonDescription> holes;
|
||||
|
||||
/** Instantiate the polygon description.
|
||||
* @param points is the list of points.
|
||||
*/
|
||||
public PolygonDescription(final List<? extends GeoPoint> points) {
|
||||
this(points, new ArrayList<>());
|
||||
}
|
||||
|
||||
/** Instantiate the polygon description.
|
||||
* @param points is the list of points.
|
||||
* @param holes is the list of holes.
|
||||
*/
|
||||
public PolygonDescription(final List<? extends GeoPoint> points, final List<? extends PolygonDescription> holes) {
|
||||
this.points = points;
|
||||
this.holes = holes;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/** Create a large GeoPolygon. This is one which has more than 100 sides and/or may have resolution problems
|
||||
* with very closely spaced points, which often occurs when the polygon was constructed to approximate curves. No tiling
|
||||
* is done, and intersections and membership are optimized for having large numbers of sides.
|
||||
|
|
|
@ -121,7 +121,8 @@ public class GeoPolygonTest {
|
|||
points.add(new GeoPoint(PlanetModel.SPHERE, 0.1, -0.5));
|
||||
points.add(new GeoPoint(PlanetModel.SPHERE, 0.0, -0.4));
|
||||
|
||||
c = GeoPolygonFactory.makeGeoPolygon(PlanetModel.SPHERE, points);
|
||||
GeoPolygonFactory.PolygonDescription pd = new GeoPolygonFactory.PolygonDescription(points);
|
||||
c = GeoPolygonFactory.makeGeoPolygon(PlanetModel.SPHERE, pd);
|
||||
//System.out.println(c);
|
||||
|
||||
// Middle point should NOT be within!!
|
||||
|
@ -129,7 +130,7 @@ public class GeoPolygonTest {
|
|||
assertTrue(!c.isWithin(gp));
|
||||
|
||||
shapes = new ArrayList<>();
|
||||
shapes.add(new GeoPolygonFactory.PolygonDescription(points));
|
||||
shapes.add(pd);
|
||||
|
||||
c = GeoPolygonFactory.makeLargeGeoPolygon(PlanetModel.SPHERE, shapes);
|
||||
assertTrue(!c.isWithin(gp));
|
||||
|
@ -141,7 +142,8 @@ public class GeoPolygonTest {
|
|||
points.add(new GeoPoint(PlanetModel.SPHERE, 0.0, -0.6));
|
||||
points.add(new GeoPoint(PlanetModel.SPHERE, -0.1, -0.5));
|
||||
|
||||
c = GeoPolygonFactory.makeGeoPolygon(PlanetModel.SPHERE, points);
|
||||
pd = new GeoPolygonFactory.PolygonDescription(points);
|
||||
c = GeoPolygonFactory.makeGeoPolygon(PlanetModel.SPHERE, pd);
|
||||
//System.out.println(c);
|
||||
|
||||
// Middle point should be within!!
|
||||
|
@ -149,7 +151,7 @@ public class GeoPolygonTest {
|
|||
assertTrue(c.isWithin(gp));
|
||||
|
||||
shapes = new ArrayList<>();
|
||||
shapes.add(new GeoPolygonFactory.PolygonDescription(points));
|
||||
shapes.add(pd);
|
||||
|
||||
c = GeoPolygonFactory.makeLargeGeoPolygon(PlanetModel.SPHERE, shapes);
|
||||
assertTrue(c.isWithin(gp));
|
||||
|
@ -170,7 +172,9 @@ public class GeoPolygonTest {
|
|||
points.add(new GeoPoint(PlanetModel.SPHERE, 0.0, -0.6));
|
||||
points.add(new GeoPoint(PlanetModel.SPHERE, -0.1, -0.5));
|
||||
|
||||
c = GeoPolygonFactory.makeGeoPolygon(PlanetModel.SPHERE, points);
|
||||
GeoPolygonFactory.PolygonDescription pd = new GeoPolygonFactory.PolygonDescription(points);
|
||||
|
||||
c = GeoPolygonFactory.makeGeoPolygon(PlanetModel.SPHERE, pd);
|
||||
|
||||
xyzBounds = new XYZBounds();
|
||||
c.getBounds(xyzBounds);
|
||||
|
@ -180,7 +184,7 @@ public class GeoPolygonTest {
|
|||
assertEquals(GeoArea.DISJOINT, xyzSolid.getRelationship(c));
|
||||
|
||||
shapes = new ArrayList<>();
|
||||
shapes.add(new GeoPolygonFactory.PolygonDescription(points));
|
||||
shapes.add(pd);
|
||||
|
||||
c = GeoPolygonFactory.makeLargeGeoPolygon(PlanetModel.SPHERE, shapes);
|
||||
|
||||
|
@ -242,9 +246,10 @@ public class GeoPolygonTest {
|
|||
gp = new GeoPoint(PlanetModel.SPHERE, 0.0, Math.PI);
|
||||
assertFalse(c.isWithin(gp));
|
||||
|
||||
GeoPolygonFactory.PolygonDescription pd = new GeoPolygonFactory.PolygonDescription(points);
|
||||
// Now, same thing for large polygon
|
||||
shapes = new ArrayList<>();
|
||||
shapes.add(new GeoPolygonFactory.PolygonDescription(points));
|
||||
shapes.add(pd);
|
||||
|
||||
c = GeoPolygonFactory.makeLargeGeoPolygon(PlanetModel.SPHERE, shapes);
|
||||
|
||||
|
@ -287,6 +292,7 @@ public class GeoPolygonTest {
|
|||
points.add(new GeoPoint(PlanetModel.SPHERE, -0.01, -0.6));
|
||||
points.add(new GeoPoint(PlanetModel.SPHERE, -0.1, -0.5));
|
||||
|
||||
pd = new GeoPolygonFactory.PolygonDescription(points);
|
||||
/*
|
||||
System.out.println("Points: ");
|
||||
for (GeoPoint p : points) {
|
||||
|
@ -294,7 +300,7 @@ public class GeoPolygonTest {
|
|||
}
|
||||
*/
|
||||
|
||||
c = GeoPolygonFactory.makeGeoPolygon(PlanetModel.SPHERE, points);
|
||||
c = GeoPolygonFactory.makeGeoPolygon(PlanetModel.SPHERE, pd);
|
||||
// Sample some points within
|
||||
gp = new GeoPoint(PlanetModel.SPHERE, 0.0, -0.5);
|
||||
assertTrue(c.isWithin(gp));
|
||||
|
@ -325,7 +331,7 @@ public class GeoPolygonTest {
|
|||
|
||||
// Now, same thing for large polygon
|
||||
shapes = new ArrayList<>();
|
||||
shapes.add(new GeoPolygonFactory.PolygonDescription(points));
|
||||
shapes.add(pd);
|
||||
|
||||
c = GeoPolygonFactory.makeLargeGeoPolygon(PlanetModel.SPHERE, shapes);
|
||||
// Sample some points within
|
||||
|
@ -996,7 +1002,7 @@ shape:
|
|||
}
|
||||
|
||||
@Test
|
||||
public void testConcavePolygonWithHole() {
|
||||
public void testPolygonWithHole() {
|
||||
ArrayList<GeoPoint> points = new ArrayList<>();
|
||||
points.add(new GeoPoint(PlanetModel.SPHERE, -1.1, -1.5));
|
||||
points.add(new GeoPoint(PlanetModel.SPHERE, 1.0, -1.6));
|
||||
|
@ -1007,11 +1013,36 @@ shape:
|
|||
hole_points.add(new GeoPoint(PlanetModel.SPHERE, 0.0, -0.6));
|
||||
hole_points.add(new GeoPoint(PlanetModel.SPHERE, 0.1, -0.5));
|
||||
hole_points.add(new GeoPoint(PlanetModel.SPHERE, 0.0, -0.4));
|
||||
GeoPolygon hole = GeoPolygonFactory.makeGeoPolygon(PlanetModel.SPHERE,hole_points);
|
||||
|
||||
GeoPolygon polygon = ((GeoCompositePolygon)GeoPolygonFactory.makeGeoPolygon(PlanetModel.SPHERE, points,Collections.singletonList(hole))).getShape(0);
|
||||
GeoPolygon polygon2 = GeoPolygonFactory.makeGeoConcavePolygon(PlanetModel.SPHERE,points,Collections.singletonList(hole));
|
||||
assertEquals(polygon,polygon2);
|
||||
GeoPolygonFactory.PolygonDescription holeDescription = new GeoPolygonFactory.PolygonDescription(hole_points);
|
||||
List<GeoPolygonFactory.PolygonDescription> holes = new ArrayList<>(1);
|
||||
holes.add(holeDescription);
|
||||
GeoPolygonFactory.PolygonDescription polygonDescription = new GeoPolygonFactory.PolygonDescription(points, holes);
|
||||
|
||||
// Create two polygons -- one simple, the other complex. Both have holes. Compare their behavior.
|
||||
GeoPolygon holeSimplePolygon = GeoPolygonFactory.makeGeoPolygon(PlanetModel.SPHERE,polygonDescription);
|
||||
List<GeoPolygonFactory.PolygonDescription> polys = new ArrayList<>(1);
|
||||
polys.add(polygonDescription);
|
||||
GeoPolygon holeComplexPolygon = GeoPolygonFactory.makeLargeGeoPolygon(PlanetModel.SPHERE,polys);
|
||||
|
||||
// Sample some nearby points outside
|
||||
GeoPoint gp;
|
||||
gp = new GeoPoint(PlanetModel.SPHERE, 0.0, -0.65);
|
||||
assertEquals(holeSimplePolygon.isWithin(gp), holeComplexPolygon.isWithin(gp));
|
||||
gp = new GeoPoint(PlanetModel.SPHERE, 0.0, -0.35);
|
||||
assertEquals(holeSimplePolygon.isWithin(gp), holeComplexPolygon.isWithin(gp));
|
||||
gp = new GeoPoint(PlanetModel.SPHERE, -0.15, -0.5);
|
||||
assertEquals(holeSimplePolygon.isWithin(gp), holeComplexPolygon.isWithin(gp));
|
||||
gp = new GeoPoint(PlanetModel.SPHERE, 0.15, -0.5);
|
||||
assertEquals(holeSimplePolygon.isWithin(gp), holeComplexPolygon.isWithin(gp));
|
||||
// Random points outside
|
||||
gp = new GeoPoint(PlanetModel.SPHERE, 0.0, 0.0);
|
||||
assertEquals(holeSimplePolygon.isWithin(gp), holeComplexPolygon.isWithin(gp));
|
||||
gp = new GeoPoint(PlanetModel.SPHERE, Math.PI * 0.5, 0.0);
|
||||
assertEquals(holeSimplePolygon.isWithin(gp), holeComplexPolygon.isWithin(gp));
|
||||
gp = new GeoPoint(PlanetModel.SPHERE, 0.0, Math.PI);
|
||||
assertEquals(holeSimplePolygon.isWithin(gp), holeComplexPolygon.isWithin(gp));
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
Loading…
Reference in New Issue