blob: f3e89d6327591e96da04690890cb0f2addbc6b9c [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.lucene.document;
import java.util.Objects;
import org.apache.lucene.geo.GeoUtils;
import org.apache.lucene.geo.Line;
import org.apache.lucene.geo.Polygon;
import org.apache.lucene.geo.Tessellator;
import org.apache.lucene.util.BytesRef;
import org.apache.lucene.util.NumericUtils;
/**
* A base shape utility class used for both LatLon (spherical) and XY (cartesian) shape fields.
* <p>
* {@link Polygon}'s and {@link Line}'s are decomposed into a triangular mesh using the {@link Tessellator} utility class.
* Each {@link Triangle} is encoded by this base class and indexed as a seven dimension multi-value field.
* <p>
* Finding all shapes that intersect a range (e.g., bounding box), or target shape, at search time is efficient.
* <p>
* This class defines the static methods for encoding the three vertices of a tessellated triangles as a seven dimension point.
* The coordinates are converted from double precision values into 32 bit integers so they are sortable at index time.
*/
public final class ShapeField {
/** vertex coordinates are encoded as 4 byte integers */
static final int BYTES = Integer.BYTES;
/** tessellated triangles are seven dimensions; the first four are the bounding box index dimensions */
protected static final FieldType TYPE = new FieldType();
static {
TYPE.setDimensions(7, 4, BYTES);
TYPE.freeze();
}
// no instance:
private ShapeField() {
}
/** polygons are decomposed into tessellated triangles using {@link org.apache.lucene.geo.Tessellator}
* these triangles are encoded and inserted as separate indexed POINT fields
*/
public static class Triangle extends Field {
/** constructor for points and lines */
Triangle(String name, int aXencoded, int aYencoded, int bXencoded, int bYencoded, int cXencoded, int cYencoded) {
super(name, TYPE);
setTriangleValue(aXencoded, aYencoded, true, bXencoded, bYencoded, true, cXencoded, cYencoded, true);
}
/** xtor from a given Tessellated Triangle object */
Triangle(String name, Tessellator.Triangle t) {
super(name, TYPE);
setTriangleValue(t.getEncodedX(0), t.getEncodedY(0), t.isEdgefromPolygon(0),
t.getEncodedX(1), t.getEncodedY(1), t.isEdgefromPolygon(1),
t.getEncodedX(2), t.getEncodedY(2), t.isEdgefromPolygon(2));
}
/** sets the vertices of the triangle as integer encoded values */
protected void setTriangleValue(int aX, int aY, boolean abFromShape, int bX, int bY, boolean bcFromShape, int cX, int cY, boolean caFromShape) {
final byte[] bytes;
if (fieldsData == null) {
bytes = new byte[7 * BYTES];
fieldsData = new BytesRef(bytes);
} else {
bytes = ((BytesRef) fieldsData).bytes;
}
encodeTriangle(bytes, aY, aX, abFromShape, bY, bX, bcFromShape, cY, cX, caFromShape);
}
}
/** Query Relation Types **/
public enum QueryRelation {
/** used for INTERSECT Queries */
INTERSECTS,
/** used for WITHIN Queries */
WITHIN,
/** used for DISJOINT Queries */
DISJOINT,
/** used for CONTAINS Queries */
CONTAINS
}
private static final int MINY_MINX_MAXY_MAXX_Y_X = 0;
private static final int MINY_MINX_Y_X_MAXY_MAXX = 1;
private static final int MAXY_MINX_Y_X_MINY_MAXX = 2;
private static final int MAXY_MINX_MINY_MAXX_Y_X = 3;
private static final int Y_MINX_MINY_X_MAXY_MAXX = 4;
private static final int Y_MINX_MINY_MAXX_MAXY_X = 5;
private static final int MAXY_MINX_MINY_X_Y_MAXX = 6;
private static final int MINY_MINX_Y_MAXX_MAXY_X = 7;
/**
* A triangle is encoded using 6 points and an extra point with encoded information in three bits of how to reconstruct it.
* Triangles are encoded with CCW orientation and might be rotated to limit the number of possible reconstructions to 2^3.
* Reconstruction always happens from west to east.
*/
public static void encodeTriangle(byte[] bytes, int aY, int aX, boolean ab, int bY, int bX, boolean bc, int cY, int cX, boolean ca) {
assert bytes.length == 7 * BYTES;
// rotate edges and place minX at the beginning
if (bX < aX || cX < aX) {
final int tempX = aX;
final int tempY = aY;
final boolean tempBool = ab;
if (bX < cX) {
aX = bX;
aY = bY;
ab = bc;
bX = cX;
bY = cY;
bc = ca;
cX = tempX;
cY = tempY;
ca = tempBool;
} else {
aX = cX;
aY = cY;
ab = ca;
cX = bX;
cY = bY;
ca = bc;
bX = tempX;
bY = tempY;
bc = tempBool;
}
} else if (aX == bX && aX == cX) {
// degenerated case, all points with same longitude
// we need to prevent that aX is in the middle (not part of the MBS)
if (bY < aY || cY < aY) {
final int tempX = aX;
final int tempY = aY;
final boolean tempBool = ab;
if (bY < cY) {
aX = bX;
aY = bY;
ab = bc;
bX = cX;
bY = cY;
bc = ca;
cX = tempX;
cY = tempY;
ca = tempBool;
} else {
aX = cX;
aY = cY;
ab = ca;
cX = bX;
cY = bY;
ca = bc;
bX = tempX;
bY = tempY;
bc = tempBool;
}
}
}
// change orientation if CW
if (GeoUtils.orient(aX, aY, bX, bY, cX, cY) == -1) {
// swap b with c
final int tempX = bX;
final int tempY = bY;
final boolean tempBool = ab;
// aX and aY do not change, ab becomes bc
ab = bc;
bX = cX;
bY = cY;
// bc does not change, ca becomes ab
cX = tempX;
cY = tempY;
ca = tempBool;
}
int minX = aX;
int minY = StrictMath.min(aY, StrictMath.min(bY, cY));
int maxX = StrictMath.max(aX, StrictMath.max(bX, cX));
int maxY = StrictMath.max(aY, StrictMath.max(bY, cY));
int bits, x, y;
if (minY == aY) {
if (maxY == bY && maxX == bX) {
y = cY;
x = cX;
bits = MINY_MINX_MAXY_MAXX_Y_X;
} else if (maxY == cY && maxX == cX) {
y = bY;
x = bX;
bits = MINY_MINX_Y_X_MAXY_MAXX;
} else {
y = bY;
x = cX;
bits = MINY_MINX_Y_MAXX_MAXY_X;
}
} else if (maxY == aY) {
if (minY == bY && maxX == bX) {
y = cY;
x = cX;
bits = MAXY_MINX_MINY_MAXX_Y_X;
} else if (minY == cY && maxX == cX) {
y = bY;
x = bX;
bits = MAXY_MINX_Y_X_MINY_MAXX;
} else {
y = cY;
x = bX;
bits = MAXY_MINX_MINY_X_Y_MAXX;
}
} else if (maxX == bX && minY == bY) {
y = aY;
x = cX;
bits = Y_MINX_MINY_MAXX_MAXY_X;
} else if (maxX == cX && maxY == cY) {
y = aY;
x = bX;
bits = Y_MINX_MINY_X_MAXY_MAXX;
} else {
throw new IllegalArgumentException("Could not encode the provided triangle");
}
bits |= (ab) ? (1 << 3) : 0;
bits |= (bc) ? (1 << 4) : 0;
bits |= (ca) ? (1 << 5) : 0;
NumericUtils.intToSortableBytes(minY, bytes, 0);
NumericUtils.intToSortableBytes(minX, bytes, BYTES);
NumericUtils.intToSortableBytes(maxY, bytes, 2 * BYTES);
NumericUtils.intToSortableBytes(maxX, bytes, 3 * BYTES);
NumericUtils.intToSortableBytes(y, bytes, 4 * BYTES);
NumericUtils.intToSortableBytes(x, bytes, 5 * 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)}.
*/
public static void decodeTriangle(byte[] t, DecodedTriangle triangle) {
final int aX, aY, bX, bY, cX, cY;
final boolean ab, bc, ca;
int bits = NumericUtils.sortableBytesToInt(t, 6 * BYTES);
//extract the first three bits
int tCode = (((1 << 3) - 1) & (bits >> 0));
switch (tCode) {
case MINY_MINX_MAXY_MAXX_Y_X:
aY = NumericUtils.sortableBytesToInt(t, 0 * BYTES);
aX = NumericUtils.sortableBytesToInt(t, 1 * BYTES);
bY = NumericUtils.sortableBytesToInt(t, 2 * BYTES);
bX = NumericUtils.sortableBytesToInt(t, 3 * BYTES);
cY = NumericUtils.sortableBytesToInt(t, 4 * BYTES);
cX = NumericUtils.sortableBytesToInt(t, 5 * BYTES);
break;
case MINY_MINX_Y_X_MAXY_MAXX:
aY = NumericUtils.sortableBytesToInt(t, 0 * BYTES);
aX = NumericUtils.sortableBytesToInt(t, 1 * BYTES);
bY = NumericUtils.sortableBytesToInt(t, 4 * BYTES);
bX = NumericUtils.sortableBytesToInt(t, 5 * BYTES);
cY = NumericUtils.sortableBytesToInt(t, 2 * BYTES);
cX = NumericUtils.sortableBytesToInt(t, 3 * BYTES);
break;
case MAXY_MINX_Y_X_MINY_MAXX:
aY = NumericUtils.sortableBytesToInt(t, 2 * BYTES);
aX = NumericUtils.sortableBytesToInt(t, 1 * BYTES);
bY = NumericUtils.sortableBytesToInt(t, 4 * BYTES);
bX = NumericUtils.sortableBytesToInt(t, 5 * BYTES);
cY = NumericUtils.sortableBytesToInt(t, 0 * BYTES);
cX = NumericUtils.sortableBytesToInt(t, 3 * BYTES);
break;
case MAXY_MINX_MINY_MAXX_Y_X:
aY = NumericUtils.sortableBytesToInt(t, 2 * BYTES);
aX = NumericUtils.sortableBytesToInt(t, 1 * BYTES);
bY = NumericUtils.sortableBytesToInt(t, 0 * BYTES);
bX = NumericUtils.sortableBytesToInt(t, 3 * BYTES);
cY = NumericUtils.sortableBytesToInt(t, 4 * BYTES);
cX = NumericUtils.sortableBytesToInt(t, 5 * BYTES);
break;
case Y_MINX_MINY_X_MAXY_MAXX:
aY = NumericUtils.sortableBytesToInt(t, 4 * BYTES);
aX = NumericUtils.sortableBytesToInt(t, 1 * BYTES);
bY = NumericUtils.sortableBytesToInt(t, 0 * BYTES);
bX = NumericUtils.sortableBytesToInt(t, 5 * BYTES);
cY = NumericUtils.sortableBytesToInt(t, 2 * BYTES);
cX = NumericUtils.sortableBytesToInt(t, 3 * BYTES);
break;
case Y_MINX_MINY_MAXX_MAXY_X:
aY = NumericUtils.sortableBytesToInt(t, 4 * BYTES);
aX = NumericUtils.sortableBytesToInt(t, 1 * BYTES);
bY = NumericUtils.sortableBytesToInt(t, 0 * BYTES);
bX = NumericUtils.sortableBytesToInt(t, 3 * BYTES);
cY = NumericUtils.sortableBytesToInt(t, 2 * BYTES);
cX = NumericUtils.sortableBytesToInt(t, 5 * BYTES);
break;
case MAXY_MINX_MINY_X_Y_MAXX:
aY = NumericUtils.sortableBytesToInt(t, 2 * BYTES);
aX = NumericUtils.sortableBytesToInt(t, 1 * BYTES);
bY = NumericUtils.sortableBytesToInt(t, 0 * BYTES);
bX = NumericUtils.sortableBytesToInt(t, 5 * BYTES);
cY = NumericUtils.sortableBytesToInt(t, 4 * BYTES);
cX = NumericUtils.sortableBytesToInt(t, 3 * BYTES);
break;
case MINY_MINX_Y_MAXX_MAXY_X:
aY = NumericUtils.sortableBytesToInt(t, 0 * BYTES);
aX = NumericUtils.sortableBytesToInt(t, 1 * BYTES);
bY = NumericUtils.sortableBytesToInt(t, 4 * BYTES);
bX = NumericUtils.sortableBytesToInt(t, 3 * BYTES);
cY = NumericUtils.sortableBytesToInt(t, 2 * BYTES);
cX = NumericUtils.sortableBytesToInt(t, 5 * BYTES);
break;
default:
throw new IllegalArgumentException("Could not decode the provided triangle");
}
//Points of the decoded triangle must be co-planar or CCW oriented
assert GeoUtils.orient(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);
resolveTriangleType(triangle);
}
private static void resolveTriangleType(DecodedTriangle triangle) {
if (triangle.aX == triangle.bX && triangle.aY == triangle.bY) {
if (triangle.aX == triangle.cX && triangle.aY == triangle.cY) {
triangle.type = DecodedTriangle.TYPE.POINT;
} else {
triangle.bX = triangle.cX;
triangle.bY = triangle.cY;
triangle.cX = triangle.aX;
triangle.cY = triangle.aY;
triangle.type = DecodedTriangle.TYPE.LINE;
}
} else if (triangle.aX == triangle.cX && triangle.aY == triangle.cY) {
triangle.type = DecodedTriangle.TYPE.LINE;
} else if (triangle.bX == triangle.cX && triangle.bY == triangle.cY) {
triangle.cX = triangle.aX;
triangle.cY = triangle.aY;
triangle.type = DecodedTriangle.TYPE.LINE;
} else {
triangle.type = DecodedTriangle.TYPE.TRIANGLE;
}
}
/**
* Represents a encoded triangle using {@link ShapeField#decodeTriangle(byte[], DecodedTriangle)}.
*/
public static class DecodedTriangle {
/** type of triangle */
public enum TYPE {
/** all coordinates are equal */
POINT,
/** first and third coordinates are equal */
LINE,
/** all coordinates are different */
TRIANGLE
}
/** x coordinate, vertex one */
public int aX;
/** y coordinate, vertex one */
public int aY;
/** x coordinate, vertex two */
public int bX;
/** y coordinate, vertex two */
public int bY;
/** x coordinate, vertex three */
public int cX;
/** y coordinate, vertex three */
public int cY;
/** represent if edge ab belongs to original shape */
public boolean ab;
/** represent if edge bc belongs to original shape */
public boolean bc;
/** represent if edge ca belongs to original shape */
public boolean ca;
/** triangle type */
public TYPE type;
/** default xtor */
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;
}
}
}