Skip to content

Commit 3bee25c

Browse files
authored
[GEO] Add support to ShapeBuilders for building Lucene geometry (#35707)
* [GEO] Add support to ShapeBuilders for building Lucene geometry This commit adds support for building lucene geometry from the ShapeBuilders. This is needed for integrating LatLonShape as the primary indexing approach for geo_shape field types. All unit and integration tests are updated to add randomization for testing both jts/s4j shapes and lucene shapes.
1 parent d9c6986 commit 3bee25c

25 files changed

+761
-235
lines changed

server/src/main/java/org/elasticsearch/common/geo/GeoUtils.java

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,11 @@ public static int geoHashLevelsForPrecision(String distance) {
233233
* @return The normalized longitude.
234234
*/
235235
public static double normalizeLon(double lon) {
236-
return centeredModulus(lon, 360);
236+
if (lon > 180d || lon <= -180d) {
237+
lon = centeredModulus(lon, 360);
238+
}
239+
// avoid -0.0
240+
return lon + 0d;
237241
}
238242

239243
/**
@@ -250,13 +254,16 @@ public static double normalizeLon(double lon) {
250254
* @see #normalizePoint(GeoPoint)
251255
*/
252256
public static double normalizeLat(double lat) {
253-
lat = centeredModulus(lat, 360);
254-
if (lat < -90) {
255-
lat = -180 - lat;
256-
} else if (lat > 90) {
257-
lat = 180 - lat;
257+
if (lat > 90d || lat < -90d) {
258+
lat = centeredModulus(lat, 360);
259+
if (lat < -90) {
260+
lat = -180 - lat;
261+
} else if (lat > 90) {
262+
lat = 180 - lat;
263+
}
258264
}
259-
return lat;
265+
// avoid -0.0
266+
return lat + 0d;
260267
}
261268

262269
/**

server/src/main/java/org/elasticsearch/common/geo/builders/CircleBuilder.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,10 +159,15 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws
159159
}
160160

161161
@Override
162-
public Circle build() {
162+
public Circle buildS4J() {
163163
return SPATIAL_CONTEXT.makeCircle(center.x, center.y, 360 * radius / unit.getEarthCircumference());
164164
}
165165

166+
@Override
167+
public Object buildLucene() {
168+
throw new UnsupportedOperationException("CIRCLE geometry is not supported");
169+
}
170+
166171
@Override
167172
public GeoShapeType type() {
168173
return TYPE;

server/src/main/java/org/elasticsearch/common/geo/builders/EnvelopeBuilder.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,10 +108,15 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws
108108
}
109109

110110
@Override
111-
public Rectangle build() {
111+
public Rectangle buildS4J() {
112112
return SPATIAL_CONTEXT.makeRectangle(topLeft.x, bottomRight.x, bottomRight.y, topLeft.y);
113113
}
114114

115+
@Override
116+
public org.apache.lucene.geo.Rectangle buildLucene() {
117+
return new org.apache.lucene.geo.Rectangle(bottomRight.y, topLeft.y, topLeft.x, bottomRight.x);
118+
}
119+
115120
@Override
116121
public GeoShapeType type() {
117122
return TYPE;

server/src/main/java/org/elasticsearch/common/geo/builders/GeometryCollectionBuilder.java

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131

3232
import java.io.IOException;
3333
import java.util.ArrayList;
34+
import java.util.Arrays;
3435
import java.util.List;
3536
import java.util.Objects;
3637

@@ -168,12 +169,12 @@ public int numDimensions() {
168169
}
169170

170171
@Override
171-
public Shape build() {
172+
public Shape buildS4J() {
172173

173174
List<Shape> shapes = new ArrayList<>(this.shapes.size());
174175

175176
for (ShapeBuilder shape : this.shapes) {
176-
shapes.add(shape.build());
177+
shapes.add(shape.buildS4J());
177178
}
178179

179180
if (shapes.size() == 1)
@@ -183,6 +184,25 @@ public Shape build() {
183184
//note: ShapeCollection is probably faster than a Multi* geom.
184185
}
185186

187+
@Override
188+
public Object buildLucene() {
189+
List<Object> shapes = new ArrayList<>(this.shapes.size());
190+
191+
for (ShapeBuilder shape : this.shapes) {
192+
Object o = shape.buildLucene();
193+
if (o.getClass().isArray()) {
194+
shapes.addAll(Arrays.asList((Object[])o));
195+
} else {
196+
shapes.add(o);
197+
}
198+
}
199+
200+
if (shapes.size() == 1) {
201+
return shapes.get(0);
202+
}
203+
return shapes.toArray(new Object[shapes.size()]);
204+
}
205+
186206
@Override
187207
public int hashCode() {
188208
return Objects.hash(shapes);

server/src/main/java/org/elasticsearch/common/geo/builders/LineStringBuilder.java

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
package org.elasticsearch.common.geo.builders;
2121

22+
import org.apache.lucene.geo.Line;
2223
import org.locationtech.jts.geom.Coordinate;
2324
import org.locationtech.jts.geom.Geometry;
2425
import org.locationtech.jts.geom.GeometryFactory;
@@ -35,6 +36,9 @@
3536
import java.util.Arrays;
3637
import java.util.List;
3738

39+
import static org.elasticsearch.common.geo.GeoUtils.normalizeLat;
40+
import static org.elasticsearch.common.geo.GeoUtils.normalizeLon;
41+
3842
public class LineStringBuilder extends ShapeBuilder<JtsGeometry, LineStringBuilder> {
3943
public static final GeoShapeType TYPE = GeoShapeType.LINESTRING;
4044

@@ -101,11 +105,11 @@ public int numDimensions() {
101105
}
102106

103107
@Override
104-
public JtsGeometry build() {
108+
public JtsGeometry buildS4J() {
105109
Coordinate[] coordinates = this.coordinates.toArray(new Coordinate[this.coordinates.size()]);
106110
Geometry geometry;
107111
if(wrapdateline) {
108-
ArrayList<LineString> strings = decompose(FACTORY, coordinates, new ArrayList<LineString>());
112+
ArrayList<LineString> strings = decomposeS4J(FACTORY, coordinates, new ArrayList<LineString>());
109113

110114
if(strings.size() == 1) {
111115
geometry = strings.get(0);
@@ -120,7 +124,23 @@ public JtsGeometry build() {
120124
return jtsGeometry(geometry);
121125
}
122126

123-
static ArrayList<LineString> decompose(GeometryFactory factory, Coordinate[] coordinates, ArrayList<LineString> strings) {
127+
@Override
128+
public Object buildLucene() {
129+
// decompose linestrings crossing dateline into array of Lines
130+
Coordinate[] coordinates = this.coordinates.toArray(new Coordinate[this.coordinates.size()]);
131+
if (wrapdateline) {
132+
ArrayList<Line> linestrings = decomposeLucene(coordinates, new ArrayList<>());
133+
if (linestrings.size() == 1) {
134+
return linestrings.get(0);
135+
} else {
136+
return linestrings.toArray(new Line[linestrings.size()]);
137+
}
138+
}
139+
return new Line(Arrays.stream(coordinates).mapToDouble(i->normalizeLat(i.y)).toArray(),
140+
Arrays.stream(coordinates).mapToDouble(i->normalizeLon(i.x)).toArray());
141+
}
142+
143+
static ArrayList<LineString> decomposeS4J(GeometryFactory factory, Coordinate[] coordinates, ArrayList<LineString> strings) {
124144
for(Coordinate[] part : decompose(+DATELINE, coordinates)) {
125145
for(Coordinate[] line : decompose(-DATELINE, part)) {
126146
strings.add(factory.createLineString(line));
@@ -129,6 +149,16 @@ static ArrayList<LineString> decompose(GeometryFactory factory, Coordinate[] coo
129149
return strings;
130150
}
131151

152+
static ArrayList<Line> decomposeLucene(Coordinate[] coordinates, ArrayList<Line> lines) {
153+
for (Coordinate[] part : decompose(+DATELINE, coordinates)) {
154+
for (Coordinate[] line : decompose(-DATELINE, part)) {
155+
lines.add(new Line(Arrays.stream(line).mapToDouble(i->normalizeLat(i.y)).toArray(),
156+
Arrays.stream(line).mapToDouble(i->normalizeLon(i.x)).toArray()));
157+
}
158+
}
159+
return lines;
160+
}
161+
132162
/**
133163
* Decompose a linestring given as array of coordinates at a vertical line.
134164
*

server/src/main/java/org/elasticsearch/common/geo/builders/MultiLineStringBuilder.java

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
package org.elasticsearch.common.geo.builders;
2121

22+
import org.apache.lucene.geo.Line;
2223
import org.elasticsearch.common.geo.GeoShapeType;
2324
import org.elasticsearch.common.geo.parsers.GeoWKTParser;
2425
import org.elasticsearch.common.geo.parsers.ShapeParser;
@@ -124,12 +125,12 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws
124125
}
125126

126127
@Override
127-
public JtsGeometry build() {
128+
public JtsGeometry buildS4J() {
128129
final Geometry geometry;
129130
if(wrapdateline) {
130131
ArrayList<LineString> parts = new ArrayList<>();
131132
for (LineStringBuilder line : lines) {
132-
LineStringBuilder.decompose(FACTORY, line.coordinates(false), parts);
133+
LineStringBuilder.decomposeS4J(FACTORY, line.coordinates(false), parts);
133134
}
134135
if(parts.size() == 1) {
135136
geometry = parts.get(0);
@@ -148,6 +149,27 @@ public JtsGeometry build() {
148149
return jtsGeometry(geometry);
149150
}
150151

152+
@Override
153+
public Object buildLucene() {
154+
if (wrapdateline) {
155+
ArrayList<Line> parts = new ArrayList<>();
156+
for (LineStringBuilder line : lines) {
157+
LineStringBuilder.decomposeLucene(line.coordinates(false), parts);
158+
}
159+
if (parts.size() == 1) {
160+
return parts.get(0);
161+
}
162+
return parts.toArray(new Line[parts.size()]);
163+
}
164+
Line[] linestrings = new Line[lines.size()];
165+
for (int i = 0; i < lines.size(); ++i) {
166+
LineStringBuilder lsb = lines.get(i);
167+
linestrings[i] = new Line(lsb.coordinates.stream().mapToDouble(c->c.y).toArray(),
168+
lsb.coordinates.stream().mapToDouble(c->c.x).toArray());
169+
}
170+
return linestrings;
171+
}
172+
151173
@Override
152174
public int hashCode() {
153175
return Objects.hash(lines);

server/src/main/java/org/elasticsearch/common/geo/builders/MultiPointBuilder.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws
6161
}
6262

6363
@Override
64-
public XShapeCollection<Point> build() {
64+
public XShapeCollection<Point> buildS4J() {
6565
//Could wrap JtsGeometry but probably slower due to conversions to/from JTS in relate()
6666
//MultiPoint geometry = FACTORY.createMultiPoint(points.toArray(new Coordinate[points.size()]));
6767
List<Point> shapes = new ArrayList<>(coordinates.size());
@@ -73,6 +73,17 @@ public XShapeCollection<Point> build() {
7373
return multiPoints;
7474
}
7575

76+
@Override
77+
public double[][] buildLucene() {
78+
double[][] points = new double[coordinates.size()][];
79+
Coordinate coord;
80+
for (int i = 0; i < coordinates.size(); ++i) {
81+
coord = coordinates.get(i);
82+
points[i] = new double[] {coord.x, coord.y};
83+
}
84+
return points;
85+
}
86+
7687
@Override
7788
public GeoShapeType type() {
7889
return TYPE;

server/src/main/java/org/elasticsearch/common/geo/builders/MultiPolygonBuilder.java

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131

3232
import java.io.IOException;
3333
import java.util.ArrayList;
34+
import java.util.Arrays;
3435
import java.util.List;
3536
import java.util.Locale;
3637
import java.util.Objects;
@@ -162,19 +163,19 @@ public int numDimensions() {
162163
}
163164

164165
@Override
165-
public Shape build() {
166+
public Shape buildS4J() {
166167

167168
List<Shape> shapes = new ArrayList<>(this.polygons.size());
168169

169170
if(wrapdateline) {
170171
for (PolygonBuilder polygon : this.polygons) {
171172
for(Coordinate[][] part : polygon.coordinates()) {
172-
shapes.add(jtsGeometry(PolygonBuilder.polygon(FACTORY, part)));
173+
shapes.add(jtsGeometry(PolygonBuilder.polygonS4J(FACTORY, part)));
173174
}
174175
}
175176
} else {
176177
for (PolygonBuilder polygon : this.polygons) {
177-
shapes.add(jtsGeometry(polygon.toPolygon(FACTORY)));
178+
shapes.add(jtsGeometry(polygon.toPolygonS4J(FACTORY)));
178179
}
179180
}
180181
if (shapes.size() == 1)
@@ -184,6 +185,33 @@ public Shape build() {
184185
//note: ShapeCollection is probably faster than a Multi* geom.
185186
}
186187

188+
@Override
189+
public Object buildLucene() {
190+
List<org.apache.lucene.geo.Polygon> shapes = new ArrayList<>(this.polygons.size());
191+
Object poly;
192+
if (wrapdateline) {
193+
for (PolygonBuilder polygon : this.polygons) {
194+
poly = polygon.buildLucene();
195+
if (poly instanceof org.apache.lucene.geo.Polygon[]) {
196+
shapes.addAll(Arrays.asList((org.apache.lucene.geo.Polygon[])poly));
197+
} else {
198+
shapes.add((org.apache.lucene.geo.Polygon)poly);
199+
}
200+
}
201+
} else {
202+
for (int i = 0; i < this.polygons.size(); ++i) {
203+
PolygonBuilder pb = this.polygons.get(i);
204+
poly = pb.buildLucene();
205+
if (poly instanceof org.apache.lucene.geo.Polygon[]) {
206+
shapes.addAll(Arrays.asList((org.apache.lucene.geo.Polygon[])poly));
207+
} else {
208+
shapes.add((org.apache.lucene.geo.Polygon)poly);
209+
}
210+
}
211+
}
212+
return shapes.stream().toArray(org.apache.lucene.geo.Polygon[]::new);
213+
}
214+
187215
@Override
188216
public int hashCode() {
189217
return Objects.hash(polygons, orientation);

server/src/main/java/org/elasticsearch/common/geo/builders/PointBuilder.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
package org.elasticsearch.common.geo.builders;
2121

22+
import org.elasticsearch.common.geo.GeoPoint;
2223
import org.elasticsearch.common.geo.GeoShapeType;
2324
import org.elasticsearch.common.geo.parsers.ShapeParser;
2425
import org.locationtech.spatial4j.shape.Point;
@@ -84,10 +85,15 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws
8485
}
8586

8687
@Override
87-
public Point build() {
88+
public Point buildS4J() {
8889
return SPATIAL_CONTEXT.makePoint(coordinates.get(0).x, coordinates.get(0).y);
8990
}
9091

92+
@Override
93+
public GeoPoint buildLucene() {
94+
return new GeoPoint(coordinates.get(0).y, coordinates.get(0).x);
95+
}
96+
9197
@Override
9298
public GeoShapeType type() {
9399
return TYPE;

0 commit comments

Comments
 (0)