Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions docs/changelog/125896.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
pr: 125896
summary: Support explicit Z/M attributes using WKT geometry
area: Geo
type: enhancement
issues: [123111]
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ public class WellKnownText {
public static final String RPAREN = ")";
public static final String COMMA = ",";
public static final String NAN = "NaN";
public static final String Z = "Z";
public static final String M = "M";
public static final int MAX_NESTED_DEPTH = 1000;

private static final String NUMBER = "<NUMBER>";
Expand Down Expand Up @@ -440,7 +442,8 @@ public static Geometry fromWKT(GeometryValidator validator, boolean coerce, Stri
*/
private static Geometry parseGeometry(StreamTokenizer stream, boolean coerce, int depth) throws IOException, ParseException {
final String type = nextWord(stream).toLowerCase(Locale.ROOT);
return switch (type) {
final boolean isExplicitlySpecifiesZorM = isZOrMNext(stream);
Geometry geometry = switch (type) {
case "point" -> parsePoint(stream);
case "multipoint" -> parseMultiPoint(stream);
case "linestring" -> parseLine(stream);
Expand All @@ -453,6 +456,16 @@ private static Geometry parseGeometry(StreamTokenizer stream, boolean coerce, in
parseCircle(stream);
default -> throw new IllegalArgumentException("Unknown geometry type: " + type);
};
checkZorMAttribute(isExplicitlySpecifiesZorM, geometry.hasZ());
return geometry;
}

private static void checkZorMAttribute(boolean isExplicitlySpecifiesZorM, boolean hasZ) {
if (isExplicitlySpecifiesZorM && hasZ == false) {
throw new IllegalArgumentException(
"When specifying 'Z' or 'M', coordinates must include three values. Only two coordinates were provided"
);
}
}

private static GeometryCollection<Geometry> parseGeometryCollection(StreamTokenizer stream, boolean coerce, int depth)
Expand Down Expand Up @@ -710,6 +723,21 @@ private static boolean isNumberNext(StreamTokenizer stream) throws IOException {
return type == StreamTokenizer.TT_WORD;
}

private static boolean isZOrMNext(StreamTokenizer stream) {
String token;
try {
token = nextWord(stream);
if (token.equals(Z) || token.equals(M)) {
return true;
}
stream.pushBack();
return false;
} catch (ParseException | IOException e) {
return false;
}

}

private static String nextEmptyOrOpen(StreamTokenizer stream) throws IOException, ParseException {
final String next = nextWord(stream);
if (next.equals(EMPTY) || next.equals(LPAREN)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@

abstract class BaseGeometryTestCase<T extends Geometry> extends AbstractWireTestCase<T> {

public static final String ZorMMustIncludeThreeValuesMsg =
"When specifying 'Z' or 'M', coordinates must include three values. Only two coordinates were provided";

@Override
protected final T createTestInstance() {
boolean hasAlt = randomBoolean();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import java.text.ParseException;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

import static org.hamcrest.Matchers.containsString;

Expand Down Expand Up @@ -93,6 +94,35 @@ private int countNestedGeometryCollections(GeometryCollection<?> geometry) {
return count;
}

public void testParseGeometryCollectionZorMWithThreeCoordinates() throws IOException, ParseException {
GeometryValidator validator = GeographyValidator.instance(true);

GeometryCollection<Geometry> expected = new GeometryCollection<>(
Arrays.asList(
new Point(20.0, 10.0, 100.0),
new Line(new double[] { 10.0, 20.0 }, new double[] { 5.0, 15.0 }, new double[] { 50.0, 150.0 })
)
);

String point = "(POINT Z (20.0 10.0 100.0)";
String lineString = "LINESTRING M (10.0 5.0 50.0, 20.0 15.0 150.0)";
assertEquals(expected, WellKnownText.fromWKT(validator, true, "GEOMETRYCOLLECTION Z " + point + ", " + lineString + ")"));

assertEquals(expected, WellKnownText.fromWKT(validator, true, "GEOMETRYCOLLECTION M " + point + ", " + lineString + ")"));
}

public void testParseGeometryCollectionZorMWithTwoCoordinatesThrowsException() {
GeometryValidator validator = GeographyValidator.instance(true);
List<String> gcWkt = List.of(
"GEOMETRYCOLLECTION Z (POINT (20.0 10.0), LINESTRING (10.0 5.0, 20.0 15.0))",
"GEOMETRYCOLLECTION M (POINT (20.0 10.0), LINESTRING (10.0 5.0, 20.0 15.0))"
);
for (String gc : gcWkt) {
IllegalArgumentException ex = expectThrows(IllegalArgumentException.class, () -> WellKnownText.fromWKT(validator, true, gc));
assertEquals(ZorMMustIncludeThreeValuesMsg, ex.getMessage());
}
}

@Override
protected GeometryCollection<Geometry> mutateInstance(GeometryCollection<Geometry> instance) {
return null;// TODO implement https://github.com/elastic/elasticsearch/issues/25929
Expand Down
20 changes: 20 additions & 0 deletions libs/geo/src/test/java/org/elasticsearch/geometry/LineTests.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

import java.io.IOException;
import java.text.ParseException;
import java.util.List;

public class LineTests extends BaseGeometryTestCase<Line> {
@Override
Expand Down Expand Up @@ -82,6 +83,25 @@ public void testWKTValidation() {
assertEquals("found Z value [6.0] but [ignore_z_value] parameter is [false]", ex.getMessage());
}

public void testParseLineZorMWithThreeCoordinates() throws IOException, ParseException {
GeometryValidator validator = GeographyValidator.instance(true);

Line expectedZ = new Line(new double[] { 20.0, 30.0 }, new double[] { 10.0, 15.0 }, new double[] { 100.0, 200.0 });
assertEquals(expectedZ, WellKnownText.fromWKT(validator, true, "LINESTRING Z (20.0 10.0 100.0, 30.0 15.0 200.0)"));

Line expectedM = new Line(new double[] { 20.0, 30.0 }, new double[] { 10.0, 15.0 }, new double[] { 100.0, 200.0 });
assertEquals(expectedM, WellKnownText.fromWKT(validator, true, "LINESTRING M (20.0 10.0 100.0, 30.0 15.0 200.0)"));
}

public void testParseLineZorMWithTwoCoordinatesThrowsException() {
GeometryValidator validator = GeographyValidator.instance(true);
List<String> linesWkt = List.of("LINESTRING Z (20.0 10.0, 30.0 15.0)", "LINESTRING M (20.0 10.0, 30.0 15.0)");
for (String line : linesWkt) {
IllegalArgumentException ex = expectThrows(IllegalArgumentException.class, () -> WellKnownText.fromWKT(validator, true, line));
assertEquals(ZorMMustIncludeThreeValuesMsg, ex.getMessage());
}
}

@Override
protected Line mutateInstance(Line instance) {
return null;// TODO implement https://github.com/elastic/elasticsearch/issues/25929
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,54 @@ public void testValidation() {
);
}

public void testParseMultiLineZorMWithThreeCoordinates() throws IOException, ParseException {
GeometryValidator validator = GeographyValidator.instance(true);
MultiLine expectedZ = new MultiLine(
List.of(
new Line(new double[] { 20.0, 30.0 }, new double[] { 10.0, 15.0 }, new double[] { 100.0, 200.0 }),
new Line(new double[] { 40.0, 50.0 }, new double[] { 20.0, 25.0 }, new double[] { 300.0, 400.0 })
)
);
assertEquals(
expectedZ,
WellKnownText.fromWKT(
validator,
true,
"MULTILINESTRING Z ((20.0 10.0 100.0, 30.0 15.0 200.0), (40.0 20.0 300.0, 50.0 25.0 400.0))"
)
);

MultiLine expectedM = new MultiLine(
List.of(
new Line(new double[] { 20.0, 30.0 }, new double[] { 10.0, 15.0 }, new double[] { 100.0, 200.0 }),
new Line(new double[] { 40.0, 50.0 }, new double[] { 20.0, 25.0 }, new double[] { 300.0, 400.0 })
)
);
assertEquals(
expectedM,
WellKnownText.fromWKT(
validator,
true,
"MULTILINESTRING M ((20.0 10.0 100.0, 30.0 15.0 200.0), (40.0 20.0 300.0, 50.0 25.0 400.0))"
)
);
}

public void testParseMultiLineZorMWithTwoCoordinatesThrowsException() {
GeometryValidator validator = GeographyValidator.instance(true);
List<String> multiLinesWkt = List.of(
"MULTILINESTRING Z ((20.0 10.0, 30.0 15.0), (40.0 20.0, 50.0 25.0))",
"MULTILINESTRING M ((20.0 10.0, 30.0 15.0), (40.0 20.0, 50.0 25.0))"
);
for (String multiLine : multiLinesWkt) {
IllegalArgumentException ex = expectThrows(
IllegalArgumentException.class,
() -> WellKnownText.fromWKT(validator, true, multiLine)
);
assertEquals(ZorMMustIncludeThreeValuesMsg, ex.getMessage());
}
}

@Override
protected MultiLine mutateInstance(MultiLine instance) {
return null;// TODO implement https://github.com/elastic/elasticsearch/issues/25929
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,27 @@ public void testValidation() {
StandardValidator.instance(true).validate(new MultiPoint(Collections.singletonList(new Point(2, 1, 3))));
}

public void testParseMultiPointWithThreeCoordinates() throws IOException, ParseException {
GeometryValidator validator = GeographyValidator.instance(true);
MultiPoint expectedZ = new MultiPoint(Arrays.asList(new Point(10, 20, 30), new Point(40, 50, 60)));
MultiPoint expectedM = new MultiPoint(Arrays.asList(new Point(10, 20, 30), new Point(40, 50, 60)));

assertEquals(expectedZ, WellKnownText.fromWKT(validator, true, "MULTIPOINT Z (10 20 30, 40 50 60)"));
assertEquals(expectedM, WellKnownText.fromWKT(validator, true, "MULTIPOINT M (10 20 30, 40 50 60)"));
}

public void testParseMultiPointWithTwoCoordinatesThrowsException() {
GeometryValidator validator = GeographyValidator.instance(true);
List<String> multiPointsWkt = List.of("MULTIPOINT Z (10 20, 40 50)", "MULTIPOINT M (10 20, 40 50)");
for (String multiPoint : multiPointsWkt) {
IllegalArgumentException ex = expectThrows(
IllegalArgumentException.class,
() -> WellKnownText.fromWKT(validator, true, multiPoint)
);
assertEquals(ZorMMustIncludeThreeValuesMsg, ex.getMessage());
}
}

@Override
protected MultiPoint mutateInstance(MultiPoint instance) {
return null;// TODO implement https://github.com/elastic/elasticsearch/issues/25929
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,48 @@ public void testValidation() {
);
}

public void testParseMultiPolygonZorMWithThreeCoordinates() throws IOException, ParseException {
GeometryValidator validator = GeographyValidator.instance(true);

MultiPolygon expected = new MultiPolygon(
List.of(
new Polygon(
new LinearRing(
new double[] { 20.0, 30.0, 40.0, 20.0 },
new double[] { 10.0, 15.0, 10.0, 10.0 },
new double[] { 100.0, 200.0, 300.0, 100.0 }
)
),
new Polygon(
new LinearRing(
new double[] { 0.0, 10.0, 10.0, 0.0 },
new double[] { 0.0, 0.0, 10.0, 0.0 },
new double[] { 10.0, 20.0, 30.0, 10.0 }
)
)
)
);
String polygonA = "(20.0 10.0 100.0, 30.0 15.0 200.0, 40.0 10.0 300.0, 20.0 10.0 100.0)";
String polygonB = "(0.0 0.0 10.0, 10.0 0.0 20.0, 10.0 10.0 30.0, 0.0 0.0 10.0)";
assertEquals(expected, WellKnownText.fromWKT(validator, true, "MULTIPOLYGON Z ((" + polygonA + "), (" + polygonB + "))"));
assertEquals(expected, WellKnownText.fromWKT(validator, true, "MULTIPOLYGON M ((" + polygonA + "), (" + polygonB + "))"));
}

public void testParseMultiPolygonZorMWithTwoCoordinatesThrowsException() {
GeometryValidator validator = GeographyValidator.instance(true);
List<String> multiPolygonsWkt = List.of(
"MULTIPOLYGON Z (((20.0 10.0, 30.0 15.0, 40.0 10.0, 20.0 10.0)), ((0.0 0.0, 10.0 0.0, 10.0 10.0, 0.0 0.0)))",
"MULTIPOLYGON M (((20.0 10.0, 30.0 15.0, 40.0 10.0, 20.0 10.0)), ((0.0 0.0, 10.0 0.0, 10.0 10.0, 0.0 0.0)))"
);
for (String multiPolygon : multiPolygonsWkt) {
IllegalArgumentException ex = expectThrows(
IllegalArgumentException.class,
() -> WellKnownText.fromWKT(validator, true, multiPolygon)
);
assertEquals(ZorMMustIncludeThreeValuesMsg, ex.getMessage());
}
}

@Override
protected MultiPolygon mutateInstance(MultiPolygon instance) {
return null;// TODO implement https://github.com/elastic/elasticsearch/issues/25929
Expand Down
17 changes: 17 additions & 0 deletions libs/geo/src/test/java/org/elasticsearch/geometry/PointTests.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@

import java.io.IOException;
import java.text.ParseException;
import java.util.List;

public class PointTests extends BaseGeometryTestCase<Point> {

@Override
protected Point createTestInstance(boolean hasAlt) {
return GeometryTestUtils.randomPoint(hasAlt);
Expand Down Expand Up @@ -58,6 +60,21 @@ public void testWKTValidation() {
assertEquals("found Z value [100.0] but [ignore_z_value] parameter is [false]", ex.getMessage());
}

public void testParsePointZorMWithThreeCoordinates() throws IOException, ParseException {
GeometryValidator validator = GeographyValidator.instance(true);
assertEquals(new Point(20, 10, 100), WellKnownText.fromWKT(validator, true, "POINT Z (20.0 10.0 100.0)"));
assertEquals(new Point(20, 10, 100), WellKnownText.fromWKT(validator, true, "POINT M (20.0 10.0 100.0)"));
}

public void testParsePointZorMWithTwoCoordinatesThrowsException() {
GeometryValidator validator = GeographyValidator.instance(true);
List<String> pointsWkt = List.of("POINT Z (20.0 10.0)", "POINT M (20.0 10.0)");
for (String point : pointsWkt) {
IllegalArgumentException ex = expectThrows(IllegalArgumentException.class, () -> WellKnownText.fromWKT(validator, true, point));
assertEquals(ZorMMustIncludeThreeValuesMsg, ex.getMessage());
}
}

@Override
protected Point mutateInstance(Point instance) {
return null;// TODO implement https://github.com/elastic/elasticsearch/issues/25929
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import java.io.IOException;
import java.text.ParseException;
import java.util.Collections;
import java.util.List;

public class PolygonTests extends BaseGeometryTestCase<Polygon> {
@Override
Expand Down Expand Up @@ -134,6 +135,61 @@ public void testWKTValidation() {
);
}

public void testParsePolygonZorMWithThreeCoordinates() throws IOException, ParseException {
GeometryValidator validator = GeographyValidator.instance(true);

Polygon expectedZ = new Polygon(
new LinearRing(
new double[] { 20.0, 30.0, 40.0, 20.0 },
new double[] { 10.0, 15.0, 10.0, 10.0 },
new double[] { 100.0, 200.0, 300.0, 100.0 }
)
);
assertEquals(
expectedZ,
WellKnownText.fromWKT(validator, true, "POLYGON Z ((20.0 10.0 100.0, 30.0 15.0 200.0, 40.0 10.0 300.0, 20.0 10.0 100.0))")
);

Polygon expectedM = new Polygon(
new LinearRing(
new double[] { 20.0, 30.0, 40.0, 20.0 },
new double[] { 10.0, 15.0, 10.0, 10.0 },
new double[] { 100.0, 200.0, 300.0, 100.0 }
)
);
assertEquals(
expectedM,
WellKnownText.fromWKT(validator, true, "POLYGON M ((20.0 10.0 100.0, 30.0 15.0 200.0, 40.0 10.0 300.0, 20.0 10.0 100.0))")
);

Polygon expectedZAutoClose = new Polygon(
new LinearRing(
new double[] { 20.0, 30.0, 40.0, 20.0 },
new double[] { 10.0, 15.0, 10.0, 10.0 },
new double[] { 100.0, 200.0, 300.0, 100.0 }
)
);
assertEquals(
expectedZAutoClose,
WellKnownText.fromWKT(validator, true, "POLYGON Z ((20.0 10.0 100.0, 30.0 15.0 200.0, 40.0 10.0 300.0))")
);
}

public void testParsePolygonZorMWithTwoCoordinatesThrowsException() {
GeometryValidator validator = GeographyValidator.instance(true);
List<String> polygonsWkt = List.of(
"POLYGON Z ((20.0 10.0, 30.0 15.0, 40.0 10.0, 20.0 10.0))",
"POLYGON M ((20.0 10.0, 30.0 15.0, 40.0 10.0, 20.0 10.0))"
);
for (String polygon : polygonsWkt) {
IllegalArgumentException ex = expectThrows(
IllegalArgumentException.class,
() -> WellKnownText.fromWKT(validator, true, polygon)
);
assertEquals(ZorMMustIncludeThreeValuesMsg, ex.getMessage());
}
}

@Override
protected Polygon mutateInstance(Polygon instance) {
return null;// TODO implement https://github.com/elastic/elasticsearch/issues/25929
Expand Down
Loading