LUCENE-8620: Update Tessellator logic to label if triangle edges belongs to the original polygon (#771)

This commit is contained in:
Ignacio Vera 2019-09-09 11:16:43 +02:00 committed by GitHub
parent 9510e06612
commit 62001b9b96
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 677 additions and 367 deletions

View File

@ -127,6 +127,9 @@ Improvements
* LUCENE-8952: Use a sort key instead of true distance in NearestNeighbor (Julie Tibshirani). * 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 Optimizations
* LUCENE-8922: DisjunctionMaxQuery more efficiently leverages impacts to skip * LUCENE-8922: DisjunctionMaxQuery more efficiently leverages impacts to skip

View File

@ -47,16 +47,16 @@ final class LatLonShapeBoundingBoxQuery extends ShapeQuery {
/** returns true if the query matches the encoded triangle */ /** returns true if the query matches the encoded triangle */
@Override @Override
protected boolean queryMatches(byte[] t, int[] scratchTriangle, QueryRelation queryRelation) { protected boolean queryMatches(byte[] t, ShapeField.DecodedTriangle scratchTriangle, QueryRelation queryRelation) {
// decode indexed triangle // decode indexed triangle
ShapeField.decodeTriangle(t, scratchTriangle); ShapeField.decodeTriangle(t, scratchTriangle);
int aY = scratchTriangle[0]; int aY = scratchTriangle.aY;
int aX = scratchTriangle[1]; int aX = scratchTriangle.aX;
int bY = scratchTriangle[2]; int bY = scratchTriangle.bY;
int bX = scratchTriangle[3]; int bX = scratchTriangle.bX;
int cY = scratchTriangle[4]; int cY = scratchTriangle.cY;
int cX = scratchTriangle[5]; int cX = scratchTriangle.cX;
if (queryRelation == QueryRelation.WITHIN) { if (queryRelation == QueryRelation.WITHIN) {
return rectangle2D.containsTriangle(aX, aY, bX, bY, cX, cY); return rectangle2D.containsTriangle(aX, aY, bX, bY, cX, cY);

View File

@ -84,15 +84,15 @@ final class LatLonShapeLineQuery extends ShapeQuery {
} }
@Override @Override
protected boolean queryMatches(byte[] t, int[] scratchTriangle, QueryRelation queryRelation) { protected boolean queryMatches(byte[] t, ShapeField.DecodedTriangle scratchTriangle, QueryRelation queryRelation) {
ShapeField.decodeTriangle(t, scratchTriangle); ShapeField.decodeTriangle(t, scratchTriangle);
double alat = GeoEncodingUtils.decodeLatitude(scratchTriangle[0]); double alat = GeoEncodingUtils.decodeLatitude(scratchTriangle.aY);
double alon = GeoEncodingUtils.decodeLongitude(scratchTriangle[1]); double alon = GeoEncodingUtils.decodeLongitude(scratchTriangle.aX);
double blat = GeoEncodingUtils.decodeLatitude(scratchTriangle[2]); double blat = GeoEncodingUtils.decodeLatitude(scratchTriangle.bY);
double blon = GeoEncodingUtils.decodeLongitude(scratchTriangle[3]); double blon = GeoEncodingUtils.decodeLongitude(scratchTriangle.bX);
double clat = GeoEncodingUtils.decodeLatitude(scratchTriangle[4]); double clat = GeoEncodingUtils.decodeLatitude(scratchTriangle.cY);
double clon = GeoEncodingUtils.decodeLongitude(scratchTriangle[5]); double clon = GeoEncodingUtils.decodeLongitude(scratchTriangle.cX);
if (queryRelation == QueryRelation.WITHIN) { if (queryRelation == QueryRelation.WITHIN) {
return line2D.relateTriangle(alon, alat, blon, blat, clon, clat) == Relation.CELL_INSIDE_QUERY; return line2D.relateTriangle(alon, alat, blon, blat, clon, clat) == Relation.CELL_INSIDE_QUERY;

View File

@ -78,15 +78,15 @@ final class LatLonShapePolygonQuery extends ShapeQuery {
} }
@Override @Override
protected boolean queryMatches(byte[] t, int[] scratchTriangle, QueryRelation queryRelation) { protected boolean queryMatches(byte[] t, ShapeField.DecodedTriangle scratchTriangle, QueryRelation queryRelation) {
ShapeField.decodeTriangle(t, scratchTriangle); ShapeField.decodeTriangle(t, scratchTriangle);
double alat = GeoEncodingUtils.decodeLatitude(scratchTriangle[0]); double alat = GeoEncodingUtils.decodeLatitude(scratchTriangle.aY);
double alon = GeoEncodingUtils.decodeLongitude(scratchTriangle[1]); double alon = GeoEncodingUtils.decodeLongitude(scratchTriangle.aX);
double blat = GeoEncodingUtils.decodeLatitude(scratchTriangle[2]); double blat = GeoEncodingUtils.decodeLatitude(scratchTriangle.bY);
double blon = GeoEncodingUtils.decodeLongitude(scratchTriangle[3]); double blon = GeoEncodingUtils.decodeLongitude(scratchTriangle.bX);
double clat = GeoEncodingUtils.decodeLatitude(scratchTriangle[4]); double clat = GeoEncodingUtils.decodeLatitude(scratchTriangle.cY);
double clon = GeoEncodingUtils.decodeLongitude(scratchTriangle[5]); double clon = GeoEncodingUtils.decodeLongitude(scratchTriangle.cX);
if (queryRelation == QueryRelation.WITHIN) { if (queryRelation == QueryRelation.WITHIN) {
return poly2D.relateTriangle(alon, alat, blon, blat, clon, clat) == Relation.CELL_INSIDE_QUERY; return poly2D.relateTriangle(alon, alat, blon, blat, clon, clat) == Relation.CELL_INSIDE_QUERY;

View File

@ -16,6 +16,8 @@
*/ */
package org.apache.lucene.document; package org.apache.lucene.document;
import java.util.Objects;
import org.apache.lucene.geo.GeoUtils; import org.apache.lucene.geo.GeoUtils;
import org.apache.lucene.geo.Line; import org.apache.lucene.geo.Line;
import org.apache.lucene.geo.Polygon; import org.apache.lucene.geo.Polygon;
@ -57,18 +59,22 @@ public final class ShapeField {
*/ */
public static class Triangle extends Field { 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) { Triangle(String name, int aXencoded, int aYencoded, int bXencoded, int bYencoded, int cXencoded, int cYencoded) {
super(name, TYPE); 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) { Triangle(String name, Tessellator.Triangle t) {
super(name, TYPE); 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 */ /** 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; final byte[] bytes;
if (fieldsData == null) { if (fieldsData == null) {
@ -77,7 +83,7 @@ public final class ShapeField {
} else { } else {
bytes = ((BytesRef) fieldsData).bytes; 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. * 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. * 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; assert bytes.length == 7 * BYTES;
int aX; int aX;
int bX; int bX;
@ -108,6 +114,7 @@ public final class ShapeField {
int aY; int aY;
int bY; int bY;
int cY; int cY;
boolean ab, bc, ca;
//change orientation if CW //change orientation if CW
if (GeoUtils.orient(aLon, aLat, bLon, bLat, cLon, cLat) == -1) { if (GeoUtils.orient(aLon, aLat, bLon, bLat, cLon, cLat) == -1) {
aX = cLon; aX = cLon;
@ -116,6 +123,9 @@ public final class ShapeField {
aY = cLat; aY = cLat;
bY = bLat; bY = bLat;
cY = aLat; cY = aLat;
ab = bcFromShape;
bc = abFromShape;
ca = caFromShape;
} else { } else {
aX = aLon; aX = aLon;
bX = bLon; bX = bLon;
@ -123,27 +133,38 @@ public final class ShapeField {
aY = aLat; aY = aLat;
bY = bLat; bY = bLat;
cY = cLat; cY = cLat;
ab = abFromShape;
bc = bcFromShape;
ca = caFromShape;
} }
//rotate edges and place minX at the beginning //rotate edges and place minX at the beginning
if (bX < aX || cX < aX) { if (bX < aX || cX < aX) {
if (bX < cX) { if (bX < cX) {
int tempX = aX; int tempX = aX;
int tempY = aY; int tempY = aY;
boolean tempBool = ab;
aX = bX; aX = bX;
aY = bY; aY = bY;
ab = bc;
bX = cX; bX = cX;
bY = cY; bY = cY;
bc = ca;
cX = tempX; cX = tempX;
cY = tempY; cY = tempY;
ca = tempBool;
} else if (cX < aX) { } else if (cX < aX) {
int tempX = aX; int tempX = aX;
int tempY = aY; int tempY = aY;
boolean tempBool = ab;
aX = cX; aX = cX;
aY = cY; aY = cY;
ab = ca;
cX = bX; cX = bX;
cY = bY; cY = bY;
ca = bc;
bX = tempX; bX = tempX;
bY = tempY; bY = tempY;
bc = tempBool;
} }
} else if (aX == bX && aX == cX) { } else if (aX == bX && aX == cX) {
//degenerated case, all points with same longitude //degenerated case, all points with same longitude
@ -152,21 +173,29 @@ public final class ShapeField {
if (bY < cY) { if (bY < cY) {
int tempX = aX; int tempX = aX;
int tempY = aY; int tempY = aY;
boolean tempBool = ab;
aX = bX; aX = bX;
aY = bY; aY = bY;
ab = bc;
bX = cX; bX = cX;
bY = cY; bY = cY;
bc = ca;
cX = tempX; cX = tempX;
cY = tempY; cY = tempY;
ca = tempBool;
} else if (cY < aY) { } else if (cY < aY) {
int tempX = aX; int tempX = aX;
int tempY = aY; int tempY = aY;
boolean tempBool = ab;
aX = cX; aX = cX;
aY = cY; aY = cY;
ab = ca;
cX = bX; cX = bX;
cY = bY; cY = bY;
ca = bc;
bX = tempX; bX = tempX;
bY = tempY; bY = tempY;
bc = tempBool;
} }
} }
} }
@ -216,6 +245,9 @@ public final class ShapeField {
} else { } else {
throw new IllegalArgumentException("Could not encode the provided triangle"); 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(minY, bytes, 0);
NumericUtils.intToSortableBytes(minX, bytes, BYTES); NumericUtils.intToSortableBytes(minX, bytes, BYTES);
NumericUtils.intToSortableBytes(maxY, bytes, 2 * BYTES); NumericUtils.intToSortableBytes(maxY, bytes, 2 * BYTES);
@ -225,83 +257,133 @@ public final class ShapeField {
NumericUtils.intToSortableBytes(bits, bytes, 6 * BYTES); NumericUtils.intToSortableBytes(bits, bytes, 6 * BYTES);
} }
/** /** Decode a triangle encoded by {@link ShapeField#encodeTriangle(byte[], int, int, boolean, int, int, boolean, int, int, boolean)}.
* Decode a triangle encoded by {@link ShapeField#encodeTriangle(byte[], int, int, int, int, int, int)}.
*/ */
public static void decodeTriangle(byte[] t, int[] triangle) { public static void decodeTriangle(byte[] t, DecodedTriangle triangle) {
assert triangle.length == 6; final int aX, aY, bX, bY, cX, cY;
final boolean ab, bc, ca;
int bits = NumericUtils.sortableBytesToInt(t, 6 * BYTES); int bits = NumericUtils.sortableBytesToInt(t, 6 * BYTES);
//extract the first three bits //extract the first three bits
int tCode = (((1 << 3) - 1) & (bits >> 0)); int tCode = (((1 << 3) - 1) & (bits >> 0));
switch (tCode) { switch (tCode) {
case MINY_MINX_MAXY_MAXX_Y_X: case MINY_MINX_MAXY_MAXX_Y_X:
triangle[0] = NumericUtils.sortableBytesToInt(t, 0 * BYTES); aY = NumericUtils.sortableBytesToInt(t, 0 * BYTES);
triangle[1] = NumericUtils.sortableBytesToInt(t, 1 * BYTES); aX = NumericUtils.sortableBytesToInt(t, 1 * BYTES);
triangle[2] = NumericUtils.sortableBytesToInt(t, 2 * BYTES); bY = NumericUtils.sortableBytesToInt(t, 2 * BYTES);
triangle[3] = NumericUtils.sortableBytesToInt(t, 3 * BYTES); bX = NumericUtils.sortableBytesToInt(t, 3 * BYTES);
triangle[4] = NumericUtils.sortableBytesToInt(t, 4 * BYTES); cY = NumericUtils.sortableBytesToInt(t, 4 * BYTES);
triangle[5] = NumericUtils.sortableBytesToInt(t, 5 * BYTES); cX = NumericUtils.sortableBytesToInt(t, 5 * BYTES);
break; break;
case MINY_MINX_Y_X_MAXY_MAXX: case MINY_MINX_Y_X_MAXY_MAXX:
triangle[0] = NumericUtils.sortableBytesToInt(t, 0 * BYTES); aY = NumericUtils.sortableBytesToInt(t, 0 * BYTES);
triangle[1] = NumericUtils.sortableBytesToInt(t, 1 * BYTES); aX = NumericUtils.sortableBytesToInt(t, 1 * BYTES);
triangle[2] = NumericUtils.sortableBytesToInt(t, 4 * BYTES); bY = NumericUtils.sortableBytesToInt(t, 4 * BYTES);
triangle[3] = NumericUtils.sortableBytesToInt(t, 5 * BYTES); bX = NumericUtils.sortableBytesToInt(t, 5 * BYTES);
triangle[4] = NumericUtils.sortableBytesToInt(t, 2 * BYTES); cY = NumericUtils.sortableBytesToInt(t, 2 * BYTES);
triangle[5] = NumericUtils.sortableBytesToInt(t, 3 * BYTES); cX = NumericUtils.sortableBytesToInt(t, 3 * BYTES);
break; break;
case MAXY_MINX_Y_X_MINY_MAXX: case MAXY_MINX_Y_X_MINY_MAXX:
triangle[0] = NumericUtils.sortableBytesToInt(t, 2 * BYTES); aY = NumericUtils.sortableBytesToInt(t, 2 * BYTES);
triangle[1] = NumericUtils.sortableBytesToInt(t, 1 * BYTES); aX = NumericUtils.sortableBytesToInt(t, 1 * BYTES);
triangle[2] = NumericUtils.sortableBytesToInt(t, 4 * BYTES); bY = NumericUtils.sortableBytesToInt(t, 4 * BYTES);
triangle[3] = NumericUtils.sortableBytesToInt(t, 5 * BYTES); bX = NumericUtils.sortableBytesToInt(t, 5 * BYTES);
triangle[4] = NumericUtils.sortableBytesToInt(t, 0 * BYTES); cY = NumericUtils.sortableBytesToInt(t, 0 * BYTES);
triangle[5] = NumericUtils.sortableBytesToInt(t, 3 * BYTES); cX = NumericUtils.sortableBytesToInt(t, 3 * BYTES);
break; break;
case MAXY_MINX_MINY_MAXX_Y_X: case MAXY_MINX_MINY_MAXX_Y_X:
triangle[0] = NumericUtils.sortableBytesToInt(t, 2 * BYTES); aY = NumericUtils.sortableBytesToInt(t, 2 * BYTES);
triangle[1] = NumericUtils.sortableBytesToInt(t, 1 * BYTES); aX = NumericUtils.sortableBytesToInt(t, 1 * BYTES);
triangle[2] = NumericUtils.sortableBytesToInt(t, 0 * BYTES); bY = NumericUtils.sortableBytesToInt(t, 0 * BYTES);
triangle[3] = NumericUtils.sortableBytesToInt(t, 3 * BYTES); bX = NumericUtils.sortableBytesToInt(t, 3 * BYTES);
triangle[4] = NumericUtils.sortableBytesToInt(t, 4 * BYTES); cY = NumericUtils.sortableBytesToInt(t, 4 * BYTES);
triangle[5] = NumericUtils.sortableBytesToInt(t, 5 * BYTES); cX = NumericUtils.sortableBytesToInt(t, 5 * BYTES);
break; break;
case Y_MINX_MINY_X_MAXY_MAXX: case Y_MINX_MINY_X_MAXY_MAXX:
triangle[0] = NumericUtils.sortableBytesToInt(t, 4 * BYTES); aY = NumericUtils.sortableBytesToInt(t, 4 * BYTES);
triangle[1] = NumericUtils.sortableBytesToInt(t, 1 * BYTES); aX = NumericUtils.sortableBytesToInt(t, 1 * BYTES);
triangle[2] = NumericUtils.sortableBytesToInt(t, 0 * BYTES); bY = NumericUtils.sortableBytesToInt(t, 0 * BYTES);
triangle[3] = NumericUtils.sortableBytesToInt(t, 5 * BYTES); bX = NumericUtils.sortableBytesToInt(t, 5 * BYTES);
triangle[4] = NumericUtils.sortableBytesToInt(t, 2 * BYTES); cY = NumericUtils.sortableBytesToInt(t, 2 * BYTES);
triangle[5] = NumericUtils.sortableBytesToInt(t, 3 * BYTES); cX = NumericUtils.sortableBytesToInt(t, 3 * BYTES);
break; break;
case Y_MINX_MINY_MAXX_MAXY_X: case Y_MINX_MINY_MAXX_MAXY_X:
triangle[0] = NumericUtils.sortableBytesToInt(t, 4 * BYTES); aY = NumericUtils.sortableBytesToInt(t, 4 * BYTES);
triangle[1] = NumericUtils.sortableBytesToInt(t, 1 * BYTES); aX = NumericUtils.sortableBytesToInt(t, 1 * BYTES);
triangle[2] = NumericUtils.sortableBytesToInt(t, 0 * BYTES); bY = NumericUtils.sortableBytesToInt(t, 0 * BYTES);
triangle[3] = NumericUtils.sortableBytesToInt(t, 3 * BYTES); bX = NumericUtils.sortableBytesToInt(t, 3 * BYTES);
triangle[4] = NumericUtils.sortableBytesToInt(t, 2 * BYTES); cY = NumericUtils.sortableBytesToInt(t, 2 * BYTES);
triangle[5] = NumericUtils.sortableBytesToInt(t, 5 * BYTES); cX = NumericUtils.sortableBytesToInt(t, 5 * BYTES);
break; break;
case MAXY_MINX_MINY_X_Y_MAXX: case MAXY_MINX_MINY_X_Y_MAXX:
triangle[0] = NumericUtils.sortableBytesToInt(t, 2 * BYTES); aY = NumericUtils.sortableBytesToInt(t, 2 * BYTES);
triangle[1] = NumericUtils.sortableBytesToInt(t, 1 * BYTES); aX = NumericUtils.sortableBytesToInt(t, 1 * BYTES);
triangle[2] = NumericUtils.sortableBytesToInt(t, 0 * BYTES); bY = NumericUtils.sortableBytesToInt(t, 0 * BYTES);
triangle[3] = NumericUtils.sortableBytesToInt(t, 5 * BYTES); bX = NumericUtils.sortableBytesToInt(t, 5 * BYTES);
triangle[4] = NumericUtils.sortableBytesToInt(t, 4 * BYTES); cY = NumericUtils.sortableBytesToInt(t, 4 * BYTES);
triangle[5] = NumericUtils.sortableBytesToInt(t, 3 * BYTES); cX = NumericUtils.sortableBytesToInt(t, 3 * BYTES);
break; break;
case MINY_MINX_Y_MAXX_MAXY_X: case MINY_MINX_Y_MAXX_MAXY_X:
triangle[0] = NumericUtils.sortableBytesToInt(t, 0 * BYTES); aY = NumericUtils.sortableBytesToInt(t, 0 * BYTES);
triangle[1] = NumericUtils.sortableBytesToInt(t, 1 * BYTES); aX = NumericUtils.sortableBytesToInt(t, 1 * BYTES);
triangle[2] = NumericUtils.sortableBytesToInt(t, 4 * BYTES); bY = NumericUtils.sortableBytesToInt(t, 4 * BYTES);
triangle[3] = NumericUtils.sortableBytesToInt(t, 3 * BYTES); bX = NumericUtils.sortableBytesToInt(t, 3 * BYTES);
triangle[4] = NumericUtils.sortableBytesToInt(t, 2 * BYTES); cY = NumericUtils.sortableBytesToInt(t, 2 * BYTES);
triangle[5] = NumericUtils.sortableBytesToInt(t, 5 * BYTES); cX = NumericUtils.sortableBytesToInt(t, 5 * BYTES);
break; break;
default: default:
throw new IllegalArgumentException("Could not decode the provided triangle"); throw new IllegalArgumentException("Could not decode the provided triangle");
} }
//Points of the decoded triangle must be co-planar or CCW oriented //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;
}
} }
} }

View File

@ -83,7 +83,7 @@ abstract class ShapeQuery extends Query {
int maxXOffset, int maxYOffset, byte[] maxTriangle); int maxXOffset, int maxYOffset, byte[] maxTriangle);
/** returns true if the provided triangle matches the query */ /** 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 */ /** relates a range of triangles (internal node) to the query */
protected Relation relateRangeToQuery(byte[] minTriangle, byte[] maxTriangle, QueryRelation queryRelation) { 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) */ /** create a visitor that adds documents that match the query using a sparse bitset. (Used by INTERSECT) */
protected IntersectVisitor getSparseIntersectVisitor(DocIdSetBuilder result) { protected IntersectVisitor getSparseIntersectVisitor(DocIdSetBuilder result) {
return new IntersectVisitor() { return new IntersectVisitor() {
final int[] scratchTriangle = new int[6]; final ShapeField.DecodedTriangle scratchTriangle = new ShapeField.DecodedTriangle();
DocIdSetBuilder.BulkAdder adder; DocIdSetBuilder.BulkAdder adder;
@Override @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) */ /** 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) { protected IntersectVisitor getDenseIntersectVisitor(FixedBitSet intersect, FixedBitSet disjoint, ShapeField.QueryRelation queryRelation) {
return new IntersectVisitor() { return new IntersectVisitor() {
final int[] scratchTriangle = new int[6]; final ShapeField.DecodedTriangle scratchTriangle = new ShapeField.DecodedTriangle();
@Override @Override
public void visit(int docID) throws IOException { public void visit(int docID) throws IOException {
if (queryRelation == ShapeField.QueryRelation.DISJOINT) { 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 */ /** 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) { private IntersectVisitor getInverseIntersectVisitor(ShapeQuery query, FixedBitSet result, int[] cost) {
return new IntersectVisitor() { return new IntersectVisitor() {
int[] scratchTriangle = new int[6]; final ShapeField.DecodedTriangle scratchTriangle = new ShapeField.DecodedTriangle();
@Override @Override
public void visit(int docID) { public void visit(int docID) {
result.clear(docID); result.clear(docID);

View File

@ -46,16 +46,16 @@ public class XYShapeBoundingBoxQuery extends ShapeQuery {
/** returns true if the query matches the encoded triangle */ /** returns true if the query matches the encoded triangle */
@Override @Override
protected boolean queryMatches(byte[] t, int[] scratchTriangle, QueryRelation queryRelation) { protected boolean queryMatches(byte[] t, ShapeField.DecodedTriangle scratchTriangle, QueryRelation queryRelation) {
// decode indexed triangle // decode indexed triangle
ShapeField.decodeTriangle(t, scratchTriangle); ShapeField.decodeTriangle(t, scratchTriangle);
int aY = scratchTriangle[0]; int aY = scratchTriangle.aY;
int aX = scratchTriangle[1]; int aX = scratchTriangle.aX;
int bY = scratchTriangle[2]; int bY = scratchTriangle.bY;
int bX = scratchTriangle[3]; int bX = scratchTriangle.bX;
int cY = scratchTriangle[4]; int cY = scratchTriangle.cY;
int cX = scratchTriangle[5]; int cX = scratchTriangle.cX;
if (queryRelation == QueryRelation.WITHIN) { if (queryRelation == QueryRelation.WITHIN) {
return rectangle2D.containsTriangle(aX, aY, bX, bY, cX, cY); return rectangle2D.containsTriangle(aX, aY, bX, bY, cX, cY);

View File

@ -86,15 +86,15 @@ final class XYShapeLineQuery extends ShapeQuery {
} }
@Override @Override
protected boolean queryMatches(byte[] t, int[] scratchTriangle, QueryRelation queryRelation) { protected boolean queryMatches(byte[] t, ShapeField.DecodedTriangle scratchTriangle, QueryRelation queryRelation) {
ShapeField.decodeTriangle(t, scratchTriangle); ShapeField.decodeTriangle(t, scratchTriangle);
double alat = decode(scratchTriangle[0]); double alat = decode(scratchTriangle.aY);
double alon = decode(scratchTriangle[1]); double alon = decode(scratchTriangle.aX);
double blat = decode(scratchTriangle[2]); double blat = decode(scratchTriangle.bY);
double blon = decode(scratchTriangle[3]); double blon = decode(scratchTriangle.bX);
double clat = decode(scratchTriangle[4]); double clat = decode(scratchTriangle.cY);
double clon = decode(scratchTriangle[5]); double clon = decode(scratchTriangle.cX);
if (queryRelation == QueryRelation.WITHIN) { if (queryRelation == QueryRelation.WITHIN) {
return line2D.relateTriangle(alon, alat, blon, blat, clon, clat) == Relation.CELL_INSIDE_QUERY; return line2D.relateTriangle(alon, alat, blon, blat, clon, clat) == Relation.CELL_INSIDE_QUERY;

View File

@ -26,6 +26,8 @@ import org.apache.lucene.geo.XYPolygon2D;
import org.apache.lucene.index.PointValues.Relation; import org.apache.lucene.index.PointValues.Relation;
import org.apache.lucene.util.NumericUtils; 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}. * Finds all previously indexed cartesian shapes that intersect the specified arbitrary cartesian {@link XYPolygon}.
* *
@ -76,15 +78,15 @@ final class XYShapePolygonQuery extends ShapeQuery {
} }
@Override @Override
protected boolean queryMatches(byte[] t, int[] scratchTriangle, QueryRelation queryRelation) { protected boolean queryMatches(byte[] t, ShapeField.DecodedTriangle scratchTriangle, QueryRelation queryRelation) {
ShapeField.decodeTriangle(t, scratchTriangle); ShapeField.decodeTriangle(t, scratchTriangle);
double alat = XYEncodingUtils.decode(scratchTriangle[0]); double alat = decode(scratchTriangle.aY);
double alon = XYEncodingUtils.decode(scratchTriangle[1]); double alon = decode(scratchTriangle.aX);
double blat = XYEncodingUtils.decode(scratchTriangle[2]); double blat = decode(scratchTriangle.bY);
double blon = XYEncodingUtils.decode(scratchTriangle[3]); double blon = decode(scratchTriangle.bX);
double clat = XYEncodingUtils.decode(scratchTriangle[4]); double clat = decode(scratchTriangle.cY);
double clon = XYEncodingUtils.decode(scratchTriangle[5]); double clon = decode(scratchTriangle.cX);
if (queryRelation == QueryRelation.WITHIN) { if (queryRelation == QueryRelation.WITHIN) {
return poly2D.relateTriangle(alon, alat, blon, blat, clon, clat) == Relation.CELL_INSIDE_QUERY; return poly2D.relateTriangle(alon, alat, blon, blat, clon, clat) == Relation.CELL_INSIDE_QUERY;

View File

@ -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 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)) { if (lastNode != null && isVertexEquals(lastNode, lastNode.next)) {
removeNode(lastNode); removeNode(lastNode, true);
lastNode = lastNode.next; lastNode = lastNode.next;
} }
@ -286,7 +286,7 @@ final public class Tessellator {
Node sharedVertex = getSharedVertex(holeNode, next); Node sharedVertex = getSharedVertex(holeNode, next);
if (sharedVertex != null) { if (sharedVertex != null) {
// Split the resulting polygon. // Split the resulting polygon.
Node node = splitPolygon(next, sharedVertex); Node node = splitPolygon(next, sharedVertex, true);
// Filter the split nodes. // Filter the split nodes.
filterPoints(node, node.next); filterPoints(node, node.next);
return; return;
@ -300,8 +300,10 @@ final public class Tessellator {
// Determine whether a hole bridge could be fetched. // Determine whether a hole bridge could be fetched.
if(outerNode != null) { 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. // Split the resulting polygon.
Node node = splitPolygon(outerNode, holeNode); Node node = splitPolygon(outerNode, holeNode, fromPolygon);
// Filter the split nodes. // Filter the split nodes.
filterPoints(node, node.next); filterPoints(node, node.next);
} }
@ -369,7 +371,7 @@ final public class Tessellator {
} }
/** Check if the provided vertex is in the polygon and return it **/ /** 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; Node next = polygon;
do { do {
if (isVertexEquals(next, vertex)) { if (isVertexEquals(next, vertex)) {
@ -417,10 +419,14 @@ final public class Tessellator {
// Determine whether the current triangle must be cut off. // 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; final boolean isReflex = area(prevNode.getX(), prevNode.getY(), currEar.getX(), currEar.getY(), nextNode.getX(), nextNode.getY()) >= 0;
if (isReflex == false && isEar(currEar, mortonOptimized) == true) { 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 // 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. // Remove the ear node.
removeNode(currEar); removeNode(currEar, caFromPolygon);
// Skipping to the next node leaves fewer slither triangles. // Skipping to the next node leaves fewer slither triangles.
currEar = nextNode.next; currEar = nextNode.next;
@ -439,7 +445,7 @@ final public class Tessellator {
continue earcut; continue earcut;
case CURE: case CURE:
// if this didn't work, try curing all small self-intersections locally // if this didn't work, try curing all small self-intersections locally
currEar = cureLocalIntersections(currEar, tessellation); currEar = cureLocalIntersections(currEar, tessellation, mortonOptimized);
state = State.SPLIT; state = State.SPLIT;
continue earcut; continue earcut;
case SPLIT: case SPLIT:
@ -531,7 +537,7 @@ final public class Tessellator {
} }
/** Iterate through all polygon nodes and remove small local self-intersections **/ /** Iterate through all polygon nodes and remove small local self-intersections **/
private static final Node cureLocalIntersections(Node startNode, final List<Triangle> tessellation) { private static final Node cureLocalIntersections(Node startNode, final List<Triangle> tessellation, final boolean mortonOptimized) {
Node node = startNode; Node node = startNode;
Node nextNode; Node nextNode;
do { do {
@ -544,12 +550,17 @@ final public class Tessellator {
&& isIntersectingPolygon(a, a.getX(), a.getY(), b.getX(), b.getY()) == false && 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()) && linesIntersect(a.getX(), a.getY(), node.getX(), node.getY(), nextNode.getX(), nextNode.getY(), b.getX(), b.getY())
&& isLocallyInside(a, b) && isLocallyInside(b, a)) { && 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 // 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 // remove two nodes involved
removeNode(node); removeNode(node, caFromPolygon);
removeNode(node.next); removeNode(node.next, caFromPolygon);
node = startNode = b; node = startNode = b;
} }
node = node.next; 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 **/ /** 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<Triangle> tessellation, final boolean mortonIndexed) { private static final boolean splitEarcut(final Object polygon, final Node start, final List<Triangle> tessellation, final boolean mortonOptimized) {
// Search for a valid diagonal that divides the polygon into two. // Search for a valid diagonal that divides the polygon into two.
Node searchNode = start; Node searchNode = start;
Node nextNode; Node nextNode;
@ -569,17 +580,17 @@ final public class Tessellator {
while (diagonal != searchNode.previous) { while (diagonal != searchNode.previous) {
if(searchNode.idx != diagonal.idx && isValidDiagonal(searchNode, diagonal)) { if(searchNode.idx != diagonal.idx && isValidDiagonal(searchNode, diagonal)) {
// Split the polygon into two at the point of the 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. // Filter the resulting polygon.
searchNode = filterPoints(searchNode, searchNode.next); searchNode = filterPoints(searchNode, searchNode.next);
splitNode = filterPoints(splitNode, splitNode.next); splitNode = filterPoints(splitNode, splitNode.next);
// Attempt to earcut both of the resulting polygons // Attempt to earcut both of the resulting polygons
if (mortonIndexed) { if (mortonOptimized) {
sortByMortonWithReset(searchNode); sortByMortonWithReset(searchNode);
sortByMortonWithReset(splitNode); sortByMortonWithReset(splitNode);
} }
earcutLinkedList(polygon, searchNode, tessellation, State.INIT, mortonIndexed); earcutLinkedList(polygon, searchNode, tessellation, State.INIT, mortonOptimized);
earcutLinkedList(polygon, splitNode, tessellation, State.INIT, mortonIndexed); earcutLinkedList(polygon, splitNode, tessellation, State.INIT, mortonOptimized);
// Finish the iterative search // Finish the iterative search
return true; return true;
} }
@ -590,14 +601,120 @@ final public class Tessellator {
return false; 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. **/ /** 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 a2 = new Node(a);
final Node b2 = new Node(b); final Node b2 = new Node(b);
final Node an = a.next; final Node an = a.next;
final Node bp = b.previous; final Node bp = b.previous;
a.next = b; a.next = b;
a.isNextEdgeFromPolygon = edgeFromPolygon;
a.nextZ = b; a.nextZ = b;
b.previous = a; b.previous = a;
b.previousZ = a; b.previousZ = a;
@ -606,6 +723,7 @@ final public class Tessellator {
an.previous = a2; an.previous = a2;
an.previousZ = a2; an.previousZ = a2;
b2.next = a2; b2.next = a2;
b2.isNextEdgeFromPolygon = edgeFromPolygon;
b2.nextZ = a2; b2.nextZ = a2;
a2.previous = b2; a2.previous = b2;
a2.previousZ = 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 */ /** 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; Node next = start;
double windingSum = 0; double windingSum = 0;
do { do {
@ -796,10 +914,13 @@ final public class Tessellator {
continueIteration = false; continueIteration = false;
nextNode = node.next; nextNode = node.next;
prevNode = node.previous; prevNode = node.previous;
if (isVertexEquals(node, nextNode) //We can filter points when they are the same, if not and they are co-linear we can only
|| area(prevNode.getX(), prevNode.getY(), node.getX(), node.getY(), nextNode.getX(), nextNode.getY()) == 0) { //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 // Remove the node
removeNode(node); removeNode(node, prevNode.isNextEdgeFromPolygon);
node = end = prevNode; node = end = prevNode;
if (node == nextNode) { if (node == nextNode) {
@ -835,9 +956,10 @@ final public class Tessellator {
} }
/** Removes a node from the doubly linked list */ /** 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.next.previous = node.previous;
node.previous.next = node.next; node.previous.next = node.next;
node.previous.isNextEdgeFromPolygon = edgeFromPolygon;
if (node.previousZ != null) { if (node.previousZ != null) {
node.previousZ.nextZ = node.nextZ; node.previousZ.nextZ = node.nextZ;
@ -873,6 +995,13 @@ final public class Tessellator {
/** compute whether the given x, y point is in a triangle; uses the winding order method */ /** 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) { 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 a = orient(x, y, ax, ay, bx, by);
int b = orient(x, y, bx, by, cx, cy); int b = orient(x, y, bx, by, cx, cy);
if (a == 0 || b == 0 || a < 0 == b < 0) { if (a == 0 || b == 0 || a < 0 == b < 0) {
@ -880,6 +1009,9 @@ final public class Tessellator {
return c == 0 || (c < 0 == (b < 0 || a < 0)); return c == 0 || (c < 0 == (b < 0 || a < 0));
} }
return false; return false;
} else {
return false;
}
} }
/** Brute force compute if a point is in the polygon by traversing entire triangulation /** 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; private final int idx;
// vertex index in the polygon // vertex index in the polygon
private final int vrtxIdx; private final int vrtxIdx;
// reference to the polygon for lat/lon values // reference to the polygon for lat/lon values;
// private final Polygon polygon;
private final double[] polyX; private final double[] polyX;
private final double[] polyY; private final double[] polyY;
// encoded x value // encoded x value
@ -920,6 +1051,8 @@ final public class Tessellator {
private Node previousZ; private Node previousZ;
// next z node // next z node
private Node nextZ; 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) { protected Node(final double[] x, final double[] y, final int index, final int vertexIndex, final boolean isGeo) {
this.idx = index; this.idx = index;
@ -933,6 +1066,7 @@ final public class Tessellator {
this.next = null; this.next = null;
this.previousZ = null; this.previousZ = null;
this.nextZ = null; this.nextZ = null;
this.isNextEdgeFromPolygon = true;
} }
/** simple deep copy constructor */ /** simple deep copy constructor */
@ -948,6 +1082,7 @@ final public class Tessellator {
this.next = other.next; this.next = other.next;
this.previousZ = other.previousZ; this.previousZ = other.previousZ;
this.nextZ = other.nextZ; this.nextZ = other.nextZ;
this.isNextEdgeFromPolygon = other.isNextEdgeFromPolygon;
} }
/** get the x value */ /** get the x value */
@ -979,9 +1114,11 @@ final public class Tessellator {
/** Triangle in the tessellated mesh */ /** Triangle in the tessellated mesh */
public final static class Triangle { public final static class Triangle {
Node[] vertex; 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.vertex = new Node[] {a, b, c};
this.edgeFromPolygon = new boolean[] {isABfromPolygon, isBCfromPolygon, isCAfromPolygon};
} }
/** get quantized x value for the given vertex */ /** get quantized x value for the given vertex */
@ -1004,6 +1141,11 @@ final public class Tessellator {
return this.vertex[vertex].getX(); 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 */ /** utility method to compute whether the point is in the triangle */
protected boolean containsPoint(double lat, double lon) { protected boolean containsPoint(double lat, double lon) {
return pointInTriangle(lon, lat, return pointInTriangle(lon, lat,
@ -1014,9 +1156,9 @@ final public class Tessellator {
/** pretty print the triangle vertices */ /** pretty print the triangle vertices */
public String toString() { public String toString() {
String result = vertex[0].x + ", " + vertex[0].y + " " + String result = vertex[0].x + ", " + vertex[0].y + " [" + edgeFromPolygon[0] + "] " +
vertex[1].x + ", " + vertex[1].y + " " + vertex[1].x + ", " + vertex[1].y + " [" + edgeFromPolygon[1] + "] " +
vertex[2].x + ", " + vertex[2].y; vertex[2].x + ", " + vertex[2].y + " [" + edgeFromPolygon[2] + "]";
return result; return result;
} }
} }

View File

@ -264,18 +264,18 @@ public abstract class BaseLatLonShapeTestCase extends BaseShapeTestCase {
} }
@Override @Override
double[] quantizeTriangle(double ax, double ay, double bx, double by, double cx, double cy) { double[] quantizeTriangle(double ax, double ay, boolean ab, double bx, double by, boolean bc, double cx, double cy, boolean ca) {
int[] decoded = encodeDecodeTriangle(ax, ay, bx, by, cx, cy); ShapeField.DecodedTriangle decoded = encodeDecodeTriangle(ax, ay, ab, bx, by, bc, cx, cy, ca);
return new double[]{decodeLatitude(decoded[0]), decodeLongitude(decoded[1]), decodeLatitude(decoded[2]), decodeLongitude(decoded[3]), decodeLatitude(decoded[4]), decodeLongitude(decoded[5])}; return new double[]{decodeLatitude(decoded.aY), decodeLongitude(decoded.aX), decodeLatitude(decoded.bY), decodeLongitude(decoded.bX), decodeLatitude(decoded.cY), decodeLongitude(decoded.cX)};
} }
@Override @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]; byte[] encoded = new byte[7 * ShapeField.BYTES];
ShapeField.encodeTriangle(encoded, encodeLatitude(ay), encodeLongitude(ax), encodeLatitude(by), encodeLongitude(bx), encodeLatitude(cy), encodeLongitude(cx)); ShapeField.encodeTriangle(encoded, encodeLatitude(ay), encodeLongitude(ax), ab, encodeLatitude(by), encodeLongitude(bx), bc, encodeLatitude(cy), encodeLongitude(cx), ca);
int[] decoded = new int[6]; ShapeField.DecodedTriangle triangle = new ShapeField.DecodedTriangle();
ShapeField.decodeTriangle(encoded, decoded); ShapeField.decodeTriangle(encoded, triangle);
return decoded; return triangle;
} }
}; };
} }

View File

@ -16,8 +16,6 @@
*/ */
package org.apache.lucene.document; package org.apache.lucene.document;
import java.util.Arrays;
import org.apache.lucene.geo.GeoUtils; import org.apache.lucene.geo.GeoUtils;
import org.apache.lucene.geo.Polygon2D; import org.apache.lucene.geo.Polygon2D;
import org.apache.lucene.index.PointValues; import org.apache.lucene.index.PointValues;
@ -55,15 +53,15 @@ public abstract class BaseShapeEncodingTestCase extends LuceneTestCase {
int cxEnc = encodeX(cx); int cxEnc = encodeX(cx);
verifyEncodingPermutations(ayEnc, axEnc, byEnc, bxEnc, cyEnc, cxEnc); verifyEncodingPermutations(ayEnc, axEnc, byEnc, bxEnc, cyEnc, cxEnc);
byte[] b = new byte[7 * ShapeField.BYTES]; byte[] b = new byte[7 * ShapeField.BYTES];
ShapeField.encodeTriangle(b, ayEnc, axEnc, byEnc, bxEnc, cyEnc, cxEnc); ShapeField.encodeTriangle(b, ayEnc, axEnc, true, byEnc, bxEnc, true, cyEnc, cxEnc, true);
int[] encoded = new int[6]; ShapeField.DecodedTriangle encoded = new ShapeField.DecodedTriangle();
ShapeField.decodeTriangle(b, encoded); ShapeField.decodeTriangle(b, encoded);
assertTrue(encoded[0] == ayEnc); assertEquals(encoded.aY, ayEnc);
assertTrue(encoded[1] == axEnc); assertEquals(encoded.aX, axEnc);
assertTrue(encoded[2] == byEnc); assertEquals(encoded.bY, byEnc);
assertTrue(encoded[3] == bxEnc); assertEquals(encoded.bX, bxEnc);
assertTrue(encoded[4] == cyEnc); assertEquals(encoded.cY, cyEnc);
assertTrue(encoded[5] == cxEnc); assertEquals(encoded.cX, cxEnc);
} }
//One shared point with MBR -> MinLat, MaxLon //One shared point with MBR -> MinLat, MaxLon
@ -82,15 +80,15 @@ public abstract class BaseShapeEncodingTestCase extends LuceneTestCase {
int cxEnc = encodeX(cx); int cxEnc = encodeX(cx);
verifyEncodingPermutations(ayEnc, axEnc, byEnc, bxEnc, cyEnc, cxEnc); verifyEncodingPermutations(ayEnc, axEnc, byEnc, bxEnc, cyEnc, cxEnc);
byte[] b = new byte[7 * ShapeField.BYTES]; byte[] b = new byte[7 * ShapeField.BYTES];
ShapeField.encodeTriangle(b, ayEnc, axEnc, byEnc, bxEnc, cyEnc, cxEnc); ShapeField.encodeTriangle(b, ayEnc, axEnc, true, byEnc, bxEnc, true, cyEnc, cxEnc, true);
int[] encoded = new int[6]; ShapeField.DecodedTriangle encoded = new ShapeField.DecodedTriangle();
ShapeField.decodeTriangle(b, encoded); ShapeField.decodeTriangle(b, encoded);
assertTrue(encoded[0] == ayEnc); assertEquals(encoded.aY, ayEnc);
assertTrue(encoded[1] == axEnc); assertEquals(encoded.aX, axEnc);
assertTrue(encoded[2] == byEnc); assertEquals(encoded.bY, byEnc);
assertTrue(encoded[3] == bxEnc); assertEquals(encoded.bX, bxEnc);
assertTrue(encoded[4] == cyEnc); assertEquals(encoded.cY, cyEnc);
assertTrue(encoded[5] == cxEnc); assertEquals(encoded.cX, cxEnc);
} }
//One shared point with MBR -> MaxLat, MaxLon //One shared point with MBR -> MaxLat, MaxLon
@ -109,15 +107,15 @@ public abstract class BaseShapeEncodingTestCase extends LuceneTestCase {
int cxEnc = encodeX(blon); int cxEnc = encodeX(blon);
verifyEncodingPermutations(ayEnc, axEnc, byEnc, bxEnc, cyEnc, cxEnc); verifyEncodingPermutations(ayEnc, axEnc, byEnc, bxEnc, cyEnc, cxEnc);
byte[] b = new byte[7 * ShapeField.BYTES]; byte[] b = new byte[7 * ShapeField.BYTES];
ShapeField.encodeTriangle(b, ayEnc, axEnc, byEnc, bxEnc, cyEnc, cxEnc); ShapeField.encodeTriangle(b, ayEnc, axEnc, true, byEnc, bxEnc, true, cyEnc, cxEnc, true);
int[] encoded = new int[6]; ShapeField.DecodedTriangle encoded = new ShapeField.DecodedTriangle();
ShapeField.decodeTriangle(b, encoded); ShapeField.decodeTriangle(b, encoded);
assertTrue(encoded[0] == ayEnc); assertEquals(encoded.aY, ayEnc);
assertTrue(encoded[1] == axEnc); assertEquals(encoded.aX, axEnc);
assertTrue(encoded[2] == byEnc); assertEquals(encoded.bY, byEnc);
assertTrue(encoded[3] == bxEnc); assertEquals(encoded.bX, bxEnc);
assertTrue(encoded[4] == cyEnc); assertEquals(encoded.cY, cyEnc);
assertTrue(encoded[5] == cxEnc); assertEquals(encoded.cX, cxEnc);
} }
//One shared point with MBR -> MaxLat, MinLon //One shared point with MBR -> MaxLat, MinLon
@ -136,15 +134,15 @@ public abstract class BaseShapeEncodingTestCase extends LuceneTestCase {
int cxEnc = encodeX(blon); int cxEnc = encodeX(blon);
verifyEncodingPermutations(ayEnc, axEnc, byEnc, bxEnc, cyEnc, cxEnc); verifyEncodingPermutations(ayEnc, axEnc, byEnc, bxEnc, cyEnc, cxEnc);
byte[] b = new byte[7 * ShapeField.BYTES]; byte[] b = new byte[7 * ShapeField.BYTES];
ShapeField.encodeTriangle(b, ayEnc, axEnc, byEnc, bxEnc, cyEnc, cxEnc); ShapeField.encodeTriangle(b, ayEnc, axEnc, true, byEnc, bxEnc, true, cyEnc, cxEnc, true);
int[] encoded = new int[6]; ShapeField.DecodedTriangle encoded = new ShapeField.DecodedTriangle();
ShapeField.decodeTriangle(b, encoded); ShapeField.decodeTriangle(b, encoded);
assertTrue(encoded[0] == ayEnc); assertEquals(encoded.aY, ayEnc);
assertTrue(encoded[1] == axEnc); assertEquals(encoded.aX, axEnc);
assertTrue(encoded[2] == byEnc); assertEquals(encoded.bY, byEnc);
assertTrue(encoded[3] == bxEnc); assertEquals(encoded.bX, bxEnc);
assertTrue(encoded[4] == cyEnc); assertEquals(encoded.cY, cyEnc);
assertTrue(encoded[5] == cxEnc); assertEquals(encoded.cX, cxEnc);
} }
//Two shared point with MBR -> [MinLat, MinLon], [MaxLat, MaxLon], third point below //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); int cxEnc = encodeX(cx);
verifyEncodingPermutations(ayEnc, axEnc, byEnc, bxEnc, cyEnc, cxEnc); verifyEncodingPermutations(ayEnc, axEnc, byEnc, bxEnc, cyEnc, cxEnc);
byte[] b = new byte[7 * ShapeField.BYTES]; byte[] b = new byte[7 * ShapeField.BYTES];
ShapeField.encodeTriangle(b, ayEnc, axEnc, byEnc, bxEnc, cyEnc, cxEnc); ShapeField.encodeTriangle(b, ayEnc, axEnc, true, byEnc, bxEnc, true, cyEnc, cxEnc, true);
int[] encoded = new int[6]; ShapeField.DecodedTriangle encoded = new ShapeField.DecodedTriangle();
ShapeField.decodeTriangle(b, encoded); ShapeField.decodeTriangle(b, encoded);
assertTrue(encoded[0] == ayEnc); assertEquals(encoded.aY, ayEnc);
assertTrue(encoded[1] == axEnc); assertEquals(encoded.aX, axEnc);
assertTrue(encoded[2] == byEnc); assertEquals(encoded.bY, byEnc);
assertTrue(encoded[3] == bxEnc); assertEquals(encoded.bX, bxEnc);
assertTrue(encoded[4] == cyEnc); assertEquals(encoded.cY, cyEnc);
assertTrue(encoded[5] == cxEnc); assertEquals(encoded.cX, cxEnc);
} }
//Two shared point with MBR -> [MinLat, MinLon], [MaxLat, MaxLon], third point above //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); int cxEnc = encodeX(cx);
verifyEncodingPermutations(ayEnc, axEnc, byEnc, bxEnc, cyEnc, cxEnc); verifyEncodingPermutations(ayEnc, axEnc, byEnc, bxEnc, cyEnc, cxEnc);
byte[] b = new byte[7 * ShapeField.BYTES]; byte[] b = new byte[7 * ShapeField.BYTES];
ShapeField.encodeTriangle(b, ayEnc, axEnc, byEnc, bxEnc, cyEnc, cxEnc); ShapeField.encodeTriangle(b, ayEnc, axEnc, true, byEnc, bxEnc, true, cyEnc, cxEnc, true);
int[] encoded = new int[6]; ShapeField.DecodedTriangle encoded = new ShapeField.DecodedTriangle();
ShapeField.decodeTriangle(b, encoded); ShapeField.decodeTriangle(b, encoded);
assertTrue(encoded[0] == ayEnc); assertEquals(encoded.aY, ayEnc);
assertTrue(encoded[1] == axEnc); assertEquals(encoded.aX, axEnc);
assertTrue(encoded[2] == byEnc); assertEquals(encoded.bY, byEnc);
assertTrue(encoded[3] == bxEnc); assertEquals(encoded.bX, bxEnc);
assertTrue(encoded[4] == cyEnc); assertEquals(encoded.cY, cyEnc);
assertTrue(encoded[5] == cxEnc); assertEquals(encoded.cX, cxEnc);
} }
//Two shared point with MBR -> [MinLat, MaxLon], [MaxLat, MinLon], third point below //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); int cxEnc = encodeX(cx);
verifyEncodingPermutations(ayEnc, axEnc, byEnc, bxEnc, cyEnc, cxEnc); verifyEncodingPermutations(ayEnc, axEnc, byEnc, bxEnc, cyEnc, cxEnc);
byte[] b = new byte[7 * ShapeField.BYTES]; byte[] b = new byte[7 * ShapeField.BYTES];
ShapeField.encodeTriangle(b, ayEnc, axEnc, byEnc, bxEnc, cyEnc, cxEnc); ShapeField.encodeTriangle(b, ayEnc, axEnc, true, byEnc, bxEnc, true, cyEnc, cxEnc, true);
int[] encoded = new int[6]; ShapeField.DecodedTriangle encoded = new ShapeField.DecodedTriangle();
ShapeField.decodeTriangle(b, encoded); ShapeField.decodeTriangle(b, encoded);
assertTrue(encoded[0] == ayEnc); assertEquals(encoded.aY, ayEnc);
assertTrue(encoded[1] == axEnc); assertEquals(encoded.aX, axEnc);
assertTrue(encoded[2] == byEnc); assertEquals(encoded.bY, byEnc);
assertTrue(encoded[3] == bxEnc); assertEquals(encoded.bX, bxEnc);
assertTrue(encoded[4] == cyEnc); assertEquals(encoded.cY, cyEnc);
assertTrue(encoded[5] == cxEnc); assertEquals(encoded.cX, cxEnc);
} }
//Two shared point with MBR -> [MinLat, MaxLon], [MaxLat, MinLon], third point above //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); int cxEnc = encodeX(cx);
verifyEncodingPermutations(ayEnc, axEnc, byEnc, bxEnc, cyEnc, cxEnc); verifyEncodingPermutations(ayEnc, axEnc, byEnc, bxEnc, cyEnc, cxEnc);
byte[] b = new byte[7 * ShapeField.BYTES]; byte[] b = new byte[7 * ShapeField.BYTES];
ShapeField.encodeTriangle(b, ayEnc, axEnc, byEnc, bxEnc, cyEnc, cxEnc); ShapeField.encodeTriangle(b, ayEnc, axEnc, true, byEnc, bxEnc, true, cyEnc, cxEnc, true);
int[] encoded = new int[6]; ShapeField.DecodedTriangle encoded = new ShapeField.DecodedTriangle();
ShapeField.decodeTriangle(b, encoded); ShapeField.decodeTriangle(b, encoded);
assertTrue(encoded[0] == ayEnc); assertEquals(encoded.aY, ayEnc);
assertTrue(encoded[1] == axEnc); assertEquals(encoded.aX, axEnc);
assertTrue(encoded[2] == byEnc); assertEquals(encoded.bY, byEnc);
assertTrue(encoded[3] == bxEnc); assertEquals(encoded.bX, bxEnc);
assertTrue(encoded[4] == cyEnc); assertEquals(encoded.cY, cyEnc);
assertTrue(encoded[5] == cxEnc); assertEquals(encoded.cX, cxEnc);
} }
//all points shared with MBR //all points shared with MBR
@ -271,15 +269,15 @@ public abstract class BaseShapeEncodingTestCase extends LuceneTestCase {
int cxEnc = encodeX(cx); int cxEnc = encodeX(cx);
verifyEncodingPermutations(ayEnc, axEnc, byEnc, bxEnc, cyEnc, cxEnc); verifyEncodingPermutations(ayEnc, axEnc, byEnc, bxEnc, cyEnc, cxEnc);
byte[] b = new byte[7 * ShapeField.BYTES]; byte[] b = new byte[7 * ShapeField.BYTES];
ShapeField.encodeTriangle(b, ayEnc, axEnc, byEnc, bxEnc, cyEnc, cxEnc); ShapeField.encodeTriangle(b, ayEnc, axEnc, true, byEnc, bxEnc, true, cyEnc, cxEnc, true);
int[] encoded = new int[6]; ShapeField.DecodedTriangle encoded = new ShapeField.DecodedTriangle();
ShapeField.decodeTriangle(b, encoded); ShapeField.decodeTriangle(b, encoded);
assertTrue(encoded[0] == ayEnc); assertEquals(encoded.aY, ayEnc);
assertTrue(encoded[1] == axEnc); assertEquals(encoded.aX, axEnc);
assertTrue(encoded[2] == byEnc); assertEquals(encoded.bY, byEnc);
assertTrue(encoded[3] == bxEnc); assertEquals(encoded.bX, bxEnc);
assertTrue(encoded[4] == cyEnc); assertEquals(encoded.cY, cyEnc);
assertTrue(encoded[5] == cxEnc); assertEquals(encoded.cX, cxEnc);
} }
//all points shared with MBR //all points shared with MBR
@ -297,15 +295,15 @@ public abstract class BaseShapeEncodingTestCase extends LuceneTestCase {
int cyEnc = encodeY(cy); int cyEnc = encodeY(cy);
int cxEnc = encodeX(cx); int cxEnc = encodeX(cx);
byte[] b = new byte[7 * ShapeField.BYTES]; byte[] b = new byte[7 * ShapeField.BYTES];
ShapeField.encodeTriangle(b, ayEnc, axEnc, byEnc, bxEnc, cyEnc, cxEnc); ShapeField.encodeTriangle(b, ayEnc, axEnc, true, byEnc, bxEnc, true, cyEnc, cxEnc, true);
int[] encoded = new int[6]; ShapeField.DecodedTriangle encoded = new ShapeField.DecodedTriangle();
ShapeField.decodeTriangle(b, encoded); ShapeField.decodeTriangle(b, encoded);
assertTrue(encoded[0] == ayEnc); assertEquals(encoded.aY, ayEnc);
assertTrue(encoded[1] == axEnc); assertEquals(encoded.aX, axEnc);
assertTrue(encoded[2] == byEnc); assertEquals(encoded.bY, byEnc);
assertTrue(encoded[3] == bxEnc); assertEquals(encoded.bX, bxEnc);
assertTrue(encoded[4] == cyEnc); assertEquals(encoded.cY, cyEnc);
assertTrue(encoded[5] == cxEnc); assertEquals(encoded.cX, cxEnc);
} }
//[a,b,c] == [c,a,b] == [b,c,a] == [c,b,a] == [b,a,c] == [a,c,b] //[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); assertTrue(GeoUtils.orient(ayEnc, axEnc, byEnc, bxEnc, cyEnc, cxEnc) != 0);
byte[] b = new byte[7 * ShapeField.BYTES]; byte[] b = new byte[7 * ShapeField.BYTES];
//[a,b,c] //[a,b,c]
ShapeField.encodeTriangle(b, ayEnc, axEnc, byEnc, bxEnc, cyEnc, cxEnc); ShapeField.encodeTriangle(b, ayEnc, axEnc, true, byEnc, bxEnc, true, cyEnc, cxEnc, false);
int[] encodedABC = new int[6]; ShapeField.DecodedTriangle encodedABC = new ShapeField.DecodedTriangle();
ShapeField.decodeTriangle(b, encodedABC); ShapeField.decodeTriangle(b, encodedABC);
//[c,a,b] //[c,a,b]
ShapeField.encodeTriangle(b, cyEnc, cxEnc, ayEnc, axEnc, byEnc, bxEnc); ShapeField.encodeTriangle(b, cyEnc, cxEnc, false, ayEnc, axEnc, true, byEnc, bxEnc, true);
int[] encodedCAB = new int[6]; ShapeField.DecodedTriangle encodedCAB = new ShapeField.DecodedTriangle();
ShapeField.decodeTriangle(b, encodedCAB); ShapeField.decodeTriangle(b, encodedCAB);
assertTrue(Arrays.equals(encodedABC, encodedCAB)); assertEquals(encodedABC, encodedCAB);
//[b,c,a] //[b,c,a]
ShapeField.encodeTriangle(b, byEnc, bxEnc, cyEnc, cxEnc, ayEnc, axEnc); ShapeField.encodeTriangle(b, byEnc, bxEnc, true, cyEnc, cxEnc, false, ayEnc, axEnc, true);
int[] encodedBCA = new int[6]; ShapeField.DecodedTriangle encodedBCA = new ShapeField.DecodedTriangle();
ShapeField.decodeTriangle(b, encodedBCA); ShapeField.decodeTriangle(b, encodedBCA);
assertTrue(Arrays.equals(encodedABC, encodedBCA)); assertEquals(encodedABC, encodedBCA);
//[c,b,a] //[c,b,a]
ShapeField.encodeTriangle(b, cyEnc, cxEnc, byEnc, bxEnc, ayEnc, axEnc); ShapeField.encodeTriangle(b, cyEnc, cxEnc, true, byEnc, bxEnc, true, ayEnc, axEnc, false);
int[] encodedCBA= new int[6]; ShapeField.DecodedTriangle encodedCBA= new ShapeField.DecodedTriangle();
ShapeField.decodeTriangle(b, encodedCBA); ShapeField.decodeTriangle(b, encodedCBA);
assertTrue(Arrays.equals(encodedABC, encodedCBA)); assertEquals(encodedABC, encodedCBA);
//[b,a,c] //[b,a,c]
ShapeField.encodeTriangle(b, byEnc, bxEnc, ayEnc, axEnc, cyEnc, cxEnc); ShapeField.encodeTriangle(b, byEnc, bxEnc, true, ayEnc, axEnc, false, cyEnc, cxEnc, true);
int[] encodedBAC= new int[6]; ShapeField.DecodedTriangle encodedBAC= new ShapeField.DecodedTriangle();
ShapeField.decodeTriangle(b, encodedBAC); ShapeField.decodeTriangle(b, encodedBAC);
assertTrue(Arrays.equals(encodedABC, encodedBAC)); assertEquals(encodedABC, encodedBAC);
//[a,c,b] //[a,c,b]
ShapeField.encodeTriangle(b, ayEnc, axEnc, cyEnc, cxEnc, byEnc, bxEnc); ShapeField.encodeTriangle(b, ayEnc, axEnc, false, cyEnc, cxEnc, true, byEnc, bxEnc, true);
int[] encodedACB= new int[6]; ShapeField.DecodedTriangle encodedACB= new ShapeField.DecodedTriangle();
ShapeField.decodeTriangle(b, encodedACB); ShapeField.decodeTriangle(b, encodedACB);
assertTrue(Arrays.equals(encodedABC, encodedACB)); assertEquals(encodedABC, encodedACB);
} }
public void testPointEncoding() { public void testPointEncoding() {
@ -350,11 +348,15 @@ public abstract class BaseShapeEncodingTestCase extends LuceneTestCase {
int latEnc = encodeY(lat); int latEnc = encodeY(lat);
int lonEnc = encodeX(lon); int lonEnc = encodeX(lon);
byte[] b = new byte[7 * ShapeField.BYTES]; byte[] b = new byte[7 * ShapeField.BYTES];
ShapeField.encodeTriangle(b, latEnc, lonEnc, latEnc, lonEnc, latEnc, lonEnc); ShapeField.encodeTriangle(b, latEnc, lonEnc, true, latEnc, lonEnc, true, latEnc, lonEnc, true);
int[] encoded = new int[6]; ShapeField.DecodedTriangle encoded = new ShapeField.DecodedTriangle();
ShapeField.decodeTriangle(b, encoded); ShapeField.decodeTriangle(b, encoded);
assertTrue(encoded[0] == latEnc && encoded[2] == latEnc && encoded[4] == latEnc); assertEquals(encoded.aY, latEnc);
assertTrue(encoded[1] == lonEnc && encoded[3] == lonEnc && encoded[5] == lonEnc); assertEquals(encoded.aX, lonEnc);
assertEquals(encoded.bY, latEnc);
assertEquals(encoded.bX, lonEnc);
assertEquals(encoded.cY, latEnc);
assertEquals(encoded.cX, lonEnc);
} }
public void testLineEncodingSameLat() { public void testLineEncodingSameLat() {
@ -365,33 +367,31 @@ public abstract class BaseShapeEncodingTestCase extends LuceneTestCase {
int axEnc = encodeX(ax); int axEnc = encodeX(ax);
int bxEnc = encodeX(bx); int bxEnc = encodeX(bx);
byte[] b = new byte[7 * ShapeField.BYTES]; byte[] b = new byte[7 * ShapeField.BYTES];
ShapeField.encodeTriangle(b, latEnc, axEnc, latEnc, bxEnc, latEnc, axEnc); ShapeField.encodeTriangle(b, latEnc, axEnc, true, latEnc, bxEnc, true, latEnc, axEnc, true);
int[] encoded = new int[6]; ShapeField.DecodedTriangle encoded = new ShapeField.DecodedTriangle();
ShapeField.decodeTriangle(b, encoded); ShapeField.decodeTriangle(b, encoded);
assertTrue(encoded[0] == latEnc); assertEquals(encoded.aY, latEnc);
assertTrue(encoded[1] == axEnc); assertEquals(encoded.aX, axEnc);
assertTrue(encoded[2] == latEnc); assertEquals(encoded.bY, latEnc);
assertTrue(encoded[3] == bxEnc); assertEquals(encoded.bX, bxEnc);
assertTrue(encoded[4] == latEnc); assertEquals(encoded.cY, latEnc);
assertTrue(encoded[5] == axEnc); assertEquals(encoded.cX, axEnc);
ShapeField.encodeTriangle(b, latEnc, axEnc, latEnc, axEnc, latEnc, bxEnc); ShapeField.encodeTriangle(b, latEnc, axEnc, true, latEnc, axEnc, true, latEnc, bxEnc, true);
encoded = new int[6];
ShapeField.decodeTriangle(b, encoded); ShapeField.decodeTriangle(b, encoded);
assertTrue(encoded[0] == latEnc); assertEquals(encoded.aY, latEnc);
assertTrue(encoded[1] == axEnc); assertEquals(encoded.aX, axEnc);
assertTrue(encoded[2] == latEnc); assertEquals(encoded.bY, latEnc);
assertTrue(encoded[3] == axEnc); assertEquals(encoded.bX, axEnc);
assertTrue(encoded[4] == latEnc); assertEquals(encoded.cY, latEnc);
assertTrue(encoded[5] == bxEnc); assertEquals(encoded.cX, bxEnc);
ShapeField.encodeTriangle(b, latEnc, bxEnc, latEnc, axEnc, latEnc, axEnc); ShapeField.encodeTriangle(b, latEnc, bxEnc, true, latEnc, axEnc, true, latEnc, axEnc, true);
encoded = new int[6];
ShapeField.decodeTriangle(b, encoded); ShapeField.decodeTriangle(b, encoded);
assertTrue(encoded[0] == latEnc); assertEquals(encoded.aY, latEnc);
assertTrue(encoded[1] == axEnc); assertEquals(encoded.aX, axEnc);
assertTrue(encoded[2] == latEnc); assertEquals(encoded.bY, latEnc);
assertTrue(encoded[3] == bxEnc); assertEquals(encoded.bX, bxEnc);
assertTrue(encoded[4] == latEnc); assertEquals(encoded.cY, latEnc);
assertTrue(encoded[5] == axEnc); assertEquals(encoded.cX, axEnc);
} }
public void testLineEncodingSameLon() { public void testLineEncodingSameLon() {
@ -402,33 +402,31 @@ public abstract class BaseShapeEncodingTestCase extends LuceneTestCase {
int byEnc = encodeY(by); int byEnc = encodeY(by);
int lonEnc = encodeX(lon); int lonEnc = encodeX(lon);
byte[] b = new byte[7 * ShapeField.BYTES]; byte[] b = new byte[7 * ShapeField.BYTES];
ShapeField.encodeTriangle(b, ayEnc, lonEnc, byEnc, lonEnc, ayEnc, lonEnc); ShapeField.encodeTriangle(b, ayEnc, lonEnc, true, byEnc, lonEnc, true, ayEnc, lonEnc, true);
int[] encoded = new int[6]; ShapeField.DecodedTriangle encoded = new ShapeField.DecodedTriangle();
ShapeField.decodeTriangle(b, encoded); ShapeField.decodeTriangle(b, encoded);
assertTrue(encoded[0] == ayEnc); assertEquals(encoded.aY, ayEnc);
assertTrue(encoded[1] == lonEnc); assertEquals(encoded.aX, lonEnc);
assertTrue(encoded[2] == byEnc); assertEquals(encoded.bY, byEnc);
assertTrue(encoded[3] == lonEnc); assertEquals(encoded.bX, lonEnc);
assertTrue(encoded[4] == ayEnc); assertEquals(encoded.cY, ayEnc);
assertTrue(encoded[5] == lonEnc); assertEquals(encoded.cX, lonEnc);
ShapeField.encodeTriangle(b, ayEnc, lonEnc, ayEnc, lonEnc, byEnc, lonEnc); ShapeField.encodeTriangle(b, ayEnc, lonEnc, true, ayEnc, lonEnc, true, byEnc, lonEnc, true);
encoded = new int[6];
ShapeField.decodeTriangle(b, encoded); ShapeField.decodeTriangle(b, encoded);
assertTrue(encoded[0] == ayEnc); assertEquals(encoded.aY, ayEnc);
assertTrue(encoded[1] == lonEnc); assertEquals(encoded.aX, lonEnc);
assertTrue(encoded[2] == ayEnc); assertEquals(encoded.bY, ayEnc);
assertTrue(encoded[3] == lonEnc); assertEquals(encoded.bX, lonEnc);
assertTrue(encoded[4] == byEnc); assertEquals(encoded.cY, byEnc);
assertTrue(encoded[5] == lonEnc); assertEquals(encoded.cX, lonEnc);
ShapeField.encodeTriangle(b, byEnc, lonEnc, ayEnc, lonEnc, ayEnc, lonEnc); ShapeField.encodeTriangle(b, byEnc, lonEnc, true, ayEnc, lonEnc, true, ayEnc, lonEnc, true);
encoded = new int[6];
ShapeField.decodeTriangle(b, encoded); ShapeField.decodeTriangle(b, encoded);
assertTrue(encoded[0] == ayEnc); assertEquals(encoded.aY, ayEnc);
assertTrue(encoded[1] == lonEnc); assertEquals(encoded.aX, lonEnc);
assertTrue(encoded[2] == byEnc); assertEquals(encoded.bY, byEnc);
assertTrue(encoded[3] == lonEnc); assertEquals(encoded.bX, lonEnc);
assertTrue(encoded[4] == ayEnc); assertEquals(encoded.cY, ayEnc);
assertTrue(encoded[5] == lonEnc); assertEquals(encoded.cX, lonEnc);
} }
public void testLineEncoding() { public void testLineEncoding() {
@ -441,33 +439,31 @@ public abstract class BaseShapeEncodingTestCase extends LuceneTestCase {
int axEnc = encodeX(ax); int axEnc = encodeX(ax);
int bxEnc = encodeX(bx); int bxEnc = encodeX(bx);
byte[] b = new byte[7 * ShapeField.BYTES]; byte[] b = new byte[7 * ShapeField.BYTES];
ShapeField.encodeTriangle(b, ayEnc, axEnc, byEnc, bxEnc, ayEnc, axEnc); ShapeField.encodeTriangle(b, ayEnc, axEnc, true, byEnc, bxEnc, true, ayEnc, axEnc, true);
int[] encoded = new int[6]; ShapeField.DecodedTriangle encoded = new ShapeField.DecodedTriangle();
ShapeField.decodeTriangle(b, encoded); ShapeField.decodeTriangle(b, encoded);
assertTrue(encoded[0] == ayEnc); assertEquals(encoded.aY, ayEnc);
assertTrue(encoded[1] == axEnc); assertEquals(encoded.aX, axEnc);
assertTrue(encoded[2] == byEnc); assertEquals(encoded.bY, byEnc);
assertTrue(encoded[3] == bxEnc); assertEquals(encoded.bX, bxEnc);
assertTrue(encoded[4] == ayEnc); assertEquals(encoded.cY, ayEnc);
assertTrue(encoded[5] == axEnc); assertEquals(encoded.cX, axEnc);
ShapeField.encodeTriangle(b, ayEnc, axEnc, ayEnc, axEnc, byEnc, bxEnc); ShapeField.encodeTriangle(b, ayEnc, axEnc, true, ayEnc, axEnc, true, byEnc, bxEnc, true);
encoded = new int[6];
ShapeField.decodeTriangle(b, encoded); ShapeField.decodeTriangle(b, encoded);
assertTrue(encoded[0] == ayEnc); assertEquals(encoded.aY, ayEnc);
assertTrue(encoded[1] == axEnc); assertEquals(encoded.aX, axEnc);
assertTrue(encoded[2] == ayEnc); assertEquals(encoded.bY, ayEnc);
assertTrue(encoded[3] == axEnc); assertEquals(encoded.bX, axEnc);
assertTrue(encoded[4] == byEnc); assertEquals(encoded.cY, byEnc);
assertTrue(encoded[5] == bxEnc); assertEquals(encoded.cX, bxEnc);
ShapeField.encodeTriangle(b, byEnc, bxEnc, ayEnc, axEnc, ayEnc, axEnc); ShapeField.encodeTriangle(b, byEnc, bxEnc, true, ayEnc, axEnc, true, ayEnc, axEnc, true);
encoded = new int[6];
ShapeField.decodeTriangle(b, encoded); ShapeField.decodeTriangle(b, encoded);
assertTrue(encoded[0] == ayEnc); assertEquals(encoded.aY, ayEnc);
assertTrue(encoded[1] == axEnc); assertEquals(encoded.aX, axEnc);
assertTrue(encoded[2] == byEnc); assertEquals(encoded.bY, byEnc);
assertTrue(encoded[3] == bxEnc); assertEquals(encoded.bX, bxEnc);
assertTrue(encoded[4] == ayEnc); assertEquals(encoded.cY, ayEnc);
assertTrue(encoded[5] == axEnc); assertEquals(encoded.cX, axEnc);
} }
public void testRandomPointEncoding() { public void testRandomPointEncoding() {
@ -505,16 +501,16 @@ public abstract class BaseShapeEncodingTestCase extends LuceneTestCase {
//quantize the triangle //quantize the triangle
byte[] b = new byte[7 * ShapeField.BYTES]; byte[] b = new byte[7 * ShapeField.BYTES];
ShapeField.encodeTriangle(b, original[0], original[1], original[2], original[3], original[4], original[5]); ShapeField.encodeTriangle(b, original[0], original[1], true, original[2], original[3], true, original[4], original[5], true);
int[] encoded = new int[6]; ShapeField.DecodedTriangle encoded = new ShapeField.DecodedTriangle();
ShapeField.decodeTriangle(b, encoded); ShapeField.decodeTriangle(b, encoded);
double[] encodedQuantize = new double[] { double[] encodedQuantize = new double[] {
decodeY(encoded[0]), decodeY(encoded.aY),
decodeX(encoded[1]), decodeX(encoded.aX),
decodeY(encoded[2]), decodeY(encoded.bY),
decodeX(encoded[3]), decodeX(encoded.bX),
decodeY(encoded[4]), decodeY(encoded.cY),
decodeX(encoded[5])}; decodeX(encoded.cX)};
int orientation = GeoUtils.orient(original[1], original[0], original[3], original[2], original[5], original[4]); int orientation = GeoUtils.orient(original[1], original[0], original[3], original[2], original[5], original[4]);
//quantize original //quantize original
@ -560,14 +556,14 @@ public abstract class BaseShapeEncodingTestCase extends LuceneTestCase {
int cyEnc = encodeY(cy); int cyEnc = encodeY(cy);
int cxEnc = encodeX(cx); int cxEnc = encodeX(cx);
byte[] b = new byte[7 * ShapeField.BYTES]; byte[] b = new byte[7 * ShapeField.BYTES];
ShapeField.encodeTriangle(b, ayEnc, axEnc, byEnc, bxEnc, cyEnc, cxEnc); ShapeField.encodeTriangle(b, ayEnc, axEnc, true, byEnc, bxEnc, true, cyEnc, cxEnc, true);
int[] encoded = new int[6]; ShapeField.DecodedTriangle encoded = new ShapeField.DecodedTriangle();
ShapeField.decodeTriangle(b, encoded); ShapeField.decodeTriangle(b, encoded);
assertTrue(encoded[0] == byEnc); assertTrue(encoded.aY == byEnc);
assertTrue(encoded[1] == bxEnc); assertTrue(encoded.aX == bxEnc);
assertTrue(encoded[2] == cyEnc); assertTrue(encoded.bY == cyEnc);
assertTrue(encoded[3] == cxEnc); assertTrue(encoded.bX == cxEnc);
assertTrue(encoded[4] == ayEnc); assertTrue(encoded.cY == ayEnc);
assertTrue(encoded[5] == axEnc); assertTrue(encoded.cX == axEnc);
} }
} }

View File

@ -545,8 +545,8 @@ public abstract class BaseShapeTestCase extends LuceneTestCase {
abstract double quantizeXCeil(double raw); abstract double quantizeXCeil(double raw);
abstract double quantizeY(double raw); abstract double quantizeY(double raw);
abstract double quantizeYCeil(double raw); abstract double quantizeYCeil(double raw);
abstract double[] quantizeTriangle(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 int[] encodeDecodeTriangle(double ax, double ay, double bx, double by, double cx, double cy); 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) { private int scaledIterationCount(int shapes) {

View File

@ -144,18 +144,18 @@ public abstract class BaseXYShapeTestCase extends BaseShapeTestCase {
} }
@Override @Override
double[] quantizeTriangle(double ax, double ay, double bx, double by, double cx, double cy) { double[] quantizeTriangle(double ax, double ay, boolean ab, double bx, double by, boolean bc, double cx, double cy, boolean ca) {
int[] decoded = encodeDecodeTriangle(ax, ay, bx, by, cx, cy); ShapeField.DecodedTriangle decoded = encodeDecodeTriangle(ax, ay, ab, bx, by, bc, cx, cy, ca);
return new double[]{decode(decoded[0]), decode(decoded[1]), decode(decoded[2]), decode(decoded[3]), decode(decoded[4]), decode(decoded[5])}; return new double[]{decode(decoded.aY), decode(decoded.aX), decode(decoded.bY), decode(decoded.bX), decode(decoded.cY), decode(decoded.cX)};
} }
@Override @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]; byte[] encoded = new byte[7 * ShapeField.BYTES];
ShapeField.encodeTriangle(encoded, encode(ay), encode(ax), encode(by), encode(bx), encode(cy), encode(cx)); ShapeField.encodeTriangle(encoded, encode(ay), encode(ax), ab, encode(by), encode(bx), bc, encode(cy), encode(cx), ca);
int[] decoded = new int[6]; ShapeField.DecodedTriangle triangle = new ShapeField.DecodedTriangle();
ShapeField.decodeTriangle(encoded, decoded); ShapeField.decodeTriangle(encoded, triangle);
return decoded; return triangle;
} }
}; };
} }

View File

@ -83,13 +83,13 @@ public class TestLatLonLineShapeQueries extends BaseLatLonShapeTestCase {
Line line = (Line)shape; Line line = (Line)shape;
Rectangle2D rectangle2D = Rectangle2D.create(new Rectangle(minLat, maxLat, minLon, maxLon)); Rectangle2D rectangle2D = Rectangle2D.create(new Rectangle(minLat, maxLat, minLon, maxLon));
for (int i = 0, j = 1; j < line.numPoints(); ++i, ++j) { 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 (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; return false;
} }
} else { } 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; return queryRelation == QueryRelation.INTERSECTS;
} }
} }
@ -110,7 +110,7 @@ public class TestLatLonLineShapeQueries extends BaseLatLonShapeTestCase {
private boolean testLine(EdgeTree queryPoly, Line line) { private boolean testLine(EdgeTree queryPoly, Line line) {
for (int i = 0, j = 1; j < line.numPoints(); ++i, ++j) { 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]); Relation r = queryPoly.relateTriangle(qTriangle[1], qTriangle[0], qTriangle[3], qTriangle[2], qTriangle[5], qTriangle[4]);
if (queryRelation == QueryRelation.DISJOINT) { if (queryRelation == QueryRelation.DISJOINT) {
if (r != Relation.CELL_OUTSIDE_QUERY) return false; if (r != Relation.CELL_OUTSIDE_QUERY) return false;

View File

@ -72,13 +72,15 @@ public class TestLatLonPolygonShapeQueries extends BaseLatLonShapeTestCase {
Rectangle2D rectangle2D = Rectangle2D.create(new Rectangle(minLat, maxLat, minLon, maxLon)); Rectangle2D rectangle2D = Rectangle2D.create(new Rectangle(minLat, maxLat, minLon, maxLon));
List<Tessellator.Triangle> tessellation = Tessellator.tessellate(p); List<Tessellator.Triangle> tessellation = Tessellator.tessellate(p);
for (Tessellator.Triangle t : tessellation) { 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 (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; return false;
} }
} else { } 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; return queryRelation == QueryRelation.INTERSECTS;
} }
} }
@ -99,7 +101,9 @@ public class TestLatLonPolygonShapeQueries extends BaseLatLonShapeTestCase {
private boolean testPolygon(EdgeTree tree, Polygon shape) { private boolean testPolygon(EdgeTree tree, Polygon shape) {
List<Tessellator.Triangle> tessellation = Tessellator.tessellate(shape); List<Tessellator.Triangle> tessellation = Tessellator.tessellate(shape);
for (Tessellator.Triangle t : tessellation) { 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]); Relation r = tree.relateTriangle(qTriangle[1], qTriangle[0], qTriangle[3], qTriangle[2], qTriangle[5], qTriangle[4]);
if (queryRelation == QueryRelation.DISJOINT) { if (queryRelation == QueryRelation.DISJOINT) {
if (r != Relation.CELL_OUTSIDE_QUERY) return false; if (r != Relation.CELL_OUTSIDE_QUERY) return false;

View File

@ -236,12 +236,13 @@ public class TestLatLonShape extends LuceneTestCase {
Tessellator.Triangle t = Tessellator.tessellate(poly).get(0); Tessellator.Triangle t = Tessellator.tessellate(poly).get(0);
byte[] encoded = new byte[7 * ShapeField.BYTES]; byte[] encoded = new byte[7 * ShapeField.BYTES];
ShapeField.encodeTriangle(encoded, encodeLatitude(t.getY(0)), encodeLongitude(t.getX(0)), ShapeField.encodeTriangle(encoded, encodeLatitude(t.getY(0)), encodeLongitude(t.getX(0)), t.isEdgefromPolygon(0),
encodeLatitude(t.getY(1)), encodeLongitude(t.getX(1)), encodeLatitude(t.getY(2)), encodeLongitude(t.getX(2))); encodeLatitude(t.getY(1)), encodeLongitude(t.getX(1)), t.isEdgefromPolygon(1),
int[] decoded = new int[6]; encodeLatitude(t.getY(2)), encodeLongitude(t.getX(2)), t.isEdgefromPolygon(2));
ShapeField.DecodedTriangle decoded = new ShapeField.DecodedTriangle();
ShapeField.decodeTriangle(encoded, decoded); 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(); Document document = new Document();
addPolygonsToDoc(FIELDNAME, document, poly); addPolygonsToDoc(FIELDNAME, document, poly);

View File

@ -81,13 +81,13 @@ public class TestXYLineShapeQueries extends BaseXYShapeTestCase {
XYLine line = (XYLine)shape; XYLine line = (XYLine)shape;
XYRectangle2D rectangle2D = XYRectangle2D.create(new XYRectangle(minX, maxX, minY, maxY)); XYRectangle2D rectangle2D = XYRectangle2D.create(new XYRectangle(minX, maxX, minY, maxY));
for (int i = 0, j = 1; j < line.numPoints(); ++i, ++j) { 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 (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; return false;
} }
} else { } 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; return queryRelation == QueryRelation.INTERSECTS;
} }
} }
@ -108,7 +108,7 @@ public class TestXYLineShapeQueries extends BaseXYShapeTestCase {
private boolean testLine(EdgeTree queryPoly, XYLine line) { private boolean testLine(EdgeTree queryPoly, XYLine line) {
for (int i = 0, j = 1; j < line.numPoints(); ++i, ++j) { 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]); Relation r = queryPoly.relateTriangle(qTriangle[1], qTriangle[0], qTriangle[3], qTriangle[2], qTriangle[5], qTriangle[4]);
if (queryRelation == QueryRelation.DISJOINT) { if (queryRelation == QueryRelation.DISJOINT) {
if (r != Relation.CELL_OUTSIDE_QUERY) return false; if (r != Relation.CELL_OUTSIDE_QUERY) return false;

View File

@ -72,13 +72,15 @@ public class TestXYPolygonShapeQueries extends BaseXYShapeTestCase {
XYRectangle2D rectangle2D = XYRectangle2D.create(new XYRectangle(minX, maxX, minY, maxY)); XYRectangle2D rectangle2D = XYRectangle2D.create(new XYRectangle(minX, maxX, minY, maxY));
List<Tessellator.Triangle> tessellation = Tessellator.tessellate(p); List<Tessellator.Triangle> tessellation = Tessellator.tessellate(p);
for (Tessellator.Triangle t : tessellation) { 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 (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; return false;
} }
} else { } 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; return queryRelation == QueryRelation.INTERSECTS;
} }
} }
@ -99,7 +101,9 @@ public class TestXYPolygonShapeQueries extends BaseXYShapeTestCase {
private boolean testPolygon(EdgeTree tree, XYPolygon shape) { private boolean testPolygon(EdgeTree tree, XYPolygon shape) {
List<Tessellator.Triangle> tessellation = Tessellator.tessellate(shape); List<Tessellator.Triangle> tessellation = Tessellator.tessellate(shape);
for (Tessellator.Triangle t : tessellation) { 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]); Relation r = tree.relateTriangle(qTriangle[1], qTriangle[0], qTriangle[3], qTriangle[2], qTriangle[5], qTriangle[4]);
if (queryRelation == QueryRelation.DISJOINT) { if (queryRelation == QueryRelation.DISJOINT) {
if (r != Relation.CELL_OUTSIDE_QUERY) return false; if (r != Relation.CELL_OUTSIDE_QUERY) return false;

View File

@ -554,6 +554,9 @@ public class TestTessellator extends LuceneTestCase {
Polygon polygon = (Polygon) SimpleWKTShapeParser.parse(wkt); Polygon polygon = (Polygon) SimpleWKTShapeParser.parse(wkt);
List<Tessellator.Triangle> tessellation = Tessellator.tessellate(polygon); List<Tessellator.Triangle> tessellation = Tessellator.tessellate(polygon);
assertEquals(area(polygon), area(tessellation), 0.0); assertEquals(area(polygon), area(tessellation), 0.0);
for (Tessellator.Triangle t : tessellation) {
checkTriangleEdgesFromPolygon(polygon, t);
}
} }
private double area(Polygon p) { private double area(Polygon p) {
@ -578,4 +581,77 @@ public class TestTessellator extends LuceneTestCase {
} }
return area; 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;
}
} }