From ac8758988ee7c9e20386c541fa046e1ec0ba34c7 Mon Sep 17 00:00:00 2001 From: Igor Motov Date: Fri, 16 Nov 2018 18:16:00 -0500 Subject: [PATCH 1/2] SQL: Add Misc Geometry Property functions Adds support for ST_Dimension, ST_GeometryType, ST_X, ST_Y, ST_XMin, ST_XMax, ST_YMin, ST_YMax functions. Relates to #29872 --- docs/reference/sql/functions/geo.asciidoc | 207 +++++++++++++++++- docs/reference/sql/functions/index.asciidoc | 1 + .../common/geo/builders/CircleBuilder.java | 15 ++ .../common/geo/builders/EnvelopeBuilder.java | 5 + .../builders/GeometryCollectionBuilder.java | 27 +++ .../geo/builders/LineStringBuilder.java | 5 + .../geo/builders/MultiLineStringBuilder.java | 19 ++ .../geo/builders/MultiPointBuilder.java | 5 + .../geo/builders/MultiPolygonBuilder.java | 23 ++ .../common/geo/builders/PointBuilder.java | 6 + .../common/geo/builders/PolygonBuilder.java | 23 ++ .../common/geo/builders/ShapeBuilder.java | 31 +++ .../common/geo/ShapeBuilderTests.java | 47 ++++ .../xpack/sql/qa/jdbc/CsvTestUtils.java | 2 + .../qa/src/main/resources/command.csv-spec | 8 + .../sql/qa/src/main/resources/docs.csv-spec | 8 + .../qa/src/main/resources/geo/docs.csv-spec | 80 ++++++- .../qa/src/main/resources/geo/geosql.csv-spec | 84 +++++++ .../qa/src/main/resources/ogc/ogc.sql-spec | 46 ++++ .../expression/function/FunctionRegistry.java | 20 +- .../function/scalar/geo/GeoProcessor.java | 36 ++- .../function/scalar/geo/GeoShape.java | 58 +++++ .../function/scalar/geo/StAswkt.java | 6 +- .../function/scalar/geo/StDimension.java | 45 ++++ .../function/scalar/geo/StGeometrytype.java | 45 ++++ .../expression/function/scalar/geo/StX.java | 45 ++++ .../function/scalar/geo/StXmax.java | 45 ++++ .../function/scalar/geo/StXmin.java | 45 ++++ .../expression/function/scalar/geo/StY.java | 45 ++++ .../function/scalar/geo/StYmax.java | 45 ++++ .../function/scalar/geo/StYmin.java | 45 ++++ .../whitelist/InternalSqlScriptUtils.java | 40 +++- .../xpack/sql/plugin/sql_whitelist.txt | 11 +- .../scalar/geo/GeoProcessorTests.java | 27 ++- .../sql/planner/QueryTranslatorTests.java | 4 +- 35 files changed, 1152 insertions(+), 52 deletions(-) create mode 100644 x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/geo/StDimension.java create mode 100644 x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/geo/StGeometrytype.java create mode 100644 x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/geo/StX.java create mode 100644 x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/geo/StXmax.java create mode 100644 x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/geo/StXmin.java create mode 100644 x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/geo/StY.java create mode 100644 x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/geo/StYmax.java create mode 100644 x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/geo/StYmin.java diff --git a/docs/reference/sql/functions/geo.asciidoc b/docs/reference/sql/functions/geo.asciidoc index 14bd5fad92c4b..99c39d1e44360 100644 --- a/docs/reference/sql/functions/geo.asciidoc +++ b/docs/reference/sql/functions/geo.asciidoc @@ -32,8 +32,8 @@ include-tagged::{sql-specs}/geo/docs.csv-spec[aswkt] -------------------------------------------------- -[[sql-functions-geo-st-as-wkt]] -===== `ST_AsWKT` +[[sql-functions-geo-st-wkt-to-sql]] +===== `ST_WKTToSQL` .Synopsis: [source, sql] @@ -54,4 +54,207 @@ Returns the geometry from WKT representation. The return type is geometry. ["source","sql",subs="attributes,macros"] -------------------------------------------------- include-tagged::{sql-specs}/geo/docs.csv-spec[aswkt] +-------------------------------------------------- + +==== Geometry Properties + +[[sql-functions-geo-dimension]] +===== `ST_Dimension` + +.Synopsis: +[source, sql] +-------------------------------------------------- +ST_Dimension(geometry<1>) +-------------------------------------------------- + +*Input*: + +<1> geometry + +*Output*: small integer + +.Description: + +Returns the dimension of the `geometry` in terms of width and length. +For example, a point has dimension 0, a line - 1 and a polygon 2. The +return type is small integer. + +["source","sql",subs="attributes,macros"] +-------------------------------------------------- +include-tagged::{sql-specs}/geo/docs.csv-spec[dimension] +-------------------------------------------------- + +[[sql-functions-geo-geometrytype]] +===== `ST_GeometryType` + +.Synopsis: +[source, sql] +-------------------------------------------------- +ST_GeometryType(geometry<1>) +-------------------------------------------------- + +*Input*: + +<1> geometry + +*Output*: string + +.Description: + +Returns the type of the `geometry` such as "Point", "Polygon", "MultiPolygon", etc. +The return type is string. + +["source","sql",subs="attributes,macros"] +-------------------------------------------------- +include-tagged::{sql-specs}/geo/docs.csv-spec[geometrytype] +-------------------------------------------------- + +[[sql-functions-geo-x]] +===== `ST_X` + +.Synopsis: +[source, sql] +-------------------------------------------------- +ST_X(geometry<1>) +-------------------------------------------------- + +*Input*: + +<1> geometry + +*Output*: double + +.Description: + +Returns the longitude of the first point in the geometry. +The return type is double. + +["source","sql",subs="attributes,macros"] +-------------------------------------------------- +include-tagged::{sql-specs}/geo/docs.csv-spec[x] +-------------------------------------------------- + +[[sql-functions-geo-y]] +===== `ST_Y` + +.Synopsis: +[source, sql] +-------------------------------------------------- +ST_Y(geometry<1>) +-------------------------------------------------- + +*Input*: + +<1> geometry + +*Output*: double + +.Description: + +Returns the the latitude of the first point in the geometry. +The return type is double. + +["source","sql",subs="attributes,macros"] +-------------------------------------------------- +include-tagged::{sql-specs}/geo/docs.csv-spec[y] +-------------------------------------------------- + +[[sql-functions-geo-xmin]] +===== `ST_XMin` + +.Synopsis: +[source, sql] +-------------------------------------------------- +ST_XMin(geometry<1>) +-------------------------------------------------- + +*Input*: + +<1> geometry + +*Output*: double + +.Description: + +Returns the minimum longitude of all points in the geometry. +The return type is double. + +["source","sql",subs="attributes,macros"] +-------------------------------------------------- +include-tagged::{sql-specs}/geo/docs.csv-spec[xmin] +-------------------------------------------------- + +[[sql-functions-geo-ymin]] +===== `ST_YMin` + +.Synopsis: +[source, sql] +-------------------------------------------------- +ST_YMin(geometry<1>) +-------------------------------------------------- + +*Input*: + +<1> geometry + +*Output*: double + +.Description: + +Returns the minimum latitude of all points in the geometry. +The return type is double. + +["source","sql",subs="attributes,macros"] +-------------------------------------------------- +include-tagged::{sql-specs}/geo/docs.csv-spec[ymin] +-------------------------------------------------- + +[[sql-functions-geo-xmax]] +===== `ST_XMax` + +.Synopsis: +[source, sql] +-------------------------------------------------- +ST_XMax(geometry<1>) +-------------------------------------------------- + +*Input*: + +<1> geometry + +*Output*: double + +.Description: + +Returns the maximum longitude of all points in the geometry. +The return type is double. + +["source","sql",subs="attributes,macros"] +-------------------------------------------------- +include-tagged::{sql-specs}/geo/docs.csv-spec[xmax] +-------------------------------------------------- + +[[sql-functions-geo-ymax]] +===== `ST_YMax` + +.Synopsis: +[source, sql] +-------------------------------------------------- +ST_YMax(geometry<1>) +-------------------------------------------------- + +*Input*: + +<1> geometry + +*Output*: double + +.Description: + +Returns the maximum latitude of all points in the geometry. +The return type is double. + +["source","sql",subs="attributes,macros"] +-------------------------------------------------- +include-tagged::{sql-specs}/geo/docs.csv-spec[ymax] -------------------------------------------------- \ No newline at end of file diff --git a/docs/reference/sql/functions/index.asciidoc b/docs/reference/sql/functions/index.asciidoc index 93ebf13119780..ed2b91d11a6b4 100644 --- a/docs/reference/sql/functions/index.asciidoc +++ b/docs/reference/sql/functions/index.asciidoc @@ -21,3 +21,4 @@ include::search.asciidoc[] include::math.asciidoc[] include::string.asciidoc[] include::type-conversion.asciidoc[] +include::geo.asciidoc[] \ No newline at end of file diff --git a/server/src/main/java/org/elasticsearch/common/geo/builders/CircleBuilder.java b/server/src/main/java/org/elasticsearch/common/geo/builders/CircleBuilder.java index c6a0743980fd3..a2f92f4dc5012 100644 --- a/server/src/main/java/org/elasticsearch/common/geo/builders/CircleBuilder.java +++ b/server/src/main/java/org/elasticsearch/common/geo/builders/CircleBuilder.java @@ -177,6 +177,21 @@ public int numDimensions() { return Double.isNaN(center.z) ? 2 : 3; } + @Override + public int inherentDimensions() { + return 2; + } + + @Override + public double firstX() { + return center.x; + } + + @Override + public double firstY() { + return center.y; + } + @Override public int hashCode() { return Objects.hash(center, radius, unit.ordinal()); diff --git a/server/src/main/java/org/elasticsearch/common/geo/builders/EnvelopeBuilder.java b/server/src/main/java/org/elasticsearch/common/geo/builders/EnvelopeBuilder.java index a878a7c6d8618..da517859b268a 100644 --- a/server/src/main/java/org/elasticsearch/common/geo/builders/EnvelopeBuilder.java +++ b/server/src/main/java/org/elasticsearch/common/geo/builders/EnvelopeBuilder.java @@ -122,6 +122,11 @@ public int numDimensions() { return Double.isNaN(topLeft.z) ? 2 : 3; } + @Override + public int inherentDimensions() { + return 2; + } + @Override public int hashCode() { return Objects.hash(topLeft, bottomRight); diff --git a/server/src/main/java/org/elasticsearch/common/geo/builders/GeometryCollectionBuilder.java b/server/src/main/java/org/elasticsearch/common/geo/builders/GeometryCollectionBuilder.java index d2ff86ea63ced..93246752234fd 100644 --- a/server/src/main/java/org/elasticsearch/common/geo/builders/GeometryCollectionBuilder.java +++ b/server/src/main/java/org/elasticsearch/common/geo/builders/GeometryCollectionBuilder.java @@ -167,6 +167,33 @@ public int numDimensions() { return shapes.get(0).numDimensions(); } + @Override + public int inherentDimensions() { + if (shapes == null || shapes.isEmpty()) { + throw new IllegalStateException("unable to get number of dimensions, " + + "GeometryCollection has not yet been initialized"); + } + return shapes.stream().mapToInt(ShapeBuilder::inherentDimensions).max().orElse(0); + } + + @Override + public double firstX() { + if (shapes == null || shapes.isEmpty()) { + throw new IllegalStateException("unable to get the first point, " + + "GeometryCollection has not yet been initialized"); + } + return shapes.get(0).firstX(); + } + + @Override + public double firstY() { + if (shapes == null || shapes.isEmpty()) { + throw new IllegalStateException("unable to get the first point, " + + "GeometryCollection has not yet been initialized"); + } + return shapes.get(0).firstY(); + } + @Override public Shape build() { diff --git a/server/src/main/java/org/elasticsearch/common/geo/builders/LineStringBuilder.java b/server/src/main/java/org/elasticsearch/common/geo/builders/LineStringBuilder.java index 035c4566a5763..5db51f24c9fe1 100644 --- a/server/src/main/java/org/elasticsearch/common/geo/builders/LineStringBuilder.java +++ b/server/src/main/java/org/elasticsearch/common/geo/builders/LineStringBuilder.java @@ -100,6 +100,11 @@ public int numDimensions() { return Double.isNaN(coordinates.get(0).z) ? 2 : 3; } + @Override + public int inherentDimensions() { + return 1; + } + @Override public JtsGeometry build() { Coordinate[] coordinates = this.coordinates.toArray(new Coordinate[this.coordinates.size()]); diff --git a/server/src/main/java/org/elasticsearch/common/geo/builders/MultiLineStringBuilder.java b/server/src/main/java/org/elasticsearch/common/geo/builders/MultiLineStringBuilder.java index 68da45bbf0c68..5b95c0b10aa07 100644 --- a/server/src/main/java/org/elasticsearch/common/geo/builders/MultiLineStringBuilder.java +++ b/server/src/main/java/org/elasticsearch/common/geo/builders/MultiLineStringBuilder.java @@ -109,6 +109,25 @@ public int numDimensions() { return lines.get(0).numDimensions(); } + @Override + public int inherentDimensions() { + return 1; + } + + public double firstX() { + if (lines == null || lines.isEmpty()) { + throw new IllegalStateException("unable to get the first coordinate"); + } + return lines.get(0).firstX(); + } + + public double firstY() { + if (lines == null || lines.isEmpty()) { + throw new IllegalStateException("unable to get the first coordinate"); + } + return lines.get(0).firstY(); + } + @Override public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { builder.startObject(); diff --git a/server/src/main/java/org/elasticsearch/common/geo/builders/MultiPointBuilder.java b/server/src/main/java/org/elasticsearch/common/geo/builders/MultiPointBuilder.java index c39cc397a34ed..def8408013519 100644 --- a/server/src/main/java/org/elasticsearch/common/geo/builders/MultiPointBuilder.java +++ b/server/src/main/java/org/elasticsearch/common/geo/builders/MultiPointBuilder.java @@ -86,4 +86,9 @@ public int numDimensions() { } return Double.isNaN(coordinates.get(0).z) ? 2 : 3; } + + @Override + public int inherentDimensions() { + return 0; + } } diff --git a/server/src/main/java/org/elasticsearch/common/geo/builders/MultiPolygonBuilder.java b/server/src/main/java/org/elasticsearch/common/geo/builders/MultiPolygonBuilder.java index bac74c29dd805..9933a5d1831bd 100644 --- a/server/src/main/java/org/elasticsearch/common/geo/builders/MultiPolygonBuilder.java +++ b/server/src/main/java/org/elasticsearch/common/geo/builders/MultiPolygonBuilder.java @@ -161,6 +161,29 @@ public int numDimensions() { return polygons.get(0).numDimensions(); } + @Override + public int inherentDimensions() { + return 2; + } + + @Override + public double firstX() { + if (polygons == null || polygons.isEmpty()) { + throw new IllegalStateException("unable to get the first point, " + + "Polygons have not yet been initialized"); + } + return polygons.get(0).firstX(); + } + + @Override + public double firstY() { + if (polygons == null || polygons.isEmpty()) { + throw new IllegalStateException("unable to get the first point, " + + "Polygons have not yet been initialized"); + } + return polygons.get(0).firstY(); + } + @Override public Shape build() { diff --git a/server/src/main/java/org/elasticsearch/common/geo/builders/PointBuilder.java b/server/src/main/java/org/elasticsearch/common/geo/builders/PointBuilder.java index f8c18f317b63a..257ac712f5eb4 100644 --- a/server/src/main/java/org/elasticsearch/common/geo/builders/PointBuilder.java +++ b/server/src/main/java/org/elasticsearch/common/geo/builders/PointBuilder.java @@ -97,4 +97,10 @@ public GeoShapeType type() { public int numDimensions() { return Double.isNaN(coordinates.get(0).z) ? 2 : 3; } + + @Override + public int inherentDimensions() { + return 0; + } + } diff --git a/server/src/main/java/org/elasticsearch/common/geo/builders/PolygonBuilder.java b/server/src/main/java/org/elasticsearch/common/geo/builders/PolygonBuilder.java index e4d48b8df4566..684739d58f9ee 100644 --- a/server/src/main/java/org/elasticsearch/common/geo/builders/PolygonBuilder.java +++ b/server/src/main/java/org/elasticsearch/common/geo/builders/PolygonBuilder.java @@ -293,6 +293,29 @@ public int numDimensions() { return shell.numDimensions(); } + @Override + public int inherentDimensions() { + return 2; + } + + @Override + public double firstX() { + if (shell == null) { + throw new IllegalStateException("unable to get the first point, " + + "Polygon has not yet been initialized"); + } + return shell.firstX(); + } + + @Override + public double firstY() { + if (shell == null) { + throw new IllegalStateException("unable to get the first point, " + + "Polygon has not yet been initialized"); + } + return shell.firstY(); + } + protected static Polygon polygon(GeometryFactory factory, Coordinate[][] polygon) { LinearRing shell = factory.createLinearRing(polygon[0]); LinearRing[] holes; diff --git a/server/src/main/java/org/elasticsearch/common/geo/builders/ShapeBuilder.java b/server/src/main/java/org/elasticsearch/common/geo/builders/ShapeBuilder.java index e90d7261c9988..742803e2024d0 100644 --- a/server/src/main/java/org/elasticsearch/common/geo/builders/ShapeBuilder.java +++ b/server/src/main/java/org/elasticsearch/common/geo/builders/ShapeBuilder.java @@ -230,6 +230,37 @@ protected static Coordinate shift(Coordinate coordinate, double dateline) { /** tracks number of dimensions for this shape */ public abstract int numDimensions(); + /** + * Tracks number of inherent dimensions (in terms of width and length) + * + * For example POINT has 0 dimension + * Line has 1 dimension + * Polygon has 2 dimensions + * GeometryCollection has the max number of dimension of its members + * + * */ + public abstract int inherentDimensions(); + + /** + * Returns the first X coordinate + */ + public double firstX() { + if (coordinates == null || coordinates.isEmpty()) { + throw new IllegalStateException("unable to get the first coordinate"); + } + return coordinates.get(0).x; + } + + /** + * Returns the first Y coordinate + */ + public double firstY() { + if (coordinates == null || coordinates.isEmpty()) { + throw new IllegalStateException("unable to get the first coordinate"); + } + return coordinates.get(0).y; + } + /** * Calculate the intersection of a line segment and a vertical dateline. * diff --git a/server/src/test/java/org/elasticsearch/common/geo/ShapeBuilderTests.java b/server/src/test/java/org/elasticsearch/common/geo/ShapeBuilderTests.java index 5dcc811b0c6be..892c673f50512 100644 --- a/server/src/test/java/org/elasticsearch/common/geo/ShapeBuilderTests.java +++ b/server/src/test/java/org/elasticsearch/common/geo/ShapeBuilderTests.java @@ -19,6 +19,7 @@ package org.elasticsearch.common.geo; +import org.elasticsearch.common.geo.parsers.ShapeParser; import org.locationtech.jts.geom.Coordinate; import org.locationtech.jts.geom.LineString; import org.locationtech.jts.geom.Polygon; @@ -39,6 +40,8 @@ import org.locationtech.spatial4j.shape.Shape; import org.locationtech.spatial4j.shape.impl.PointImpl; +import java.io.IOException; + import static org.elasticsearch.test.hamcrest.ElasticsearchGeoAssertions.assertMultiLineString; import static org.elasticsearch.test.hamcrest.ElasticsearchGeoAssertions.assertMultiPolygon; import static org.elasticsearch.test.hamcrest.ElasticsearchGeoAssertions.assertPolygon; @@ -699,4 +702,48 @@ public void testPolygon3D() { assertEquals(expected, pb.toString()); } + + public void testBuilderProperties() throws IOException { + ShapeBuilder builder = new CircleBuilder().radius("10000m").center(10, 20); + assertEquals(2, builder.inherentDimensions()); + assertEquals(10.0, builder.firstX(), 0.0001); + assertEquals(20.0, builder.firstY(), 0.0001); + + builder = ShapeParser.parse("linestring (10 20, 30 40, 50 55)"); + assertEquals(1, builder.inherentDimensions()); + assertEquals(10.0, builder.firstX(), 0.0001); + assertEquals(20.0, builder.firstY(), 0.0001); + + + builder = ShapeParser.parse("multilinestring ((10 20, 30 40, 50 55), (1 2, 3 4))"); + assertEquals(1, builder.inherentDimensions()); + assertEquals(10.0, builder.firstX(), 0.0001); + assertEquals(20.0, builder.firstY(), 0.0001); + + + builder = ShapeParser.parse("point (10 20)"); + assertEquals(0, builder.inherentDimensions()); + assertEquals(10.0, builder.firstX(), 0.0001); + assertEquals(20.0, builder.firstY(), 0.0001); + + builder = ShapeParser.parse("geometrycollection(point (10 20),linestring(10 20, 30 40, 50 55))"); + assertEquals(1, builder.inherentDimensions()); + assertEquals(10.0, builder.firstX(), 0.0001); + assertEquals(20.0, builder.firstY(), 0.0001); + + builder = ShapeParser.parse("polygon ((20 40, 10 20, 30 10, 40 40, 20 40))"); + assertEquals(2, builder.inherentDimensions()); + assertEquals(20.0, builder.firstX(), 0.0001); + assertEquals(40.0, builder.firstY(), 0.0001); + + builder = ShapeParser.parse("multipolygon (((20 40, 10 20, 30 10, 40 40, 20 40)), ((2 4, 1 2, 3 1, 4 4, 2 4)))"); + assertEquals(2, builder.inherentDimensions()); + assertEquals(20.0, builder.firstX(), 0.0001); + assertEquals(40.0, builder.firstY(), 0.0001); + + builder = ShapeParser.parse("multipoint ((10 20), (30 20))"); + assertEquals(0, builder.inherentDimensions()); + assertEquals(10.0, builder.firstX(), 0.0001); + assertEquals(20.0, builder.firstY(), 0.0001); + } } diff --git a/x-pack/plugin/sql/qa/src/main/java/org/elasticsearch/xpack/sql/qa/jdbc/CsvTestUtils.java b/x-pack/plugin/sql/qa/src/main/java/org/elasticsearch/xpack/sql/qa/jdbc/CsvTestUtils.java index 618cc05c6c50d..daa4e5b4d0c87 100644 --- a/x-pack/plugin/sql/qa/src/main/java/org/elasticsearch/xpack/sql/qa/jdbc/CsvTestUtils.java +++ b/x-pack/plugin/sql/qa/src/main/java/org/elasticsearch/xpack/sql/qa/jdbc/CsvTestUtils.java @@ -155,6 +155,8 @@ private static String resolveColumnType(String type) { return "timestamp"; case "bt": return "byte"; + case "sh": + return "short"; default: return type; } diff --git a/x-pack/plugin/sql/qa/src/main/resources/command.csv-spec b/x-pack/plugin/sql/qa/src/main/resources/command.csv-spec index 34e8e1fbf1967..1b7688a4fe0b3 100644 --- a/x-pack/plugin/sql/qa/src/main/resources/command.csv-spec +++ b/x-pack/plugin/sql/qa/src/main/resources/command.csv-spec @@ -102,8 +102,16 @@ SUBSTRING |SCALAR UCASE |SCALAR ST_ASTEXT |SCALAR ST_ASWKT |SCALAR +ST_DIMENSION |SCALAR +ST_GEOMETRYTYPE |SCALAR ST_GEOMFROMTEXT |SCALAR ST_WKTTOSQL |SCALAR +ST_X |SCALAR +ST_XMAX |SCALAR +ST_XMIN |SCALAR +ST_Y |SCALAR +ST_YMAX |SCALAR +ST_YMIN |SCALAR CAST |SCALAR CONVERT |SCALAR SCORE |SCORE diff --git a/x-pack/plugin/sql/qa/src/main/resources/docs.csv-spec b/x-pack/plugin/sql/qa/src/main/resources/docs.csv-spec index 437c66a4ea199..e0fc4cea4b62a 100644 --- a/x-pack/plugin/sql/qa/src/main/resources/docs.csv-spec +++ b/x-pack/plugin/sql/qa/src/main/resources/docs.csv-spec @@ -279,8 +279,16 @@ SUBSTRING |SCALAR UCASE |SCALAR ST_ASTEXT |SCALAR ST_ASWKT |SCALAR +ST_DIMENSION |SCALAR +ST_GEOMETRYTYPE |SCALAR ST_GEOMFROMTEXT |SCALAR ST_WKTTOSQL |SCALAR +ST_X |SCALAR +ST_XMAX |SCALAR +ST_XMIN |SCALAR +ST_Y |SCALAR +ST_YMAX |SCALAR +ST_YMIN |SCALAR CAST |SCALAR CONVERT |SCALAR SCORE |SCORE diff --git a/x-pack/plugin/sql/qa/src/main/resources/geo/docs.csv-spec b/x-pack/plugin/sql/qa/src/main/resources/geo/docs.csv-spec index 4444a86e233c8..a318667c10adc 100644 --- a/x-pack/plugin/sql/qa/src/main/resources/geo/docs.csv-spec +++ b/x-pack/plugin/sql/qa/src/main/resources/geo/docs.csv-spec @@ -4,7 +4,7 @@ /////////////////////////////// // -// ST_AsWKT() +// Geometry Conversion // /////////////////////////////// @@ -25,3 +25,81 @@ SELECT CAST(ST_WKTToSQL('POINT (10 20)') AS STRING) location; point (10.0 20.0) // end::wkttosql ; + +/////////////////////////////// +// +// Geometry Properties +// +/////////////////////////////// + +selectDimension +// tag::dimension +SELECT ST_Dimension(ST_WKTToSQL('POINT (10 20)')) dim; + + dim:sh +0 +// end::dimension +; + +selectGeometryType +// tag::geometrytype +SELECT ST_GeometryType(ST_WKTToSQL('POINT (10 20)')) type; + + type:s +Point +// end::geometrytype +; + +selectX +// tag::x +SELECT ST_X(ST_WKTToSQL('POINT (10 20)')) x; + + x:d +10.0 +// end::x +; + +selectY +// tag::y +SELECT ST_Y(ST_WKTToSQL('POINT (10 20)')) y; + + y:d +20.0 +// end::y +; + +selectXMin +// tag::xmin +SELECT ST_XMin(ST_WKTToSQL('POLYGON ((20 40, 10 20, 30 10, 40 40, 20 40))')) x; + + x:d +10.0 +// end::xmin +; + +selectYMin +// tag::ymin +SELECT ST_YMin(ST_WKTToSQL('POLYGON ((20 40, 10 20, 30 10, 40 40, 20 40))')) y; + + y:d +10.0 +// end::ymin +; + +selectXMax +// tag::xmax +SELECT ST_XMax(ST_WKTToSQL('POLYGON ((20 40, 10 20, 30 10, 40 40, 20 40))')) x; + + x:d +40.0 +// end::xmax +; + +selectYMax +// tag::ymax +SELECT ST_YMax(ST_WKTToSQL('POLYGON ((20 40, 10 20, 30 10, 40 40, 20 40))')) y; + + y:d +40.0 +// end::ymax +; \ No newline at end of file diff --git a/x-pack/plugin/sql/qa/src/main/resources/geo/geosql.csv-spec b/x-pack/plugin/sql/qa/src/main/resources/geo/geosql.csv-spec index 7ec42197efa5a..5a804e0d88633 100644 --- a/x-pack/plugin/sql/qa/src/main/resources/geo/geosql.csv-spec +++ b/x-pack/plugin/sql/qa/src/main/resources/geo/geosql.csv-spec @@ -146,4 +146,88 @@ Asia |Singapore Asia |Sydney Europe |Amsterdam Europe |Berlin +; + +groupByDimension +SELECT COUNT(*) cnt, ST_Dimension(location) dim FROM geo GROUP BY ST_Dimension(location); + + cnt:l | dim:sh +15 |0 +; + +groupByGeometryType +SELECT COUNT(*) cnt, ST_GeometryType(location) gt FROM geo GROUP BY ST_GeometryType(location); + + cnt:l | gt:s +15 |Point +; + +groupByEastWest +SELECT COUNT(*) cnt, FLOOR(ST_X(location)/90) east FROM geo GROUP BY east ORDER BY east; + + cnt:l | east:l +3 |-2 +3 |-1 +4 |0 +5 |1 +; + +groupByNorthSouth +SELECT COUNT(*) cnt, FLOOR(ST_Y(location)/45) north FROM geo GROUP BY north ORDER BY north; + + cnt:l | north:l +1 |-1 +9 |0 +5 |1 +; + +groupByXMaxYMin +SELECT COUNT(*) cnt, FLOOR(ST_YMin(location)/45) north, FLOOR(ST_XMax(location)/90) east FROM geo GROUP BY north, east ORDER BY north, east; + + cnt:l | north:l | east:l +1 |-1 |1 +3 |0 |-2 +2 |0 |-1 +4 |0 |1 +1 |1 |-1 +4 |1 |0 +; + +groupByXMinYMax +SELECT COUNT(*) cnt, FLOOR(ST_YMax(location)/45) north, FLOOR(ST_XMin(location)/90) east FROM geo GROUP BY north, east ORDER BY north, east; + + cnt:l | north:l | east:l +1 |-1 |1 +3 |0 |-2 +2 |0 |-1 +4 |0 |1 +1 |1 |-1 +4 |1 |0 +; + + +selectFilterByXOfLocation +SELECT city, ST_X(shape) x, ST_Y(shape) y FROM geo WHERE ST_X(location) > 0 ORDER BY ST_Y(location); + + city:s | x:d | y:d +Sydney |151.208629 |-33.863385 +Singapore |103.855535 |1.295868 +Hong Kong |114.183925 |22.281397 +Tokyo |139.76402225 |35.669616 +Seoul |127.060851 |37.509132 +Munich |11.537505 |48.146321 +Paris |2.351773 |48.845538 +Amsterdam |4.850312 |52.347557 +Berlin |13.390889 |52.486701 +; + +selectFilterByRegionPoint +SELECT city, region FROM geo WHERE ST_X(ST_WKTTOSQL(region_point)) < 0 ORDER BY ST_X(location); + + city:s | region:s +San Francisco |Americas +Mountain View |Americas +Phoenix |Americas +Chicago |Americas +New York |Americas ; \ No newline at end of file diff --git a/x-pack/plugin/sql/qa/src/main/resources/ogc/ogc.sql-spec b/x-pack/plugin/sql/qa/src/main/resources/ogc/ogc.sql-spec index 0bb78fa10a04f..4f9ca3b973eb9 100644 --- a/x-pack/plugin/sql/qa/src/main/resources/ogc/ogc.sql-spec +++ b/x-pack/plugin/sql/qa/src/main/resources/ogc/ogc.sql-spec @@ -34,3 +34,49 @@ SELECT fid, name, num_lanes, aliases, REPLACE(UCASE(ST_AsText(centerline)), '.0' selectSinglePoint SELECT ST_GeomFromText('point (10.0 12.0)') point; + +// +// Geometry Property Functions +// +// H2GIS doesn't follow the standard here that mandates ST_Dimension returns SMALLINT +selectLakesProps +SELECT fid, CAST(ST_Dimension(shore) AS INTEGER) dim, ST_GeometryType(shore) type FROM lakes ORDER BY fid; +selectRoadSegmentsProps +SELECT fid, CAST(ST_Dimension(centerline) AS INTEGER) dim, ST_GeometryType(centerline) type FROM road_segments ORDER BY fid; +selectDividedRoutesProps +SELECT fid, CAST(ST_Dimension(centerlines) AS INTEGER) dim, ST_GeometryType(centerlines) type FROM divided_routes ORDER BY fid; +selectForestsProps +SELECT fid, CAST(ST_Dimension(boundary) AS INTEGER) dim, ST_GeometryType(boundary) type FROM forests ORDER BY fid; +selectBridgesProps +SELECT fid, CAST(ST_Dimension(position) AS INTEGER) dim, ST_GeometryType(position) type FROM bridges ORDER BY fid; +selectStreamsProps +SELECT fid, CAST(ST_Dimension(centerline) AS INTEGER) dim, ST_GeometryType(centerline) type FROM streams ORDER BY fid; +selectBuildingsProps +SELECT fid, CAST(ST_Dimension(position) AS INTEGER) dim1, CAST(ST_Dimension(footprint) AS INTEGER) dim2, ST_GeometryType(position) type1, ST_GeometryType(footprint) type2 FROM buildings ORDER BY fid; +selectPondsProps +SELECT fid, CAST(ST_Dimension(shores) AS INTEGER) dim, ST_GeometryType(shores) type FROM ponds ORDER BY fid; +selectNamedPlacesProps +SELECT fid, CAST(ST_Dimension(boundary) AS INTEGER) dim, ST_GeometryType(boundary) type FROM named_places ORDER BY fid; +selectMapNeatLinesProps +SELECT fid, CAST(ST_Dimension(neatline) AS INTEGER) dim, ST_GeometryType(neatline) type FROM map_neatlines ORDER BY fid; + +selectLakesXY +SELECT fid, ST_X(shore) x, ST_Y(shore) y, ST_XMin(shore) min_x, ST_YMin(shore) min_y, ST_XMax(shore) max_x, ST_YMax(shore) max_y FROM lakes ORDER BY fid; +selectRoadSegmentsXY +SELECT fid, ST_X(centerline) x, ST_Y(centerline) y, ST_XMin(centerline) min_x, ST_YMin(centerline) min_y, ST_XMax(centerline) max_x, ST_YMax(centerline) max_y FROM road_segments ORDER BY fid; +selectDividedRoutesXY +SELECT fid, ST_X(centerlines) x, ST_Y(centerlines) y, ST_XMin(centerlines) min_x, ST_YMin(centerlines) min_y, ST_XMax(centerlines) max_x, ST_YMax(centerlines) max_y FROM divided_routes ORDER BY fid; +selectForestsXY +SELECT fid, ST_X(boundary) x, ST_Y(boundary) y, ST_XMin(boundary) min_x, ST_YMin(boundary) min_y, ST_XMax(boundary) max_x, ST_YMax(boundary) max_y FROM forests ORDER BY fid; +selectBridgesXY +SELECT fid, ST_X(position) x, ST_Y(position) y, ST_XMin(position) min_x, ST_YMin(position) min_y, ST_XMax(position) max_x, ST_YMax(position) max_y FROM bridges ORDER BY fid; +selectStreamsXY +SELECT fid, ST_X(centerline) x, ST_Y(centerline) y, ST_XMin(centerline) min_x, ST_YMin(centerline) min_y, ST_XMax(centerline) max_x, ST_YMax(centerline) max_y FROM streams ORDER BY fid; +selectBuildingsXY +SELECT fid, ST_X(position) x, ST_Y(position) y, ST_XMin(position) min_x, ST_YMin(position) min_y, ST_XMax(position) max_x, ST_YMax(position) max_y FROM buildings ORDER BY fid; +selectPondsXY +SELECT fid, ST_X(shores) x, ST_Y(shores) y, ST_XMin(shores) min_x, ST_YMin(shores) min_y, ST_XMax(shores) max_x, ST_YMax(shores) max_y FROM ponds ORDER BY fid; +selectNamedPlacesXY +SELECT fid, ST_X(boundary) x, ST_Y(boundary) y, ST_XMin(boundary) min_x, ST_YMin(boundary) min_y, ST_XMax(boundary) max_x, ST_YMax(boundary) max_y FROM named_places ORDER BY fid; +selectMapNeatLinesXY +SELECT fid, ST_X(neatline) x, ST_Y(neatline) y, ST_XMin(neatline) min_x, ST_YMin(neatline) min_y, ST_XMax(neatline) max_x, ST_YMax(neatline) max_y FROM map_neatlines ORDER BY fid; diff --git a/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/FunctionRegistry.java b/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/FunctionRegistry.java index b32e2a2a31a1a..56e4881b4ab64 100644 --- a/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/FunctionRegistry.java +++ b/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/FunctionRegistry.java @@ -35,7 +35,15 @@ import org.elasticsearch.xpack.sql.expression.function.scalar.datetime.WeekOfYear; import org.elasticsearch.xpack.sql.expression.function.scalar.datetime.Year; import org.elasticsearch.xpack.sql.expression.function.scalar.geo.StAswkt; +import org.elasticsearch.xpack.sql.expression.function.scalar.geo.StDimension; +import org.elasticsearch.xpack.sql.expression.function.scalar.geo.StGeometrytype; +import org.elasticsearch.xpack.sql.expression.function.scalar.geo.StXmax; +import org.elasticsearch.xpack.sql.expression.function.scalar.geo.StYmax; +import org.elasticsearch.xpack.sql.expression.function.scalar.geo.StXmin; +import org.elasticsearch.xpack.sql.expression.function.scalar.geo.StYmin; import org.elasticsearch.xpack.sql.expression.function.scalar.geo.StWkttosql; +import org.elasticsearch.xpack.sql.expression.function.scalar.geo.StX; +import org.elasticsearch.xpack.sql.expression.function.scalar.geo.StY; import org.elasticsearch.xpack.sql.expression.function.scalar.math.ACos; import org.elasticsearch.xpack.sql.expression.function.scalar.math.ASin; import org.elasticsearch.xpack.sql.expression.function.scalar.math.ATan; @@ -215,8 +223,16 @@ private void defineDefaultFunctions() { def(UCase.class, UCase::new)); // Geo Functions - addToMap(def(StAswkt.class, StAswkt::new, "ST_ASTEXT")); - addToMap(def(StWkttosql.class, StWkttosql::new, "ST_GEOMFROMTEXT")); + addToMap(def(StDimension.class, StDimension::new), + def(StGeometrytype.class, StGeometrytype::new), + def(StAswkt.class, StAswkt::new, "ST_ASTEXT"), + def(StX.class, StX::new), + def(StY.class, StY::new), + def(StXmin.class, StXmin::new), + def(StYmin.class, StYmin::new), + def(StXmax.class, StXmax::new), + def(StYmax.class, StYmax::new), + def(StWkttosql.class, StWkttosql::new, "ST_GEOMFROMTEXT")); // DataType conversion addToMap(def(Cast.class, Cast::new, "CONVERT")); // Special diff --git a/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/geo/GeoProcessor.java b/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/geo/GeoProcessor.java index 3d571b42f3c92..93aedda84b397 100644 --- a/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/geo/GeoProcessor.java +++ b/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/geo/GeoProcessor.java @@ -6,7 +6,6 @@ package org.elasticsearch.xpack.sql.expression.function.scalar.geo; import org.elasticsearch.common.geo.GeoPoint; -import org.elasticsearch.common.geo.builders.PointBuilder; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.xpack.sql.SqlIllegalArgumentException; @@ -17,40 +16,33 @@ public class GeoProcessor implements Processor { - private interface GeoPointFunction { - default R apply(Object o) { - if (!(o instanceof GeoPoint)) { - throw new SqlIllegalArgumentException("A geo_point is required; received [{}]", o); - } - return doApply((GeoPoint) o); - } - - R doApply(GeoPoint s); - } - - private interface GeoShapeFunction { default R apply(Object o) { - if (!(o instanceof GeoShape)) { + if (o instanceof GeoPoint) { + return doApply(new GeoShape((GeoPoint) o)); + } else if (o instanceof GeoShape) { + return doApply((GeoShape) o); + } else { throw new SqlIllegalArgumentException("A geo_shape is required; received [{}]", o); } - - return doApply((GeoShape) o); } R doApply(GeoShape s); } public enum GeoOperation { - ASWKT_POINT((GeoPoint p) -> new PointBuilder(p.getLon(), p.getLat()).toWKT()), - ASWKT_SHAPE(GeoShape::toString); + ASWKT(GeoShape::toString), + DIMENSION(GeoShape::dimension), + GEOMETRY_TYPE(GeoShape::geometryType), + X(GeoShape::x), + Y(GeoShape::y), + X_MIN(GeoShape::minX), + Y_MIN(GeoShape::minY), + X_MAX(GeoShape::maxX), + Y_MAX(GeoShape::maxY); private final Function apply; - GeoOperation(GeoPointFunction apply) { - this.apply = l -> l == null ? null : apply.apply(l); - } - GeoOperation(GeoShapeFunction apply) { this.apply = l -> l == null ? null : apply.apply(l); } diff --git a/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/geo/GeoShape.java b/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/geo/GeoShape.java index c7400aad18e8e..316966ecac4eb 100644 --- a/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/geo/GeoShape.java +++ b/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/geo/GeoShape.java @@ -5,10 +5,14 @@ */ package org.elasticsearch.xpack.sql.expression.function.scalar.geo; +import org.elasticsearch.common.geo.GeoPoint; +import org.elasticsearch.common.geo.GeoShapeType; +import org.elasticsearch.common.geo.builders.PointBuilder; import org.elasticsearch.common.geo.builders.ShapeBuilder; import org.elasticsearch.common.geo.parsers.ShapeParser; import org.elasticsearch.common.xcontent.ToXContentFragment; import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.xpack.sql.SqlIllegalArgumentException; import java.io.IOException; @@ -22,6 +26,10 @@ public class GeoShape implements ToXContentFragment { private final ShapeBuilder shapeBuilder; + public GeoShape(GeoPoint point) { + shapeBuilder = new PointBuilder(point.getLon(), point.getLat()); + } + public GeoShape(Object value) throws IOException { shapeBuilder = ShapeParser.parse(value); } @@ -35,4 +43,54 @@ public String toString() { public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { return builder.value(shapeBuilder.toWKT()); } + + public int dimension() { + return shapeBuilder.inherentDimensions(); + } + + public String geometryType() { + return geometryType(shapeBuilder.type()); + } + + static String geometryType(GeoShapeType type) { + switch (type) { + case POINT: return "Point"; + case CIRCLE: return "Circle"; + case POLYGON: return "Polygon"; + case ENVELOPE: return "Envelope"; + case LINESTRING: return "LineString"; + case MULTIPOINT: return "MultiPoint"; + case MULTIPOLYGON: return "MultiPolygon"; + case MULTILINESTRING: return "MultiLineString"; + case GEOMETRYCOLLECTION: return "GeometryCollection"; + default: + throw new SqlIllegalArgumentException("Unsupported geometry type [" + type + "]"); + + } + } + + public double x() { + return shapeBuilder.firstX(); + } + + + public double y() { + return shapeBuilder.firstY(); + } + + public double minX() { + return shapeBuilder.build().getBoundingBox().getMinX(); + } + + public double minY() { + return shapeBuilder.build().getBoundingBox().getMinY(); + } + + public double maxX() { + return shapeBuilder.build().getBoundingBox().getMaxX(); + } + + public double maxY() { + return shapeBuilder.build().getBoundingBox().getMaxY(); + } } diff --git a/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/geo/StAswkt.java b/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/geo/StAswkt.java index 4e3b8a7c53bb2..42743a5e06673 100644 --- a/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/geo/StAswkt.java +++ b/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/geo/StAswkt.java @@ -34,11 +34,7 @@ protected StAswkt replaceChild(Expression newChild) { @Override protected GeoOperation operation() { - if (field().dataType() == DataType.GEO_POINT) { - return GeoOperation.ASWKT_POINT; - } else { - return GeoOperation.ASWKT_SHAPE; - } + return GeoOperation.ASWKT; } @Override diff --git a/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/geo/StDimension.java b/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/geo/StDimension.java new file mode 100644 index 0000000000000..5cf131e81289c --- /dev/null +++ b/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/geo/StDimension.java @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +package org.elasticsearch.xpack.sql.expression.function.scalar.geo; + + +import org.elasticsearch.xpack.sql.expression.Expression; +import org.elasticsearch.xpack.sql.expression.function.scalar.geo.GeoProcessor.GeoOperation; +import org.elasticsearch.xpack.sql.tree.Location; +import org.elasticsearch.xpack.sql.tree.NodeInfo; +import org.elasticsearch.xpack.sql.type.DataType; + +/** + * ST_Dimension function that takes a geometry and returns its dimension + */ +public class StDimension extends UnaryGeoFunction { + + public StDimension(Location location, Expression field) { + super(location, field); + } + + @Override + protected NodeInfo info() { + return NodeInfo.create(this, StDimension::new, field()); + } + + @Override + protected StDimension replaceChild(Expression newChild) { + return new StDimension(location(), newChild); + } + + @Override + protected GeoOperation operation() { + return GeoOperation.DIMENSION; + } + + @Override + public DataType dataType() { + return DataType.SHORT; + } + +} diff --git a/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/geo/StGeometrytype.java b/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/geo/StGeometrytype.java new file mode 100644 index 0000000000000..f332e52c9f4d3 --- /dev/null +++ b/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/geo/StGeometrytype.java @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +package org.elasticsearch.xpack.sql.expression.function.scalar.geo; + + +import org.elasticsearch.xpack.sql.expression.Expression; +import org.elasticsearch.xpack.sql.expression.function.scalar.geo.GeoProcessor.GeoOperation; +import org.elasticsearch.xpack.sql.tree.Location; +import org.elasticsearch.xpack.sql.tree.NodeInfo; +import org.elasticsearch.xpack.sql.type.DataType; + +/** + * ST_GeometryType function that takes a geometry and returns its type + */ +public class StGeometrytype extends UnaryGeoFunction { + + public StGeometrytype(Location location, Expression field) { + super(location, field); + } + + @Override + protected NodeInfo info() { + return NodeInfo.create(this, StGeometrytype::new, field()); + } + + @Override + protected StGeometrytype replaceChild(Expression newChild) { + return new StGeometrytype(location(), newChild); + } + + @Override + protected GeoOperation operation() { + return GeoOperation.GEOMETRY_TYPE; + } + + @Override + public DataType dataType() { + return DataType.KEYWORD; + } + +} diff --git a/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/geo/StX.java b/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/geo/StX.java new file mode 100644 index 0000000000000..885d58366ec08 --- /dev/null +++ b/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/geo/StX.java @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +package org.elasticsearch.xpack.sql.expression.function.scalar.geo; + + +import org.elasticsearch.xpack.sql.expression.Expression; +import org.elasticsearch.xpack.sql.expression.function.scalar.geo.GeoProcessor.GeoOperation; +import org.elasticsearch.xpack.sql.tree.Location; +import org.elasticsearch.xpack.sql.tree.NodeInfo; +import org.elasticsearch.xpack.sql.type.DataType; + +/** + * ST_X function that takes a geometry and returns X of the first coordinate + */ +public class StX extends UnaryGeoFunction { + + public StX(Location location, Expression field) { + super(location, field); + } + + @Override + protected NodeInfo info() { + return NodeInfo.create(this, StX::new, field()); + } + + @Override + protected StX replaceChild(Expression newChild) { + return new StX(location(), newChild); + } + + @Override + protected GeoOperation operation() { + return GeoOperation.X; + } + + @Override + public DataType dataType() { + return DataType.DOUBLE; + } + +} diff --git a/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/geo/StXmax.java b/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/geo/StXmax.java new file mode 100644 index 0000000000000..a8e607dc0d894 --- /dev/null +++ b/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/geo/StXmax.java @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +package org.elasticsearch.xpack.sql.expression.function.scalar.geo; + + +import org.elasticsearch.xpack.sql.expression.Expression; +import org.elasticsearch.xpack.sql.expression.function.scalar.geo.GeoProcessor.GeoOperation; +import org.elasticsearch.xpack.sql.tree.Location; +import org.elasticsearch.xpack.sql.tree.NodeInfo; +import org.elasticsearch.xpack.sql.type.DataType; + +/** + * ST_XMax function that takes a geometry and returns max X of all coordinates + */ +public class StXmax extends UnaryGeoFunction { + + public StXmax(Location location, Expression field) { + super(location, field); + } + + @Override + protected NodeInfo info() { + return NodeInfo.create(this, StXmax::new, field()); + } + + @Override + protected StXmax replaceChild(Expression newChild) { + return new StXmax(location(), newChild); + } + + @Override + protected GeoOperation operation() { + return GeoOperation.X_MAX; + } + + @Override + public DataType dataType() { + return DataType.DOUBLE; + } + +} diff --git a/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/geo/StXmin.java b/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/geo/StXmin.java new file mode 100644 index 0000000000000..9b17e4244f85a --- /dev/null +++ b/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/geo/StXmin.java @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +package org.elasticsearch.xpack.sql.expression.function.scalar.geo; + + +import org.elasticsearch.xpack.sql.expression.Expression; +import org.elasticsearch.xpack.sql.expression.function.scalar.geo.GeoProcessor.GeoOperation; +import org.elasticsearch.xpack.sql.tree.Location; +import org.elasticsearch.xpack.sql.tree.NodeInfo; +import org.elasticsearch.xpack.sql.type.DataType; + +/** + * ST_XMin function that takes a geometry and returns min X of all coordinates + */ +public class StXmin extends UnaryGeoFunction { + + public StXmin(Location location, Expression field) { + super(location, field); + } + + @Override + protected NodeInfo info() { + return NodeInfo.create(this, StXmin::new, field()); + } + + @Override + protected StXmin replaceChild(Expression newChild) { + return new StXmin(location(), newChild); + } + + @Override + protected GeoOperation operation() { + return GeoOperation.X_MIN; + } + + @Override + public DataType dataType() { + return DataType.DOUBLE; + } + +} diff --git a/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/geo/StY.java b/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/geo/StY.java new file mode 100644 index 0000000000000..4e71b72d066b6 --- /dev/null +++ b/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/geo/StY.java @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +package org.elasticsearch.xpack.sql.expression.function.scalar.geo; + + +import org.elasticsearch.xpack.sql.expression.Expression; +import org.elasticsearch.xpack.sql.expression.function.scalar.geo.GeoProcessor.GeoOperation; +import org.elasticsearch.xpack.sql.tree.Location; +import org.elasticsearch.xpack.sql.tree.NodeInfo; +import org.elasticsearch.xpack.sql.type.DataType; + +/** + * ST_Y function that takes a geometry and returns Y of the first coordinate + */ +public class StY extends UnaryGeoFunction { + + public StY(Location location, Expression field) { + super(location, field); + } + + @Override + protected NodeInfo info() { + return NodeInfo.create(this, StY::new, field()); + } + + @Override + protected StY replaceChild(Expression newChild) { + return new StY(location(), newChild); + } + + @Override + protected GeoOperation operation() { + return GeoOperation.Y; + } + + @Override + public DataType dataType() { + return DataType.DOUBLE; + } + +} diff --git a/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/geo/StYmax.java b/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/geo/StYmax.java new file mode 100644 index 0000000000000..09c6cb2a1fc9a --- /dev/null +++ b/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/geo/StYmax.java @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +package org.elasticsearch.xpack.sql.expression.function.scalar.geo; + + +import org.elasticsearch.xpack.sql.expression.Expression; +import org.elasticsearch.xpack.sql.expression.function.scalar.geo.GeoProcessor.GeoOperation; +import org.elasticsearch.xpack.sql.tree.Location; +import org.elasticsearch.xpack.sql.tree.NodeInfo; +import org.elasticsearch.xpack.sql.type.DataType; + +/** + * ST_YMax function that takes a geometry and returns max Y of all coordinates + */ +public class StYmax extends UnaryGeoFunction { + + public StYmax(Location location, Expression field) { + super(location, field); + } + + @Override + protected NodeInfo info() { + return NodeInfo.create(this, StYmax::new, field()); + } + + @Override + protected StYmax replaceChild(Expression newChild) { + return new StYmax(location(), newChild); + } + + @Override + protected GeoOperation operation() { + return GeoOperation.Y_MAX; + } + + @Override + public DataType dataType() { + return DataType.DOUBLE; + } + +} diff --git a/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/geo/StYmin.java b/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/geo/StYmin.java new file mode 100644 index 0000000000000..6a25e44410c0c --- /dev/null +++ b/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/geo/StYmin.java @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +package org.elasticsearch.xpack.sql.expression.function.scalar.geo; + + +import org.elasticsearch.xpack.sql.expression.Expression; +import org.elasticsearch.xpack.sql.expression.function.scalar.geo.GeoProcessor.GeoOperation; +import org.elasticsearch.xpack.sql.tree.Location; +import org.elasticsearch.xpack.sql.tree.NodeInfo; +import org.elasticsearch.xpack.sql.type.DataType; + +/** + * ST_YMin function that takes a geometry and returns min Y of all coordinates + */ +public class StYmin extends UnaryGeoFunction { + + public StYmin(Location location, Expression field) { + super(location, field); + } + + @Override + protected NodeInfo info() { + return NodeInfo.create(this, StYmin::new, field()); + } + + @Override + protected StYmin replaceChild(Expression newChild) { + return new StYmin(location(), newChild); + } + + @Override + protected GeoOperation operation() { + return GeoOperation.Y_MIN; + } + + @Override + public DataType dataType() { + return DataType.DOUBLE; + } + +} diff --git a/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/whitelist/InternalSqlScriptUtils.java b/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/whitelist/InternalSqlScriptUtils.java index 085314a9a05fc..90a4ce9464f8c 100644 --- a/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/whitelist/InternalSqlScriptUtils.java +++ b/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/whitelist/InternalSqlScriptUtils.java @@ -402,15 +402,43 @@ public static String ucase(String s) { return (String) StringOperation.UCASE.apply(s); } - public static String aswktPoint(Object v) { - return GeoProcessor.GeoOperation.ASWKT_POINT.apply(v).toString(); - } - - public static String aswktShape(Object v) { - return GeoProcessor.GeoOperation.ASWKT_SHAPE.apply(v).toString(); + public static String aswkt(Object v) { + return GeoProcessor.GeoOperation.ASWKT.apply(v).toString(); } public static GeoShape wktToSql(String wktString) { return StWkttosqlProcessor.apply(wktString); } + + public static Integer dimension(Object g) { + return (Integer) GeoProcessor.GeoOperation.DIMENSION.apply(g); + } + + public static String geometryType(Object g) { + return (String) GeoProcessor.GeoOperation.GEOMETRY_TYPE.apply(g); + } + + public static Double x(Object g) { + return (Double) GeoProcessor.GeoOperation.X.apply(g); + } + + public static Double y(Object g) { + return (Double) GeoProcessor.GeoOperation.Y.apply(g); + } + + public static Double xMin(Object g) { + return (Double) GeoProcessor.GeoOperation.X_MIN.apply(g); + } + + public static Double yMin(Object g) { + return (Double) GeoProcessor.GeoOperation.Y_MIN.apply(g); + } + + public static Double xMax(Object g) { + return (Double) GeoProcessor.GeoOperation.X_MAX.apply(g); + } + + public static Double yMax(Object g) { + return (Double) GeoProcessor.GeoOperation.Y_MAX.apply(g); + } } diff --git a/x-pack/plugin/sql/src/main/resources/org/elasticsearch/xpack/sql/plugin/sql_whitelist.txt b/x-pack/plugin/sql/src/main/resources/org/elasticsearch/xpack/sql/plugin/sql_whitelist.txt index 9310e8bab2e81..96a259be119ad 100644 --- a/x-pack/plugin/sql/src/main/resources/org/elasticsearch/xpack/sql/plugin/sql_whitelist.txt +++ b/x-pack/plugin/sql/src/main/resources/org/elasticsearch/xpack/sql/plugin/sql_whitelist.txt @@ -122,7 +122,14 @@ class org.elasticsearch.xpack.sql.expression.function.scalar.whitelist.InternalS String space(Number) String substring(String, Number, Number) String ucase(String) - String aswktPoint(Object) - String aswktShape(Object) + String aswkt(Object) GeoShape wktToSql(String) + Integer dimension(Object) + String geometryType(Object) + Double x(Object) + Double y(Object) + Double xMin(Object) + Double yMin(Object) + Double xMax(Object) + Double yMax(Object) } diff --git a/x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/expression/function/scalar/geo/GeoProcessorTests.java b/x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/expression/function/scalar/geo/GeoProcessorTests.java index 20330995e5926..0c033d69a7f45 100644 --- a/x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/expression/function/scalar/geo/GeoProcessorTests.java +++ b/x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/expression/function/scalar/geo/GeoProcessorTests.java @@ -6,12 +6,14 @@ package org.elasticsearch.xpack.sql.expression.function.scalar.geo; import org.elasticsearch.common.geo.GeoPoint; +import org.elasticsearch.common.geo.GeoShapeType; import org.elasticsearch.common.io.stream.Writeable.Reader; import org.elasticsearch.test.AbstractWireSerializingTestCase; import org.elasticsearch.xpack.sql.SqlIllegalArgumentException; import org.elasticsearch.xpack.sql.expression.function.scalar.geo.GeoProcessor.GeoOperation; import java.io.IOException; +import java.util.Locale; public class GeoProcessorTests extends AbstractWireSerializingTestCase { public static GeoProcessor randomGeoProcessor() { @@ -33,23 +35,28 @@ protected GeoProcessor mutateInstance(GeoProcessor instance) throws IOException return new GeoProcessor(randomValueOtherThan(instance.processor(), () -> randomFrom(GeoOperation.values()))); } - public void testApply() throws Exception { - GeoProcessor proc = new GeoProcessor(GeoOperation.ASWKT_POINT); + public void testAsWKTApply() throws Exception { + GeoProcessor proc = new GeoProcessor(GeoOperation.ASWKT); assertNull(proc.process(null)); assertEquals("point (10.0 20.0)", proc.process(new GeoPoint(20, 10))); - proc = new GeoProcessor(GeoOperation.ASWKT_SHAPE); assertNull(proc.process(null)); assertEquals("point (10.0 20.0)", proc.process(new GeoShape("POINT (10 20)"))); } - public void testTypeCheck() { - GeoProcessor procPoint = new GeoProcessor(GeoOperation.ASWKT_POINT); - SqlIllegalArgumentException siae = expectThrows(SqlIllegalArgumentException.class, () -> procPoint.process("string")); - assertEquals("A geo_point is required; received [string]", siae.getMessage()); - - GeoProcessor procShape = new GeoProcessor(GeoOperation.ASWKT_SHAPE); - siae = expectThrows(SqlIllegalArgumentException.class, () -> procShape.process("string")); + public void testAsWKTTypeCheck() { + GeoProcessor proc = new GeoProcessor(GeoOperation.ASWKT); + SqlIllegalArgumentException siae = expectThrows(SqlIllegalArgumentException.class, () -> proc.process("string")); assertEquals("A geo_shape is required; received [string]", siae.getMessage()); + + siae = expectThrows(SqlIllegalArgumentException.class, () -> proc.process(42)); + assertEquals("A geo_shape is required; received [42]", siae.getMessage()); + } + + public void testAllTypesAreProcessed() { + for (GeoShapeType type : GeoShapeType.values()) { + String geometryType = GeoShape.geometryType(type); + assertEquals(geometryType.toUpperCase(Locale.ROOT), type.shapeName().toUpperCase(Locale.ROOT)); + } } } diff --git a/x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/planner/QueryTranslatorTests.java b/x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/planner/QueryTranslatorTests.java index b6167ea81e5b6..242c9150386f1 100644 --- a/x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/planner/QueryTranslatorTests.java +++ b/x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/planner/QueryTranslatorTests.java @@ -378,7 +378,7 @@ public void testTranslateStAsWktForPoints() { assertNull(translation.query); AggFilter aggFilter = translation.aggFilter; assertEquals("InternalSqlScriptUtils.nullSafeFilter(InternalSqlScriptUtils.eq(" + - "InternalSqlScriptUtils.aswktPoint(InternalSqlScriptUtils.docValue(doc,params.v0))," + + "InternalSqlScriptUtils.aswkt(InternalSqlScriptUtils.docValue(doc,params.v0))," + "params.v1)" + ")", aggFilter.scriptTemplate().toString()); @@ -396,7 +396,7 @@ public void testTranslateStAsWktForShapes() { assertNull(translation.query); AggFilter aggFilter = translation.aggFilter; assertEquals("InternalSqlScriptUtils.nullSafeFilter(InternalSqlScriptUtils.eq(" + - "InternalSqlScriptUtils.aswktShape(InternalSqlScriptUtils.docValue(doc,params.v0))," + + "InternalSqlScriptUtils.aswkt(InternalSqlScriptUtils.docValue(doc,params.v0))," + "params.v1)" + ")", aggFilter.scriptTemplate().toString()); From 67b8f53559a873d0bddaa5e7c6669453320def2e Mon Sep 17 00:00:00 2001 From: Igor Motov Date: Wed, 21 Nov 2018 15:56:50 -0500 Subject: [PATCH 2/2] Update GeoShape after merge --- .../sql/expression/function/scalar/geo/GeoShape.java | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/geo/GeoShape.java b/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/geo/GeoShape.java index 316966ecac4eb..220e1e25746f7 100644 --- a/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/geo/GeoShape.java +++ b/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/geo/GeoShape.java @@ -73,24 +73,23 @@ public double x() { return shapeBuilder.firstX(); } - public double y() { return shapeBuilder.firstY(); } public double minX() { - return shapeBuilder.build().getBoundingBox().getMinX(); + return shapeBuilder.buildS4J().getBoundingBox().getMinX(); } public double minY() { - return shapeBuilder.build().getBoundingBox().getMinY(); + return shapeBuilder.buildS4J().getBoundingBox().getMinY(); } public double maxX() { - return shapeBuilder.build().getBoundingBox().getMaxX(); + return shapeBuilder.buildS4J().getBoundingBox().getMaxX(); } public double maxY() { - return shapeBuilder.build().getBoundingBox().getMaxY(); + return shapeBuilder.buildS4J().getBoundingBox().getMaxY(); } }