Tessellator: Improve logic when two holes share the same vertex with the polygon (#13980)

This commit tries to improve the algorithm by:

1.- If there is only one vertex, then there is not further checks and that's the one used.

2.- if there is a common vertex, it first compute the signed area of the join, if they have different sign, 
it chooses the negative one as that's the convex union. If they have the same sign, it computes the angle 
of the join and chooses the smallest angle.
This commit is contained in:
Ignacio Vera 2024-11-11 13:28:09 +01:00 committed by GitHub
parent 12ca4779b9
commit 65457224fb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 205 additions and 207 deletions

View File

@ -97,6 +97,8 @@ Bug Fixes
* GITHUB#12686: Added support for highlighting IndexOrDocValuesQuery. (Prudhvi Godithi)
* GITHUB#13927: Fix StoredFieldsConsumer finish. (linfn)
* GITHUB#13944: Ensure deterministic order of clauses for `DisjunctionMaxQuery#toString`. (Laurent Jakubina)
* GITHUB#13841: Improve Tessellatorlogic when two holes share the same vertex with the polygon which was failing
in valid polygons. (Ignacio Vera)
Build
---------------------

View File

@ -20,7 +20,6 @@ import static org.apache.lucene.geo.GeoEncodingUtils.encodeLatitude;
import static org.apache.lucene.geo.GeoEncodingUtils.encodeLongitude;
import static org.apache.lucene.geo.GeoUtils.lineCrossesLine;
import static org.apache.lucene.geo.GeoUtils.lineOverlapLine;
import static org.apache.lucene.geo.GeoUtils.orient;
import java.util.ArrayList;
import java.util.HashMap;
@ -215,7 +214,7 @@ public final class Tessellator {
* Creates a circular doubly linked list using polygon points. The order is governed by the
* specified winding order
*/
private static final Node createDoublyLinkedList(
private static Node createDoublyLinkedList(
final double[] x,
final double[] y,
final WindingOrder polyWindingOrder,
@ -243,7 +242,7 @@ public final class Tessellator {
return filterPoints(lastNode, null);
}
private static final Node eliminateHoles(final XYPolygon polygon, Node outerNode) {
private static Node eliminateHoles(final XYPolygon polygon, Node outerNode) {
// Define a list to hole a reference to each filtered hole list.
final List<Node> holeList = new ArrayList<>();
// keep a reference to the hole
@ -273,8 +272,8 @@ public final class Tessellator {
return eliminateHoles(holeList, holeListPolygons, outerNode);
}
/** Links every hole into the outer loop, producing a single-ring polygon without holes. * */
private static final Node eliminateHoles(final Polygon polygon, Node outerNode) {
/** Links every hole into the outer loop, producing a single-ring polygon without holes. */
private static Node eliminateHoles(final Polygon polygon, Node outerNode) {
// Define a list to hole a reference to each filtered hole list.
final List<Node> holeList = new ArrayList<>();
// keep a reference to the hole
@ -304,7 +303,7 @@ public final class Tessellator {
return eliminateHoles(holeList, holeListPolygons, outerNode);
}
private static final Node eliminateHoles(
private static Node eliminateHoles(
List<Node> holeList, final Map<Node, ?> holeListPolygons, Node outerNode) {
// Sort the hole vertices by x coordinate
holeList.sort(
@ -350,30 +349,19 @@ public final class Tessellator {
}
/** Finds a bridge between vertices that connects a hole with an outer ring, and links it */
private static final void eliminateHole(
private static void eliminateHole(
final Node holeNode,
Node outerNode,
double holeMinX,
double holeMaxX,
double holeMinY,
double holeMaxY) {
// Attempt to find a common point between the HoleNode and OuterNode.
Node next = outerNode;
do {
if (Rectangle.containsPoint(
next.getY(), next.getX(), holeMinY, holeMaxY, holeMinX, holeMaxX)) {
Node sharedVertex = getSharedVertex(holeNode, next);
if (sharedVertex != null) {
// Split the resulting polygon.
Node node = splitPolygon(next, sharedVertex, true);
// Filter the split nodes.
filterPoints(node, node.next);
return;
}
}
next = next.next;
} while (next != outerNode);
// Attempt to merge the hole using a common point between if it exists.
if (maybeMergeHoleWithSharedVertices(
holeNode, outerNode, holeMinX, holeMaxX, holeMinY, holeMaxY)) {
return;
}
// Attempt to find a logical bridge between the HoleNode and OuterNode.
outerNode = fetchHoleBridge(holeNode, outerNode);
@ -390,12 +378,112 @@ public final class Tessellator {
}
}
/**
* Choose a common vertex between the polygon and the hole if it exists and return true, otherwise
* return false
*/
private static boolean maybeMergeHoleWithSharedVertices(
final Node holeNode,
Node outerNode,
double holeMinX,
double holeMaxX,
double holeMinY,
double holeMaxY) {
// Attempt to find a common point between the HoleNode and OuterNode.
Node sharedVertex = null;
Node sharedVertexConnection = null;
Node next = outerNode;
do {
if (Rectangle.containsPoint(
next.getY(), next.getX(), holeMinY, holeMaxY, holeMinX, holeMaxX)) {
Node newSharedVertex = getSharedVertex(holeNode, next);
if (newSharedVertex != null) {
if (sharedVertex == null) {
sharedVertex = newSharedVertex;
sharedVertexConnection = next;
} else if (newSharedVertex.equals(sharedVertex)) {
// This can only happen if this vertex has been already used for a bridge. We need to
// choose the right one.
sharedVertexConnection =
getSharedInsideVertex(sharedVertex, sharedVertexConnection, next);
}
}
}
next = next.next;
} while (next != outerNode);
if (sharedVertex != null) {
// Split the resulting polygon.
Node node = splitPolygon(sharedVertexConnection, sharedVertex, true);
// Filter the split nodes.
filterPoints(node, node.next);
return true;
}
return false;
}
/** Check if the provided vertex is in the polygon and return it */
private static Node getSharedVertex(final Node polygon, final Node vertex) {
Node next = polygon;
do {
if (isVertexEquals(next, vertex)) {
return next;
}
next = next.next;
} while (next != polygon);
return null;
}
/** Choose the vertex that has a smaller angle with the hole vertex */
static Node getSharedInsideVertex(Node holeVertex, Node candidateA, Node candidateB) {
assert isVertexEquals(holeVertex, candidateA) && isVertexEquals(holeVertex, candidateB);
// we are joining candidate.prevNode -> holeVertex.node -> holeVertex.nextNode.
// A negative area means a convex angle. if both are convex/reflex choose the point of
// minimum angle
final double a1 =
area(
candidateA.previous.getX(),
candidateA.previous.getY(),
holeVertex.getX(),
holeVertex.getY(),
holeVertex.next.getX(),
holeVertex.next.getY());
final double a2 =
area(
candidateB.previous.getX(),
candidateB.previous.getY(),
holeVertex.getX(),
holeVertex.getY(),
holeVertex.next.getX(),
holeVertex.next.getY());
if (a1 < 0 != a2 < 0) {
// one is convex, the other reflex, get the convex one
return a1 < a2 ? candidateA : candidateB;
} else {
// both are convex / reflex, choose the smallest angle
final double angle1 = angle(candidateA.previous, candidateA, holeVertex.next);
final double angle2 = angle(candidateB.previous, candidateB, holeVertex.next);
return angle1 < angle2 ? candidateA : candidateB;
}
}
private static double angle(Node a, Node b, Node c) {
final double ax = a.getX() - b.getX();
final double ay = a.getY() - b.getY();
final double cx = c.getX() - b.getX();
final double cy = c.getY() - b.getY();
final double dotProduct = ax * cx + ay * cy;
final double aLength = Math.sqrt(ax * ax + ay * ay);
final double bLength = Math.sqrt(cx * cx + cy * cy);
return Math.acos(dotProduct / (aLength * bLength));
}
/**
* David Eberly's algorithm for finding a bridge between a hole and outer polygon
*
* <p>see: http://www.geometrictools.com/Documentation/TriangulationByEarClipping.pdf
*/
private static final Node fetchHoleBridge(final Node holeNode, final Node outerNode) {
private static Node fetchHoleBridge(final Node holeNode, final Node outerNode) {
Node p = outerNode;
double qx = Double.NEGATIVE_INFINITY;
final double hx = holeNode.getX();
@ -453,34 +541,8 @@ public final class Tessellator {
return connection;
}
/** Check if the provided vertex is in the polygon and return it * */
private static Node getSharedVertex(final Node polygon, final Node vertex) {
Node next = polygon;
do {
if (isVertexEquals(next, vertex)) {
// make sure we are not crossing the polygon. This might happen when several holes share the
// same polygon vertex.
boolean crosses =
GeoUtils.lineCrossesLine(
next.previous.getX(),
next.previous.getY(),
vertex.next.getX(),
vertex.next.getY(),
next.next.getX(),
next.next.getY(),
vertex.previous.getX(),
vertex.previous.getY());
if (crosses == false) {
return next;
}
}
next = next.next;
} while (next != polygon);
return null;
}
/** Finds the left-most hole of a polygon ring. * */
private static final Node fetchLeftmost(final Node start) {
private static Node fetchLeftmost(final Node start) {
Node node = start;
Node leftMost = start;
do {
@ -502,7 +564,7 @@ public final class Tessellator {
* Main ear slicing loop which triangulates the vertices of a polygon, provided as a doubly-linked
* list. *
*/
private static final List<Triangle> earcutLinkedList(
private static List<Triangle> earcutLinkedList(
Object polygon,
Node currEar,
final List<Triangle> tessellation,
@ -587,7 +649,7 @@ public final class Tessellator {
}
/** Determines whether a polygon node forms a valid ear with adjacent nodes. * */
private static final boolean isEar(final Node ear, final boolean mortonOptimized) {
private static boolean isEar(final Node ear, final boolean mortonOptimized) {
if (mortonOptimized == true) {
return mortonIsEar(ear);
}
@ -623,7 +685,7 @@ public final class Tessellator {
* Uses morton code for speed to determine whether or a polygon node forms a valid ear w/ adjacent
* nodes
*/
private static final boolean mortonIsEar(final Node ear) {
private static boolean mortonIsEar(final Node ear) {
// triangle bbox (flip the bits so negative encoded values are < positive encoded values)
int minTX = StrictMath.min(StrictMath.min(ear.previous.x, ear.x), ear.next.x) ^ 0x80000000;
int minTY = StrictMath.min(StrictMath.min(ear.previous.y, ear.y), ear.next.y) ^ 0x80000000;
@ -740,7 +802,7 @@ public final class Tessellator {
}
/** Iterate through all polygon nodes and remove small local self-intersections * */
private static final Node cureLocalIntersections(
private static Node cureLocalIntersections(
Node startNode, final List<Triangle> tessellation, final boolean mortonOptimized) {
Node node = startNode;
Node nextNode;
@ -794,7 +856,7 @@ public final class Tessellator {
* Attempt to split a polygon and independently triangulate each side. Return true if the polygon
* was splitted *
*/
private static final boolean splitEarcut(
private static boolean splitEarcut(
final Object polygon,
final Node start,
final List<Triangle> tessellation,
@ -858,7 +920,7 @@ public final class Tessellator {
* Uses morton code for speed to determine whether or not and edge defined by a and b overlaps
* with a polygon edge
*/
private static final void mortonCheckIntersection(final Node a, final Node b) {
private static void mortonCheckIntersection(final Node a, final Node b) {
// edge bbox (flip the bits so negative encoded values are < positive encoded values)
int minTX = StrictMath.min(a.x, a.next.x) ^ 0x80000000;
int minTY = StrictMath.min(a.y, a.next.y) ^ 0x80000000;
@ -974,7 +1036,7 @@ public final class Tessellator {
* Uses morton code for speed to determine whether or not and edge defined by a and b overlaps
* with a polygon edge
*/
private static final boolean isMortonEdgeFromPolygon(final Node a, final Node b) {
private static boolean isMortonEdgeFromPolygon(final Node a, final Node b) {
// edge bbox (flip the bits so negative encoded values are < positive encoded values)
final int minTX = StrictMath.min(a.x, b.x) ^ 0x80000000;
final int minTY = StrictMath.min(a.y, b.y) ^ 0x80000000;
@ -1060,7 +1122,7 @@ public final class Tessellator {
}
/** Links two polygon vertices using a bridge. * */
private static final Node splitPolygon(final Node a, final Node b, boolean edgeFromPolygon) {
private static Node splitPolygon(final Node a, final Node b, boolean edgeFromPolygon) {
final Node a2 = new Node(a);
final Node b2 = new Node(b);
final Node an = a.next;
@ -1136,7 +1198,7 @@ public final class Tessellator {
return windingSum;
}
private static final boolean isLocallyInside(final Node a, final Node b) {
private static boolean isLocallyInside(final Node a, final Node b) {
double area =
area(
a.previous.getX(), a.previous.getY(), a.getX(), a.getY(), a.next.getX(), a.next.getY());
@ -1156,7 +1218,7 @@ public final class Tessellator {
}
/** Determine whether the middle point of a polygon diagonal is contained within the polygon */
private static final boolean middleInsert(
private static boolean middleInsert(
final Node start, final double x0, final double y0, final double x1, final double y1) {
Node node = start;
Node nextNode;
@ -1179,7 +1241,7 @@ public final class Tessellator {
}
/** Determines if the diagonal of a polygon is intersecting with any polygon elements. * */
private static final boolean isIntersectingPolygon(
private static boolean isIntersectingPolygon(
final Node start, final double x0, final double y0, final double x1, final double y1) {
Node node = start;
Node nextNode;
@ -1198,7 +1260,7 @@ public final class Tessellator {
}
/** Determines whether two line segments intersect. * */
public static final boolean linesIntersect(
public static boolean linesIntersect(
final double aX0,
final double aY0,
final double aX1,
@ -1212,7 +1274,7 @@ public final class Tessellator {
}
/** Interlinks polygon nodes in Z-Order. It reset the values on the z values* */
private static final void sortByMortonWithReset(Node start) {
private static void sortByMortonWithReset(Node start) {
Node next = start;
do {
next.previousZ = next.previous;
@ -1223,7 +1285,7 @@ public final class Tessellator {
}
/** Interlinks polygon nodes in Z-Order. * */
private static final void sortByMorton(Node start) {
private static void sortByMorton(Node start) {
start.previousZ.nextZ = null;
start.previousZ = null;
// Sort the generated ring using Z ordering.
@ -1234,7 +1296,7 @@ public final class Tessellator {
* Simon Tatham's doubly-linked list O(n log n) mergesort see:
* http://www.chiark.greenend.org.uk/~sgtatham/algorithms/listsort.html
*/
private static final void tathamSort(Node list) {
private static void tathamSort(Node list) {
Node p, q, e, tail;
int i, numMerges, pSize, qSize;
int inSize = 1;
@ -1290,7 +1352,7 @@ public final class Tessellator {
}
/** Eliminate colinear/duplicate points from the doubly linked list */
private static final Node filterPoints(final Node start, Node end) {
private static Node filterPoints(final Node start, Node end) {
if (start == null) {
return start;
}
@ -1343,7 +1405,7 @@ public final class Tessellator {
/**
* Creates a node and optionally links it with a previous node in a circular doubly-linked list
*/
private static final Node insertNode(
private static Node insertNode(
final double[] x,
final double[] y,
int index,
@ -1370,7 +1432,7 @@ public final class Tessellator {
}
/** Removes a node from the doubly linked list */
private static final void removeNode(Node node, boolean edgeFromPolygon) {
private static void removeNode(Node node, boolean edgeFromPolygon) {
node.next.previous = node.previous;
node.previous.next = node.next;
node.previous.isNextEdgeFromPolygon = edgeFromPolygon;
@ -1384,16 +1446,16 @@ public final class Tessellator {
}
/** Determines if two point vertices are equal. * */
private static final boolean isVertexEquals(final Node a, final Node b) {
private static boolean isVertexEquals(final Node a, final Node b) {
return isVertexEquals(a, b.getX(), b.getY());
}
/** Determines if two point vertices are equal. * */
private static final boolean isVertexEquals(final Node a, final double x, final double y) {
private static boolean isVertexEquals(final Node a, final double x, final double y) {
return a.getX() == x && a.getY() == y;
}
/** Compute signed area of triangle */
/** Compute signed area of triangle, negative means convex angle and positive reflex angle. */
private static double area(
final double aX,
final double aY,
@ -1419,29 +1481,6 @@ public final class Tessellator {
&& (bx - x) * (cy - y) - (cx - x) * (by - y) >= 0;
}
/** compute whether the given x, y point is in a triangle; uses the winding order method */
public static boolean pointInTriangle(
double x, double y, double ax, double ay, double bx, double by, double cx, double cy) {
double minX = StrictMath.min(ax, StrictMath.min(bx, cx));
double minY = StrictMath.min(ay, StrictMath.min(by, cy));
double maxX = StrictMath.max(ax, StrictMath.max(bx, cx));
double maxY = StrictMath.max(ay, StrictMath.max(by, cy));
// check the bounding box because if the triangle is degenerated, e.g points and lines, we need
// to filter out
// coplanar points that are not part of the triangle.
if (x >= minX && x <= maxX && y >= minY && y <= maxY) {
int a = orient(x, y, ax, ay, bx, by);
int b = orient(x, y, bx, by, cx, cy);
if (a == 0 || b == 0 || a < 0 == b < 0) {
int c = orient(x, y, cx, cy, ax, ay);
return c == 0 || (c < 0 == (b < 0 || a < 0));
}
return false;
} else {
return false;
}
}
/**
* Implementation of this interface will receive calls with internal data at each step of the
* triangulation algorithm. This is of use for debugging complex cases, as well as gaining insight
@ -1508,7 +1547,7 @@ public final class Tessellator {
}
/** Circular Doubly-linked list used for polygon coordinates */
protected static class Node {
static class Node {
// node index in the linked list
private final int idx;
// vertex index in the polygon
@ -1524,9 +1563,9 @@ public final class Tessellator {
private final long morton;
// previous node
private Node previous;
Node previous;
// next node
private Node next;
Node next;
// previous z node
private Node previousZ;
// next z node
@ -1534,7 +1573,7 @@ public final class Tessellator {
// if the edge from this node to the next node is part of the polygon edges
private boolean isNextEdgeFromPolygon;
protected Node(
Node(
final double[] x,
final double[] y,
final int index,
@ -1600,7 +1639,7 @@ public final class Tessellator {
Node[] vertex;
boolean[] edgeFromPolygon;
protected Triangle(
private Triangle(
Node a,
boolean isABfromPolygon,
Node b,
@ -1636,19 +1675,6 @@ public final class Tessellator {
return edgeFromPolygon[startVertex];
}
/** utility method to compute whether the point is in the triangle */
protected boolean containsPoint(double lat, double lon) {
return pointInTriangle(
lon,
lat,
vertex[0].getX(),
vertex[0].getY(),
vertex[1].getX(),
vertex[1].getY(),
vertex[2].getX(),
vertex[2].getY());
}
/** pretty print the triangle vertices */
@Override
public String toString() {

View File

@ -430,11 +430,7 @@ public class TestTessellator extends LuceneTestCase {
+ "(6.9735097 51.6245538,6.9736199 51.624605,6.9736853 51.6246203,6.9737516 51.6246231,6.9738024 51.6246107,6.9738324 51.6245878,6.9738425 51.6245509,6.9738332 51.6245122,6.9738039 51.6244869,6.9737616 51.6244687,6.9737061 51.6244625,6.9736445 51.6244749,6.9735736 51.6245046,6.9735097 51.6245538)),"
+ "((6.9731576 51.6249947,6.9731361 51.6250664,6.9731161 51.6251037,6.9731022 51.6250803,6.9731277 51.62502,6.9731576 51.6249947)))";
Polygon[] polygons = (Polygon[]) SimpleWKTShapeParser.parse(wkt);
for (Polygon polygon : polygons) {
List<Tessellator.Triangle> tessellation =
Tessellator.tessellate(polygon, random().nextBoolean());
assertTrue(tessellation.size() > 0);
}
checkMultiPolygon(polygons, 0.0);
}
public void testComplexPolygon27() throws Exception {
@ -684,13 +680,7 @@ public class TestTessellator extends LuceneTestCase {
public void testComplexPolygon40() throws Exception {
String wkt = GeoTestUtil.readShape("lucene-9251.wkt.gz");
Polygon polygon = (Polygon) SimpleWKTShapeParser.parse(wkt);
List<Tessellator.Triangle> tessellation =
Tessellator.tessellate(polygon, random().nextBoolean());
// calculate the area of big polygons have numerical error
assertEquals(area(polygon), area(tessellation), 1e-12);
for (Tessellator.Triangle t : tessellation) {
checkTriangleEdgesFromPolygon(polygon, t);
}
checkPolygon(polygon, 1e-12);
}
public void testComplexPolygon41() throws Exception {
@ -706,15 +696,7 @@ public class TestTessellator extends LuceneTestCase {
public void testComplexPolygon42() throws Exception {
String geoJson = GeoTestUtil.readShape("lucene-9417.geojson.gz");
Polygon[] polygons = Polygon.fromGeoJSON(geoJson);
for (Polygon polygon : polygons) {
List<Tessellator.Triangle> tessellation =
Tessellator.tessellate(polygon, random().nextBoolean());
// calculate the area of big polygons have numerical error
assertEquals(area(polygon), area(tessellation), 1e-11);
for (Tessellator.Triangle t : tessellation) {
checkTriangleEdgesFromPolygon(polygon, t);
}
}
checkMultiPolygon(polygons, 1e-11);
}
public void testComplexPolygon43() throws Exception {
@ -727,12 +709,7 @@ public class TestTessellator extends LuceneTestCase {
+ "(-88.3245325358123 41.9306419084828,-88.3245478066552 41.9305086556331,-88.3245658060855 41.930351580587,-88.3242368660096 41.9303327977821,-88.3242200926128 41.9304905242189,-88.324206161464 41.9306215207536,-88.3245325358123 41.9306419084828),"
+ "(-88.3236767661893 41.9307089429871,-88.3237008716322 41.930748885445,-88.323876104365 41.9306891087739,-88.324063438129 41.9306252050871,-88.3239244290607 41.930399373909,-88.3237349076233 41.9304653056436,-88.3235653339759 41.9305242981369,-88.3236767661893 41.9307089429871))";
Polygon polygon = (Polygon) SimpleWKTShapeParser.parse(wkt);
List<Tessellator.Triangle> tessellation =
Tessellator.tessellate(polygon, random().nextBoolean());
assertEquals(area(polygon), area(tessellation), 1e-11);
for (Tessellator.Triangle t : tessellation) {
checkTriangleEdgesFromPolygon(polygon, t);
}
checkPolygon(polygon, 1e-11);
}
public void testComplexPolygon44() throws Exception {
@ -748,12 +725,7 @@ public class TestTessellator extends LuceneTestCase {
"Polygon self-intersection at lat=34.21165542666664 lon=-83.88787058666672",
ex.getMessage());
} else {
List<Tessellator.Triangle> tessellation =
Tessellator.tessellate(polygons[i], random().nextBoolean());
assertEquals(area(polygons[i]), area(tessellation), 0.0);
for (Tessellator.Triangle t : tessellation) {
checkTriangleEdgesFromPolygon(polygons[i], t);
}
checkPolygon(polygons[i], 0.0);
}
}
}
@ -761,55 +733,26 @@ public class TestTessellator extends LuceneTestCase {
public void testComplexPolygon45() throws Exception {
String geoJson = GeoTestUtil.readShape("lucene-10470.geojson.gz");
Polygon[] polygons = Polygon.fromGeoJSON(geoJson);
for (Polygon polygon : polygons) {
List<Tessellator.Triangle> tessellation =
Tessellator.tessellate(polygon, random().nextBoolean());
// calculate the area of big polygons have numerical error
assertEquals(area(polygon), area(tessellation), 1e-11);
for (Tessellator.Triangle t : tessellation) {
checkTriangleEdgesFromPolygon(polygon, t);
}
}
checkMultiPolygon(polygons, 1e-11);
}
public void testComplexPolygon46() throws Exception {
String wkt = GeoTestUtil.readShape("lucene-10470.wkt.gz");
Polygon polygon = (Polygon) SimpleWKTShapeParser.parse(wkt);
List<Tessellator.Triangle> tessellation =
Tessellator.tessellate(polygon, random().nextBoolean());
// calculate the area of big polygons have numerical error
assertEquals(area(polygon), area(tessellation), 1e-11);
for (Tessellator.Triangle t : tessellation) {
checkTriangleEdgesFromPolygon(polygon, t);
}
checkPolygon(polygon, 1e-11);
}
public void testComplexPolygon47() throws Exception {
String geoJson = GeoTestUtil.readShape("lucene-10470-2.geojson.gz");
Polygon[] polygons = Polygon.fromGeoJSON(geoJson);
for (Polygon polygon : polygons) {
List<Tessellator.Triangle> tessellation =
Tessellator.tessellate(polygon, random().nextBoolean());
// calculate the area of big polygons have numerical error
assertEquals(area(polygon), area(tessellation), 1e-11);
for (Tessellator.Triangle t : tessellation) {
checkTriangleEdgesFromPolygon(polygon, t);
}
}
checkMultiPolygon(polygons, 1e-11);
}
@Nightly
public void testComplexPolygon48() throws Exception {
String geoJson = GeoTestUtil.readShape("lucene-10470-3.geojson.gz");
Polygon[] polygons = Polygon.fromGeoJSON(geoJson);
for (Polygon polygon : polygons) {
List<Tessellator.Triangle> tessellation = Tessellator.tessellate(polygon, true);
// calculate the area of big polygons have numerical error
assertEquals(area(polygon), area(tessellation), 1e-11);
for (Tessellator.Triangle t : tessellation) {
checkTriangleEdgesFromPolygon(polygon, t);
}
}
checkMultiPolygon(polygons, 1e-11);
}
public void testComplexPolygon49() throws Exception {
@ -817,25 +760,14 @@ public class TestTessellator extends LuceneTestCase {
"POLYGON((77.500 13.500, 77.550 13.500, 77.530 13.470, 77.570 13.470,"
+ "77.550 13.500, 77.600 13.500, 77.600 13.400, 77.500 13.400, 77.500 13.500))";
Polygon polygon = (Polygon) SimpleWKTShapeParser.parse(wkt);
List<Tessellator.Triangle> tessellation =
Tessellator.tessellate(polygon, random().nextBoolean());
assertEquals(area(polygon), area(tessellation), 1e-11);
for (Tessellator.Triangle t : tessellation) {
checkTriangleEdgesFromPolygon(polygon, t);
}
checkPolygon(polygon, 1e-11);
}
public void testComplexPolygon50() throws Exception {
String geoJson = GeoTestUtil.readShape("lucene-10563-1.geojson.gz");
Polygon[] polygons = Polygon.fromGeoJSON(geoJson);
assertEquals("Only one polygon", 1, polygons.length);
Polygon polygon = polygons[0];
List<Tessellator.Triangle> tessellation = Tessellator.tessellate(polygon, true);
// calculate the area of big polygons have numerical error
assertEquals(area(polygon), area(tessellation), 1e-11);
for (Tessellator.Triangle t : tessellation) {
checkTriangleEdgesFromPolygon(polygon, t);
}
checkPolygon(polygons[0], 1e-11);
}
public void testComplexPolygon50_WithMonitor() throws Exception {
@ -893,25 +825,13 @@ public class TestTessellator extends LuceneTestCase {
public void testComplexPolygon53() throws Exception {
String geoJson = GeoTestUtil.readShape("github-11986-1.geojson.gz");
Polygon[] polygons = Polygon.fromGeoJSON(geoJson);
for (Polygon polygon : polygons) {
List<Tessellator.Triangle> tessellation = Tessellator.tessellate(polygon, true);
assertEquals(area(polygon), area(tessellation), 0.0);
for (Tessellator.Triangle t : tessellation) {
checkTriangleEdgesFromPolygon(polygon, t);
}
}
checkMultiPolygon(polygons, 0.0);
}
public void testComplexPolygon54() throws Exception {
String geoJson = GeoTestUtil.readShape("github-11986-2.geojson.gz");
Polygon[] polygons = Polygon.fromGeoJSON(geoJson);
for (Polygon polygon : polygons) {
List<Tessellator.Triangle> tessellation = Tessellator.tessellate(polygon, true);
assertEquals(area(polygon), area(tessellation), 0.0);
for (Tessellator.Triangle t : tessellation) {
checkTriangleEdgesFromPolygon(polygon, t);
}
}
checkMultiPolygon(polygons, 0.0);
}
public void testComplexPolygon55() throws Exception {
@ -936,6 +856,41 @@ public class TestTessellator extends LuceneTestCase {
}
}
public void testComplexPolygon57() throws Exception {
String geoJson = GeoTestUtil.readShape("github-13841-1.geojson.gz");
Polygon[] polygons = Polygon.fromGeoJSON(geoJson);
checkMultiPolygon(polygons, 3e-11);
}
@Nightly
public void testComplexPolygon58() throws Exception {
String wkt = GeoTestUtil.readShape("github-13841-2.wkt.gz");
checkMultiPolygon(wkt);
}
@Nightly
public void testComplexPolygon59() throws Exception {
String wkt = GeoTestUtil.readShape("github-13841-3.wkt.gz");
Polygon[] polygons = (Polygon[]) SimpleWKTShapeParser.parse(wkt);
checkMultiPolygon(polygons, 1e-11);
}
public void testComplexPolygon60() throws Exception {
String wkt =
"POLYGON((0 0, 5 1, 10 0, 11 5, 10 10,5 11, 0 10, 1 5, 0 0),"
+ "(1 5, 1 7, 2 7, 1 5), (1 5, 4 8, 5 8, 1 5),"
+ "(1 5, 3 6, 7 7, 1 5), (1 5, 2 3, 1 3, 1 5),"
+ "(1 5, 3 4, 4 4, 1 5), (1 5, 5 6, 6 6, 1 5),"
+ "(11 5, 10 3, 10 4, 11 5), (11 5,8 3, 8 4, 11 5),"
+ "(11 5,5 4, 5 5, 11 5), (11 5, 4.5 3, 4 3, 11 5),"
+ "(11 5, 8 6, 9 7, 11 5), (11 5, 10 8, 10 7, 11 5),"
+ "(5 11, 2 10, 3 10, 5 11), (5 11, 3 9, 4 9, 5 11),"
+ "(5 11, 5.5 8, 6 7, 5 11), (5 11, 8 8, 9 8, 5 11),"
+ "(5 1, 2 0.5, 3 1, 5 1), (5 1, 8 0.5, 7 2, 5 1),"
+ "(5 1, 3 2, 3 3, 5 1), (5 1, 5 2, 6 2, 5 1))";
checkPolygon(wkt);
}
private static class TestCountingMonitor implements Tessellator.Monitor {
private int count = 0;
private int splitsStarted = 0;
@ -958,11 +913,26 @@ public class TestTessellator extends LuceneTestCase {
}
}
private void checkMultiPolygon(String wkt) throws Exception {
Polygon[] polygons = (Polygon[]) SimpleWKTShapeParser.parse(wkt);
checkMultiPolygon(polygons, 0.0);
}
private void checkMultiPolygon(Polygon[] polygons, double delta) {
for (Polygon polygon : polygons) {
checkPolygon(polygon, delta);
}
}
private void checkPolygon(String wkt) throws Exception {
Polygon polygon = (Polygon) SimpleWKTShapeParser.parse(wkt);
checkPolygon(polygon, 0.0);
}
private void checkPolygon(Polygon polygon, double delta) {
List<Tessellator.Triangle> tessellation =
Tessellator.tessellate(polygon, random().nextBoolean());
assertEquals(area(polygon), area(tessellation), 0.0);
assertEquals(area(polygon), area(tessellation), delta);
for (Tessellator.Triangle t : tessellation) {
checkTriangleEdgesFromPolygon(polygon, t);
}