Skip to content

Commit 20b58f0

Browse files
authored
[GEO] Fork Lucene's LatLonShape Classes to local lucene package (#36794)
Lucene 7.6 uses a smaller encoding for LatLonShape. This commit forks the LatLonShape classes to Elasticsearch's local lucene package. These classes will be removed on the release of Lucene 7.6.
1 parent a838759 commit 20b58f0

File tree

12 files changed

+2330
-21
lines changed

12 files changed

+2330
-21
lines changed

buildSrc/src/main/resources/forbidden/es-server-signatures.txt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,3 +147,15 @@ org.apache.logging.log4j.Logger#error(java.lang.Object)
147147
org.apache.logging.log4j.Logger#error(java.lang.Object, java.lang.Throwable)
148148
org.apache.logging.log4j.Logger#fatal(java.lang.Object)
149149
org.apache.logging.log4j.Logger#fatal(java.lang.Object, java.lang.Throwable)
150+
151+
# Remove once Lucene 7.7 is integrated
152+
@defaultMessage Use org.apache.lucene.document.XLatLonShape classes instead
153+
org.apache.lucene.document.LatLonShape
154+
org.apache.lucene.document.LatLonShapeBoundingBoxQuery
155+
org.apache.lucene.document.LatLonShapeLineQuery
156+
org.apache.lucene.document.LatLonShapePolygonQuery
157+
org.apache.lucene.document.LatLonShapeQuery
158+
159+
org.apache.lucene.geo.Rectangle2D @ use @org.apache.lucene.geo.XRectangle2D instead
160+
161+
org.apache.lucene.geo.Tessellator @ use @org.apache.lucene.geo.XTessellator instead
Lines changed: 373 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,373 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package org.apache.lucene.document;
18+
19+
import org.apache.lucene.geo.GeoUtils;
20+
import org.apache.lucene.geo.Line;
21+
import org.apache.lucene.geo.Polygon;
22+
import org.apache.lucene.geo.XTessellator;
23+
import org.apache.lucene.geo.XTessellator.Triangle;
24+
import org.apache.lucene.index.PointValues;
25+
import org.apache.lucene.search.Query;
26+
import org.apache.lucene.util.BytesRef;
27+
import org.apache.lucene.util.NumericUtils;
28+
29+
import java.util.ArrayList;
30+
import java.util.List;
31+
32+
import static org.apache.lucene.geo.GeoEncodingUtils.encodeLatitude;
33+
import static org.apache.lucene.geo.GeoEncodingUtils.encodeLongitude;
34+
35+
/**
36+
* An indexed shape utility class.
37+
* <p>
38+
* {@link Polygon}'s are decomposed into a triangular mesh using the {@link XTessellator} utility class
39+
* Each {@link Triangle} is encoded and indexed as a multi-value field.
40+
* <p>
41+
* Finding all shapes that intersect a range (e.g., bounding box) at search time is efficient.
42+
* <p>
43+
* This class defines static factory methods for common operations:
44+
* <ul>
45+
* <li>{@link #createIndexableFields(String, Polygon)} for matching polygons that intersect a bounding box.
46+
* <li>{@link #newBoxQuery newBoxQuery()} for matching polygons that intersect a bounding box.
47+
* </ul>
48+
49+
* <b>WARNING</b>: Like {@link LatLonPoint}, vertex values are indexed with some loss of precision from the
50+
* original {@code double} values (4.190951585769653E-8 for the latitude component
51+
* and 8.381903171539307E-8 for longitude).
52+
* @see PointValues
53+
* @see LatLonDocValuesField
54+
*
55+
* @lucene.experimental
56+
*/
57+
public class XLatLonShape {
58+
public static final int BYTES = LatLonPoint.BYTES;
59+
60+
protected static final FieldType TYPE = new FieldType();
61+
static {
62+
TYPE.setDimensions(7, 4, BYTES);
63+
TYPE.freeze();
64+
}
65+
66+
// no instance:
67+
private XLatLonShape() {
68+
}
69+
70+
/** create indexable fields for polygon geometry */
71+
public static Field[] createIndexableFields(String fieldName, Polygon polygon) {
72+
// the lionshare of the indexing is done by the tessellator
73+
List<Triangle> tessellation = XTessellator.tessellate(polygon);
74+
List<LatLonTriangle> fields = new ArrayList<>();
75+
for (Triangle t : tessellation) {
76+
fields.add(new LatLonTriangle(fieldName, t));
77+
}
78+
return fields.toArray(new Field[fields.size()]);
79+
}
80+
81+
/** create indexable fields for line geometry */
82+
public static Field[] createIndexableFields(String fieldName, Line line) {
83+
int numPoints = line.numPoints();
84+
Field[] fields = new Field[numPoints - 1];
85+
// create "flat" triangles
86+
for (int i = 0, j = 1; j < numPoints; ++i, ++j) {
87+
fields[i] = new LatLonTriangle(fieldName, line.getLat(i), line.getLon(i), line.getLat(j), line.getLon(j),
88+
line.getLat(i), line.getLon(i));
89+
}
90+
return fields;
91+
}
92+
93+
/** create indexable fields for point geometry */
94+
public static Field[] createIndexableFields(String fieldName, double lat, double lon) {
95+
return new Field[] {new LatLonTriangle(fieldName, lat, lon, lat, lon, lat, lon)};
96+
}
97+
98+
/** create a query to find all polygons that intersect a defined bounding box
99+
**/
100+
public static Query newBoxQuery(String field, QueryRelation queryRelation,
101+
double minLatitude, double maxLatitude, double minLongitude, double maxLongitude) {
102+
return new XLatLonShapeBoundingBoxQuery(field, queryRelation, minLatitude, maxLatitude, minLongitude, maxLongitude);
103+
}
104+
105+
/** create a query to find all polygons that intersect a provided linestring (or array of linestrings)
106+
* note: does not support dateline crossing
107+
**/
108+
public static Query newLineQuery(String field, QueryRelation queryRelation, Line... lines) {
109+
return new XLatLonShapeLineQuery(field, queryRelation, lines);
110+
}
111+
112+
/** create a query to find all polygons that intersect a provided polygon (or array of polygons)
113+
* note: does not support dateline crossing
114+
**/
115+
public static Query newPolygonQuery(String field, QueryRelation queryRelation, Polygon... polygons) {
116+
return new XLatLonShapePolygonQuery(field, queryRelation, polygons);
117+
}
118+
119+
/** polygons are decomposed into tessellated triangles using {@link XTessellator}
120+
* these triangles are encoded and inserted as separate indexed POINT fields
121+
*/
122+
private static class LatLonTriangle extends Field {
123+
124+
LatLonTriangle(String name, double aLat, double aLon, double bLat, double bLon, double cLat, double cLon) {
125+
super(name, TYPE);
126+
setTriangleValue(encodeLongitude(aLon), encodeLatitude(aLat), encodeLongitude(bLon), encodeLatitude(bLat),
127+
encodeLongitude(cLon), encodeLatitude(cLat));
128+
}
129+
130+
LatLonTriangle(String name, Triangle t) {
131+
super(name, TYPE);
132+
setTriangleValue(t.getEncodedX(0), t.getEncodedY(0), t.getEncodedX(1), t.getEncodedY(1),
133+
t.getEncodedX(2), t.getEncodedY(2));
134+
}
135+
136+
137+
public void setTriangleValue(int aX, int aY, int bX, int bY, int cX, int cY) {
138+
final byte[] bytes;
139+
140+
if (fieldsData == null) {
141+
bytes = new byte[7 * BYTES];
142+
fieldsData = new BytesRef(bytes);
143+
} else {
144+
bytes = ((BytesRef) fieldsData).bytes;
145+
}
146+
encodeTriangle(bytes, aY, aX, bY, bX, cY, cX);
147+
}
148+
}
149+
150+
/** Query Relation Types **/
151+
public enum QueryRelation {
152+
INTERSECTS, WITHIN, DISJOINT
153+
}
154+
155+
private static final int MINY_MINX_MAXY_MAXX_Y_X = 0;
156+
private static final int MINY_MINX_Y_X_MAXY_MAXX = 1;
157+
private static final int MAXY_MINX_Y_X_MINY_MAXX = 2;
158+
private static final int MAXY_MINX_MINY_MAXX_Y_X = 3;
159+
private static final int Y_MINX_MINY_X_MAXY_MAXX = 4;
160+
private static final int Y_MINX_MINY_MAXX_MAXY_X = 5;
161+
private static final int MAXY_MINX_MINY_X_Y_MAXX = 6;
162+
private static final int MINY_MINX_Y_MAXX_MAXY_X = 7;
163+
164+
/**
165+
* A triangle is encoded using 6 points and an extra point with encoded information in three bits of how to reconstruct it.
166+
* Triangles are encoded with CCW orientation and might be rotated to limit the number of possible reconstructions to 2^3.
167+
* Reconstruction always happens from west to east.
168+
*/
169+
public static void encodeTriangle(byte[] bytes, int aLat, int aLon, int bLat, int bLon, int cLat, int cLon) {
170+
assert bytes.length == 7 * BYTES;
171+
int aX;
172+
int bX;
173+
int cX;
174+
int aY;
175+
int bY;
176+
int cY;
177+
//change orientation if CW
178+
if (GeoUtils.orient(aLon, aLat, bLon, bLat, cLon, cLat) == -1) {
179+
aX = cLon;
180+
bX = bLon;
181+
cX = aLon;
182+
aY = cLat;
183+
bY = bLat;
184+
cY = aLat;
185+
} else {
186+
aX = aLon;
187+
bX = bLon;
188+
cX = cLon;
189+
aY = aLat;
190+
bY = bLat;
191+
cY = cLat;
192+
}
193+
//rotate edges and place minX at the beginning
194+
if (bX < aX || cX < aX) {
195+
if (bX < cX) {
196+
int tempX = aX;
197+
int tempY = aY;
198+
aX = bX;
199+
aY = bY;
200+
bX = cX;
201+
bY = cY;
202+
cX = tempX;
203+
cY = tempY;
204+
} else if (cX < aX) {
205+
int tempX = aX;
206+
int tempY = aY;
207+
aX = cX;
208+
aY = cY;
209+
cX = bX;
210+
cY = bY;
211+
bX = tempX;
212+
bY = tempY;
213+
}
214+
} else if (aX == bX && aX == cX) {
215+
//degenerated case, all points with same longitude
216+
//we need to prevent that aX is in the middle (not part of the MBS)
217+
if (bY < aY || cY < aY) {
218+
if (bY < cY) {
219+
int tempX = aX;
220+
int tempY = aY;
221+
aX = bX;
222+
aY = bY;
223+
bX = cX;
224+
bY = cY;
225+
cX = tempX;
226+
cY = tempY;
227+
} else if (cY < aY) {
228+
int tempX = aX;
229+
int tempY = aY;
230+
aX = cX;
231+
aY = cY;
232+
cX = bX;
233+
cY = bY;
234+
bX = tempX;
235+
bY = tempY;
236+
}
237+
}
238+
}
239+
240+
int minX = aX;
241+
int minY = StrictMath.min(aY, StrictMath.min(bY, cY));
242+
int maxX = StrictMath.max(aX, StrictMath.max(bX, cX));
243+
int maxY = StrictMath.max(aY, StrictMath.max(bY, cY));
244+
245+
int bits, x, y;
246+
if (minY == aY) {
247+
if (maxY == bY && maxX == bX) {
248+
y = cY;
249+
x = cX;
250+
bits = MINY_MINX_MAXY_MAXX_Y_X;
251+
} else if (maxY == cY && maxX == cX) {
252+
y = bY;
253+
x = bX;
254+
bits = MINY_MINX_Y_X_MAXY_MAXX;
255+
} else {
256+
y = bY;
257+
x = cX;
258+
bits = MINY_MINX_Y_MAXX_MAXY_X;
259+
}
260+
} else if (maxY == aY) {
261+
if (minY == bY && maxX == bX) {
262+
y = cY;
263+
x = cX;
264+
bits = MAXY_MINX_MINY_MAXX_Y_X;
265+
} else if (minY == cY && maxX == cX) {
266+
y = bY;
267+
x = bX;
268+
bits = MAXY_MINX_Y_X_MINY_MAXX;
269+
} else {
270+
y = cY;
271+
x = bX;
272+
bits = MAXY_MINX_MINY_X_Y_MAXX;
273+
}
274+
} else if (maxX == bX && minY == bY) {
275+
y = aY;
276+
x = cX;
277+
bits = Y_MINX_MINY_MAXX_MAXY_X;
278+
} else if (maxX == cX && maxY == cY) {
279+
y = aY;
280+
x = bX;
281+
bits = Y_MINX_MINY_X_MAXY_MAXX;
282+
} else {
283+
throw new IllegalArgumentException("Could not encode the provided triangle");
284+
}
285+
NumericUtils.intToSortableBytes(minY, bytes, 0);
286+
NumericUtils.intToSortableBytes(minX, bytes, BYTES);
287+
NumericUtils.intToSortableBytes(maxY, bytes, 2 * BYTES);
288+
NumericUtils.intToSortableBytes(maxX, bytes, 3 * BYTES);
289+
NumericUtils.intToSortableBytes(y, bytes, 4 * BYTES);
290+
NumericUtils.intToSortableBytes(x, bytes, 5 * BYTES);
291+
NumericUtils.intToSortableBytes(bits, bytes, 6 * BYTES);
292+
}
293+
294+
/**
295+
* Decode a triangle encoded by {@link XLatLonShape#encodeTriangle(byte[], int, int, int, int, int, int)}.
296+
*/
297+
public static void decodeTriangle(byte[] t, int[] triangle) {
298+
assert triangle.length == 6;
299+
int bits = NumericUtils.sortableBytesToInt(t, 6 * XLatLonShape.BYTES);
300+
//extract the first three bits
301+
int tCode = (((1 << 3) - 1) & (bits >> 0));
302+
switch (tCode) {
303+
case MINY_MINX_MAXY_MAXX_Y_X:
304+
triangle[0] = NumericUtils.sortableBytesToInt(t, 0 * XLatLonShape.BYTES);
305+
triangle[1] = NumericUtils.sortableBytesToInt(t, 1 * XLatLonShape.BYTES);
306+
triangle[2] = NumericUtils.sortableBytesToInt(t, 2 * XLatLonShape.BYTES);
307+
triangle[3] = NumericUtils.sortableBytesToInt(t, 3 * XLatLonShape.BYTES);
308+
triangle[4] = NumericUtils.sortableBytesToInt(t, 4 * XLatLonShape.BYTES);
309+
triangle[5] = NumericUtils.sortableBytesToInt(t, 5 * XLatLonShape.BYTES);
310+
break;
311+
case MINY_MINX_Y_X_MAXY_MAXX:
312+
triangle[0] = NumericUtils.sortableBytesToInt(t, 0 * XLatLonShape.BYTES);
313+
triangle[1] = NumericUtils.sortableBytesToInt(t, 1 * XLatLonShape.BYTES);
314+
triangle[2] = NumericUtils.sortableBytesToInt(t, 4 * XLatLonShape.BYTES);
315+
triangle[3] = NumericUtils.sortableBytesToInt(t, 5 * XLatLonShape.BYTES);
316+
triangle[4] = NumericUtils.sortableBytesToInt(t, 2 * XLatLonShape.BYTES);
317+
triangle[5] = NumericUtils.sortableBytesToInt(t, 3 * XLatLonShape.BYTES);
318+
break;
319+
case MAXY_MINX_Y_X_MINY_MAXX:
320+
triangle[0] = NumericUtils.sortableBytesToInt(t, 2 * XLatLonShape.BYTES);
321+
triangle[1] = NumericUtils.sortableBytesToInt(t, 1 * XLatLonShape.BYTES);
322+
triangle[2] = NumericUtils.sortableBytesToInt(t, 4 * XLatLonShape.BYTES);
323+
triangle[3] = NumericUtils.sortableBytesToInt(t, 5 * XLatLonShape.BYTES);
324+
triangle[4] = NumericUtils.sortableBytesToInt(t, 0 * XLatLonShape.BYTES);
325+
triangle[5] = NumericUtils.sortableBytesToInt(t, 3 * XLatLonShape.BYTES);
326+
break;
327+
case MAXY_MINX_MINY_MAXX_Y_X:
328+
triangle[0] = NumericUtils.sortableBytesToInt(t, 2 * XLatLonShape.BYTES);
329+
triangle[1] = NumericUtils.sortableBytesToInt(t, 1 * XLatLonShape.BYTES);
330+
triangle[2] = NumericUtils.sortableBytesToInt(t, 0 * XLatLonShape.BYTES);
331+
triangle[3] = NumericUtils.sortableBytesToInt(t, 3 * XLatLonShape.BYTES);
332+
triangle[4] = NumericUtils.sortableBytesToInt(t, 4 * XLatLonShape.BYTES);
333+
triangle[5] = NumericUtils.sortableBytesToInt(t, 5 * XLatLonShape.BYTES);
334+
break;
335+
case Y_MINX_MINY_X_MAXY_MAXX:
336+
triangle[0] = NumericUtils.sortableBytesToInt(t, 4 * XLatLonShape.BYTES);
337+
triangle[1] = NumericUtils.sortableBytesToInt(t, 1 * XLatLonShape.BYTES);
338+
triangle[2] = NumericUtils.sortableBytesToInt(t, 0 * XLatLonShape.BYTES);
339+
triangle[3] = NumericUtils.sortableBytesToInt(t, 5 * XLatLonShape.BYTES);
340+
triangle[4] = NumericUtils.sortableBytesToInt(t, 2 * XLatLonShape.BYTES);
341+
triangle[5] = NumericUtils.sortableBytesToInt(t, 3 * XLatLonShape.BYTES);
342+
break;
343+
case Y_MINX_MINY_MAXX_MAXY_X:
344+
triangle[0] = NumericUtils.sortableBytesToInt(t, 4 * XLatLonShape.BYTES);
345+
triangle[1] = NumericUtils.sortableBytesToInt(t, 1 * XLatLonShape.BYTES);
346+
triangle[2] = NumericUtils.sortableBytesToInt(t, 0 * XLatLonShape.BYTES);
347+
triangle[3] = NumericUtils.sortableBytesToInt(t, 3 * XLatLonShape.BYTES);
348+
triangle[4] = NumericUtils.sortableBytesToInt(t, 2 * XLatLonShape.BYTES);
349+
triangle[5] = NumericUtils.sortableBytesToInt(t, 5 * XLatLonShape.BYTES);
350+
break;
351+
case MAXY_MINX_MINY_X_Y_MAXX:
352+
triangle[0] = NumericUtils.sortableBytesToInt(t, 2 * XLatLonShape.BYTES);
353+
triangle[1] = NumericUtils.sortableBytesToInt(t, 1 * XLatLonShape.BYTES);
354+
triangle[2] = NumericUtils.sortableBytesToInt(t, 0 * XLatLonShape.BYTES);
355+
triangle[3] = NumericUtils.sortableBytesToInt(t, 5 * XLatLonShape.BYTES);
356+
triangle[4] = NumericUtils.sortableBytesToInt(t, 4 * XLatLonShape.BYTES);
357+
triangle[5] = NumericUtils.sortableBytesToInt(t, 3 * XLatLonShape.BYTES);
358+
break;
359+
case MINY_MINX_Y_MAXX_MAXY_X:
360+
triangle[0] = NumericUtils.sortableBytesToInt(t, 0 * XLatLonShape.BYTES);
361+
triangle[1] = NumericUtils.sortableBytesToInt(t, 1 * XLatLonShape.BYTES);
362+
triangle[2] = NumericUtils.sortableBytesToInt(t, 4 * XLatLonShape.BYTES);
363+
triangle[3] = NumericUtils.sortableBytesToInt(t, 3 * XLatLonShape.BYTES);
364+
triangle[4] = NumericUtils.sortableBytesToInt(t, 2 * XLatLonShape.BYTES);
365+
triangle[5] = NumericUtils.sortableBytesToInt(t, 5 * XLatLonShape.BYTES);
366+
break;
367+
default:
368+
throw new IllegalArgumentException("Could not decode the provided triangle");
369+
}
370+
//Points of the decoded triangle must be co-planar or CCW oriented
371+
assert GeoUtils.orient(triangle[1], triangle[0], triangle[3], triangle[2], triangle[5], triangle[4]) >= 0;
372+
}
373+
}

0 commit comments

Comments
 (0)