Skip to content

Commit 692ae7c

Browse files
committed
Support explicit Z/M coordinate values in WKT parsing for GeoPoint geometry
1 parent 789eb2f commit 692ae7c

File tree

3 files changed

+77
-2
lines changed

3 files changed

+77
-2
lines changed

libs/geo/src/main/java/org/elasticsearch/geometry/Point.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
*/
1717
public class Point implements Geometry {
1818
public static final Point EMPTY = new Point();
19+
public static final String Z = "Z";
20+
public static final String M = "M";
1921

2022
private final double y;
2123
private final double x;

libs/geo/src/main/java/org/elasticsearch/geometry/utils/WellKnownText.java

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -250,7 +250,6 @@ private static void parseGeometry(ByteBuffer byteBuffer, StringBuilder sb) {
250250
case 1018 -> parseBBox(byteBuffer, true, sb);
251251
default -> throw new IllegalArgumentException("Unknown geometry type: " + type);
252252
}
253-
;
254253
}
255254

256255
private static void writeCoordinate(ByteBuffer byteBuffer, boolean hasZ, StringBuilder sb) {
@@ -472,15 +471,23 @@ private static GeometryCollection<Geometry> parseGeometryCollection(StreamTokeni
472471
}
473472

474473
private static Point parsePoint(StreamTokenizer stream) throws IOException, ParseException {
475-
if (nextEmptyOrOpen(stream).equals(EMPTY)) {
474+
if (isEmptyNext(stream)) {
476475
return Point.EMPTY;
477476
}
477+
String token = nextZOrMOrOpen(stream);
478+
boolean hasZOrM = token.equals(Point.Z) || token.equals(Point.M);
479+
if (hasZOrM) {
480+
nextOpener(stream);
481+
}
478482
double lon = nextNumber(stream);
479483
double lat = nextNumber(stream);
480484
Point pt;
481485
if (isNumberNext(stream)) {
482486
pt = new Point(lon, lat, nextNumber(stream));
483487
} else {
488+
if (hasZOrM) {
489+
throw new ParseException("'POINT Z' or 'POINT M' must have three coordinates, but only two were found.", stream.lineno());
490+
}
484491
pt = new Point(lon, lat);
485492
}
486493
nextCloser(stream);
@@ -710,6 +717,14 @@ private static boolean isNumberNext(StreamTokenizer stream) throws IOException {
710717
return type == StreamTokenizer.TT_WORD;
711718
}
712719

720+
private static boolean isEmptyNext(StreamTokenizer stream) throws ParseException, IOException {
721+
if (nextWord(stream).equals(EMPTY)) {
722+
return true;
723+
}
724+
stream.pushBack();
725+
return false;
726+
}
727+
713728
private static String nextEmptyOrOpen(StreamTokenizer stream) throws IOException, ParseException {
714729
final String next = nextWord(stream);
715730
if (next.equals(EMPTY) || next.equals(LPAREN)) {
@@ -718,6 +733,17 @@ private static String nextEmptyOrOpen(StreamTokenizer stream) throws IOException
718733
throw new ParseException("expected " + EMPTY + " or " + LPAREN + " but found: " + tokenString(stream), stream.lineno());
719734
}
720735

736+
private static String nextZOrMOrOpen(StreamTokenizer stream) throws ParseException, IOException {
737+
final String next = nextWord(stream);
738+
if (next.equals(Point.Z) || next.equals(Point.M) || next.equals(LPAREN)) {
739+
return next;
740+
}
741+
throw new ParseException(
742+
"expected " + LPAREN + " or " + Point.Z + " or " + Point.M + " but found: " + tokenString(stream),
743+
stream.lineno()
744+
);
745+
}
746+
721747
private static String nextCloser(StreamTokenizer stream) throws IOException, ParseException {
722748
if (nextWord(stream).equals(RPAREN)) {
723749
return RPAREN;

libs/geo/src/test/java/org/elasticsearch/geometry/PointTests.java

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
import java.io.IOException;
1919
import java.text.ParseException;
20+
import java.util.List;
2021

2122
public class PointTests extends BaseGeometryTestCase<Point> {
2223
@Override
@@ -58,6 +59,52 @@ public void testWKTValidation() {
5859
assertEquals("found Z value [100.0] but [ignore_z_value] parameter is [false]", ex.getMessage());
5960
}
6061

62+
public void testParsePointZWithThreeCoordinates() throws IOException, ParseException {
63+
GeometryValidator validator = GeographyValidator.instance(true);
64+
assertEquals(new Point(20, 10, 100), WellKnownText.fromWKT(validator, true, "POINT Z (20.0 10.0 100.0)"));
65+
}
66+
67+
public void testParsePointMWithThreeCoordinates() throws IOException, ParseException {
68+
GeometryValidator validator = GeographyValidator.instance(true);
69+
assertEquals(new Point(20, 10, 100), WellKnownText.fromWKT(validator, true, "POINT M (20.0 10.0 100.0)"));
70+
}
71+
72+
public void testParsePointZWithTwoCoordinatesThrowsException() {
73+
GeometryValidator validator = GeographyValidator.instance(true);
74+
ParseException ex = expectThrows(ParseException.class, () -> WellKnownText.fromWKT(validator, true, "POINT Z (20.0 10.0)"));
75+
assertEquals("'POINT Z' or 'POINT M' must have three coordinates, but only two were found.", ex.getMessage());
76+
}
77+
78+
public void testParsePointMWithTwoCoordinatesThrowsException() {
79+
GeometryValidator validator = StandardValidator.instance(true);
80+
ParseException ex = expectThrows(ParseException.class, () -> WellKnownText.fromWKT(validator, true, "POINT M (20.0 10.0)"));
81+
assertEquals("'POINT Z' or 'POINT M' must have three coordinates, but only two were found.", ex.getMessage());
82+
}
83+
84+
public void testParsePointZMFormatNotSupported() {
85+
GeometryValidator validator = StandardValidator.instance(true);
86+
List<String> points = List.of("POINT ZM (20.0 10.0 100.0 200.0)", "POINT ZM (20.0 10.0 100.0)", "POINT ZM (20.0 10.0)");
87+
for (String point : points) {
88+
ParseException ex = expectThrows(ParseException.class, () -> WellKnownText.fromWKT(validator, true, point));
89+
assertEquals("expected ( or Z or M but found: ZM", ex.getMessage());
90+
}
91+
}
92+
93+
public void testParsePointZWithEmpty() {
94+
GeometryValidator validator = StandardValidator.instance(true);
95+
ParseException ex = expectThrows(ParseException.class, () -> WellKnownText.fromWKT(validator, true, "POINT Z EMPTY"));
96+
assertEquals("expected ( but found: EMPTY", ex.getMessage());
97+
}
98+
99+
public void testParsePointZOrMWithTwoCoordinates() {
100+
GeometryValidator validator = StandardValidator.instance(true);
101+
List<String> points = List.of("POINT Z (20.0 10.0)", "POINT M (20.0 10.0)");
102+
for (String point : points) {
103+
ParseException ex = expectThrows(ParseException.class, () -> WellKnownText.fromWKT(validator, true, point));
104+
assertEquals("'POINT Z' or 'POINT M' must have three coordinates, but only two were found.", ex.getMessage());
105+
}
106+
}
107+
61108
@Override
62109
protected Point mutateInstance(Point instance) {
63110
return null;// TODO implement https://github.com/elastic/elasticsearch/issues/25929

0 commit comments

Comments
 (0)