Skip to content

Commit 2e7d62c

Browse files
authored
Geo: improve handling of out of bounds points in linestrings (#47939)
Brings handling of out of bounds points in linestrings in line with points. Now points with latitude above 90 and below -90 are handled the same way as for points by adjusting the longitude by moving it by 180 degrees. Relates to #43916
1 parent f861927 commit 2e7d62c

File tree

5 files changed

+137
-18
lines changed

5 files changed

+137
-18
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -308,7 +308,7 @@ public static void normalizePoint(double[] lonLat, boolean normLon, boolean norm
308308
assert lonLat != null && lonLat.length == 2;
309309

310310
normLat = normLat && (lonLat[1] > 90 || lonLat[1] < -90);
311-
normLon = normLon && (lonLat[0] > 180 || lonLat[0] < -180);
311+
normLon = normLon && (lonLat[0] > 180 || lonLat[0] < -180 || normLat);
312312

313313
if (normLat) {
314314
lonLat[1] = centeredModulus(lonLat[1], 360);

server/src/main/java/org/elasticsearch/index/mapper/GeoShapeIndexer.java

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -225,15 +225,16 @@ protected static double intersection(double p1x, double p2x, double dateline) {
225225
* Splits the specified line by datelines and adds them to the supplied lines array
226226
*/
227227
private List<Line> decomposeGeometry(Line line, List<Line> lines) {
228-
for (Line part : decompose(line)) {
229-
double[] lats = new double[part.length()];
230-
double[] lons = new double[part.length()];
231-
for (int i = 0; i < part.length(); i++) {
232-
lats[i] = normalizeLat(part.getY(i));
233-
lons[i] = normalizeLonMinus180Inclusive(part.getX(i));
234-
}
235-
lines.add(new Line(lons, lats));
228+
double[] lons = new double[line.length()];
229+
double[] lats = new double[lons.length];
230+
231+
for (int i = 0; i < lons.length; i++) {
232+
double[] lonLat = new double[] {line.getX(i), line.getY(i)};
233+
normalizePoint(lonLat,false, true);
234+
lons[i] = lonLat[0];
235+
lats[i] = lonLat[1];
236236
}
237+
lines.addAll(decompose(lons, lats));
237238
return lines;
238239
}
239240

@@ -253,12 +254,11 @@ private List<Line> decomposeGeometry(Line line, List<Line> lines) {
253254
/**
254255
* Decompose a linestring given as array of coordinates by anti-meridian.
255256
*
256-
* @param line linestring that should be decomposed
257+
* @param lons longitudes of the linestring that should be decomposed
258+
* @param lats latitudes of the linestring that should be decomposed
257259
* @return array of linestrings given as coordinate arrays
258260
*/
259-
private List<Line> decompose(Line line) {
260-
double[] lons = line.getX();
261-
double[] lats = line.getY();
261+
private List<Line> decompose(double[] lons, double[] lats) {
262262
int offset = 0;
263263
ArrayList<Line> parts = new ArrayList<>();
264264

server/src/test/java/org/elasticsearch/common/geo/GeometryIndexerTests.java

Lines changed: 48 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,10 @@
3838

3939
import java.io.IOException;
4040
import java.text.ParseException;
41+
import java.util.ArrayList;
4142
import java.util.Arrays;
4243
import java.util.Collections;
44+
import java.util.List;
4345

4446
import static org.hamcrest.Matchers.instanceOf;
4547

@@ -155,32 +157,59 @@ public void testLine() {
155157
*/
156158
public double length(Line line) {
157159
double distance = 0;
160+
double[] prev = new double[]{line.getLon(0), line.getLat(0)};
161+
GeoUtils.normalizePoint(prev, false, true);
158162
for (int i = 1; i < line.length(); i++) {
159-
distance += Math.sqrt((line.getLat(i) - line.getLat(i - 1)) * (line.getLat(i) - line.getLat(i - 1)) +
160-
(line.getLon(i) - line.getLon(i - 1)) * (line.getLon(i) - line.getLon(i - 1)));
163+
double[] cur = new double[]{line.getLon(i), line.getLat(i)};
164+
GeoUtils.normalizePoint(cur, false, true);
165+
distance += Math.sqrt((cur[0] - prev[0]) * (cur[0] - prev[0]) + (cur[1] - prev[1]) * (cur[1] - prev[1]));
166+
prev = cur;
161167
}
162168
return distance;
163169
}
164170

165171
/**
166-
* A simple tests that generates a random lines crossing anti-merdian and checks that the decomposed segments of this line
172+
* Removes the points on the antimeridian that are introduced during linestring decomposition
173+
*/
174+
public static MultiPoint remove180s(MultiPoint points) {
175+
List<Point> list = new ArrayList<>();
176+
points.forEach(point -> {
177+
if (Math.abs(point.getLon()) - 180.0 > 0.000001) {
178+
list.add(point);
179+
}
180+
});
181+
if (list.isEmpty()) {
182+
return MultiPoint.EMPTY;
183+
}
184+
return new MultiPoint(list);
185+
}
186+
187+
/**
188+
* A randomized test that generates a random lines crossing anti-merdian and checks that the decomposed segments of this line
167189
* have the same total length (measured using Euclidean distances between neighboring points) as the original line.
190+
*
191+
* It also extracts all points from these lines, performs normalization of these points and then compares that the resulting
192+
* points of line normalization match the points of points normalization with the exception of points that were created on the
193+
* antimeridian as the result of line decomposition.
168194
*/
169195
public void testRandomLine() {
170196
int size = randomIntBetween(2, 20);
171197
int shift = randomIntBetween(-2, 2);
172198
double[] originalLats = new double[size];
173199
double[] originalLons = new double[size];
174200

201+
// Generate a random line that goes over poles and stretches beyond -180 and +180
175202
for (int i = 0; i < size; i++) {
176-
originalLats[i] = GeometryTestUtils.randomLat();
203+
// from time to time go over poles
204+
originalLats[i] = randomInt(4) == 0 ? GeometryTestUtils.randomLat() : GeometryTestUtils.randomLon();
177205
originalLons[i] = GeometryTestUtils.randomLon() + shift * 360;
178206
if (randomInt(3) == 0) {
179207
shift += randomFrom(-2, -1, 1, 2);
180208
}
181209
}
182210
Line original = new Line(originalLons, originalLats);
183211

212+
// Check that the length of original and decomposed lines is the same
184213
Geometry decomposed = indexer.prepareForIndexing(original);
185214
double decomposedLength = 0;
186215
if (decomposed instanceof Line) {
@@ -192,8 +221,19 @@ public void testRandomLine() {
192221
decomposedLength += length(lines.get(i));
193222
}
194223
}
195-
196224
assertEquals("Different Lengths between " + original + " and " + decomposed, length(original), decomposedLength, 0.001);
225+
226+
// Check that normalized linestring generates the same points as the normalized multipoint based on the same set of points
227+
MultiPoint decomposedViaLines = remove180s(GeometryTestUtils.toMultiPoint(decomposed));
228+
MultiPoint originalPoints = GeometryTestUtils.toMultiPoint(original);
229+
MultiPoint decomposedViaPoint = remove180s(GeometryTestUtils.toMultiPoint(indexer.prepareForIndexing(originalPoints)));
230+
assertEquals(decomposedViaPoint.size(), decomposedViaLines.size());
231+
for (int i=0; i<decomposedViaPoint.size(); i++) {
232+
assertEquals("Difference between decomposing lines " + decomposedViaLines + " and points " + decomposedViaPoint +
233+
" at the position " + i, decomposedViaPoint.get(i).getLat(), decomposedViaLines.get(i).getLat(), 0.0001);
234+
assertEquals("Difference between decomposing lines " + decomposedViaLines + " and points " + decomposedViaPoint +
235+
" at the position " + i, decomposedViaPoint.get(i).getLon(), decomposedViaLines.get(i).getLon(), 0.0001);
236+
}
197237
}
198238

199239
public void testMultiLine() {
@@ -231,6 +271,9 @@ public void testPoint() {
231271

232272
point = new Point(180, 180);
233273
assertEquals(new Point(0, 0), indexer.prepareForIndexing(point));
274+
275+
point = new Point(-180, -180);
276+
assertEquals(new Point(0, 0), indexer.prepareForIndexing(point));
234277
}
235278

236279
public void testMultiPoint() {

server/src/test/java/org/elasticsearch/index/search/geo/GeoUtilsTests.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -386,6 +386,10 @@ public void testNormalizePointEdgeCases() {
386386
assertNormalizedPoint(new GeoPoint(180.0, 360.0), new GeoPoint(0.0, 180.0));
387387
assertNormalizedPoint(new GeoPoint(-90.0, -180.0), new GeoPoint(-90.0, -180.0));
388388
assertNormalizedPoint(new GeoPoint(90.0, 180.0), new GeoPoint(90.0, 180.0));
389+
assertNormalizedPoint(new GeoPoint(100.0, 180.0), new GeoPoint(80.0, 0.0));
390+
assertNormalizedPoint(new GeoPoint(100.0, -180.0), new GeoPoint(80.0, 0.0));
391+
assertNormalizedPoint(new GeoPoint(-100.0, 180.0), new GeoPoint(-80.0, 0.0));
392+
assertNormalizedPoint(new GeoPoint(-100.0, -180.0), new GeoPoint(-80.0, 0.0));
389393
}
390394

391395
public void testParseGeoPoint() throws IOException {

test/framework/src/main/java/org/elasticsearch/geo/GeometryTestUtils.java

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import org.elasticsearch.geometry.Circle;
2424
import org.elasticsearch.geometry.Geometry;
2525
import org.elasticsearch.geometry.GeometryCollection;
26+
import org.elasticsearch.geometry.GeometryVisitor;
2627
import org.elasticsearch.geometry.Line;
2728
import org.elasticsearch.geometry.LinearRing;
2829
import org.elasticsearch.geometry.MultiLine;
@@ -34,6 +35,8 @@
3435
import org.elasticsearch.test.ESTestCase;
3536

3637
import java.util.ArrayList;
38+
import java.util.Arrays;
39+
import java.util.Collections;
3740
import java.util.List;
3841
import java.util.function.Function;
3942

@@ -186,4 +189,73 @@ protected static Geometry randomGeometry(int level, boolean hasAlt) {
186189
);
187190
return geometry.apply(hasAlt);
188191
}
192+
193+
/**
194+
* Extracts all vertices of the supplied geometry
195+
*/
196+
public static MultiPoint toMultiPoint(Geometry geometry) {
197+
return geometry.visit(new GeometryVisitor<>() {
198+
@Override
199+
public MultiPoint visit(Circle circle) throws RuntimeException {
200+
throw new UnsupportedOperationException("not supporting circles yet");
201+
}
202+
203+
@Override
204+
public MultiPoint visit(GeometryCollection<?> collection) throws RuntimeException {
205+
List<Point> points = new ArrayList<>();
206+
collection.forEach(geometry -> toMultiPoint(geometry).forEach(points::add));
207+
return new MultiPoint(points);
208+
}
209+
210+
@Override
211+
public MultiPoint visit(Line line) throws RuntimeException {
212+
List<Point> points = new ArrayList<>();
213+
for (int i = 0; i < line.length(); i++) {
214+
points.add(new Point(line.getX(i), line.getY(i), line.getZ(i)));
215+
}
216+
return new MultiPoint(points);
217+
}
218+
219+
@Override
220+
public MultiPoint visit(LinearRing ring) throws RuntimeException {
221+
return visit((Line) ring);
222+
}
223+
224+
@Override
225+
public MultiPoint visit(MultiLine multiLine) throws RuntimeException {
226+
return visit((GeometryCollection<?>) multiLine);
227+
}
228+
229+
@Override
230+
public MultiPoint visit(MultiPoint multiPoint) throws RuntimeException {
231+
return multiPoint;
232+
}
233+
234+
@Override
235+
public MultiPoint visit(MultiPolygon multiPolygon) throws RuntimeException {
236+
return visit((GeometryCollection<?>) multiPolygon);
237+
}
238+
239+
@Override
240+
public MultiPoint visit(Point point) throws RuntimeException {
241+
return new MultiPoint(Collections.singletonList(point));
242+
}
243+
244+
@Override
245+
public MultiPoint visit(Polygon polygon) throws RuntimeException {
246+
List<Geometry> multiPoints = new ArrayList<>();
247+
multiPoints.add(toMultiPoint(polygon.getPolygon()));
248+
for (int i = 0; i < polygon.getNumberOfHoles(); i++) {
249+
multiPoints.add(toMultiPoint(polygon.getHole(i)));
250+
}
251+
return toMultiPoint(new GeometryCollection<>(multiPoints));
252+
}
253+
254+
@Override
255+
public MultiPoint visit(Rectangle rectangle) throws RuntimeException {
256+
return new MultiPoint(Arrays.asList(new Point(rectangle.getMinX(), rectangle.getMinY(), rectangle.getMinZ()),
257+
new Point(rectangle.getMaxX(), rectangle.getMaxY(), rectangle.getMaxZ())));
258+
}
259+
});
260+
}
189261
}

0 commit comments

Comments
 (0)