diff --git a/lucene/CHANGES.txt b/lucene/CHANGES.txt index 5a57a042f5e..0cf9a191f74 100644 --- a/lucene/CHANGES.txt +++ b/lucene/CHANGES.txt @@ -127,6 +127,9 @@ Improvements * LUCENE-8952: Use a sort key instead of true distance in NearestNeighbor (Julie Tibshirani). +* LUCENE-8620: Tessellator labels the edges of the generated triangles whether they belong to + the original polygon. This information is added to the triangle encoding. (Ignacio Vera) + Optimizations * LUCENE-8922: DisjunctionMaxQuery more efficiently leverages impacts to skip diff --git a/lucene/sandbox/src/java/org/apache/lucene/document/LatLonShapeBoundingBoxQuery.java b/lucene/sandbox/src/java/org/apache/lucene/document/LatLonShapeBoundingBoxQuery.java index 5645645da26..3097ca63fdc 100644 --- a/lucene/sandbox/src/java/org/apache/lucene/document/LatLonShapeBoundingBoxQuery.java +++ b/lucene/sandbox/src/java/org/apache/lucene/document/LatLonShapeBoundingBoxQuery.java @@ -47,16 +47,16 @@ final class LatLonShapeBoundingBoxQuery extends ShapeQuery { /** returns true if the query matches the encoded triangle */ @Override - protected boolean queryMatches(byte[] t, int[] scratchTriangle, QueryRelation queryRelation) { + protected boolean queryMatches(byte[] t, ShapeField.DecodedTriangle scratchTriangle, QueryRelation queryRelation) { // decode indexed triangle ShapeField.decodeTriangle(t, scratchTriangle); - int aY = scratchTriangle[0]; - int aX = scratchTriangle[1]; - int bY = scratchTriangle[2]; - int bX = scratchTriangle[3]; - int cY = scratchTriangle[4]; - int cX = scratchTriangle[5]; + int aY = scratchTriangle.aY; + int aX = scratchTriangle.aX; + int bY = scratchTriangle.bY; + int bX = scratchTriangle.bX; + int cY = scratchTriangle.cY; + int cX = scratchTriangle.cX; if (queryRelation == QueryRelation.WITHIN) { return rectangle2D.containsTriangle(aX, aY, bX, bY, cX, cY); diff --git a/lucene/sandbox/src/java/org/apache/lucene/document/LatLonShapeLineQuery.java b/lucene/sandbox/src/java/org/apache/lucene/document/LatLonShapeLineQuery.java index 93705650e3d..5b2bdea4da2 100644 --- a/lucene/sandbox/src/java/org/apache/lucene/document/LatLonShapeLineQuery.java +++ b/lucene/sandbox/src/java/org/apache/lucene/document/LatLonShapeLineQuery.java @@ -84,15 +84,15 @@ final class LatLonShapeLineQuery extends ShapeQuery { } @Override - protected boolean queryMatches(byte[] t, int[] scratchTriangle, QueryRelation queryRelation) { + protected boolean queryMatches(byte[] t, ShapeField.DecodedTriangle scratchTriangle, QueryRelation queryRelation) { ShapeField.decodeTriangle(t, scratchTriangle); - double alat = GeoEncodingUtils.decodeLatitude(scratchTriangle[0]); - double alon = GeoEncodingUtils.decodeLongitude(scratchTriangle[1]); - double blat = GeoEncodingUtils.decodeLatitude(scratchTriangle[2]); - double blon = GeoEncodingUtils.decodeLongitude(scratchTriangle[3]); - double clat = GeoEncodingUtils.decodeLatitude(scratchTriangle[4]); - double clon = GeoEncodingUtils.decodeLongitude(scratchTriangle[5]); + double alat = GeoEncodingUtils.decodeLatitude(scratchTriangle.aY); + double alon = GeoEncodingUtils.decodeLongitude(scratchTriangle.aX); + double blat = GeoEncodingUtils.decodeLatitude(scratchTriangle.bY); + double blon = GeoEncodingUtils.decodeLongitude(scratchTriangle.bX); + double clat = GeoEncodingUtils.decodeLatitude(scratchTriangle.cY); + double clon = GeoEncodingUtils.decodeLongitude(scratchTriangle.cX); if (queryRelation == QueryRelation.WITHIN) { return line2D.relateTriangle(alon, alat, blon, blat, clon, clat) == Relation.CELL_INSIDE_QUERY; diff --git a/lucene/sandbox/src/java/org/apache/lucene/document/LatLonShapePolygonQuery.java b/lucene/sandbox/src/java/org/apache/lucene/document/LatLonShapePolygonQuery.java index bcdd3ae5e45..38129a3e2fd 100644 --- a/lucene/sandbox/src/java/org/apache/lucene/document/LatLonShapePolygonQuery.java +++ b/lucene/sandbox/src/java/org/apache/lucene/document/LatLonShapePolygonQuery.java @@ -78,15 +78,15 @@ final class LatLonShapePolygonQuery extends ShapeQuery { } @Override - protected boolean queryMatches(byte[] t, int[] scratchTriangle, QueryRelation queryRelation) { + protected boolean queryMatches(byte[] t, ShapeField.DecodedTriangle scratchTriangle, QueryRelation queryRelation) { ShapeField.decodeTriangle(t, scratchTriangle); - double alat = GeoEncodingUtils.decodeLatitude(scratchTriangle[0]); - double alon = GeoEncodingUtils.decodeLongitude(scratchTriangle[1]); - double blat = GeoEncodingUtils.decodeLatitude(scratchTriangle[2]); - double blon = GeoEncodingUtils.decodeLongitude(scratchTriangle[3]); - double clat = GeoEncodingUtils.decodeLatitude(scratchTriangle[4]); - double clon = GeoEncodingUtils.decodeLongitude(scratchTriangle[5]); + double alat = GeoEncodingUtils.decodeLatitude(scratchTriangle.aY); + double alon = GeoEncodingUtils.decodeLongitude(scratchTriangle.aX); + double blat = GeoEncodingUtils.decodeLatitude(scratchTriangle.bY); + double blon = GeoEncodingUtils.decodeLongitude(scratchTriangle.bX); + double clat = GeoEncodingUtils.decodeLatitude(scratchTriangle.cY); + double clon = GeoEncodingUtils.decodeLongitude(scratchTriangle.cX); if (queryRelation == QueryRelation.WITHIN) { return poly2D.relateTriangle(alon, alat, blon, blat, clon, clat) == Relation.CELL_INSIDE_QUERY; diff --git a/lucene/sandbox/src/java/org/apache/lucene/document/ShapeField.java b/lucene/sandbox/src/java/org/apache/lucene/document/ShapeField.java index e4e9eaa04fe..5e2279e28d2 100644 --- a/lucene/sandbox/src/java/org/apache/lucene/document/ShapeField.java +++ b/lucene/sandbox/src/java/org/apache/lucene/document/ShapeField.java @@ -16,6 +16,8 @@ */ package org.apache.lucene.document; +import java.util.Objects; + import org.apache.lucene.geo.GeoUtils; import org.apache.lucene.geo.Line; import org.apache.lucene.geo.Polygon; @@ -57,18 +59,22 @@ public final class ShapeField { */ public static class Triangle extends Field { + // constructor for points and lines Triangle(String name, int aXencoded, int aYencoded, int bXencoded, int bYencoded, int cXencoded, int cYencoded) { super(name, TYPE); - setTriangleValue(aXencoded, aYencoded, bXencoded, bYencoded, cXencoded, cYencoded); + setTriangleValue(aXencoded, aYencoded, true, bXencoded, bYencoded, true, cXencoded, cYencoded, true); } + Triangle(String name, Tessellator.Triangle t) { super(name, TYPE); - setTriangleValue(t.getEncodedX(0), t.getEncodedY(0), t.getEncodedX(1), t.getEncodedY(1), t.getEncodedX(2), t.getEncodedY(2)); + setTriangleValue(t.getEncodedX(0), t.getEncodedY(0), t.isEdgefromPolygon(0), + t.getEncodedX(1), t.getEncodedY(1), t.isEdgefromPolygon(1), + t.getEncodedX(2), t.getEncodedY(2), t.isEdgefromPolygon(2)); } /** sets the vertices of the triangle as integer encoded values */ - protected void setTriangleValue(int aX, int aY, int bX, int bY, int cX, int cY) { + protected void setTriangleValue(int aX, int aY, boolean abFromShape, int bX, int bY, boolean bcFromShape, int cX, int cY, boolean caFromShape) { final byte[] bytes; if (fieldsData == null) { @@ -77,7 +83,7 @@ public final class ShapeField { } else { bytes = ((BytesRef) fieldsData).bytes; } - encodeTriangle(bytes, aY, aX, bY, bX, cY, cX); + encodeTriangle(bytes, aY, aX, abFromShape, bY, bX, bcFromShape, cY, cX, caFromShape); } } @@ -100,7 +106,7 @@ public final class ShapeField { * Triangles are encoded with CCW orientation and might be rotated to limit the number of possible reconstructions to 2^3. * Reconstruction always happens from west to east. */ - public static void encodeTriangle(byte[] bytes, int aLat, int aLon, int bLat, int bLon, int cLat, int cLon) { + public static void encodeTriangle(byte[] bytes, int aLat, int aLon, boolean abFromShape, int bLat, int bLon, boolean bcFromShape, int cLat, int cLon, boolean caFromShape) { assert bytes.length == 7 * BYTES; int aX; int bX; @@ -108,6 +114,7 @@ public final class ShapeField { int aY; int bY; int cY; + boolean ab, bc, ca; //change orientation if CW if (GeoUtils.orient(aLon, aLat, bLon, bLat, cLon, cLat) == -1) { aX = cLon; @@ -116,6 +123,9 @@ public final class ShapeField { aY = cLat; bY = bLat; cY = aLat; + ab = bcFromShape; + bc = abFromShape; + ca = caFromShape; } else { aX = aLon; bX = bLon; @@ -123,27 +133,38 @@ public final class ShapeField { aY = aLat; bY = bLat; cY = cLat; + ab = abFromShape; + bc = bcFromShape; + ca = caFromShape; } //rotate edges and place minX at the beginning if (bX < aX || cX < aX) { if (bX < cX) { int tempX = aX; int tempY = aY; + boolean tempBool = ab; aX = bX; aY = bY; + ab = bc; bX = cX; bY = cY; + bc = ca; cX = tempX; cY = tempY; + ca = tempBool; } else if (cX < aX) { int tempX = aX; int tempY = aY; + boolean tempBool = ab; aX = cX; aY = cY; + ab = ca; cX = bX; cY = bY; + ca = bc; bX = tempX; bY = tempY; + bc = tempBool; } } else if (aX == bX && aX == cX) { //degenerated case, all points with same longitude @@ -152,21 +173,29 @@ public final class ShapeField { if (bY < cY) { int tempX = aX; int tempY = aY; + boolean tempBool = ab; aX = bX; aY = bY; + ab = bc; bX = cX; bY = cY; + bc = ca; cX = tempX; cY = tempY; + ca = tempBool; } else if (cY < aY) { int tempX = aX; int tempY = aY; + boolean tempBool = ab; aX = cX; aY = cY; + ab = ca; cX = bX; cY = bY; + ca = bc; bX = tempX; bY = tempY; + bc = tempBool; } } } @@ -216,6 +245,9 @@ public final class ShapeField { } else { throw new IllegalArgumentException("Could not encode the provided triangle"); } + bits |= (ab) ? (1 << 3) : 0; + bits |= (bc) ? (1 << 4) : 0; + bits |= (ca) ? (1 << 5) : 0; NumericUtils.intToSortableBytes(minY, bytes, 0); NumericUtils.intToSortableBytes(minX, bytes, BYTES); NumericUtils.intToSortableBytes(maxY, bytes, 2 * BYTES); @@ -225,83 +257,133 @@ public final class ShapeField { NumericUtils.intToSortableBytes(bits, bytes, 6 * BYTES); } - /** - * Decode a triangle encoded by {@link ShapeField#encodeTriangle(byte[], int, int, int, int, int, int)}. + /** Decode a triangle encoded by {@link ShapeField#encodeTriangle(byte[], int, int, boolean, int, int, boolean, int, int, boolean)}. */ - public static void decodeTriangle(byte[] t, int[] triangle) { - assert triangle.length == 6; + public static void decodeTriangle(byte[] t, DecodedTriangle triangle) { + final int aX, aY, bX, bY, cX, cY; + final boolean ab, bc, ca; int bits = NumericUtils.sortableBytesToInt(t, 6 * BYTES); //extract the first three bits int tCode = (((1 << 3) - 1) & (bits >> 0)); switch (tCode) { case MINY_MINX_MAXY_MAXX_Y_X: - triangle[0] = NumericUtils.sortableBytesToInt(t, 0 * BYTES); - triangle[1] = NumericUtils.sortableBytesToInt(t, 1 * BYTES); - triangle[2] = NumericUtils.sortableBytesToInt(t, 2 * BYTES); - triangle[3] = NumericUtils.sortableBytesToInt(t, 3 * BYTES); - triangle[4] = NumericUtils.sortableBytesToInt(t, 4 * BYTES); - triangle[5] = NumericUtils.sortableBytesToInt(t, 5 * BYTES); + aY = NumericUtils.sortableBytesToInt(t, 0 * BYTES); + aX = NumericUtils.sortableBytesToInt(t, 1 * BYTES); + bY = NumericUtils.sortableBytesToInt(t, 2 * BYTES); + bX = NumericUtils.sortableBytesToInt(t, 3 * BYTES); + cY = NumericUtils.sortableBytesToInt(t, 4 * BYTES); + cX = NumericUtils.sortableBytesToInt(t, 5 * BYTES); break; case MINY_MINX_Y_X_MAXY_MAXX: - triangle[0] = NumericUtils.sortableBytesToInt(t, 0 * BYTES); - triangle[1] = NumericUtils.sortableBytesToInt(t, 1 * BYTES); - triangle[2] = NumericUtils.sortableBytesToInt(t, 4 * BYTES); - triangle[3] = NumericUtils.sortableBytesToInt(t, 5 * BYTES); - triangle[4] = NumericUtils.sortableBytesToInt(t, 2 * BYTES); - triangle[5] = NumericUtils.sortableBytesToInt(t, 3 * BYTES); + aY = NumericUtils.sortableBytesToInt(t, 0 * BYTES); + aX = NumericUtils.sortableBytesToInt(t, 1 * BYTES); + bY = NumericUtils.sortableBytesToInt(t, 4 * BYTES); + bX = NumericUtils.sortableBytesToInt(t, 5 * BYTES); + cY = NumericUtils.sortableBytesToInt(t, 2 * BYTES); + cX = NumericUtils.sortableBytesToInt(t, 3 * BYTES); break; case MAXY_MINX_Y_X_MINY_MAXX: - triangle[0] = NumericUtils.sortableBytesToInt(t, 2 * BYTES); - triangle[1] = NumericUtils.sortableBytesToInt(t, 1 * BYTES); - triangle[2] = NumericUtils.sortableBytesToInt(t, 4 * BYTES); - triangle[3] = NumericUtils.sortableBytesToInt(t, 5 * BYTES); - triangle[4] = NumericUtils.sortableBytesToInt(t, 0 * BYTES); - triangle[5] = NumericUtils.sortableBytesToInt(t, 3 * BYTES); + aY = NumericUtils.sortableBytesToInt(t, 2 * BYTES); + aX = NumericUtils.sortableBytesToInt(t, 1 * BYTES); + bY = NumericUtils.sortableBytesToInt(t, 4 * BYTES); + bX = NumericUtils.sortableBytesToInt(t, 5 * BYTES); + cY = NumericUtils.sortableBytesToInt(t, 0 * BYTES); + cX = NumericUtils.sortableBytesToInt(t, 3 * BYTES); break; case MAXY_MINX_MINY_MAXX_Y_X: - triangle[0] = NumericUtils.sortableBytesToInt(t, 2 * BYTES); - triangle[1] = NumericUtils.sortableBytesToInt(t, 1 * BYTES); - triangle[2] = NumericUtils.sortableBytesToInt(t, 0 * BYTES); - triangle[3] = NumericUtils.sortableBytesToInt(t, 3 * BYTES); - triangle[4] = NumericUtils.sortableBytesToInt(t, 4 * BYTES); - triangle[5] = NumericUtils.sortableBytesToInt(t, 5 * BYTES); + aY = NumericUtils.sortableBytesToInt(t, 2 * BYTES); + aX = NumericUtils.sortableBytesToInt(t, 1 * BYTES); + bY = NumericUtils.sortableBytesToInt(t, 0 * BYTES); + bX = NumericUtils.sortableBytesToInt(t, 3 * BYTES); + cY = NumericUtils.sortableBytesToInt(t, 4 * BYTES); + cX = NumericUtils.sortableBytesToInt(t, 5 * BYTES); break; case Y_MINX_MINY_X_MAXY_MAXX: - triangle[0] = NumericUtils.sortableBytesToInt(t, 4 * BYTES); - triangle[1] = NumericUtils.sortableBytesToInt(t, 1 * BYTES); - triangle[2] = NumericUtils.sortableBytesToInt(t, 0 * BYTES); - triangle[3] = NumericUtils.sortableBytesToInt(t, 5 * BYTES); - triangle[4] = NumericUtils.sortableBytesToInt(t, 2 * BYTES); - triangle[5] = NumericUtils.sortableBytesToInt(t, 3 * BYTES); + aY = NumericUtils.sortableBytesToInt(t, 4 * BYTES); + aX = NumericUtils.sortableBytesToInt(t, 1 * BYTES); + bY = NumericUtils.sortableBytesToInt(t, 0 * BYTES); + bX = NumericUtils.sortableBytesToInt(t, 5 * BYTES); + cY = NumericUtils.sortableBytesToInt(t, 2 * BYTES); + cX = NumericUtils.sortableBytesToInt(t, 3 * BYTES); break; case Y_MINX_MINY_MAXX_MAXY_X: - triangle[0] = NumericUtils.sortableBytesToInt(t, 4 * BYTES); - triangle[1] = NumericUtils.sortableBytesToInt(t, 1 * BYTES); - triangle[2] = NumericUtils.sortableBytesToInt(t, 0 * BYTES); - triangle[3] = NumericUtils.sortableBytesToInt(t, 3 * BYTES); - triangle[4] = NumericUtils.sortableBytesToInt(t, 2 * BYTES); - triangle[5] = NumericUtils.sortableBytesToInt(t, 5 * BYTES); + aY = NumericUtils.sortableBytesToInt(t, 4 * BYTES); + aX = NumericUtils.sortableBytesToInt(t, 1 * BYTES); + bY = NumericUtils.sortableBytesToInt(t, 0 * BYTES); + bX = NumericUtils.sortableBytesToInt(t, 3 * BYTES); + cY = NumericUtils.sortableBytesToInt(t, 2 * BYTES); + cX = NumericUtils.sortableBytesToInt(t, 5 * BYTES); break; case MAXY_MINX_MINY_X_Y_MAXX: - triangle[0] = NumericUtils.sortableBytesToInt(t, 2 * BYTES); - triangle[1] = NumericUtils.sortableBytesToInt(t, 1 * BYTES); - triangle[2] = NumericUtils.sortableBytesToInt(t, 0 * BYTES); - triangle[3] = NumericUtils.sortableBytesToInt(t, 5 * BYTES); - triangle[4] = NumericUtils.sortableBytesToInt(t, 4 * BYTES); - triangle[5] = NumericUtils.sortableBytesToInt(t, 3 * BYTES); + aY = NumericUtils.sortableBytesToInt(t, 2 * BYTES); + aX = NumericUtils.sortableBytesToInt(t, 1 * BYTES); + bY = NumericUtils.sortableBytesToInt(t, 0 * BYTES); + bX = NumericUtils.sortableBytesToInt(t, 5 * BYTES); + cY = NumericUtils.sortableBytesToInt(t, 4 * BYTES); + cX = NumericUtils.sortableBytesToInt(t, 3 * BYTES); break; case MINY_MINX_Y_MAXX_MAXY_X: - triangle[0] = NumericUtils.sortableBytesToInt(t, 0 * BYTES); - triangle[1] = NumericUtils.sortableBytesToInt(t, 1 * BYTES); - triangle[2] = NumericUtils.sortableBytesToInt(t, 4 * BYTES); - triangle[3] = NumericUtils.sortableBytesToInt(t, 3 * BYTES); - triangle[4] = NumericUtils.sortableBytesToInt(t, 2 * BYTES); - triangle[5] = NumericUtils.sortableBytesToInt(t, 5 * BYTES); + aY = NumericUtils.sortableBytesToInt(t, 0 * BYTES); + aX = NumericUtils.sortableBytesToInt(t, 1 * BYTES); + bY = NumericUtils.sortableBytesToInt(t, 4 * BYTES); + bX = NumericUtils.sortableBytesToInt(t, 3 * BYTES); + cY = NumericUtils.sortableBytesToInt(t, 2 * BYTES); + cX = NumericUtils.sortableBytesToInt(t, 5 * BYTES); break; default: throw new IllegalArgumentException("Could not decode the provided triangle"); } //Points of the decoded triangle must be co-planar or CCW oriented - assert GeoUtils.orient(triangle[1], triangle[0], triangle[3], triangle[2], triangle[5], triangle[4]) >= 0; + assert GeoUtils.orient(aX, aY, bX, bY, cX, cY) >= 0; + ab = (bits & 1 << 3) == 1 << 3; + bc = (bits & 1 << 4) == 1 << 4; + ca = (bits & 1 << 5) == 1 << 5; + triangle.setValues(aX, aY, ab, bX, bY, bc, cX, cY, ca); + } + + /** + * Represents a encoded triangle using {@link ShapeField#decodeTriangle(byte[], DecodedTriangle)}. + */ + public static class DecodedTriangle { + //Triangle vertices + public int aX, aY, bX, bY, cX, cY; + //Represent if edges belongs to original shape + public boolean ab, bc, ca; + + public DecodedTriangle() { + } + + private void setValues(int aX, int aY, boolean ab, int bX, int bY, boolean bc, int cX, int cY, boolean ca) { + this.aX = aX; + this.aY = aY; + this.ab = ab; + this.bX = bX; + this.bY = bY; + this.bc = bc; + this.cX = cX; + this.cY = cY; + this.ca = ca; + } + + @Override + public int hashCode() { + return Objects.hash(aX, aY, bX, bY, cX, cY, ab, bc, ca); + } + + @Override + public boolean equals(Object o) { + DecodedTriangle other = (DecodedTriangle) o; + return aX == other.aX && bX == other.bX && cX == other.cX + && aY == other.aY && bY == other.bY && cY == other.cY + && ab == other.ab && bc == other.bc && ca == other.ca; + } + + /** pretty print the triangle vertices */ + public String toString() { + String result = aX + ", " + aY + " " + + bX + ", " + bY + " " + + cX + ", " + cY + " " + "[" + ab + "," +bc + "," + ca + "]"; + return result; + } } } diff --git a/lucene/sandbox/src/java/org/apache/lucene/document/ShapeQuery.java b/lucene/sandbox/src/java/org/apache/lucene/document/ShapeQuery.java index a290b685fc6..b94a2fbb01d 100644 --- a/lucene/sandbox/src/java/org/apache/lucene/document/ShapeQuery.java +++ b/lucene/sandbox/src/java/org/apache/lucene/document/ShapeQuery.java @@ -83,7 +83,7 @@ abstract class ShapeQuery extends Query { int maxXOffset, int maxYOffset, byte[] maxTriangle); /** returns true if the provided triangle matches the query */ - protected abstract boolean queryMatches(byte[] triangle, int[] scratchTriangle, ShapeField.QueryRelation queryRelation); + protected abstract boolean queryMatches(byte[] triangle, ShapeField.DecodedTriangle scratchTriangle, ShapeField.QueryRelation queryRelation); /** relates a range of triangles (internal node) to the query */ protected Relation relateRangeToQuery(byte[] minTriangle, byte[] maxTriangle, QueryRelation queryRelation) { @@ -110,7 +110,7 @@ abstract class ShapeQuery extends Query { /** create a visitor that adds documents that match the query using a sparse bitset. (Used by INTERSECT) */ protected IntersectVisitor getSparseIntersectVisitor(DocIdSetBuilder result) { return new IntersectVisitor() { - final int[] scratchTriangle = new int[6]; + final ShapeField.DecodedTriangle scratchTriangle = new ShapeField.DecodedTriangle(); DocIdSetBuilder.BulkAdder adder; @Override @@ -150,7 +150,7 @@ abstract class ShapeQuery extends Query { /** create a visitor that adds documents that match the query using a dense bitset. (Used by WITHIN, DISJOINT) */ protected IntersectVisitor getDenseIntersectVisitor(FixedBitSet intersect, FixedBitSet disjoint, ShapeField.QueryRelation queryRelation) { return new IntersectVisitor() { - final int[] scratchTriangle = new int[6]; + final ShapeField.DecodedTriangle scratchTriangle = new ShapeField.DecodedTriangle(); @Override public void visit(int docID) throws IOException { if (queryRelation == ShapeField.QueryRelation.DISJOINT) { @@ -330,7 +330,7 @@ abstract class ShapeQuery extends Query { /** create a visitor that clears documents that do NOT match the polygon query; used with INTERSECTS */ private IntersectVisitor getInverseIntersectVisitor(ShapeQuery query, FixedBitSet result, int[] cost) { return new IntersectVisitor() { - int[] scratchTriangle = new int[6]; + final ShapeField.DecodedTriangle scratchTriangle = new ShapeField.DecodedTriangle(); @Override public void visit(int docID) { result.clear(docID); diff --git a/lucene/sandbox/src/java/org/apache/lucene/document/XYShapeBoundingBoxQuery.java b/lucene/sandbox/src/java/org/apache/lucene/document/XYShapeBoundingBoxQuery.java index 21fa5b48d51..1979d08946c 100644 --- a/lucene/sandbox/src/java/org/apache/lucene/document/XYShapeBoundingBoxQuery.java +++ b/lucene/sandbox/src/java/org/apache/lucene/document/XYShapeBoundingBoxQuery.java @@ -46,16 +46,16 @@ public class XYShapeBoundingBoxQuery extends ShapeQuery { /** returns true if the query matches the encoded triangle */ @Override - protected boolean queryMatches(byte[] t, int[] scratchTriangle, QueryRelation queryRelation) { + protected boolean queryMatches(byte[] t, ShapeField.DecodedTriangle scratchTriangle, QueryRelation queryRelation) { // decode indexed triangle ShapeField.decodeTriangle(t, scratchTriangle); - int aY = scratchTriangle[0]; - int aX = scratchTriangle[1]; - int bY = scratchTriangle[2]; - int bX = scratchTriangle[3]; - int cY = scratchTriangle[4]; - int cX = scratchTriangle[5]; + int aY = scratchTriangle.aY; + int aX = scratchTriangle.aX; + int bY = scratchTriangle.bY; + int bX = scratchTriangle.bX; + int cY = scratchTriangle.cY; + int cX = scratchTriangle.cX; if (queryRelation == QueryRelation.WITHIN) { return rectangle2D.containsTriangle(aX, aY, bX, bY, cX, cY); diff --git a/lucene/sandbox/src/java/org/apache/lucene/document/XYShapeLineQuery.java b/lucene/sandbox/src/java/org/apache/lucene/document/XYShapeLineQuery.java index b8ec71094d7..d4386f8882b 100644 --- a/lucene/sandbox/src/java/org/apache/lucene/document/XYShapeLineQuery.java +++ b/lucene/sandbox/src/java/org/apache/lucene/document/XYShapeLineQuery.java @@ -86,15 +86,15 @@ final class XYShapeLineQuery extends ShapeQuery { } @Override - protected boolean queryMatches(byte[] t, int[] scratchTriangle, QueryRelation queryRelation) { + protected boolean queryMatches(byte[] t, ShapeField.DecodedTriangle scratchTriangle, QueryRelation queryRelation) { ShapeField.decodeTriangle(t, scratchTriangle); - double alat = decode(scratchTriangle[0]); - double alon = decode(scratchTriangle[1]); - double blat = decode(scratchTriangle[2]); - double blon = decode(scratchTriangle[3]); - double clat = decode(scratchTriangle[4]); - double clon = decode(scratchTriangle[5]); + double alat = decode(scratchTriangle.aY); + double alon = decode(scratchTriangle.aX); + double blat = decode(scratchTriangle.bY); + double blon = decode(scratchTriangle.bX); + double clat = decode(scratchTriangle.cY); + double clon = decode(scratchTriangle.cX); if (queryRelation == QueryRelation.WITHIN) { return line2D.relateTriangle(alon, alat, blon, blat, clon, clat) == Relation.CELL_INSIDE_QUERY; diff --git a/lucene/sandbox/src/java/org/apache/lucene/document/XYShapePolygonQuery.java b/lucene/sandbox/src/java/org/apache/lucene/document/XYShapePolygonQuery.java index e1b4e9916b3..71be742771b 100644 --- a/lucene/sandbox/src/java/org/apache/lucene/document/XYShapePolygonQuery.java +++ b/lucene/sandbox/src/java/org/apache/lucene/document/XYShapePolygonQuery.java @@ -26,6 +26,8 @@ import org.apache.lucene.geo.XYPolygon2D; import org.apache.lucene.index.PointValues.Relation; import org.apache.lucene.util.NumericUtils; +import static org.apache.lucene.geo.XYEncodingUtils.decode; + /** * Finds all previously indexed cartesian shapes that intersect the specified arbitrary cartesian {@link XYPolygon}. * @@ -76,15 +78,15 @@ final class XYShapePolygonQuery extends ShapeQuery { } @Override - protected boolean queryMatches(byte[] t, int[] scratchTriangle, QueryRelation queryRelation) { + protected boolean queryMatches(byte[] t, ShapeField.DecodedTriangle scratchTriangle, QueryRelation queryRelation) { ShapeField.decodeTriangle(t, scratchTriangle); - double alat = XYEncodingUtils.decode(scratchTriangle[0]); - double alon = XYEncodingUtils.decode(scratchTriangle[1]); - double blat = XYEncodingUtils.decode(scratchTriangle[2]); - double blon = XYEncodingUtils.decode(scratchTriangle[3]); - double clat = XYEncodingUtils.decode(scratchTriangle[4]); - double clon = XYEncodingUtils.decode(scratchTriangle[5]); + double alat = decode(scratchTriangle.aY); + double alon = decode(scratchTriangle.aX); + double blat = decode(scratchTriangle.bY); + double blon = decode(scratchTriangle.bX); + double clat = decode(scratchTriangle.cY); + double clon = decode(scratchTriangle.cX); if (queryRelation == QueryRelation.WITHIN) { return poly2D.relateTriangle(alon, alat, blon, blat, clon, clat) == Relation.CELL_INSIDE_QUERY; diff --git a/lucene/sandbox/src/java/org/apache/lucene/geo/Tessellator.java b/lucene/sandbox/src/java/org/apache/lucene/geo/Tessellator.java index e46df18af1a..bd9547575f4 100644 --- a/lucene/sandbox/src/java/org/apache/lucene/geo/Tessellator.java +++ b/lucene/sandbox/src/java/org/apache/lucene/geo/Tessellator.java @@ -175,7 +175,7 @@ final public class Tessellator { } // if first and last node are the same then remove the end node and set lastNode to the start if (lastNode != null && isVertexEquals(lastNode, lastNode.next)) { - removeNode(lastNode); + removeNode(lastNode, true); lastNode = lastNode.next; } @@ -286,7 +286,7 @@ final public class Tessellator { Node sharedVertex = getSharedVertex(holeNode, next); if (sharedVertex != null) { // Split the resulting polygon. - Node node = splitPolygon(next, sharedVertex); + Node node = splitPolygon(next, sharedVertex, true); // Filter the split nodes. filterPoints(node, node.next); return; @@ -300,8 +300,10 @@ final public class Tessellator { // Determine whether a hole bridge could be fetched. if(outerNode != null) { + // compute if the bridge overlaps with a polygon edge. + boolean fromPolygon = isPointInLine(outerNode, outerNode.next, holeNode) || isPointInLine(holeNode, holeNode.next, outerNode); // Split the resulting polygon. - Node node = splitPolygon(outerNode, holeNode); + Node node = splitPolygon(outerNode, holeNode, fromPolygon); // Filter the split nodes. filterPoints(node, node.next); } @@ -369,7 +371,7 @@ final public class Tessellator { } /** Check if the provided vertex is in the polygon and return it **/ - private static Node getSharedVertex(Node polygon, Node vertex) { + private static Node getSharedVertex(final Node polygon, final Node vertex) { Node next = polygon; do { if (isVertexEquals(next, vertex)) { @@ -417,10 +419,14 @@ final public class Tessellator { // Determine whether the current triangle must be cut off. final boolean isReflex = area(prevNode.getX(), prevNode.getY(), currEar.getX(), currEar.getY(), nextNode.getX(), nextNode.getY()) >= 0; if (isReflex == false && isEar(currEar, mortonOptimized) == true) { + // Compute if edges belong to the polygon + boolean abFromPolygon = prevNode.isNextEdgeFromPolygon; + boolean bcFromPolygon = currEar.isNextEdgeFromPolygon; + boolean caFromPolygon = isEdgeFromPolygon(prevNode, nextNode, mortonOptimized); // Return the triangulated data - tessellation.add(new Triangle(prevNode, currEar, nextNode)); + tessellation.add(new Triangle(prevNode, abFromPolygon, currEar, bcFromPolygon, nextNode, caFromPolygon)); // Remove the ear node. - removeNode(currEar); + removeNode(currEar, caFromPolygon); // Skipping to the next node leaves fewer slither triangles. currEar = nextNode.next; @@ -439,7 +445,7 @@ final public class Tessellator { continue earcut; case CURE: // if this didn't work, try curing all small self-intersections locally - currEar = cureLocalIntersections(currEar, tessellation); + currEar = cureLocalIntersections(currEar, tessellation, mortonOptimized); state = State.SPLIT; continue earcut; case SPLIT: @@ -531,7 +537,7 @@ final public class Tessellator { } /** Iterate through all polygon nodes and remove small local self-intersections **/ - private static final Node cureLocalIntersections(Node startNode, final List tessellation) { + private static final Node cureLocalIntersections(Node startNode, final List tessellation, final boolean mortonOptimized) { Node node = startNode; Node nextNode; do { @@ -544,12 +550,17 @@ final public class Tessellator { && isIntersectingPolygon(a, a.getX(), a.getY(), b.getX(), b.getY()) == false && linesIntersect(a.getX(), a.getY(), node.getX(), node.getY(), nextNode.getX(), nextNode.getY(), b.getX(), b.getY()) && isLocallyInside(a, b) && isLocallyInside(b, a)) { + // compute edges from polygon + boolean abFromPolygon = (a.next == node) ? a.isNextEdgeFromPolygon : isEdgeFromPolygon(a, node, mortonOptimized); + boolean bcFromPolygon = (node.next == b) ? node.isNextEdgeFromPolygon : isEdgeFromPolygon(node, b, mortonOptimized); + boolean caFromPolygon = (b.next == a) ? b.isNextEdgeFromPolygon : isEdgeFromPolygon(a, b, mortonOptimized); + tessellation.add(new Triangle(a, abFromPolygon, node, bcFromPolygon, b, caFromPolygon)); // Return the triangulated vertices to the tessellation - tessellation.add(new Triangle(a, node, b)); + tessellation.add(new Triangle(a, abFromPolygon, node, bcFromPolygon, b, caFromPolygon)); // remove two nodes involved - removeNode(node); - removeNode(node.next); + removeNode(node, caFromPolygon); + removeNode(node.next, caFromPolygon); node = startNode = b; } node = node.next; @@ -559,7 +570,7 @@ final public class Tessellator { } /** Attempt to split a polygon and independently triangulate each side. Return true if the polygon was splitted **/ - private static final boolean splitEarcut(Object polygon, final Node start, final List tessellation, final boolean mortonIndexed) { + private static final boolean splitEarcut(final Object polygon, final Node start, final List tessellation, final boolean mortonOptimized) { // Search for a valid diagonal that divides the polygon into two. Node searchNode = start; Node nextNode; @@ -569,17 +580,17 @@ final public class Tessellator { while (diagonal != searchNode.previous) { if(searchNode.idx != diagonal.idx && isValidDiagonal(searchNode, diagonal)) { // Split the polygon into two at the point of the diagonal - Node splitNode = splitPolygon(searchNode, diagonal); + Node splitNode = splitPolygon(searchNode, diagonal, isEdgeFromPolygon(searchNode, diagonal, mortonOptimized)); // Filter the resulting polygon. searchNode = filterPoints(searchNode, searchNode.next); splitNode = filterPoints(splitNode, splitNode.next); // Attempt to earcut both of the resulting polygons - if (mortonIndexed) { + if (mortonOptimized) { sortByMortonWithReset(searchNode); sortByMortonWithReset(splitNode); } - earcutLinkedList(polygon, searchNode, tessellation, State.INIT, mortonIndexed); - earcutLinkedList(polygon, splitNode, tessellation, State.INIT, mortonIndexed); + earcutLinkedList(polygon, searchNode, tessellation, State.INIT, mortonOptimized); + earcutLinkedList(polygon, splitNode, tessellation, State.INIT, mortonOptimized); // Finish the iterative search return true; } @@ -590,14 +601,120 @@ final public class Tessellator { return false; } + /** Computes if edge defined by a and b overlaps with a polygon edge **/ + private static boolean isEdgeFromPolygon(final Node a, final Node b, final boolean isMorton) { + if (isMorton) { + return isMortonEdgeFromPolygon(a, b); + } + Node next = a; + do { + if (isPointInLine(next, next.next, a) && isPointInLine(next, next.next, b)) { + return next.isNextEdgeFromPolygon; + } + if (isPointInLine(next, next.previous, a) && isPointInLine(next, next.previous, b)) { + return next.previous.isNextEdgeFromPolygon; + } + next = next.next; + } while(next != a); + return false; + } + + /** 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) { + // 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; + final int maxTX = StrictMath.max(a.x, b.x) ^ 0x80000000; + final int maxTY = StrictMath.max(a.y, b.y) ^ 0x80000000; + + // z-order range for the current edge; + final long minZ = BitUtil.interleave(minTX, minTY); + final long maxZ = BitUtil.interleave(maxTX, maxTY); + + // now make sure we don't have other points inside the potential ear; + + // look for points inside edge in both directions + Node p = a.previousZ; + Node n = a.nextZ; + while (p != null && Long.compareUnsigned(p.morton, minZ) >= 0 + && n != null && Long.compareUnsigned(n.morton, maxZ) <= 0) { + if (isPointInLine(p, p.next, a) && isPointInLine(p, p.next, b)) { + return p.isNextEdgeFromPolygon; + } + if (isPointInLine(p, p.previous, a) && isPointInLine(p, p.previous, b)) { + return p.previous.isNextEdgeFromPolygon; + } + + p = p.previousZ; + + if (isPointInLine(n, n.next, a) && isPointInLine(n, n.next, b)) { + return n.isNextEdgeFromPolygon; + } + if (isPointInLine(n, n.previous, a) && isPointInLine(n, n.previous, b)) { + return n.previous.isNextEdgeFromPolygon; + } + + n = n.nextZ; + } + + // first look for points inside the edge in decreasing z-order + while (p != null && Long.compareUnsigned(p.morton, minZ) >= 0) { + if (isPointInLine(p, p.next, a) && isPointInLine(p, p.next, b)) { + return p.isNextEdgeFromPolygon; + } + if (isPointInLine(p, p.previous, a) && isPointInLine(p, p.previous, b)) { + return p.previous.isNextEdgeFromPolygon; + } + p = p.previousZ; + } + // then look for points in increasing z-order + while (n != null && + Long.compareUnsigned(n.morton, maxZ) <= 0) { + if (isPointInLine(n, n.next, a) && isPointInLine(n, n.next, b)) { + return n.isNextEdgeFromPolygon; + } + if (isPointInLine(n, n.previous, a) && isPointInLine(n, n.previous, b)) { + return n.previous.isNextEdgeFromPolygon; + } + n = n.nextZ; + } + return false; + } + + private static boolean isPointInLine(final Node a, final Node b, final Node point) { + return isPointInLine(a, b, point.getX(), point.getY()); + } + + private static boolean isPointInLine(final Node a, final Node b, final double lon, final double lat) { + final double dxc = lon - a.getX(); + final double dyc = lat - a.getY(); + + final double dxl = b.getX() - a.getX(); + final double dyl = b.getY() - a.getY(); + + if (dxc * dyl - dyc * dxl == 0) { + if (Math.abs(dxl) >= Math.abs(dyl)) { + return dxl > 0 ? + a.getX() <= lon && lon <= b.getX() : + b.getX() <= lon && lon <= a.getX(); + } else { + return dyl > 0 ? + a.getY() <= lat && lat <= b.getY() : + b.getY() <= lat && lat <= a.getY(); + } + } + return false; + } + /** Links two polygon vertices using a bridge. **/ - private static final Node splitPolygon(final Node a, final Node b) { + private static final 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; final Node bp = b.previous; a.next = b; + a.isNextEdgeFromPolygon = edgeFromPolygon; a.nextZ = b; b.previous = a; b.previousZ = a; @@ -606,6 +723,7 @@ final public class Tessellator { an.previous = a2; an.previousZ = a2; b2.next = a2; + b2.isNextEdgeFromPolygon = edgeFromPolygon; b2.nextZ = a2; a2.previous = b2; a2.previousZ = b2; @@ -628,7 +746,7 @@ final public class Tessellator { } /** Determine whether the polygon defined between node start and node end is CW */ - private static boolean isCWPolygon(Node start, Node end) { + private static boolean isCWPolygon(final Node start, final Node end) { Node next = start; double windingSum = 0; do { @@ -796,10 +914,13 @@ final public class Tessellator { continueIteration = false; nextNode = node.next; prevNode = node.previous; - if (isVertexEquals(node, nextNode) - || area(prevNode.getX(), prevNode.getY(), node.getX(), node.getY(), nextNode.getX(), nextNode.getY()) == 0) { + //We can filter points when they are the same, if not and they are co-linear we can only + //remove it if both edges have the same value in .isNextEdgeFromPolygon + if (isVertexEquals(node, nextNode) || + (prevNode.isNextEdgeFromPolygon == node.isNextEdgeFromPolygon && + area(prevNode.getX(), prevNode.getY(), node.getX(), node.getY(), nextNode.getX(), nextNode.getY()) == 0)) { // Remove the node - removeNode(node); + removeNode(node, prevNode.isNextEdgeFromPolygon); node = end = prevNode; if (node == nextNode) { @@ -835,9 +956,10 @@ final public class Tessellator { } /** Removes a node from the doubly linked list */ - private static final void removeNode(Node node) { + private static final void removeNode(Node node, boolean edgeFromPolygon) { node.next.previous = node.previous; node.previous.next = node.next; + node.previous.isNextEdgeFromPolygon = edgeFromPolygon; if (node.previousZ != null) { node.previousZ.nextZ = node.nextZ; @@ -873,13 +995,23 @@ final public class Tessellator { /** 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) { - 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)); + 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; } - return false; } /** Brute force compute if a point is in the polygon by traversing entire triangulation @@ -901,8 +1033,7 @@ final public class Tessellator { private final int idx; // vertex index in the polygon private final int vrtxIdx; - // reference to the polygon for lat/lon values -// private final Polygon polygon; + // reference to the polygon for lat/lon values; private final double[] polyX; private final double[] polyY; // encoded x value @@ -920,6 +1051,8 @@ final public class Tessellator { private Node previousZ; // next z node private Node nextZ; + // if the edge from this node to the next node is part of the polygon edges + private boolean isNextEdgeFromPolygon; protected Node(final double[] x, final double[] y, final int index, final int vertexIndex, final boolean isGeo) { this.idx = index; @@ -933,6 +1066,7 @@ final public class Tessellator { this.next = null; this.previousZ = null; this.nextZ = null; + this.isNextEdgeFromPolygon = true; } /** simple deep copy constructor */ @@ -948,6 +1082,7 @@ final public class Tessellator { this.next = other.next; this.previousZ = other.previousZ; this.nextZ = other.nextZ; + this.isNextEdgeFromPolygon = other.isNextEdgeFromPolygon; } /** get the x value */ @@ -979,9 +1114,11 @@ final public class Tessellator { /** Triangle in the tessellated mesh */ public final static class Triangle { Node[] vertex; + boolean[] edgeFromPolygon; - protected Triangle(Node a, Node b, Node c) { + protected Triangle(Node a, boolean isABfromPolygon, Node b, boolean isBCfromPolygon, Node c, boolean isCAfromPolygon) { this.vertex = new Node[] {a, b, c}; + this.edgeFromPolygon = new boolean[] {isABfromPolygon, isBCfromPolygon, isCAfromPolygon}; } /** get quantized x value for the given vertex */ @@ -1004,6 +1141,11 @@ final public class Tessellator { return this.vertex[vertex].getX(); } + /** get if edge is shared with the polygon for the given edge */ + public boolean isEdgefromPolygon(int startVertex) { + 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, @@ -1014,9 +1156,9 @@ final public class Tessellator { /** pretty print the triangle vertices */ public String toString() { - String result = vertex[0].x + ", " + vertex[0].y + " " + - vertex[1].x + ", " + vertex[1].y + " " + - vertex[2].x + ", " + vertex[2].y; + String result = vertex[0].x + ", " + vertex[0].y + " [" + edgeFromPolygon[0] + "] " + + vertex[1].x + ", " + vertex[1].y + " [" + edgeFromPolygon[1] + "] " + + vertex[2].x + ", " + vertex[2].y + " [" + edgeFromPolygon[2] + "]"; return result; } } diff --git a/lucene/sandbox/src/test/org/apache/lucene/document/BaseLatLonShapeTestCase.java b/lucene/sandbox/src/test/org/apache/lucene/document/BaseLatLonShapeTestCase.java index 0ca3563bb40..5095bdc0403 100644 --- a/lucene/sandbox/src/test/org/apache/lucene/document/BaseLatLonShapeTestCase.java +++ b/lucene/sandbox/src/test/org/apache/lucene/document/BaseLatLonShapeTestCase.java @@ -264,18 +264,18 @@ public abstract class BaseLatLonShapeTestCase extends BaseShapeTestCase { } @Override - double[] quantizeTriangle(double ax, double ay, double bx, double by, double cx, double cy) { - int[] decoded = encodeDecodeTriangle(ax, ay, bx, by, cx, cy); - return new double[]{decodeLatitude(decoded[0]), decodeLongitude(decoded[1]), decodeLatitude(decoded[2]), decodeLongitude(decoded[3]), decodeLatitude(decoded[4]), decodeLongitude(decoded[5])}; + double[] quantizeTriangle(double ax, double ay, boolean ab, double bx, double by, boolean bc, double cx, double cy, boolean ca) { + ShapeField.DecodedTriangle decoded = encodeDecodeTriangle(ax, ay, ab, bx, by, bc, cx, cy, ca); + return new double[]{decodeLatitude(decoded.aY), decodeLongitude(decoded.aX), decodeLatitude(decoded.bY), decodeLongitude(decoded.bX), decodeLatitude(decoded.cY), decodeLongitude(decoded.cX)}; } @Override - int[] encodeDecodeTriangle(double ax, double ay, double bx, double by, double cx, double cy) { + ShapeField.DecodedTriangle encodeDecodeTriangle(double ax, double ay, boolean ab, double bx, double by, boolean bc, double cx, double cy, boolean ca) { byte[] encoded = new byte[7 * ShapeField.BYTES]; - ShapeField.encodeTriangle(encoded, encodeLatitude(ay), encodeLongitude(ax), encodeLatitude(by), encodeLongitude(bx), encodeLatitude(cy), encodeLongitude(cx)); - int[] decoded = new int[6]; - ShapeField.decodeTriangle(encoded, decoded); - return decoded; + ShapeField.encodeTriangle(encoded, encodeLatitude(ay), encodeLongitude(ax), ab, encodeLatitude(by), encodeLongitude(bx), bc, encodeLatitude(cy), encodeLongitude(cx), ca); + ShapeField.DecodedTriangle triangle = new ShapeField.DecodedTriangle(); + ShapeField.decodeTriangle(encoded, triangle); + return triangle; } }; } diff --git a/lucene/sandbox/src/test/org/apache/lucene/document/BaseShapeEncodingTestCase.java b/lucene/sandbox/src/test/org/apache/lucene/document/BaseShapeEncodingTestCase.java index daa9bacb380..5d7579f5f1b 100644 --- a/lucene/sandbox/src/test/org/apache/lucene/document/BaseShapeEncodingTestCase.java +++ b/lucene/sandbox/src/test/org/apache/lucene/document/BaseShapeEncodingTestCase.java @@ -16,8 +16,6 @@ */ package org.apache.lucene.document; -import java.util.Arrays; - import org.apache.lucene.geo.GeoUtils; import org.apache.lucene.geo.Polygon2D; import org.apache.lucene.index.PointValues; @@ -55,15 +53,15 @@ public abstract class BaseShapeEncodingTestCase extends LuceneTestCase { int cxEnc = encodeX(cx); verifyEncodingPermutations(ayEnc, axEnc, byEnc, bxEnc, cyEnc, cxEnc); byte[] b = new byte[7 * ShapeField.BYTES]; - ShapeField.encodeTriangle(b, ayEnc, axEnc, byEnc, bxEnc, cyEnc, cxEnc); - int[] encoded = new int[6]; + ShapeField.encodeTriangle(b, ayEnc, axEnc, true, byEnc, bxEnc, true, cyEnc, cxEnc, true); + ShapeField.DecodedTriangle encoded = new ShapeField.DecodedTriangle(); ShapeField.decodeTriangle(b, encoded); - assertTrue(encoded[0] == ayEnc); - assertTrue(encoded[1] == axEnc); - assertTrue(encoded[2] == byEnc); - assertTrue(encoded[3] == bxEnc); - assertTrue(encoded[4] == cyEnc); - assertTrue(encoded[5] == cxEnc); + assertEquals(encoded.aY, ayEnc); + assertEquals(encoded.aX, axEnc); + assertEquals(encoded.bY, byEnc); + assertEquals(encoded.bX, bxEnc); + assertEquals(encoded.cY, cyEnc); + assertEquals(encoded.cX, cxEnc); } //One shared point with MBR -> MinLat, MaxLon @@ -82,15 +80,15 @@ public abstract class BaseShapeEncodingTestCase extends LuceneTestCase { int cxEnc = encodeX(cx); verifyEncodingPermutations(ayEnc, axEnc, byEnc, bxEnc, cyEnc, cxEnc); byte[] b = new byte[7 * ShapeField.BYTES]; - ShapeField.encodeTriangle(b, ayEnc, axEnc, byEnc, bxEnc, cyEnc, cxEnc); - int[] encoded = new int[6]; + ShapeField.encodeTriangle(b, ayEnc, axEnc, true, byEnc, bxEnc, true, cyEnc, cxEnc, true); + ShapeField.DecodedTriangle encoded = new ShapeField.DecodedTriangle(); ShapeField.decodeTriangle(b, encoded); - assertTrue(encoded[0] == ayEnc); - assertTrue(encoded[1] == axEnc); - assertTrue(encoded[2] == byEnc); - assertTrue(encoded[3] == bxEnc); - assertTrue(encoded[4] == cyEnc); - assertTrue(encoded[5] == cxEnc); + assertEquals(encoded.aY, ayEnc); + assertEquals(encoded.aX, axEnc); + assertEquals(encoded.bY, byEnc); + assertEquals(encoded.bX, bxEnc); + assertEquals(encoded.cY, cyEnc); + assertEquals(encoded.cX, cxEnc); } //One shared point with MBR -> MaxLat, MaxLon @@ -109,15 +107,15 @@ public abstract class BaseShapeEncodingTestCase extends LuceneTestCase { int cxEnc = encodeX(blon); verifyEncodingPermutations(ayEnc, axEnc, byEnc, bxEnc, cyEnc, cxEnc); byte[] b = new byte[7 * ShapeField.BYTES]; - ShapeField.encodeTriangle(b, ayEnc, axEnc, byEnc, bxEnc, cyEnc, cxEnc); - int[] encoded = new int[6]; + ShapeField.encodeTriangle(b, ayEnc, axEnc, true, byEnc, bxEnc, true, cyEnc, cxEnc, true); + ShapeField.DecodedTriangle encoded = new ShapeField.DecodedTriangle(); ShapeField.decodeTriangle(b, encoded); - assertTrue(encoded[0] == ayEnc); - assertTrue(encoded[1] == axEnc); - assertTrue(encoded[2] == byEnc); - assertTrue(encoded[3] == bxEnc); - assertTrue(encoded[4] == cyEnc); - assertTrue(encoded[5] == cxEnc); + assertEquals(encoded.aY, ayEnc); + assertEquals(encoded.aX, axEnc); + assertEquals(encoded.bY, byEnc); + assertEquals(encoded.bX, bxEnc); + assertEquals(encoded.cY, cyEnc); + assertEquals(encoded.cX, cxEnc); } //One shared point with MBR -> MaxLat, MinLon @@ -136,15 +134,15 @@ public abstract class BaseShapeEncodingTestCase extends LuceneTestCase { int cxEnc = encodeX(blon); verifyEncodingPermutations(ayEnc, axEnc, byEnc, bxEnc, cyEnc, cxEnc); byte[] b = new byte[7 * ShapeField.BYTES]; - ShapeField.encodeTriangle(b, ayEnc, axEnc, byEnc, bxEnc, cyEnc, cxEnc); - int[] encoded = new int[6]; + ShapeField.encodeTriangle(b, ayEnc, axEnc, true, byEnc, bxEnc, true, cyEnc, cxEnc, true); + ShapeField.DecodedTriangle encoded = new ShapeField.DecodedTriangle(); ShapeField.decodeTriangle(b, encoded); - assertTrue(encoded[0] == ayEnc); - assertTrue(encoded[1] == axEnc); - assertTrue(encoded[2] == byEnc); - assertTrue(encoded[3] == bxEnc); - assertTrue(encoded[4] == cyEnc); - assertTrue(encoded[5] == cxEnc); + assertEquals(encoded.aY, ayEnc); + assertEquals(encoded.aX, axEnc); + assertEquals(encoded.bY, byEnc); + assertEquals(encoded.bX, bxEnc); + assertEquals(encoded.cY, cyEnc); + assertEquals(encoded.cX, cxEnc); } //Two shared point with MBR -> [MinLat, MinLon], [MaxLat, MaxLon], third point below @@ -163,15 +161,15 @@ public abstract class BaseShapeEncodingTestCase extends LuceneTestCase { int cxEnc = encodeX(cx); verifyEncodingPermutations(ayEnc, axEnc, byEnc, bxEnc, cyEnc, cxEnc); byte[] b = new byte[7 * ShapeField.BYTES]; - ShapeField.encodeTriangle(b, ayEnc, axEnc, byEnc, bxEnc, cyEnc, cxEnc); - int[] encoded = new int[6]; + ShapeField.encodeTriangle(b, ayEnc, axEnc, true, byEnc, bxEnc, true, cyEnc, cxEnc, true); + ShapeField.DecodedTriangle encoded = new ShapeField.DecodedTriangle(); ShapeField.decodeTriangle(b, encoded); - assertTrue(encoded[0] == ayEnc); - assertTrue(encoded[1] == axEnc); - assertTrue(encoded[2] == byEnc); - assertTrue(encoded[3] == bxEnc); - assertTrue(encoded[4] == cyEnc); - assertTrue(encoded[5] == cxEnc); + assertEquals(encoded.aY, ayEnc); + assertEquals(encoded.aX, axEnc); + assertEquals(encoded.bY, byEnc); + assertEquals(encoded.bX, bxEnc); + assertEquals(encoded.cY, cyEnc); + assertEquals(encoded.cX, cxEnc); } //Two shared point with MBR -> [MinLat, MinLon], [MaxLat, MaxLon], third point above @@ -190,15 +188,15 @@ public abstract class BaseShapeEncodingTestCase extends LuceneTestCase { int cxEnc = encodeX(cx); verifyEncodingPermutations(ayEnc, axEnc, byEnc, bxEnc, cyEnc, cxEnc); byte[] b = new byte[7 * ShapeField.BYTES]; - ShapeField.encodeTriangle(b, ayEnc, axEnc, byEnc, bxEnc, cyEnc, cxEnc); - int[] encoded = new int[6]; + ShapeField.encodeTriangle(b, ayEnc, axEnc, true, byEnc, bxEnc, true, cyEnc, cxEnc, true); + ShapeField.DecodedTriangle encoded = new ShapeField.DecodedTriangle(); ShapeField.decodeTriangle(b, encoded); - assertTrue(encoded[0] == ayEnc); - assertTrue(encoded[1] == axEnc); - assertTrue(encoded[2] == byEnc); - assertTrue(encoded[3] == bxEnc); - assertTrue(encoded[4] == cyEnc); - assertTrue(encoded[5] == cxEnc); + assertEquals(encoded.aY, ayEnc); + assertEquals(encoded.aX, axEnc); + assertEquals(encoded.bY, byEnc); + assertEquals(encoded.bX, bxEnc); + assertEquals(encoded.cY, cyEnc); + assertEquals(encoded.cX, cxEnc); } //Two shared point with MBR -> [MinLat, MaxLon], [MaxLat, MinLon], third point below @@ -217,15 +215,15 @@ public abstract class BaseShapeEncodingTestCase extends LuceneTestCase { int cxEnc = encodeX(cx); verifyEncodingPermutations(ayEnc, axEnc, byEnc, bxEnc, cyEnc, cxEnc); byte[] b = new byte[7 * ShapeField.BYTES]; - ShapeField.encodeTriangle(b, ayEnc, axEnc, byEnc, bxEnc, cyEnc, cxEnc); - int[] encoded = new int[6]; + ShapeField.encodeTriangle(b, ayEnc, axEnc, true, byEnc, bxEnc, true, cyEnc, cxEnc, true); + ShapeField.DecodedTriangle encoded = new ShapeField.DecodedTriangle(); ShapeField.decodeTriangle(b, encoded); - assertTrue(encoded[0] == ayEnc); - assertTrue(encoded[1] == axEnc); - assertTrue(encoded[2] == byEnc); - assertTrue(encoded[3] == bxEnc); - assertTrue(encoded[4] == cyEnc); - assertTrue(encoded[5] == cxEnc); + assertEquals(encoded.aY, ayEnc); + assertEquals(encoded.aX, axEnc); + assertEquals(encoded.bY, byEnc); + assertEquals(encoded.bX, bxEnc); + assertEquals(encoded.cY, cyEnc); + assertEquals(encoded.cX, cxEnc); } //Two shared point with MBR -> [MinLat, MaxLon], [MaxLat, MinLon], third point above @@ -244,15 +242,15 @@ public abstract class BaseShapeEncodingTestCase extends LuceneTestCase { int cxEnc = encodeX(cx); verifyEncodingPermutations(ayEnc, axEnc, byEnc, bxEnc, cyEnc, cxEnc); byte[] b = new byte[7 * ShapeField.BYTES]; - ShapeField.encodeTriangle(b, ayEnc, axEnc, byEnc, bxEnc, cyEnc, cxEnc); - int[] encoded = new int[6]; + ShapeField.encodeTriangle(b, ayEnc, axEnc, true, byEnc, bxEnc, true, cyEnc, cxEnc, true); + ShapeField.DecodedTriangle encoded = new ShapeField.DecodedTriangle(); ShapeField.decodeTriangle(b, encoded); - assertTrue(encoded[0] == ayEnc); - assertTrue(encoded[1] == axEnc); - assertTrue(encoded[2] == byEnc); - assertTrue(encoded[3] == bxEnc); - assertTrue(encoded[4] == cyEnc); - assertTrue(encoded[5] == cxEnc); + assertEquals(encoded.aY, ayEnc); + assertEquals(encoded.aX, axEnc); + assertEquals(encoded.bY, byEnc); + assertEquals(encoded.bX, bxEnc); + assertEquals(encoded.cY, cyEnc); + assertEquals(encoded.cX, cxEnc); } //all points shared with MBR @@ -271,15 +269,15 @@ public abstract class BaseShapeEncodingTestCase extends LuceneTestCase { int cxEnc = encodeX(cx); verifyEncodingPermutations(ayEnc, axEnc, byEnc, bxEnc, cyEnc, cxEnc); byte[] b = new byte[7 * ShapeField.BYTES]; - ShapeField.encodeTriangle(b, ayEnc, axEnc, byEnc, bxEnc, cyEnc, cxEnc); - int[] encoded = new int[6]; + ShapeField.encodeTriangle(b, ayEnc, axEnc, true, byEnc, bxEnc, true, cyEnc, cxEnc, true); + ShapeField.DecodedTriangle encoded = new ShapeField.DecodedTriangle(); ShapeField.decodeTriangle(b, encoded); - assertTrue(encoded[0] == ayEnc); - assertTrue(encoded[1] == axEnc); - assertTrue(encoded[2] == byEnc); - assertTrue(encoded[3] == bxEnc); - assertTrue(encoded[4] == cyEnc); - assertTrue(encoded[5] == cxEnc); + assertEquals(encoded.aY, ayEnc); + assertEquals(encoded.aX, axEnc); + assertEquals(encoded.bY, byEnc); + assertEquals(encoded.bX, bxEnc); + assertEquals(encoded.cY, cyEnc); + assertEquals(encoded.cX, cxEnc); } //all points shared with MBR @@ -297,15 +295,15 @@ public abstract class BaseShapeEncodingTestCase extends LuceneTestCase { int cyEnc = encodeY(cy); int cxEnc = encodeX(cx); byte[] b = new byte[7 * ShapeField.BYTES]; - ShapeField.encodeTriangle(b, ayEnc, axEnc, byEnc, bxEnc, cyEnc, cxEnc); - int[] encoded = new int[6]; + ShapeField.encodeTriangle(b, ayEnc, axEnc, true, byEnc, bxEnc, true, cyEnc, cxEnc, true); + ShapeField.DecodedTriangle encoded = new ShapeField.DecodedTriangle(); ShapeField.decodeTriangle(b, encoded); - assertTrue(encoded[0] == ayEnc); - assertTrue(encoded[1] == axEnc); - assertTrue(encoded[2] == byEnc); - assertTrue(encoded[3] == bxEnc); - assertTrue(encoded[4] == cyEnc); - assertTrue(encoded[5] == cxEnc); + assertEquals(encoded.aY, ayEnc); + assertEquals(encoded.aX, axEnc); + assertEquals(encoded.bY, byEnc); + assertEquals(encoded.bX, bxEnc); + assertEquals(encoded.cY, cyEnc); + assertEquals(encoded.cX, cxEnc); } //[a,b,c] == [c,a,b] == [b,c,a] == [c,b,a] == [b,a,c] == [a,c,b] @@ -314,34 +312,34 @@ public abstract class BaseShapeEncodingTestCase extends LuceneTestCase { assertTrue(GeoUtils.orient(ayEnc, axEnc, byEnc, bxEnc, cyEnc, cxEnc) != 0); byte[] b = new byte[7 * ShapeField.BYTES]; //[a,b,c] - ShapeField.encodeTriangle(b, ayEnc, axEnc, byEnc, bxEnc, cyEnc, cxEnc); - int[] encodedABC = new int[6]; + ShapeField.encodeTriangle(b, ayEnc, axEnc, true, byEnc, bxEnc, true, cyEnc, cxEnc, false); + ShapeField.DecodedTriangle encodedABC = new ShapeField.DecodedTriangle(); ShapeField.decodeTriangle(b, encodedABC); //[c,a,b] - ShapeField.encodeTriangle(b, cyEnc, cxEnc, ayEnc, axEnc, byEnc, bxEnc); - int[] encodedCAB = new int[6]; + ShapeField.encodeTriangle(b, cyEnc, cxEnc, false, ayEnc, axEnc, true, byEnc, bxEnc, true); + ShapeField.DecodedTriangle encodedCAB = new ShapeField.DecodedTriangle(); ShapeField.decodeTriangle(b, encodedCAB); - assertTrue(Arrays.equals(encodedABC, encodedCAB)); + assertEquals(encodedABC, encodedCAB); //[b,c,a] - ShapeField.encodeTriangle(b, byEnc, bxEnc, cyEnc, cxEnc, ayEnc, axEnc); - int[] encodedBCA = new int[6]; + ShapeField.encodeTriangle(b, byEnc, bxEnc, true, cyEnc, cxEnc, false, ayEnc, axEnc, true); + ShapeField.DecodedTriangle encodedBCA = new ShapeField.DecodedTriangle(); ShapeField.decodeTriangle(b, encodedBCA); - assertTrue(Arrays.equals(encodedABC, encodedBCA)); + assertEquals(encodedABC, encodedBCA); //[c,b,a] - ShapeField.encodeTriangle(b, cyEnc, cxEnc, byEnc, bxEnc, ayEnc, axEnc); - int[] encodedCBA= new int[6]; + ShapeField.encodeTriangle(b, cyEnc, cxEnc, true, byEnc, bxEnc, true, ayEnc, axEnc, false); + ShapeField.DecodedTriangle encodedCBA= new ShapeField.DecodedTriangle(); ShapeField.decodeTriangle(b, encodedCBA); - assertTrue(Arrays.equals(encodedABC, encodedCBA)); + assertEquals(encodedABC, encodedCBA); //[b,a,c] - ShapeField.encodeTriangle(b, byEnc, bxEnc, ayEnc, axEnc, cyEnc, cxEnc); - int[] encodedBAC= new int[6]; + ShapeField.encodeTriangle(b, byEnc, bxEnc, true, ayEnc, axEnc, false, cyEnc, cxEnc, true); + ShapeField.DecodedTriangle encodedBAC= new ShapeField.DecodedTriangle(); ShapeField.decodeTriangle(b, encodedBAC); - assertTrue(Arrays.equals(encodedABC, encodedBAC)); + assertEquals(encodedABC, encodedBAC); //[a,c,b] - ShapeField.encodeTriangle(b, ayEnc, axEnc, cyEnc, cxEnc, byEnc, bxEnc); - int[] encodedACB= new int[6]; + ShapeField.encodeTriangle(b, ayEnc, axEnc, false, cyEnc, cxEnc, true, byEnc, bxEnc, true); + ShapeField.DecodedTriangle encodedACB= new ShapeField.DecodedTriangle(); ShapeField.decodeTriangle(b, encodedACB); - assertTrue(Arrays.equals(encodedABC, encodedACB)); + assertEquals(encodedABC, encodedACB); } public void testPointEncoding() { @@ -350,11 +348,15 @@ public abstract class BaseShapeEncodingTestCase extends LuceneTestCase { int latEnc = encodeY(lat); int lonEnc = encodeX(lon); byte[] b = new byte[7 * ShapeField.BYTES]; - ShapeField.encodeTriangle(b, latEnc, lonEnc, latEnc, lonEnc, latEnc, lonEnc); - int[] encoded = new int[6]; + ShapeField.encodeTriangle(b, latEnc, lonEnc, true, latEnc, lonEnc, true, latEnc, lonEnc, true); + ShapeField.DecodedTriangle encoded = new ShapeField.DecodedTriangle(); ShapeField.decodeTriangle(b, encoded); - assertTrue(encoded[0] == latEnc && encoded[2] == latEnc && encoded[4] == latEnc); - assertTrue(encoded[1] == lonEnc && encoded[3] == lonEnc && encoded[5] == lonEnc); + assertEquals(encoded.aY, latEnc); + assertEquals(encoded.aX, lonEnc); + assertEquals(encoded.bY, latEnc); + assertEquals(encoded.bX, lonEnc); + assertEquals(encoded.cY, latEnc); + assertEquals(encoded.cX, lonEnc); } public void testLineEncodingSameLat() { @@ -365,33 +367,31 @@ public abstract class BaseShapeEncodingTestCase extends LuceneTestCase { int axEnc = encodeX(ax); int bxEnc = encodeX(bx); byte[] b = new byte[7 * ShapeField.BYTES]; - ShapeField.encodeTriangle(b, latEnc, axEnc, latEnc, bxEnc, latEnc, axEnc); - int[] encoded = new int[6]; + ShapeField.encodeTriangle(b, latEnc, axEnc, true, latEnc, bxEnc, true, latEnc, axEnc, true); + ShapeField.DecodedTriangle encoded = new ShapeField.DecodedTriangle(); ShapeField.decodeTriangle(b, encoded); - assertTrue(encoded[0] == latEnc); - assertTrue(encoded[1] == axEnc); - assertTrue(encoded[2] == latEnc); - assertTrue(encoded[3] == bxEnc); - assertTrue(encoded[4] == latEnc); - assertTrue(encoded[5] == axEnc); - ShapeField.encodeTriangle(b, latEnc, axEnc, latEnc, axEnc, latEnc, bxEnc); - encoded = new int[6]; + assertEquals(encoded.aY, latEnc); + assertEquals(encoded.aX, axEnc); + assertEquals(encoded.bY, latEnc); + assertEquals(encoded.bX, bxEnc); + assertEquals(encoded.cY, latEnc); + assertEquals(encoded.cX, axEnc); + ShapeField.encodeTriangle(b, latEnc, axEnc, true, latEnc, axEnc, true, latEnc, bxEnc, true); ShapeField.decodeTriangle(b, encoded); - assertTrue(encoded[0] == latEnc); - assertTrue(encoded[1] == axEnc); - assertTrue(encoded[2] == latEnc); - assertTrue(encoded[3] == axEnc); - assertTrue(encoded[4] == latEnc); - assertTrue(encoded[5] == bxEnc); - ShapeField.encodeTriangle(b, latEnc, bxEnc, latEnc, axEnc, latEnc, axEnc); - encoded = new int[6]; + assertEquals(encoded.aY, latEnc); + assertEquals(encoded.aX, axEnc); + assertEquals(encoded.bY, latEnc); + assertEquals(encoded.bX, axEnc); + assertEquals(encoded.cY, latEnc); + assertEquals(encoded.cX, bxEnc); + ShapeField.encodeTriangle(b, latEnc, bxEnc, true, latEnc, axEnc, true, latEnc, axEnc, true); ShapeField.decodeTriangle(b, encoded); - assertTrue(encoded[0] == latEnc); - assertTrue(encoded[1] == axEnc); - assertTrue(encoded[2] == latEnc); - assertTrue(encoded[3] == bxEnc); - assertTrue(encoded[4] == latEnc); - assertTrue(encoded[5] == axEnc); + assertEquals(encoded.aY, latEnc); + assertEquals(encoded.aX, axEnc); + assertEquals(encoded.bY, latEnc); + assertEquals(encoded.bX, bxEnc); + assertEquals(encoded.cY, latEnc); + assertEquals(encoded.cX, axEnc); } public void testLineEncodingSameLon() { @@ -402,33 +402,31 @@ public abstract class BaseShapeEncodingTestCase extends LuceneTestCase { int byEnc = encodeY(by); int lonEnc = encodeX(lon); byte[] b = new byte[7 * ShapeField.BYTES]; - ShapeField.encodeTriangle(b, ayEnc, lonEnc, byEnc, lonEnc, ayEnc, lonEnc); - int[] encoded = new int[6]; + ShapeField.encodeTriangle(b, ayEnc, lonEnc, true, byEnc, lonEnc, true, ayEnc, lonEnc, true); + ShapeField.DecodedTriangle encoded = new ShapeField.DecodedTriangle(); ShapeField.decodeTriangle(b, encoded); - assertTrue(encoded[0] == ayEnc); - assertTrue(encoded[1] == lonEnc); - assertTrue(encoded[2] == byEnc); - assertTrue(encoded[3] == lonEnc); - assertTrue(encoded[4] == ayEnc); - assertTrue(encoded[5] == lonEnc); - ShapeField.encodeTriangle(b, ayEnc, lonEnc, ayEnc, lonEnc, byEnc, lonEnc); - encoded = new int[6]; + assertEquals(encoded.aY, ayEnc); + assertEquals(encoded.aX, lonEnc); + assertEquals(encoded.bY, byEnc); + assertEquals(encoded.bX, lonEnc); + assertEquals(encoded.cY, ayEnc); + assertEquals(encoded.cX, lonEnc); + ShapeField.encodeTriangle(b, ayEnc, lonEnc, true, ayEnc, lonEnc, true, byEnc, lonEnc, true); ShapeField.decodeTriangle(b, encoded); - assertTrue(encoded[0] == ayEnc); - assertTrue(encoded[1] == lonEnc); - assertTrue(encoded[2] == ayEnc); - assertTrue(encoded[3] == lonEnc); - assertTrue(encoded[4] == byEnc); - assertTrue(encoded[5] == lonEnc); - ShapeField.encodeTriangle(b, byEnc, lonEnc, ayEnc, lonEnc, ayEnc, lonEnc); - encoded = new int[6]; + assertEquals(encoded.aY, ayEnc); + assertEquals(encoded.aX, lonEnc); + assertEquals(encoded.bY, ayEnc); + assertEquals(encoded.bX, lonEnc); + assertEquals(encoded.cY, byEnc); + assertEquals(encoded.cX, lonEnc); + ShapeField.encodeTriangle(b, byEnc, lonEnc, true, ayEnc, lonEnc, true, ayEnc, lonEnc, true); ShapeField.decodeTriangle(b, encoded); - assertTrue(encoded[0] == ayEnc); - assertTrue(encoded[1] == lonEnc); - assertTrue(encoded[2] == byEnc); - assertTrue(encoded[3] == lonEnc); - assertTrue(encoded[4] == ayEnc); - assertTrue(encoded[5] == lonEnc); + assertEquals(encoded.aY, ayEnc); + assertEquals(encoded.aX, lonEnc); + assertEquals(encoded.bY, byEnc); + assertEquals(encoded.bX, lonEnc); + assertEquals(encoded.cY, ayEnc); + assertEquals(encoded.cX, lonEnc); } public void testLineEncoding() { @@ -441,33 +439,31 @@ public abstract class BaseShapeEncodingTestCase extends LuceneTestCase { int axEnc = encodeX(ax); int bxEnc = encodeX(bx); byte[] b = new byte[7 * ShapeField.BYTES]; - ShapeField.encodeTriangle(b, ayEnc, axEnc, byEnc, bxEnc, ayEnc, axEnc); - int[] encoded = new int[6]; + ShapeField.encodeTriangle(b, ayEnc, axEnc, true, byEnc, bxEnc, true, ayEnc, axEnc, true); + ShapeField.DecodedTriangle encoded = new ShapeField.DecodedTriangle(); ShapeField.decodeTriangle(b, encoded); - assertTrue(encoded[0] == ayEnc); - assertTrue(encoded[1] == axEnc); - assertTrue(encoded[2] == byEnc); - assertTrue(encoded[3] == bxEnc); - assertTrue(encoded[4] == ayEnc); - assertTrue(encoded[5] == axEnc); - ShapeField.encodeTriangle(b, ayEnc, axEnc, ayEnc, axEnc, byEnc, bxEnc); - encoded = new int[6]; + assertEquals(encoded.aY, ayEnc); + assertEquals(encoded.aX, axEnc); + assertEquals(encoded.bY, byEnc); + assertEquals(encoded.bX, bxEnc); + assertEquals(encoded.cY, ayEnc); + assertEquals(encoded.cX, axEnc); + ShapeField.encodeTriangle(b, ayEnc, axEnc, true, ayEnc, axEnc, true, byEnc, bxEnc, true); ShapeField.decodeTriangle(b, encoded); - assertTrue(encoded[0] == ayEnc); - assertTrue(encoded[1] == axEnc); - assertTrue(encoded[2] == ayEnc); - assertTrue(encoded[3] == axEnc); - assertTrue(encoded[4] == byEnc); - assertTrue(encoded[5] == bxEnc); - ShapeField.encodeTriangle(b, byEnc, bxEnc, ayEnc, axEnc, ayEnc, axEnc); - encoded = new int[6]; + assertEquals(encoded.aY, ayEnc); + assertEquals(encoded.aX, axEnc); + assertEquals(encoded.bY, ayEnc); + assertEquals(encoded.bX, axEnc); + assertEquals(encoded.cY, byEnc); + assertEquals(encoded.cX, bxEnc); + ShapeField.encodeTriangle(b, byEnc, bxEnc, true, ayEnc, axEnc, true, ayEnc, axEnc, true); ShapeField.decodeTriangle(b, encoded); - assertTrue(encoded[0] == ayEnc); - assertTrue(encoded[1] == axEnc); - assertTrue(encoded[2] == byEnc); - assertTrue(encoded[3] == bxEnc); - assertTrue(encoded[4] == ayEnc); - assertTrue(encoded[5] == axEnc); + assertEquals(encoded.aY, ayEnc); + assertEquals(encoded.aX, axEnc); + assertEquals(encoded.bY, byEnc); + assertEquals(encoded.bX, bxEnc); + assertEquals(encoded.cY, ayEnc); + assertEquals(encoded.cX, axEnc); } public void testRandomPointEncoding() { @@ -505,16 +501,16 @@ public abstract class BaseShapeEncodingTestCase extends LuceneTestCase { //quantize the triangle byte[] b = new byte[7 * ShapeField.BYTES]; - ShapeField.encodeTriangle(b, original[0], original[1], original[2], original[3], original[4], original[5]); - int[] encoded = new int[6]; + ShapeField.encodeTriangle(b, original[0], original[1], true, original[2], original[3], true, original[4], original[5], true); + ShapeField.DecodedTriangle encoded = new ShapeField.DecodedTriangle(); ShapeField.decodeTriangle(b, encoded); double[] encodedQuantize = new double[] { - decodeY(encoded[0]), - decodeX(encoded[1]), - decodeY(encoded[2]), - decodeX(encoded[3]), - decodeY(encoded[4]), - decodeX(encoded[5])}; + decodeY(encoded.aY), + decodeX(encoded.aX), + decodeY(encoded.bY), + decodeX(encoded.bX), + decodeY(encoded.cY), + decodeX(encoded.cX)}; int orientation = GeoUtils.orient(original[1], original[0], original[3], original[2], original[5], original[4]); //quantize original @@ -560,14 +556,14 @@ public abstract class BaseShapeEncodingTestCase extends LuceneTestCase { int cyEnc = encodeY(cy); int cxEnc = encodeX(cx); byte[] b = new byte[7 * ShapeField.BYTES]; - ShapeField.encodeTriangle(b, ayEnc, axEnc, byEnc, bxEnc, cyEnc, cxEnc); - int[] encoded = new int[6]; + ShapeField.encodeTriangle(b, ayEnc, axEnc, true, byEnc, bxEnc, true, cyEnc, cxEnc, true); + ShapeField.DecodedTriangle encoded = new ShapeField.DecodedTriangle(); ShapeField.decodeTriangle(b, encoded); - assertTrue(encoded[0] == byEnc); - assertTrue(encoded[1] == bxEnc); - assertTrue(encoded[2] == cyEnc); - assertTrue(encoded[3] == cxEnc); - assertTrue(encoded[4] == ayEnc); - assertTrue(encoded[5] == axEnc); + assertTrue(encoded.aY == byEnc); + assertTrue(encoded.aX == bxEnc); + assertTrue(encoded.bY == cyEnc); + assertTrue(encoded.bX == cxEnc); + assertTrue(encoded.cY == ayEnc); + assertTrue(encoded.cX == axEnc); } } diff --git a/lucene/sandbox/src/test/org/apache/lucene/document/BaseShapeTestCase.java b/lucene/sandbox/src/test/org/apache/lucene/document/BaseShapeTestCase.java index 1e4e173dcdd..a7f53feaa31 100644 --- a/lucene/sandbox/src/test/org/apache/lucene/document/BaseShapeTestCase.java +++ b/lucene/sandbox/src/test/org/apache/lucene/document/BaseShapeTestCase.java @@ -545,8 +545,8 @@ public abstract class BaseShapeTestCase extends LuceneTestCase { abstract double quantizeXCeil(double raw); abstract double quantizeY(double raw); abstract double quantizeYCeil(double raw); - abstract double[] quantizeTriangle(double ax, double ay, double bx, double by, double cx, double cy); - abstract int[] encodeDecodeTriangle(double ax, double ay, double bx, double by, double cx, double cy); + abstract double[] quantizeTriangle(double ax, double ay, boolean ab, double bx, double by, boolean bc, double cx, double cy, boolean ca); + abstract ShapeField.DecodedTriangle encodeDecodeTriangle(double ax, double ay, boolean ab, double bx, double by, boolean bc, double cx, double cy, boolean ca); } private int scaledIterationCount(int shapes) { diff --git a/lucene/sandbox/src/test/org/apache/lucene/document/BaseXYShapeTestCase.java b/lucene/sandbox/src/test/org/apache/lucene/document/BaseXYShapeTestCase.java index b706b5f20cd..0ef15c5983b 100644 --- a/lucene/sandbox/src/test/org/apache/lucene/document/BaseXYShapeTestCase.java +++ b/lucene/sandbox/src/test/org/apache/lucene/document/BaseXYShapeTestCase.java @@ -144,18 +144,18 @@ public abstract class BaseXYShapeTestCase extends BaseShapeTestCase { } @Override - double[] quantizeTriangle(double ax, double ay, double bx, double by, double cx, double cy) { - int[] decoded = encodeDecodeTriangle(ax, ay, bx, by, cx, cy); - return new double[]{decode(decoded[0]), decode(decoded[1]), decode(decoded[2]), decode(decoded[3]), decode(decoded[4]), decode(decoded[5])}; + double[] quantizeTriangle(double ax, double ay, boolean ab, double bx, double by, boolean bc, double cx, double cy, boolean ca) { + ShapeField.DecodedTriangle decoded = encodeDecodeTriangle(ax, ay, ab, bx, by, bc, cx, cy, ca); + return new double[]{decode(decoded.aY), decode(decoded.aX), decode(decoded.bY), decode(decoded.bX), decode(decoded.cY), decode(decoded.cX)}; } @Override - int[] encodeDecodeTriangle(double ax, double ay, double bx, double by, double cx, double cy) { + ShapeField.DecodedTriangle encodeDecodeTriangle(double ax, double ay, boolean ab, double bx, double by, boolean bc, double cx, double cy, boolean ca) { byte[] encoded = new byte[7 * ShapeField.BYTES]; - ShapeField.encodeTriangle(encoded, encode(ay), encode(ax), encode(by), encode(bx), encode(cy), encode(cx)); - int[] decoded = new int[6]; - ShapeField.decodeTriangle(encoded, decoded); - return decoded; + ShapeField.encodeTriangle(encoded, encode(ay), encode(ax), ab, encode(by), encode(bx), bc, encode(cy), encode(cx), ca); + ShapeField.DecodedTriangle triangle = new ShapeField.DecodedTriangle(); + ShapeField.decodeTriangle(encoded, triangle); + return triangle; } }; } diff --git a/lucene/sandbox/src/test/org/apache/lucene/document/TestLatLonLineShapeQueries.java b/lucene/sandbox/src/test/org/apache/lucene/document/TestLatLonLineShapeQueries.java index d7ed52946d1..fa31b00071e 100644 --- a/lucene/sandbox/src/test/org/apache/lucene/document/TestLatLonLineShapeQueries.java +++ b/lucene/sandbox/src/test/org/apache/lucene/document/TestLatLonLineShapeQueries.java @@ -83,13 +83,13 @@ public class TestLatLonLineShapeQueries extends BaseLatLonShapeTestCase { Line line = (Line)shape; Rectangle2D rectangle2D = Rectangle2D.create(new Rectangle(minLat, maxLat, minLon, maxLon)); for (int i = 0, j = 1; j < line.numPoints(); ++i, ++j) { - int[] decoded = encoder.encodeDecodeTriangle(line.getLon(i), line.getLat(i), line.getLon(j), line.getLat(j), line.getLon(i), line.getLat(i)); + ShapeField.DecodedTriangle decoded = encoder.encodeDecodeTriangle(line.getLon(i), line.getLat(i), true, line.getLon(j), line.getLat(j), true, line.getLon(i), line.getLat(i), true); if (queryRelation == QueryRelation.WITHIN) { - if (rectangle2D.containsTriangle(decoded[1], decoded[0], decoded[3], decoded[2], decoded[5], decoded[4]) == false) { + if (rectangle2D.containsTriangle(decoded.aX, decoded.aY, decoded.bX, decoded.bY, decoded.cX, decoded.cY) == false) { return false; } } else { - if (rectangle2D.intersectsTriangle(decoded[1], decoded[0], decoded[3], decoded[2], decoded[5], decoded[4]) == true) { + if (rectangle2D.intersectsTriangle(decoded.aX, decoded.aY, decoded.bX, decoded.bY, decoded.cX, decoded.cY) == true) { return queryRelation == QueryRelation.INTERSECTS; } } @@ -110,7 +110,7 @@ public class TestLatLonLineShapeQueries extends BaseLatLonShapeTestCase { private boolean testLine(EdgeTree queryPoly, Line line) { for (int i = 0, j = 1; j < line.numPoints(); ++i, ++j) { - double[] qTriangle = encoder.quantizeTriangle(line.getLon(i), line.getLat(i), line.getLon(j), line.getLat(j), line.getLon(i), line.getLat(i)); + double[] qTriangle = encoder.quantizeTriangle(line.getLon(i), line.getLat(i), true, line.getLon(j), line.getLat(j), true, line.getLon(i), line.getLat(i), true); Relation r = queryPoly.relateTriangle(qTriangle[1], qTriangle[0], qTriangle[3], qTriangle[2], qTriangle[5], qTriangle[4]); if (queryRelation == QueryRelation.DISJOINT) { if (r != Relation.CELL_OUTSIDE_QUERY) return false; diff --git a/lucene/sandbox/src/test/org/apache/lucene/document/TestLatLonPolygonShapeQueries.java b/lucene/sandbox/src/test/org/apache/lucene/document/TestLatLonPolygonShapeQueries.java index 8b3cab4edda..8fdbf5c182c 100644 --- a/lucene/sandbox/src/test/org/apache/lucene/document/TestLatLonPolygonShapeQueries.java +++ b/lucene/sandbox/src/test/org/apache/lucene/document/TestLatLonPolygonShapeQueries.java @@ -72,13 +72,15 @@ public class TestLatLonPolygonShapeQueries extends BaseLatLonShapeTestCase { Rectangle2D rectangle2D = Rectangle2D.create(new Rectangle(minLat, maxLat, minLon, maxLon)); List tessellation = Tessellator.tessellate(p); for (Tessellator.Triangle t : tessellation) { - int[] decoded = encoder.encodeDecodeTriangle(t.getX(0), t.getY(0), t.getX(1), t.getY(1), t.getX(2), t.getY(2)); + ShapeField.DecodedTriangle decoded = encoder.encodeDecodeTriangle(t.getX(0), t.getY(0), t.isEdgefromPolygon(0), + t.getX(1), t.getY(1), t.isEdgefromPolygon(1), + t.getX(2), t.getY(2), t.isEdgefromPolygon(2)); if (queryRelation == QueryRelation.WITHIN) { - if (rectangle2D.containsTriangle(decoded[1], decoded[0], decoded[3], decoded[2], decoded[5], decoded[4]) == false) { + if (rectangle2D.containsTriangle(decoded.aX, decoded.aY, decoded.bX, decoded.bY, decoded.cX, decoded.cY) == false) { return false; } } else { - if (rectangle2D.intersectsTriangle(decoded[1], decoded[0], decoded[3], decoded[2], decoded[5], decoded[4]) == true) { + if (rectangle2D.intersectsTriangle(decoded.aX, decoded.aY, decoded.bX, decoded.bY, decoded.cX, decoded.cY) == true) { return queryRelation == QueryRelation.INTERSECTS; } } @@ -99,7 +101,9 @@ public class TestLatLonPolygonShapeQueries extends BaseLatLonShapeTestCase { private boolean testPolygon(EdgeTree tree, Polygon shape) { List tessellation = Tessellator.tessellate(shape); for (Tessellator.Triangle t : tessellation) { - double[] qTriangle = encoder.quantizeTriangle(t.getX(0), t.getY(0), t.getX(1), t.getY(1), t.getX(2), t.getY(2)); + double[] qTriangle = encoder.quantizeTriangle(t.getX(0), t.getY(0), t.isEdgefromPolygon(0), + t.getX(1), t.getY(1), t.isEdgefromPolygon(1), + t.getX(2), t.getY(2), t.isEdgefromPolygon(2)); Relation r = tree.relateTriangle(qTriangle[1], qTriangle[0], qTriangle[3], qTriangle[2], qTriangle[5], qTriangle[4]); if (queryRelation == QueryRelation.DISJOINT) { if (r != Relation.CELL_OUTSIDE_QUERY) return false; diff --git a/lucene/sandbox/src/test/org/apache/lucene/document/TestLatLonShape.java b/lucene/sandbox/src/test/org/apache/lucene/document/TestLatLonShape.java index 66948a42ebc..22ae32c5ea8 100644 --- a/lucene/sandbox/src/test/org/apache/lucene/document/TestLatLonShape.java +++ b/lucene/sandbox/src/test/org/apache/lucene/document/TestLatLonShape.java @@ -236,12 +236,13 @@ public class TestLatLonShape extends LuceneTestCase { Tessellator.Triangle t = Tessellator.tessellate(poly).get(0); byte[] encoded = new byte[7 * ShapeField.BYTES]; - ShapeField.encodeTriangle(encoded, encodeLatitude(t.getY(0)), encodeLongitude(t.getX(0)), - encodeLatitude(t.getY(1)), encodeLongitude(t.getX(1)), encodeLatitude(t.getY(2)), encodeLongitude(t.getX(2))); - int[] decoded = new int[6]; + ShapeField.encodeTriangle(encoded, encodeLatitude(t.getY(0)), encodeLongitude(t.getX(0)), t.isEdgefromPolygon(0), + encodeLatitude(t.getY(1)), encodeLongitude(t.getX(1)), t.isEdgefromPolygon(1), + encodeLatitude(t.getY(2)), encodeLongitude(t.getX(2)), t.isEdgefromPolygon(2)); + ShapeField.DecodedTriangle decoded = new ShapeField.DecodedTriangle(); ShapeField.decodeTriangle(encoded, decoded); - int expected =rectangle2D.intersectsTriangle(decoded[1], decoded[0], decoded[3], decoded[2], decoded[5], decoded[4]) ? 0 : 1; + int expected =rectangle2D.intersectsTriangle(decoded.aX, decoded.aY, decoded.bX, decoded.bY, decoded.cX, decoded.cY) ? 0 : 1; Document document = new Document(); addPolygonsToDoc(FIELDNAME, document, poly); diff --git a/lucene/sandbox/src/test/org/apache/lucene/document/TestXYLineShapeQueries.java b/lucene/sandbox/src/test/org/apache/lucene/document/TestXYLineShapeQueries.java index c66b9d1e079..5f91175c604 100644 --- a/lucene/sandbox/src/test/org/apache/lucene/document/TestXYLineShapeQueries.java +++ b/lucene/sandbox/src/test/org/apache/lucene/document/TestXYLineShapeQueries.java @@ -81,13 +81,13 @@ public class TestXYLineShapeQueries extends BaseXYShapeTestCase { XYLine line = (XYLine)shape; XYRectangle2D rectangle2D = XYRectangle2D.create(new XYRectangle(minX, maxX, minY, maxY)); for (int i = 0, j = 1; j < line.numPoints(); ++i, ++j) { - int[] decoded = encoder.encodeDecodeTriangle(line.getX(i), line.getY(i), line.getX(j), line.getY(j), line.getX(i), line.getY(i)); + ShapeField.DecodedTriangle decoded = encoder.encodeDecodeTriangle(line.getX(i), line.getY(i), true, line.getX(j), line.getY(j), true, line.getX(i), line.getY(i), true); if (queryRelation == QueryRelation.WITHIN) { - if (rectangle2D.containsTriangle(decoded[1], decoded[0], decoded[3], decoded[2], decoded[5], decoded[4]) == false) { + if (rectangle2D.containsTriangle(decoded.aX, decoded.aY, decoded.bX, decoded.bY, decoded.cX, decoded.cY) == false) { return false; } } else { - if (rectangle2D.intersectsTriangle(decoded[1], decoded[0], decoded[3], decoded[2], decoded[5], decoded[4]) == true) { + if (rectangle2D.intersectsTriangle(decoded.aX, decoded.aY, decoded.bX, decoded.bY, decoded.cX, decoded.cY) == true) { return queryRelation == QueryRelation.INTERSECTS; } } @@ -108,7 +108,7 @@ public class TestXYLineShapeQueries extends BaseXYShapeTestCase { private boolean testLine(EdgeTree queryPoly, XYLine line) { for (int i = 0, j = 1; j < line.numPoints(); ++i, ++j) { - double[] qTriangle = encoder.quantizeTriangle(line.getX(i), line.getY(i), line.getX(j), line.getY(j), line.getX(i), line.getY(i)); + double[] qTriangle = encoder.quantizeTriangle(line.getX(i), line.getY(i), true, line.getX(j), line.getY(j), true, line.getX(i), line.getY(i), true); Relation r = queryPoly.relateTriangle(qTriangle[1], qTriangle[0], qTriangle[3], qTriangle[2], qTriangle[5], qTriangle[4]); if (queryRelation == QueryRelation.DISJOINT) { if (r != Relation.CELL_OUTSIDE_QUERY) return false; diff --git a/lucene/sandbox/src/test/org/apache/lucene/document/TestXYPolygonShapeQueries.java b/lucene/sandbox/src/test/org/apache/lucene/document/TestXYPolygonShapeQueries.java index 82d887aa258..bbcc554f26a 100644 --- a/lucene/sandbox/src/test/org/apache/lucene/document/TestXYPolygonShapeQueries.java +++ b/lucene/sandbox/src/test/org/apache/lucene/document/TestXYPolygonShapeQueries.java @@ -72,13 +72,15 @@ public class TestXYPolygonShapeQueries extends BaseXYShapeTestCase { XYRectangle2D rectangle2D = XYRectangle2D.create(new XYRectangle(minX, maxX, minY, maxY)); List tessellation = Tessellator.tessellate(p); for (Tessellator.Triangle t : tessellation) { - int[] decoded = encoder.encodeDecodeTriangle(t.getX(0), t.getY(0), t.getX(1), t.getY(1), t.getX(2), t.getY(2)); + ShapeField.DecodedTriangle decoded = encoder.encodeDecodeTriangle(t.getX(0), t.getY(0), t.isEdgefromPolygon(0), + t.getX(1), t.getY(1), t.isEdgefromPolygon(1), + t.getX(2), t.getY(2), t.isEdgefromPolygon(2)); if (queryRelation == QueryRelation.WITHIN) { - if (rectangle2D.containsTriangle(decoded[1], decoded[0], decoded[3], decoded[2], decoded[5], decoded[4]) == false) { + if (rectangle2D.containsTriangle(decoded.aX, decoded.aY, decoded.bX, decoded.bY, decoded.cX, decoded.cY) == false) { return false; } } else { - if (rectangle2D.intersectsTriangle(decoded[1], decoded[0], decoded[3], decoded[2], decoded[5], decoded[4]) == true) { + if (rectangle2D.intersectsTriangle(decoded.aX, decoded.aY, decoded.bX, decoded.bY, decoded.cX, decoded.cY) == true) { return queryRelation == QueryRelation.INTERSECTS; } } @@ -99,7 +101,9 @@ public class TestXYPolygonShapeQueries extends BaseXYShapeTestCase { private boolean testPolygon(EdgeTree tree, XYPolygon shape) { List tessellation = Tessellator.tessellate(shape); for (Tessellator.Triangle t : tessellation) { - double[] qTriangle = encoder.quantizeTriangle(t.getX(0), t.getY(0), t.getX(1), t.getY(1), t.getX(2), t.getY(2)); + double[] qTriangle = encoder.quantizeTriangle(t.getX(0), t.getY(0), t.isEdgefromPolygon(0), + t.getX(1), t.getY(1), t.isEdgefromPolygon(1), + t.getX(2), t.getY(2), t.isEdgefromPolygon(2)); Relation r = tree.relateTriangle(qTriangle[1], qTriangle[0], qTriangle[3], qTriangle[2], qTriangle[5], qTriangle[4]); if (queryRelation == QueryRelation.DISJOINT) { if (r != Relation.CELL_OUTSIDE_QUERY) return false; diff --git a/lucene/sandbox/src/test/org/apache/lucene/geo/TestTessellator.java b/lucene/sandbox/src/test/org/apache/lucene/geo/TestTessellator.java index a6dbd5ff241..485ae9a9624 100644 --- a/lucene/sandbox/src/test/org/apache/lucene/geo/TestTessellator.java +++ b/lucene/sandbox/src/test/org/apache/lucene/geo/TestTessellator.java @@ -554,6 +554,9 @@ public class TestTessellator extends LuceneTestCase { Polygon polygon = (Polygon) SimpleWKTShapeParser.parse(wkt); List tessellation = Tessellator.tessellate(polygon); assertEquals(area(polygon), area(tessellation), 0.0); + for (Tessellator.Triangle t : tessellation) { + checkTriangleEdgesFromPolygon(polygon, t); + } } private double area(Polygon p) { @@ -578,4 +581,77 @@ public class TestTessellator extends LuceneTestCase { } return area; } + + private void checkTriangleEdgesFromPolygon(Polygon p, Tessellator.Triangle t) { + // first edge + assertEquals(t.isEdgefromPolygon(0), isEdgeFromPolygon(p, t.getX(0), t.getY(0), t.getX(1), t.getY(1))); + // second edge + assertEquals(t.isEdgefromPolygon(1), isEdgeFromPolygon(p, t.getX(1), t.getY(1), t.getX(2), t.getY(2))); + // third edge + assertEquals(t.isEdgefromPolygon(2), isEdgeFromPolygon(p, t.getX(2), t.getY(2), t.getX(0), t.getY(0))); + } + + private boolean isEdgeFromPolygon(Polygon p, double aLon, double aLat, double bLon, double bLat) { + for (int i = 0; i < p.getPolyLats().length - 1; i++) { + if (isPointInLine(p.getPolyLon(i), p.getPolyLat(i), p.getPolyLon(i + 1), p.getPolyLat(i + 1), aLon, aLat) && + isPointInLine(p.getPolyLon(i), p.getPolyLat(i), p.getPolyLon(i + 1), p.getPolyLat(i + 1), bLon, bLat)) { + return true; + } + if (p.getPolyLon(i) != p.getPolyLon(i + 1) || p.getPolyLat(i) != p.getPolyLat(i + 1)) { + //Check for co-planar points + final int length = p.getPolyLats().length; + final int offset = i + 2; + int j = 0; + int index = getIndex(length, j + offset); + while (j < length && area(p.getPolyLon(i), p.getPolyLat(i), p.getPolyLon(i + 1), p.getPolyLat(i + 1), p.getPolyLon(index), p.getPolyLat(index)) == 0) { + if (isPointInLine(p.getPolyLon(i), p.getPolyLat(i), p.getPolyLon(index), p.getPolyLat(index), aLon, aLat) && + isPointInLine(p.getPolyLon(i), p.getPolyLat(i), p.getPolyLon(index), p.getPolyLat(index), bLon, bLat)) { + return true; + } + index = getIndex(length, ++j + offset); + } + } + } + if (p.getHoles() != null && p.getHoles().length > 0) { + for (Polygon hole : p.getHoles()) { + if (isEdgeFromPolygon(hole, aLon, aLat, bLon, bLat)) { + return true; + } + } + } + return false; + } + + private int getIndex(int size, int index) { + if (index < size) { + return index; + } + return index - size; + } + + /** Compute signed area of triangle */ + private double area(final double aX, final double aY, final double bX, final double bY, + final double cX, final double cY) { + return (bY - aY) * (cX - bX) - (bX - aX) * (cY - bY); + } + + private boolean isPointInLine(final double aX, final double aY, final double bX, final double bY, double lon, double lat) { + double dxc = lon - aX; + double dyc = lat - aY; + + double dxl = bX - aX; + double dyl = bY - aY; + + if (dxc * dyl - dyc * dxl == 0) { + if (Math.abs(dxl) >= Math.abs(dyl)) + return dxl > 0 ? + aX <= lon && lon <= bX : + bX <= lon && lon <= aX; + else + return dyl > 0 ? + aY <= lat && lat <= bY : + bY <= lat && lat <= aY; + } + return false; + } } \ No newline at end of file