|
@@ -64,18 +64,21 @@ public class GeoPolygonDecomposer {
|
|
|
if (polygon.isEmpty()) {
|
|
|
return;
|
|
|
}
|
|
|
- int numEdges = polygon.getPolygon().length() - 1; // Last point is repeated
|
|
|
+ LinearRing shell = filterRing(polygon.getPolygon());
|
|
|
+ LinearRing[] holes = new LinearRing[polygon.getNumberOfHoles()];
|
|
|
+ int numEdges = shell.length() - 1; // Last point is repeated
|
|
|
for (int i = 0; i < polygon.getNumberOfHoles(); i++) {
|
|
|
- numEdges += polygon.getHole(i).length() - 1;
|
|
|
- validateHole(polygon.getPolygon(), polygon.getHole(i));
|
|
|
+ holes[i] = filterRing(polygon.getHole(i));
|
|
|
+ numEdges += holes[i].length() - 1;
|
|
|
+ validateHole(shell, holes[i]);
|
|
|
}
|
|
|
|
|
|
Edge[] edges = new Edge[numEdges];
|
|
|
- Edge[] holeComponents = new Edge[polygon.getNumberOfHoles()];
|
|
|
+ Edge[] holeComponents = new Edge[holes.length];
|
|
|
final AtomicBoolean translated = new AtomicBoolean(false);
|
|
|
- int offset = createEdges(0, orientation, polygon.getPolygon(), null, edges, 0, translated);
|
|
|
+ int offset = createEdges(0, orientation, shell, null, edges, 0, translated);
|
|
|
for (int i = 0; i < polygon.getNumberOfHoles(); i++) {
|
|
|
- int length = createEdges(i + 1, orientation, polygon.getPolygon(), polygon.getHole(i), edges, offset, translated);
|
|
|
+ int length = createEdges(i + 1, orientation, shell, holes[i], edges, offset, translated);
|
|
|
holeComponents[i] = edges[offset];
|
|
|
offset += length;
|
|
|
}
|
|
@@ -88,6 +91,52 @@ public class GeoPolygonDecomposer {
|
|
|
compose(edges, holeComponents, numHoles, collector);
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * This method removes duplicated points and coplanar points on vertical lines (vertical lines
|
|
|
+ * do not cross the dateline).
|
|
|
+ */
|
|
|
+ private static LinearRing filterRing(LinearRing linearRing) {
|
|
|
+ // first we check if there is anything to filter
|
|
|
+ int numPoints = linearRing.length();
|
|
|
+ int count = 2;
|
|
|
+ for (int i = 1; i < numPoints - 1; i++) {
|
|
|
+ if (linearRing.getLon(i - 1) == linearRing.getLon(i)) {
|
|
|
+ if (linearRing.getLat(i - 1) == linearRing.getLat(i)) {
|
|
|
+ // same point
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ if (linearRing.getLon(i - 1) == linearRing.getLon(i + 1) &&
|
|
|
+ linearRing.getLat(i - 1) > linearRing.getLat(i) != linearRing.getLat(i + 1) > linearRing.getLat(i)) {
|
|
|
+ // coplanar
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ count++;
|
|
|
+ }
|
|
|
+ if (numPoints == count) {
|
|
|
+ return linearRing;
|
|
|
+ }
|
|
|
+ // Second filter the points
|
|
|
+ double[] lons = new double[count];
|
|
|
+ double[] lats = new double[count];
|
|
|
+ lats[0] = lats[count - 1] = linearRing.getLat(0);
|
|
|
+ lons[0] = lons[count - 1] = linearRing.getLon(0);
|
|
|
+ count = 0;
|
|
|
+ for (int i = 1; i < numPoints - 1; i++) {
|
|
|
+ if (linearRing.getLon(i - 1) == linearRing.getLon(i)) {
|
|
|
+ if (linearRing.getLat(i - 1) == linearRing.getLat(i) ||
|
|
|
+ linearRing.getLon(i - 1) == linearRing.getLon(i + 1)) {
|
|
|
+ // filter
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ count++;
|
|
|
+ lats[count] = linearRing.getLat(i);
|
|
|
+ lons[count] = linearRing.getLon(i);
|
|
|
+ }
|
|
|
+ return new LinearRing(lons, lats);
|
|
|
+ }
|
|
|
+
|
|
|
private static void validateHole(LinearRing shell, LinearRing hole) {
|
|
|
Set<Point> exterior = new HashSet<>();
|
|
|
Set<Point> interior = new HashSet<>();
|
|
@@ -328,6 +377,7 @@ public class GeoPolygonDecomposer {
|
|
|
private static int intersections(double dateline, Edge[] edges) {
|
|
|
int numIntersections = 0;
|
|
|
assert !Double.isNaN(dateline);
|
|
|
+ int maxComponent = 0;
|
|
|
for (int i = 0; i < edges.length; i++) {
|
|
|
Point p1 = edges[i].coordinate;
|
|
|
Point p2 = edges[i].next.coordinate;
|
|
@@ -338,12 +388,47 @@ public class GeoPolygonDecomposer {
|
|
|
if (!Double.isNaN(position)) {
|
|
|
edges[i].intersection(position);
|
|
|
numIntersections++;
|
|
|
+ maxComponent = Math.max(maxComponent, edges[i].component);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (maxComponent > 0) {
|
|
|
+ // we might detect polygons touching the dateline as intersections
|
|
|
+ // Here we clean them up
|
|
|
+ for (int i = 0; i < maxComponent; i++) {
|
|
|
+ if (clearComponentTouchingDateline(edges, i + 1)) {
|
|
|
+ numIntersections--;
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
Arrays.sort(edges, INTERSECTION_ORDER);
|
|
|
return numIntersections;
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * Checks the number of dateline intersections detected for a component. If there is only
|
|
|
+ * one, it clears it as it means that the component just touches the dateline.
|
|
|
+ *
|
|
|
+ * @param edges set of edges that may intersect with the dateline
|
|
|
+ * @param component The component to check
|
|
|
+ * @return true if the component touches the dateline.
|
|
|
+ */
|
|
|
+ private static boolean clearComponentTouchingDateline(Edge[] edges, int component) {
|
|
|
+ Edge intersection = null;
|
|
|
+ for (int j = 0; j < edges.length; j++) {
|
|
|
+ if (edges[j].intersect != Edge.MAX_COORDINATE && edges[j].component == component) {
|
|
|
+ if (intersection == null) {
|
|
|
+ intersection = edges[j];
|
|
|
+ } else {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (intersection != null) {
|
|
|
+ intersection.intersect = Edge.MAX_COORDINATE;
|
|
|
+ }
|
|
|
+ return intersection != null;
|
|
|
+ }
|
|
|
+
|
|
|
|
|
|
private static Edge[] edges(Edge[] edges, int numHoles, List<List<Point[]>> components) {
|
|
|
ArrayList<Edge> mainEdges = new ArrayList<>(edges.length);
|