All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.lucene.document.ShapeField Maven / Gradle / Ivy

/*
 * 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.
 * 

* {@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. *

* Finding all shapes that intersect a range (e.g., bounding box), or target shape, at search time is efficient. *

* 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; } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy