From 478e0e1054f4285634b5e02a2278c48bac0daf90 Mon Sep 17 00:00:00 2001 From: Karl Wright Date: Tue, 26 Apr 2016 08:56:53 -0400 Subject: [PATCH] Add GeoPolygonFactory support for the new shape. --- .../spatial3d/geom/GeoPolygonFactory.java | 109 +++++++++++++++++- 1 file changed, 108 insertions(+), 1 deletion(-) diff --git a/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/GeoPolygonFactory.java b/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/GeoPolygonFactory.java index 436b5aca1aa..cd46d4278a8 100755 --- a/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/GeoPolygonFactory.java +++ b/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/GeoPolygonFactory.java @@ -113,7 +113,114 @@ public class GeoPolygonFactory { } throw new IllegalArgumentException("cannot find a point that is inside the polygon "+filteredPointList); } + + /** Use this class to specify a polygon with associated holes. + */ + public static class PolygonDescription { + /** The list of points */ + public final List points; + /** The list of holes */ + public final List holes; + /** Instantiate the polygon description. + * @param points is the list of points. + * @param holes is the list of holes. + */ + public PolygonDescription(final List points, final List 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. + * + * This method does very little checking for legality. It expects the incoming shapes to not intersect + * each other. The shapes can be disjoint or nested. If the shapes listed are nested, then we are describing holes. + * There is no limit to the depth of holes. However, if a shape is nested within another it must be explicitly + * described as being a child of the other shape. + * + * Membership in any given shape is described by the clockwise/counterclockwise direction of the points. The + * clockwise direction indicates that a point inside is "in-set", while a counter-clockwise direction implies that + * a point inside is "out-of-set". + * + * @param planetModel is the planet model. + * @param shapesList is the list of polygons we should be making. + * @return the GeoPolygon, or null if it cannot be constructed. + */ + public static GeoPolygon makeLargeGeoPolygon(final PlanetModel planetModel, + final List shapesList) { + + // We're going to be building a single-level list of shapes in the end, with a single point that we know to be inside/outside, which is + // not on an edge. + + final List> pointsList = new ArrayList<>(); + + List testPointShape = null; + for (final PolygonDescription shape : shapesList) { + // Convert this shape and its holes to a general list of shapes. We also need to identify exactly one + // legal, non-degenerate shape with no children that we can use to find a test point. We also optimize + // to choose as small as possible a polygon for determining the in-set-ness of the test point. + testPointShape = convertPolygon(pointsList, shape, testPointShape); + } + + // If there's no polygon we can use to determine a test point, we throw up. + if (testPointShape == null) { + throw new IllegalArgumentException("couldn't find a non-degenerate polygon for in-set determination"); + } + + // 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++) { + // Pick the next random pole + final GeoPoint pole = pickPole(generator, planetModel, testPointShape); + // Is it inside or outside? + final Boolean isPoleInside = isInsidePolygon(pole, testPointShape); + if (isPoleInside != null) { + // Legal pole + return new GeoComplexPolygon(planetModel, pointsList, pole, isPoleInside); + } + // If pole choice was illegal, try another one + } + throw new IllegalArgumentException("cannot find a point that is inside the polygon "+testPointShape); + + } + + /** Convert a polygon description to a list of shapes. Also locate an optimal shape for evaluating a test point. + * @param pointsList is the structure to add new polygons to. + * @param shape is the current polygon description. + * @param testPointShape is the current best choice for a low-level polygon to evaluate. + * @return an updated best-choice for a test point polygon, and update the points list. + */ + private static List convertPolygon(final List> pointsList, final PolygonDescription shape, List testPointShape) { + // First, remove duplicate points. If degenerate, just ignore the shape. + final List filteredPoints = filterPoints(shape.points); + if (filteredPoints == null) { + return testPointShape; + } + + // Non-degenerate. Check if this is a candidate for in-set determination. + if (shape.holes.size() == 0) { + // This shape is a candidate for a test point. + if (testPointShape == null || testPointShape.size() > filteredPoints.size()) { + testPointShape = filteredPoints; + } + } + + pointsList.add(filteredPoints); + + // Now, do all holes too + for (final PolygonDescription hole : shape.holes) { + testPointShape = convertPolygon(pointsList, hole, testPointShape); + } + + // Done; return the updated test point shape. + return testPointShape; + } + /** * Create a GeoPolygon using the specified points and holes and a test point. * @@ -169,7 +276,7 @@ public class GeoPolygonFactory { * @param input with input list of points * @return the filtered list, or null if we can't get a legit polygon from the input. */ - static List filterPoints(final List input) { + static List filterPoints(final List input) { final List noIdenticalPoints = new ArrayList<>(input.size());