From 5dd2dc98b22563b3d7ba6d9ad552cffef43a7d3e Mon Sep 17 00:00:00 2001 From: ncordon Date: Mon, 6 Oct 2025 13:55:13 +0200 Subject: [PATCH 01/45] Adds a first version of st_simplify which is the identity --- x-pack/plugin/esql/build.gradle | 1 + .../function/EsqlFunctionRegistry.java | 2 + .../function/scalar/spatial/StSimplify.java | 109 ++++++++++++++++++ 3 files changed, 112 insertions(+) create mode 100644 x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplify.java diff --git a/x-pack/plugin/esql/build.gradle b/x-pack/plugin/esql/build.gradle index 734c0b62eb729..ab19434a19bc7 100644 --- a/x-pack/plugin/esql/build.gradle +++ b/x-pack/plugin/esql/build.gradle @@ -37,6 +37,7 @@ dependencies { compileOnly project(xpackModule('ml')) compileOnly project(path: xpackModule('mapper-aggregate-metric')) compileOnly project(path: xpackModule('downsample')) + compileOnly "org.locationtech.jts:jts-core:${versions.jts}" implementation project(xpackModule('kql')) implementation project('compute') implementation project('compute:ann') diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/EsqlFunctionRegistry.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/EsqlFunctionRegistry.java index 2d2c644bbcace..163b10ba1b997 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/EsqlFunctionRegistry.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/EsqlFunctionRegistry.java @@ -166,6 +166,7 @@ import org.elasticsearch.xpack.esql.expression.function.scalar.spatial.StGeohash; import org.elasticsearch.xpack.esql.expression.function.scalar.spatial.StGeohex; import org.elasticsearch.xpack.esql.expression.function.scalar.spatial.StGeotile; +import org.elasticsearch.xpack.esql.expression.function.scalar.spatial.StSimplify; import org.elasticsearch.xpack.esql.expression.function.scalar.spatial.StX; import org.elasticsearch.xpack.esql.expression.function.scalar.spatial.StXMax; import org.elasticsearch.xpack.esql.expression.function.scalar.spatial.StXMin; @@ -438,6 +439,7 @@ private static FunctionDefinition[][] functions() { def(SpatialWithin.class, SpatialWithin::new, "st_within"), def(StDistance.class, StDistance::new, "st_distance"), def(StEnvelope.class, StEnvelope::new, "st_envelope"), + def(StSimplify.class, StSimplify::new, "st_simplify"), def(StGeohash.class, StGeohash::new, "st_geohash"), def(StGeotile.class, StGeotile::new, "st_geotile"), def(StGeohex.class, StGeohex::new, "st_geohex"), diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplify.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplify.java new file mode 100644 index 0000000000000..6f6df806d694f --- /dev/null +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplify.java @@ -0,0 +1,109 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +package org.elasticsearch.xpack.esql.expression.function.scalar.spatial; + +import org.elasticsearch.common.io.stream.StreamOutput; +import org.elasticsearch.compute.data.Block; +import org.elasticsearch.compute.data.Page; +import org.elasticsearch.compute.operator.EvalOperator; +import org.elasticsearch.xpack.esql.core.expression.Expression; +import org.elasticsearch.xpack.esql.core.expression.function.scalar.ScalarFunction; +import org.elasticsearch.xpack.esql.core.tree.NodeInfo; +import org.elasticsearch.xpack.esql.core.tree.Source; +import org.elasticsearch.xpack.esql.core.type.DataType; +import org.elasticsearch.xpack.esql.evaluator.mapper.EvaluatorMapper; +import org.elasticsearch.xpack.esql.expression.function.Example; +import org.elasticsearch.xpack.esql.expression.function.FunctionInfo; +import org.elasticsearch.xpack.esql.expression.function.Param; + +import java.io.IOException; +import java.util.List; + +import static org.elasticsearch.xpack.esql.core.type.DataType.GEO_SHAPE; + +public class StSimplify extends ScalarFunction implements EvaluatorMapper { + Expression geometry; + Expression tolerance; + + @FunctionInfo( + returnType = "geo_shape", + description = "Simplifies the input geometry with a given tolerance", + examples = @Example(file = "spatial", tag = "st_simplify") + ) + public StSimplify(Source source, + @Param( + name = "geometry", + type = { "geo_point", "geo_shape", "cartesian_point", "cartesian_shape" }, + description = "Expression of type `geo_point`, `geo_shape`, `cartesian_point` or `cartesian_shape`. " + + "If `null`, the function returns `null`." + ) Expression geometry, + @Param( + name = "tolerance", + type = { "double" }, + description = "Tolerance for the geometry simplification" + ) + Expression tolerance) { + super(source, List.of(geometry, tolerance)); + this.geometry = geometry; + this.tolerance = tolerance; + } + + @Override + public DataType dataType() { + return GEO_SHAPE; + } + + @Override + public Expression replaceChildren(List newChildren) { + return new StSimplify(source(), newChildren.get(0), newChildren.get(1)); + } + + @Override + protected NodeInfo info() { + return NodeInfo.create(this, StSimplify::new, geometry, tolerance); + } + + @Override + public String getWriteableName() { + return "StSimplify"; + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + + } + + @Override + public EvalOperator.ExpressionEvaluator.Factory toEvaluator(ToEvaluator toEvaluator) { + // Get evaluators for the child expressions + EvalOperator.ExpressionEvaluator.Factory geometryEval = toEvaluator.apply(geometry); + EvalOperator.ExpressionEvaluator.Factory toleranceEval = toEvaluator.apply(tolerance); + + return dvrCtx -> { + EvalOperator.ExpressionEvaluator geometryEvaluator = geometryEval.get(dvrCtx); + // toleranceEvaluator is not used since we just return the geometry + + return new EvalOperator.ExpressionEvaluator() { + @Override + public void close() { + + } + + @Override + public Block eval(Page page) { + return geometryEvaluator.eval(page); + } + + @Override + public long baseRamBytesUsed() { + return 0; + } + }; + }; + } +} From b787a477cb68414b571e030e5b2508982e644e00 Mon Sep 17 00:00:00 2001 From: ncordon Date: Tue, 7 Oct 2025 11:39:45 +0200 Subject: [PATCH 02/45] This does not work :____ --- x-pack/plugin/esql/build.gradle | 2 +- .../function/scalar/spatial/StSimplify.java | 35 ++++++++++++++++++- 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/x-pack/plugin/esql/build.gradle b/x-pack/plugin/esql/build.gradle index ab19434a19bc7..9aeaa789bf9e0 100644 --- a/x-pack/plugin/esql/build.gradle +++ b/x-pack/plugin/esql/build.gradle @@ -37,7 +37,7 @@ dependencies { compileOnly project(xpackModule('ml')) compileOnly project(path: xpackModule('mapper-aggregate-metric')) compileOnly project(path: xpackModule('downsample')) - compileOnly "org.locationtech.jts:jts-core:${versions.jts}" + implementation "org.locationtech.jts:jts-core:${versions.jts}" implementation project(xpackModule('kql')) implementation project('compute') implementation project('compute:ann') diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplify.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplify.java index 6f6df806d694f..7d729d18ef23b 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplify.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplify.java @@ -7,10 +7,13 @@ package org.elasticsearch.xpack.esql.expression.function.scalar.spatial; +import org.apache.lucene.util.BytesRef; import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.compute.data.Block; +import org.elasticsearch.compute.data.DoubleBlock; import org.elasticsearch.compute.data.Page; import org.elasticsearch.compute.operator.EvalOperator; +import org.elasticsearch.geometry.utils.WellKnownText; import org.elasticsearch.xpack.esql.core.expression.Expression; import org.elasticsearch.xpack.esql.core.expression.function.scalar.ScalarFunction; import org.elasticsearch.xpack.esql.core.tree.NodeInfo; @@ -20,11 +23,17 @@ import org.elasticsearch.xpack.esql.expression.function.Example; import org.elasticsearch.xpack.esql.expression.function.FunctionInfo; import org.elasticsearch.xpack.esql.expression.function.Param; +import org.elasticsearch.xpack.esql.expression.function.scalar.math.AbsDoubleEvaluator; +import org.locationtech.jts.io.ParseException; +import org.locationtech.jts.io.WKTReader; +import org.locationtech.jts.io.WKTWriter; +import org.locationtech.jts.simplify.DouglasPeuckerSimplifier; import java.io.IOException; import java.util.List; import static org.elasticsearch.xpack.esql.core.type.DataType.GEO_SHAPE; +import static org.elasticsearch.xpack.esql.expression.function.scalar.spatial.SpatialRelatesUtils.makeGeometryFromLiteral; public class StSimplify extends ScalarFunction implements EvaluatorMapper { Expression geometry; @@ -86,7 +95,7 @@ public EvalOperator.ExpressionEvaluator.Factory toEvaluator(ToEvaluator toEvalua return dvrCtx -> { EvalOperator.ExpressionEvaluator geometryEvaluator = geometryEval.get(dvrCtx); - // toleranceEvaluator is not used since we just return the geometry + EvalOperator.ExpressionEvaluator toleranceEvaluator = toleranceEval.get(dvrCtx); return new EvalOperator.ExpressionEvaluator() { @Override @@ -96,6 +105,30 @@ public void close() { @Override public Block eval(Page page) { + var isGeometryFoldable = geometry.foldable(); + var isToleranceFoldable = tolerance.foldable(); + + if (isGeometryFoldable) { + var esGeometry = makeGeometryFromLiteral(toEvaluator.foldCtx(), geometry); + DoubleBlock fieldValBlock = (DoubleBlock) toleranceEvaluator.eval(page); + String wkt = WellKnownText.toWKT(esGeometry); + WKTReader reader = new WKTReader(); + try { + org.locationtech.jts.geom.Geometry jtsGeometry = reader.read(wkt); + + org.locationtech.jts.geom.Geometry simplifiedGeometry = DouglasPeuckerSimplifier.simplify(jtsGeometry, 0); + WKTWriter writer = new WKTWriter(); + String simplifiedWkt = writer.write(simplifiedGeometry); + var numBytes = simplifiedWkt.getBytes().length; + var builder = dvrCtx.blockFactory().newBytesRefBlockBuilder(numBytes); + builder.appendBytesRef(new BytesRef(simplifiedWkt)); + return builder.build(); + } catch (Exception e) { + throw new RuntimeException(e); + } + + + } return geometryEvaluator.eval(page); } From 4413503bb66548ac15fb8fae68afae1e737b7b6c Mon Sep 17 00:00:00 2001 From: ncordon Date: Tue, 7 Oct 2025 14:59:03 +0200 Subject: [PATCH 03/45] Adds first working version, but only for foldable geometries --- .../function/scalar/spatial/StSimplify.java | 57 +++++++++++-------- 1 file changed, 32 insertions(+), 25 deletions(-) diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplify.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplify.java index 7d729d18ef23b..3245f0a9e6c32 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplify.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplify.java @@ -10,9 +10,11 @@ import org.apache.lucene.util.BytesRef; import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.compute.data.Block; -import org.elasticsearch.compute.data.DoubleBlock; import org.elasticsearch.compute.data.Page; import org.elasticsearch.compute.operator.EvalOperator; +import org.elasticsearch.geometry.Geometry; +import org.elasticsearch.geometry.utils.StandardValidator; +import org.elasticsearch.geometry.utils.WellKnownBinary; import org.elasticsearch.geometry.utils.WellKnownText; import org.elasticsearch.xpack.esql.core.expression.Expression; import org.elasticsearch.xpack.esql.core.expression.function.scalar.ScalarFunction; @@ -23,13 +25,12 @@ import org.elasticsearch.xpack.esql.expression.function.Example; import org.elasticsearch.xpack.esql.expression.function.FunctionInfo; import org.elasticsearch.xpack.esql.expression.function.Param; -import org.elasticsearch.xpack.esql.expression.function.scalar.math.AbsDoubleEvaluator; -import org.locationtech.jts.io.ParseException; import org.locationtech.jts.io.WKTReader; import org.locationtech.jts.io.WKTWriter; import org.locationtech.jts.simplify.DouglasPeuckerSimplifier; import java.io.IOException; +import java.nio.ByteOrder; import java.util.List; import static org.elasticsearch.xpack.esql.core.type.DataType.GEO_SHAPE; @@ -44,19 +45,16 @@ public class StSimplify extends ScalarFunction implements EvaluatorMapper { description = "Simplifies the input geometry with a given tolerance", examples = @Example(file = "spatial", tag = "st_simplify") ) - public StSimplify(Source source, + public StSimplify( + Source source, @Param( name = "geometry", type = { "geo_point", "geo_shape", "cartesian_point", "cartesian_shape" }, description = "Expression of type `geo_point`, `geo_shape`, `cartesian_point` or `cartesian_shape`. " + "If `null`, the function returns `null`." ) Expression geometry, - @Param( - name = "tolerance", - type = { "double" }, - description = "Tolerance for the geometry simplification" - ) - Expression tolerance) { + @Param(name = "tolerance", type = { "double" }, description = "Tolerance for the geometry simplification") Expression tolerance + ) { super(source, List.of(geometry, tolerance)); this.geometry = geometry; this.tolerance = tolerance; @@ -107,28 +105,37 @@ public void close() { public Block eval(Page page) { var isGeometryFoldable = geometry.foldable(); var isToleranceFoldable = tolerance.foldable(); + var positionCount = page.getPositionCount(); if (isGeometryFoldable) { var esGeometry = makeGeometryFromLiteral(toEvaluator.foldCtx(), geometry); - DoubleBlock fieldValBlock = (DoubleBlock) toleranceEvaluator.eval(page); String wkt = WellKnownText.toWKT(esGeometry); WKTReader reader = new WKTReader(); - try { - org.locationtech.jts.geom.Geometry jtsGeometry = reader.read(wkt); - - org.locationtech.jts.geom.Geometry simplifiedGeometry = DouglasPeuckerSimplifier.simplify(jtsGeometry, 0); - WKTWriter writer = new WKTWriter(); - String simplifiedWkt = writer.write(simplifiedGeometry); - var numBytes = simplifiedWkt.getBytes().length; - var builder = dvrCtx.blockFactory().newBytesRefBlockBuilder(numBytes); - builder.appendBytesRef(new BytesRef(simplifiedWkt)); - return builder.build(); - } catch (Exception e) { - throw new RuntimeException(e); - } - + try (var result = dvrCtx.blockFactory().newBytesRefVectorBuilder(positionCount)) { + for (int p = 0; p < positionCount; p++) { + try { + org.locationtech.jts.geom.Geometry jtsGeometry = reader.read(wkt); + org.locationtech.jts.geom.Geometry simplifiedGeometry = DouglasPeuckerSimplifier.simplify( + jtsGeometry, + 0 + ); + WKTWriter writer = new WKTWriter(); + String simplifiedWkt = writer.write(simplifiedGeometry); + Geometry esGeometryResult = WellKnownText.fromWKT( + StandardValidator.instance(true), + false, + simplifiedWkt + ); + result.appendBytesRef(new BytesRef(WellKnownBinary.toWKB(esGeometryResult, ByteOrder.LITTLE_ENDIAN))); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + return result.build().asBlock(); + } } + // identity return geometryEvaluator.eval(page); } From 805ab8939454d28de7d3d01e7d6ff57e3bb7f09a Mon Sep 17 00:00:00 2001 From: ncordon Date: Tue, 7 Oct 2025 15:59:32 +0200 Subject: [PATCH 04/45] Adds implementation for non foldable geometry --- .../function/scalar/spatial/StSimplify.java | 72 +++++++++++-------- 1 file changed, 42 insertions(+), 30 deletions(-) diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplify.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplify.java index 3245f0a9e6c32..5be397845b620 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplify.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplify.java @@ -10,6 +10,7 @@ import org.apache.lucene.util.BytesRef; import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.compute.data.Block; +import org.elasticsearch.compute.data.BytesRefVectorBlock; import org.elasticsearch.compute.data.Page; import org.elasticsearch.compute.operator.EvalOperator; import org.elasticsearch.geometry.Geometry; @@ -87,13 +88,9 @@ public void writeTo(StreamOutput out) throws IOException { @Override public EvalOperator.ExpressionEvaluator.Factory toEvaluator(ToEvaluator toEvaluator) { - // Get evaluators for the child expressions - EvalOperator.ExpressionEvaluator.Factory geometryEval = toEvaluator.apply(geometry); - EvalOperator.ExpressionEvaluator.Factory toleranceEval = toEvaluator.apply(tolerance); - return dvrCtx -> { - EvalOperator.ExpressionEvaluator geometryEvaluator = geometryEval.get(dvrCtx); - EvalOperator.ExpressionEvaluator toleranceEvaluator = toleranceEval.get(dvrCtx); + EvalOperator.ExpressionEvaluator geometryEvaluator = toEvaluator.apply(geometry).get(dvrCtx); + EvalOperator.ExpressionEvaluator toleranceEvaluator = toEvaluator.apply(tolerance).get(dvrCtx); return new EvalOperator.ExpressionEvaluator() { @Override @@ -106,37 +103,52 @@ public Block eval(Page page) { var isGeometryFoldable = geometry.foldable(); var isToleranceFoldable = tolerance.foldable(); var positionCount = page.getPositionCount(); - - if (isGeometryFoldable) { - var esGeometry = makeGeometryFromLiteral(toEvaluator.foldCtx(), geometry); - String wkt = WellKnownText.toWKT(esGeometry); - WKTReader reader = new WKTReader(); - - try (var result = dvrCtx.blockFactory().newBytesRefVectorBuilder(positionCount)) { - for (int p = 0; p < positionCount; p++) { - try { + var validator = StandardValidator.instance(true); + WKTReader reader = new WKTReader(); + WKTWriter writer = new WKTWriter(); + + // TODO We are not extracting non foldable geometries + // TODO We are not using the tolerance + try (var result = dvrCtx.blockFactory().newBytesRefVectorBuilder(positionCount)) { + if (isGeometryFoldable) { + var esGeometry = makeGeometryFromLiteral(toEvaluator.foldCtx(), geometry); + String wkt = WellKnownText.toWKT(esGeometry); + + try { + org.locationtech.jts.geom.Geometry jtsGeometry = reader.read(wkt); + org.locationtech.jts.geom.Geometry simplifiedGeometry = DouglasPeuckerSimplifier.simplify(jtsGeometry, 0); + String simplifiedWkt = writer.write(simplifiedGeometry); + Geometry esGeometryResult = WellKnownText.fromWKT(validator, false, simplifiedWkt); + + for (int p = 0; p < positionCount; p++) { + result.appendBytesRef(new BytesRef(WellKnownBinary.toWKB(esGeometryResult, ByteOrder.LITTLE_ENDIAN))); + } + } catch (Exception e) { + throw new RuntimeException(e); + } + } else { + // Geometry is non foldable + BytesRefVectorBlock block = (BytesRefVectorBlock) geometryEvaluator.eval(page); + var bytesRefVector = block.asVector(); + + try { + for (int p = 0; p < positionCount; p++) { + var destRef = bytesRefVector.getBytesRef(p, new BytesRef()); + var wkt = WellKnownText.fromWKB(destRef.bytes, destRef.offset, destRef.length); org.locationtech.jts.geom.Geometry jtsGeometry = reader.read(wkt); - org.locationtech.jts.geom.Geometry simplifiedGeometry = DouglasPeuckerSimplifier.simplify( - jtsGeometry, - 0 - ); - WKTWriter writer = new WKTWriter(); + org.locationtech.jts.geom.Geometry simplifiedGeometry = + DouglasPeuckerSimplifier.simplify(jtsGeometry, 0); String simplifiedWkt = writer.write(simplifiedGeometry); - Geometry esGeometryResult = WellKnownText.fromWKT( - StandardValidator.instance(true), - false, - simplifiedWkt - ); + Geometry esGeometryResult = WellKnownText.fromWKT(validator, false, simplifiedWkt); result.appendBytesRef(new BytesRef(WellKnownBinary.toWKB(esGeometryResult, ByteOrder.LITTLE_ENDIAN))); - } catch (Exception e) { - throw new RuntimeException(e); } + } catch (Exception e) { + throw new RuntimeException(e); } - return result.build().asBlock(); } + + return result.build().asBlock(); } - // identity - return geometryEvaluator.eval(page); } @Override From ed96b60f0273f8ad830f184e9077b8cd8252fa14 Mon Sep 17 00:00:00 2001 From: ncordon Date: Wed, 8 Oct 2025 18:20:10 +0200 Subject: [PATCH 05/45] Adds tests --- .../src/main/resources/spatial.csv-spec | 72 +++++++++++++++++++ .../xpack/esql/action/EsqlCapabilities.java | 5 ++ .../esql/expression/ExpressionWritables.java | 4 +- .../function/scalar/spatial/StSimplify.java | 47 +++++++++--- 4 files changed, 116 insertions(+), 12 deletions(-) diff --git a/x-pack/plugin/esql/qa/testFixtures/src/main/resources/spatial.csv-spec b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/spatial.csv-spec index 34e5c2394ff54..e5c7ecd8320a6 100644 --- a/x-pack/plugin/esql/qa/testFixtures/src/main/resources/spatial.csv-spec +++ b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/spatial.csv-spec @@ -3024,3 +3024,75 @@ wkt:keyword |pt:cartesian_point "POINT(111)" |null // end::to_cartesianpoint-str-parse-error-result[] ; + +stSimplifyMultiRow +required_capability: st_simplify + +FROM airports +| SORT name +| LIMIT 5 +| EVAL result = st_simplify(TO_GEOSHAPE("POLYGON((-10 -60, 120 -60, 120 60, -10 60, -10 -60))"), 1.0) +| KEEP name, result +; + +name:text | result:geo_shape +Aba Tenna D. Yilma Int'l | POLYGON ((-10.0 -60.0, -10.0 60.0, 120.0 60.0, 120.0 -60.0, -10.0 -60.0)) +Abdul Rachman Saleh | POLYGON ((-10.0 -60.0, -10.0 60.0, 120.0 60.0, 120.0 -60.0, -10.0 -60.0)) +Abidjan Port Bouet | POLYGON ((-10.0 -60.0, -10.0 60.0, 120.0 60.0, 120.0 -60.0, -10.0 -60.0)) +Abu Dhabi Int'l | POLYGON ((-10.0 -60.0, -10.0 60.0, 120.0 60.0, 120.0 -60.0, -10.0 -60.0)) +Abuja Int'l | POLYGON ((-10.0 -60.0, -10.0 60.0, 120.0 60.0, 120.0 -60.0, -10.0 -60.0)) +; + +stSimplifyMultiRowWithPoints +required_capability: st_simplify + +FROM airports +| SORT name +| LIMIT 5 +| EVAL result = st_simplify(location, 0.0) +| KEEP location, result +; + +location:geo_point | result:geo_shape +POINT (41.857756722253 9.61267784753569) | POINT (41.857756722253 9.61267784753569) +POINT (112.711418617258 -7.92998002840567) | POINT (112.711418617258 -7.92998002840567) +POINT (-3.93221929167636 5.2543984451492) | POINT (-3.93221929167636 5.2543984451492) +POINT (54.6463293225558 24.4272271529764) | POINT (54.6463293225558 24.4272271529764) +POINT (7.27025993974356 9.00437659781094) | POINT (7.27025993974356 9.00437659781094) +; + +stSimplifyNoSimplification +required_capability: st_simplify + +ROW geo_shape = TO_GEOSHAPE("POLYGON((0 0, 1 0.1, 2 0, 2 2, 1 1.9, 0 2, 0 0))") +| EVAL result = st_simplify(geo_shape, 0.01) +| KEEP result +; + +result:geo_shape +POLYGON ((0.0 0.0, 0.0 2.0, 1.0 1.9, 2.0 2.0, 2.0 0.0, 1.0 0.1, 0.0 0.0)) +; + +stSimplifyWithSimplification +required_capability: st_simplify + +ROW geo_shape = TO_GEOSHAPE("POLYGON((0 0, 1 0.1, 2 0, 2 2, 1 1.9, 0 2, 0 0))") +| EVAL result = st_simplify(geo_shape, 0.2) +| KEEP result +; + +result:geo_shape +POLYGON ((0.0 0.0, 0.0 2.0, 2.0 2.0, 2.0 0.0, 0.0 0.0)) +; + +stSimplifyEmptySimplification +required_capability: st_simplify + +ROW geo_shape = TO_GEOSHAPE("POLYGON((0 0, 1 0.1, 2 0, 2 2, 1 1.9, 0 2, 0 0))") +| EVAL result = st_simplify(geo_shape, 2.0) +| KEEP result +; + +result:geo_shape +POLYGON EMPTY +; diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/action/EsqlCapabilities.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/action/EsqlCapabilities.java index 81bc6a5d89a0f..c568d71c20312 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/action/EsqlCapabilities.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/action/EsqlCapabilities.java @@ -78,6 +78,11 @@ public enum Cap { */ ST_DISJOINT, + /** + * Support for spatial simplification {@code ST_SIMPLIFY} + */ + ST_SIMPLIFY, + /** * The introduction of the {@code VALUES} agg. */ diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/ExpressionWritables.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/ExpressionWritables.java index ed7e8ebb57003..38d538af17c3e 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/ExpressionWritables.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/ExpressionWritables.java @@ -71,6 +71,7 @@ import org.elasticsearch.xpack.esql.expression.function.scalar.spatial.StGeohash; import org.elasticsearch.xpack.esql.expression.function.scalar.spatial.StGeohex; import org.elasticsearch.xpack.esql.expression.function.scalar.spatial.StGeotile; +import org.elasticsearch.xpack.esql.expression.function.scalar.spatial.StSimplify; import org.elasticsearch.xpack.esql.expression.function.scalar.spatial.StX; import org.elasticsearch.xpack.esql.expression.function.scalar.spatial.StXMax; import org.elasticsearch.xpack.esql.expression.function.scalar.spatial.StXMin; @@ -253,7 +254,8 @@ private static List spatials() { StDistance.ENTRY, StGeohash.ENTRY, StGeotile.ENTRY, - StGeohex.ENTRY + StGeohex.ENTRY, + StSimplify.ENTRY ); } diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplify.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplify.java index 5be397845b620..21ec65e897c51 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplify.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplify.java @@ -8,9 +8,12 @@ package org.elasticsearch.xpack.esql.expression.function.scalar.spatial; import org.apache.lucene.util.BytesRef; +import org.elasticsearch.common.io.stream.NamedWriteableRegistry; +import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.compute.data.Block; import org.elasticsearch.compute.data.BytesRefVectorBlock; +import org.elasticsearch.compute.data.DoubleVector; import org.elasticsearch.compute.data.Page; import org.elasticsearch.compute.operator.EvalOperator; import org.elasticsearch.geometry.Geometry; @@ -18,14 +21,14 @@ import org.elasticsearch.geometry.utils.WellKnownBinary; import org.elasticsearch.geometry.utils.WellKnownText; import org.elasticsearch.xpack.esql.core.expression.Expression; -import org.elasticsearch.xpack.esql.core.expression.function.scalar.ScalarFunction; import org.elasticsearch.xpack.esql.core.tree.NodeInfo; import org.elasticsearch.xpack.esql.core.tree.Source; import org.elasticsearch.xpack.esql.core.type.DataType; -import org.elasticsearch.xpack.esql.evaluator.mapper.EvaluatorMapper; import org.elasticsearch.xpack.esql.expression.function.Example; import org.elasticsearch.xpack.esql.expression.function.FunctionInfo; import org.elasticsearch.xpack.esql.expression.function.Param; +import org.elasticsearch.xpack.esql.expression.function.scalar.EsqlScalarFunction; +import org.elasticsearch.xpack.esql.io.stream.PlanStreamInput; import org.locationtech.jts.io.WKTReader; import org.locationtech.jts.io.WKTWriter; import org.locationtech.jts.simplify.DouglasPeuckerSimplifier; @@ -37,7 +40,12 @@ import static org.elasticsearch.xpack.esql.core.type.DataType.GEO_SHAPE; import static org.elasticsearch.xpack.esql.expression.function.scalar.spatial.SpatialRelatesUtils.makeGeometryFromLiteral; -public class StSimplify extends ScalarFunction implements EvaluatorMapper { +public class StSimplify extends EsqlScalarFunction { + public static final NamedWriteableRegistry.Entry ENTRY = new NamedWriteableRegistry.Entry( + Expression.class, + "StSimplify", + StSimplify::new + ); Expression geometry; Expression tolerance; @@ -61,6 +69,14 @@ public StSimplify( this.tolerance = tolerance; } + private StSimplify(StreamInput in) throws IOException { + this( + Source.readFrom((PlanStreamInput) in), + in.readNamedWriteable(Expression.class), + in.readNamedWriteable(Expression.class) + ); + } + @Override public DataType dataType() { return GEO_SHAPE; @@ -78,12 +94,14 @@ protected NodeInfo info() { @Override public String getWriteableName() { - return "StSimplify"; + return ENTRY.name; } @Override public void writeTo(StreamOutput out) throws IOException { - + source().writeTo(out); + out.writeNamedWriteable(geometry); + out.writeNamedWriteable(tolerance); } @Override @@ -101,11 +119,11 @@ public void close() { @Override public Block eval(Page page) { var isGeometryFoldable = geometry.foldable(); - var isToleranceFoldable = tolerance.foldable(); var positionCount = page.getPositionCount(); var validator = StandardValidator.instance(true); WKTReader reader = new WKTReader(); WKTWriter writer = new WKTWriter(); + DoubleVector tolerances = (DoubleVector) toleranceEvaluator.eval(page).asVector(); // TODO We are not extracting non foldable geometries // TODO We are not using the tolerance @@ -116,11 +134,13 @@ public Block eval(Page page) { try { org.locationtech.jts.geom.Geometry jtsGeometry = reader.read(wkt); - org.locationtech.jts.geom.Geometry simplifiedGeometry = DouglasPeuckerSimplifier.simplify(jtsGeometry, 0); - String simplifiedWkt = writer.write(simplifiedGeometry); - Geometry esGeometryResult = WellKnownText.fromWKT(validator, false, simplifiedWkt); for (int p = 0; p < positionCount; p++) { + double distanceTolerance = tolerances.getDouble(p); + org.locationtech.jts.geom.Geometry simplifiedGeometry = DouglasPeuckerSimplifier.simplify(jtsGeometry, distanceTolerance); + String simplifiedWkt = writer.write(simplifiedGeometry); + Geometry esGeometryResult = WellKnownText.fromWKT(validator, false, simplifiedWkt); + result.appendBytesRef(new BytesRef(WellKnownBinary.toWKB(esGeometryResult, ByteOrder.LITTLE_ENDIAN))); } } catch (Exception e) { @@ -133,11 +153,14 @@ public Block eval(Page page) { try { for (int p = 0; p < positionCount; p++) { + double distanceTolerance = tolerances.getDouble(p); var destRef = bytesRefVector.getBytesRef(p, new BytesRef()); var wkt = WellKnownText.fromWKB(destRef.bytes, destRef.offset, destRef.length); org.locationtech.jts.geom.Geometry jtsGeometry = reader.read(wkt); - org.locationtech.jts.geom.Geometry simplifiedGeometry = - DouglasPeuckerSimplifier.simplify(jtsGeometry, 0); + org.locationtech.jts.geom.Geometry simplifiedGeometry = DouglasPeuckerSimplifier.simplify( + jtsGeometry, + distanceTolerance + ); String simplifiedWkt = writer.write(simplifiedGeometry); Geometry esGeometryResult = WellKnownText.fromWKT(validator, false, simplifiedWkt); result.appendBytesRef(new BytesRef(WellKnownBinary.toWKB(esGeometryResult, ByteOrder.LITTLE_ENDIAN))); @@ -145,7 +168,9 @@ public Block eval(Page page) { } catch (Exception e) { throw new RuntimeException(e); } + block.close(); } + tolerances.close(); return result.build().asBlock(); } From e5e3d445557f416846e9790c2d17358a703b9e67 Mon Sep 17 00:00:00 2001 From: ncordon Date: Thu, 9 Oct 2025 15:12:09 +0200 Subject: [PATCH 06/45] Checkpoint --- .../function/scalar/spatial/StSimplify.java | 139 +++++++----------- 1 file changed, 53 insertions(+), 86 deletions(-) diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplify.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplify.java index 21ec65e897c51..8cd1d4e60ea46 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplify.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplify.java @@ -11,12 +11,13 @@ import org.elasticsearch.common.io.stream.NamedWriteableRegistry; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; -import org.elasticsearch.compute.data.Block; -import org.elasticsearch.compute.data.BytesRefVectorBlock; -import org.elasticsearch.compute.data.DoubleVector; -import org.elasticsearch.compute.data.Page; +import org.elasticsearch.compute.ann.ConvertEvaluator; +import org.elasticsearch.compute.ann.Evaluator; +import org.elasticsearch.compute.ann.Fixed; +import org.elasticsearch.compute.data.BytesRefBlock; import org.elasticsearch.compute.operator.EvalOperator; import org.elasticsearch.geometry.Geometry; +import org.elasticsearch.geometry.utils.GeometryValidator; import org.elasticsearch.geometry.utils.StandardValidator; import org.elasticsearch.geometry.utils.WellKnownBinary; import org.elasticsearch.geometry.utils.WellKnownText; @@ -38,7 +39,6 @@ import java.util.List; import static org.elasticsearch.xpack.esql.core.type.DataType.GEO_SHAPE; -import static org.elasticsearch.xpack.esql.expression.function.scalar.spatial.SpatialRelatesUtils.makeGeometryFromLiteral; public class StSimplify extends EsqlScalarFunction { public static final NamedWriteableRegistry.Entry ENTRY = new NamedWriteableRegistry.Entry( @@ -70,11 +70,7 @@ public StSimplify( } private StSimplify(StreamInput in) throws IOException { - this( - Source.readFrom((PlanStreamInput) in), - in.readNamedWriteable(Expression.class), - in.readNamedWriteable(Expression.class) - ); + this(Source.readFrom((PlanStreamInput) in), in.readNamedWriteable(Expression.class), in.readNamedWriteable(Expression.class)); } @Override @@ -104,83 +100,54 @@ public void writeTo(StreamOutput out) throws IOException { out.writeNamedWriteable(tolerance); } + private static class GeoSimplifier { + static GeometryValidator validator = StandardValidator.instance(true); + static WKTReader reader = new WKTReader(); + static WKTWriter writer = new WKTWriter(); + + public static BytesRef geoSourceAndConstantTolerance(BytesRef inputGeometry, @Fixed double inputTolerance) { + String wkt = WellKnownText.fromWKB(inputGeometry.bytes, inputGeometry.offset, inputGeometry.length); + try { + org.locationtech.jts.geom.Geometry jtsGeometry = reader.read(wkt); + org.locationtech.jts.geom.Geometry simplifiedGeometry = DouglasPeuckerSimplifier.simplify(jtsGeometry, inputTolerance); + String simplifiedWkt = writer.write(simplifiedGeometry); + Geometry esGeometryResult = WellKnownText.fromWKT(validator, false, simplifiedWkt); + return new BytesRef(WellKnownBinary.toWKB(esGeometryResult, ByteOrder.LITTLE_ENDIAN)); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + } + @Override public EvalOperator.ExpressionEvaluator.Factory toEvaluator(ToEvaluator toEvaluator) { - return dvrCtx -> { - EvalOperator.ExpressionEvaluator geometryEvaluator = toEvaluator.apply(geometry).get(dvrCtx); - EvalOperator.ExpressionEvaluator toleranceEvaluator = toEvaluator.apply(tolerance).get(dvrCtx); - - return new EvalOperator.ExpressionEvaluator() { - @Override - public void close() { - - } - - @Override - public Block eval(Page page) { - var isGeometryFoldable = geometry.foldable(); - var positionCount = page.getPositionCount(); - var validator = StandardValidator.instance(true); - WKTReader reader = new WKTReader(); - WKTWriter writer = new WKTWriter(); - DoubleVector tolerances = (DoubleVector) toleranceEvaluator.eval(page).asVector(); - - // TODO We are not extracting non foldable geometries - // TODO We are not using the tolerance - try (var result = dvrCtx.blockFactory().newBytesRefVectorBuilder(positionCount)) { - if (isGeometryFoldable) { - var esGeometry = makeGeometryFromLiteral(toEvaluator.foldCtx(), geometry); - String wkt = WellKnownText.toWKT(esGeometry); - - try { - org.locationtech.jts.geom.Geometry jtsGeometry = reader.read(wkt); - - for (int p = 0; p < positionCount; p++) { - double distanceTolerance = tolerances.getDouble(p); - org.locationtech.jts.geom.Geometry simplifiedGeometry = DouglasPeuckerSimplifier.simplify(jtsGeometry, distanceTolerance); - String simplifiedWkt = writer.write(simplifiedGeometry); - Geometry esGeometryResult = WellKnownText.fromWKT(validator, false, simplifiedWkt); - - result.appendBytesRef(new BytesRef(WellKnownBinary.toWKB(esGeometryResult, ByteOrder.LITTLE_ENDIAN))); - } - } catch (Exception e) { - throw new RuntimeException(e); - } - } else { - // Geometry is non foldable - BytesRefVectorBlock block = (BytesRefVectorBlock) geometryEvaluator.eval(page); - var bytesRefVector = block.asVector(); - - try { - for (int p = 0; p < positionCount; p++) { - double distanceTolerance = tolerances.getDouble(p); - var destRef = bytesRefVector.getBytesRef(p, new BytesRef()); - var wkt = WellKnownText.fromWKB(destRef.bytes, destRef.offset, destRef.length); - org.locationtech.jts.geom.Geometry jtsGeometry = reader.read(wkt); - org.locationtech.jts.geom.Geometry simplifiedGeometry = DouglasPeuckerSimplifier.simplify( - jtsGeometry, - distanceTolerance - ); - String simplifiedWkt = writer.write(simplifiedGeometry); - Geometry esGeometryResult = WellKnownText.fromWKT(validator, false, simplifiedWkt); - result.appendBytesRef(new BytesRef(WellKnownBinary.toWKB(esGeometryResult, ByteOrder.LITTLE_ENDIAN))); - } - } catch (Exception e) { - throw new RuntimeException(e); - } - block.close(); - } - tolerances.close(); - - return result.build().asBlock(); - } - } - - @Override - public long baseRamBytesUsed() { - return 0; - } - }; - }; + EvalOperator.ExpressionEvaluator.Factory geometryEvaluator = toEvaluator.apply(geometry); + + if (tolerance.foldable() == false) { + throw new IllegalArgumentException("tolerance must be foldable"); + } + double inputTolerance = (double) tolerance.fold(toEvaluator.foldCtx()); + + if (geometry.foldable()) { + BytesRef inputGeometry = (BytesRef) geometry.fold(toEvaluator.foldCtx()); + return new StSimplifyFoldableGeoAndConstantToleranceEvaluator.Factory(source(), inputGeometry, inputTolerance); + } + return new StSimplifyNonFoldableGeoAndConstantToleranceEvaluator.Factory(source(), geometryEvaluator, inputTolerance); + } + + @Evaluator(extraName = "NonFoldableGeoAndConstantTolerance", warnExceptions = { IllegalArgumentException.class }) + static BytesRef processNonFoldableGeoAndConstantTolerance( + BytesRef inputGeometry, + @Fixed double inputTolerance + ) { + return GeoSimplifier.geoSourceAndConstantTolerance(inputGeometry, inputTolerance); + } + + @Evaluator(extraName = "FoldableGeoAndConstantTolerance", warnExceptions = { IllegalArgumentException.class }) + static BytesRef processFoldableGeoAndConstantTolerance( + @Fixed BytesRef inputGeometry, + @Fixed double inputTolerance + ) { + return GeoSimplifier.geoSourceAndConstantTolerance(inputGeometry, inputTolerance); } } From ac2e0be5dcdff7962113a8ea6f9fe7f1ba0f3300 Mon Sep 17 00:00:00 2001 From: ncordon Date: Thu, 9 Oct 2025 18:20:51 +0200 Subject: [PATCH 07/45] Adds more tests --- .../plugin/esql/licenses/jts-core-LICENSE.txt | 31 ++++ .../plugin/esql/licenses/jts-core-NOTICE.txt | 1 + .../src/main/resources/spatial.csv-spec | 48 ++++++ ...dableGeoAndConstantToleranceEvaluator.java | 114 +++++++++++++ ...dableGeoAndConstantToleranceEvaluator.java | 155 ++++++++++++++++++ .../function/scalar/spatial/StSimplify.java | 25 +-- 6 files changed, 354 insertions(+), 20 deletions(-) create mode 100644 x-pack/plugin/esql/licenses/jts-core-LICENSE.txt create mode 100644 x-pack/plugin/esql/licenses/jts-core-NOTICE.txt create mode 100644 x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyFoldableGeoAndConstantToleranceEvaluator.java create mode 100644 x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyNonFoldableGeoAndConstantToleranceEvaluator.java diff --git a/x-pack/plugin/esql/licenses/jts-core-LICENSE.txt b/x-pack/plugin/esql/licenses/jts-core-LICENSE.txt new file mode 100644 index 0000000000000..bc03db03a5926 --- /dev/null +++ b/x-pack/plugin/esql/licenses/jts-core-LICENSE.txt @@ -0,0 +1,31 @@ +Eclipse Distribution License - v 1.0 + +Copyright (c) 2007, Eclipse Foundation, Inc. and its licensors. + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + Neither the name of the Eclipse Foundation, Inc. nor the names of its + contributors may be used to endorse or promote products derived from this + software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + diff --git a/x-pack/plugin/esql/licenses/jts-core-NOTICE.txt b/x-pack/plugin/esql/licenses/jts-core-NOTICE.txt new file mode 100644 index 0000000000000..8d1c8b69c3fce --- /dev/null +++ b/x-pack/plugin/esql/licenses/jts-core-NOTICE.txt @@ -0,0 +1 @@ + diff --git a/x-pack/plugin/esql/qa/testFixtures/src/main/resources/spatial.csv-spec b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/spatial.csv-spec index e5c7ecd8320a6..fd8e19370e957 100644 --- a/x-pack/plugin/esql/qa/testFixtures/src/main/resources/spatial.csv-spec +++ b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/spatial.csv-spec @@ -3025,6 +3025,10 @@ wkt:keyword |pt:cartesian_point // end::to_cartesianpoint-str-parse-error-result[] ; +############################################### +# Tests for ST_SIMPLIFY +############################################### + stSimplifyMultiRow required_capability: st_simplify @@ -3096,3 +3100,47 @@ ROW geo_shape = TO_GEOSHAPE("POLYGON((0 0, 1 0.1, 2 0, 2 2, 1 1.9, 0 2, 0 0))") result:geo_shape POLYGON EMPTY ; + +stSimplifyNull +required_capability: st_simplify + +ROW geo_shape = NULL +| EVAL result = st_simplify(geo_shape, 2.0) +| KEEP result +; + +result:geo_shape +NULL +; + +stSimplifyCartesianPoint +required_capability: st_simplify + +# TODO Why cannot we use a latitud outside of -90, 90 when there are tests +# that use the TO_CARTESIANPOINT that are doing it already? +ROW wkt = ["POINT(97.11 75.53)", "POINT(80.93 72.77)"] +| MV_EXPAND wkt +| EVAL pt = TO_CARTESIANPOINT(wkt) +| EVAL result = st_simplify(pt, 2.0) +| KEEP result +; + +result:geo_shape +POINT (97.11 75.53) +POINT (80.93 72.77) +; + +stSimplifyCartesianShape +required_capability: st_simplify + +ROW wkt = ["POINT(97.11 75.53)", "POLYGON((0 0, 1 0.1, 2 0, 2 2, 1 1.9, 0 2, 0 0))"] +| MV_EXPAND wkt +| EVAL geom = TO_CARTESIANSHAPE(wkt) +| EVAL result = st_simplify(geom, 0.2) +| KEEP result +; + +result:geo_shape +POINT (97.11 75.53) +POLYGON ((0.0 0.0, 0.0 2.0, 2.0 2.0, 2.0 0.0, 0.0 0.0)) +; diff --git a/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyFoldableGeoAndConstantToleranceEvaluator.java b/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyFoldableGeoAndConstantToleranceEvaluator.java new file mode 100644 index 0000000000000..5bcde39b51a91 --- /dev/null +++ b/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyFoldableGeoAndConstantToleranceEvaluator.java @@ -0,0 +1,114 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License +// 2.0; you may not use this file except in compliance with the Elastic License +// 2.0. +package org.elasticsearch.xpack.esql.expression.function.scalar.spatial; + +import java.lang.IllegalArgumentException; +import java.lang.Override; +import java.lang.String; +import org.apache.lucene.util.BytesRef; +import org.apache.lucene.util.RamUsageEstimator; +import org.elasticsearch.compute.data.Block; +import org.elasticsearch.compute.data.BytesRefBlock; +import org.elasticsearch.compute.data.Page; +import org.elasticsearch.compute.operator.DriverContext; +import org.elasticsearch.compute.operator.EvalOperator; +import org.elasticsearch.compute.operator.Warnings; +import org.elasticsearch.xpack.esql.core.tree.Source; + +/** + * {@link EvalOperator.ExpressionEvaluator} implementation for {@link StSimplify}. + * This class is generated. Edit {@code EvaluatorImplementer} instead. + */ +public final class StSimplifyFoldableGeoAndConstantToleranceEvaluator implements EvalOperator.ExpressionEvaluator { + private static final long BASE_RAM_BYTES_USED = RamUsageEstimator.shallowSizeOfInstance(StSimplifyFoldableGeoAndConstantToleranceEvaluator.class); + + private final Source source; + + private final BytesRef inputGeometry; + + private final double inputTolerance; + + private final DriverContext driverContext; + + private Warnings warnings; + + public StSimplifyFoldableGeoAndConstantToleranceEvaluator(Source source, BytesRef inputGeometry, + double inputTolerance, DriverContext driverContext) { + this.source = source; + this.inputGeometry = inputGeometry; + this.inputTolerance = inputTolerance; + this.driverContext = driverContext; + } + + @Override + public Block eval(Page page) { + return eval(page.getPositionCount()); + } + + @Override + public long baseRamBytesUsed() { + long baseRamBytesUsed = BASE_RAM_BYTES_USED; + return baseRamBytesUsed; + } + + public BytesRefBlock eval(int positionCount) { + try(BytesRefBlock.Builder result = driverContext.blockFactory().newBytesRefBlockBuilder(positionCount)) { + position: for (int p = 0; p < positionCount; p++) { + try { + result.appendBytesRef(StSimplify.processFoldableGeoAndConstantTolerance(this.inputGeometry, this.inputTolerance)); + } catch (IllegalArgumentException e) { + warnings().registerException(e); + result.appendNull(); + } + } + return result.build(); + } + } + + @Override + public String toString() { + return "StSimplifyFoldableGeoAndConstantToleranceEvaluator[" + "inputGeometry=" + inputGeometry + ", inputTolerance=" + inputTolerance + "]"; + } + + @Override + public void close() { + } + + private Warnings warnings() { + if (warnings == null) { + this.warnings = Warnings.createWarnings( + driverContext.warningsMode(), + source.source().getLineNumber(), + source.source().getColumnNumber(), + source.text() + ); + } + return warnings; + } + + static class Factory implements EvalOperator.ExpressionEvaluator.Factory { + private final Source source; + + private final BytesRef inputGeometry; + + private final double inputTolerance; + + public Factory(Source source, BytesRef inputGeometry, double inputTolerance) { + this.source = source; + this.inputGeometry = inputGeometry; + this.inputTolerance = inputTolerance; + } + + @Override + public StSimplifyFoldableGeoAndConstantToleranceEvaluator get(DriverContext context) { + return new StSimplifyFoldableGeoAndConstantToleranceEvaluator(source, inputGeometry, inputTolerance, context); + } + + @Override + public String toString() { + return "StSimplifyFoldableGeoAndConstantToleranceEvaluator[" + "inputGeometry=" + inputGeometry + ", inputTolerance=" + inputTolerance + "]"; + } + } +} diff --git a/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyNonFoldableGeoAndConstantToleranceEvaluator.java b/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyNonFoldableGeoAndConstantToleranceEvaluator.java new file mode 100644 index 0000000000000..909fc4eb35d83 --- /dev/null +++ b/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyNonFoldableGeoAndConstantToleranceEvaluator.java @@ -0,0 +1,155 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License +// 2.0; you may not use this file except in compliance with the Elastic License +// 2.0. +package org.elasticsearch.xpack.esql.expression.function.scalar.spatial; + +import java.lang.IllegalArgumentException; +import java.lang.Override; +import java.lang.String; +import org.apache.lucene.util.BytesRef; +import org.apache.lucene.util.RamUsageEstimator; +import org.elasticsearch.compute.data.Block; +import org.elasticsearch.compute.data.BytesRefBlock; +import org.elasticsearch.compute.data.BytesRefVector; +import org.elasticsearch.compute.data.Page; +import org.elasticsearch.compute.operator.DriverContext; +import org.elasticsearch.compute.operator.EvalOperator; +import org.elasticsearch.compute.operator.Warnings; +import org.elasticsearch.core.Releasables; +import org.elasticsearch.xpack.esql.core.tree.Source; + +/** + * {@link EvalOperator.ExpressionEvaluator} implementation for {@link StSimplify}. + * This class is generated. Edit {@code EvaluatorImplementer} instead. + */ +public final class StSimplifyNonFoldableGeoAndConstantToleranceEvaluator implements EvalOperator.ExpressionEvaluator { + private static final long BASE_RAM_BYTES_USED = RamUsageEstimator.shallowSizeOfInstance(StSimplifyNonFoldableGeoAndConstantToleranceEvaluator.class); + + private final Source source; + + private final EvalOperator.ExpressionEvaluator inputGeometry; + + private final double inputTolerance; + + private final DriverContext driverContext; + + private Warnings warnings; + + public StSimplifyNonFoldableGeoAndConstantToleranceEvaluator(Source source, + EvalOperator.ExpressionEvaluator inputGeometry, double inputTolerance, + DriverContext driverContext) { + this.source = source; + this.inputGeometry = inputGeometry; + this.inputTolerance = inputTolerance; + this.driverContext = driverContext; + } + + @Override + public Block eval(Page page) { + try (BytesRefBlock inputGeometryBlock = (BytesRefBlock) inputGeometry.eval(page)) { + BytesRefVector inputGeometryVector = inputGeometryBlock.asVector(); + if (inputGeometryVector == null) { + return eval(page.getPositionCount(), inputGeometryBlock); + } + return eval(page.getPositionCount(), inputGeometryVector); + } + } + + @Override + public long baseRamBytesUsed() { + long baseRamBytesUsed = BASE_RAM_BYTES_USED; + baseRamBytesUsed += inputGeometry.baseRamBytesUsed(); + return baseRamBytesUsed; + } + + public BytesRefBlock eval(int positionCount, BytesRefBlock inputGeometryBlock) { + try(BytesRefBlock.Builder result = driverContext.blockFactory().newBytesRefBlockBuilder(positionCount)) { + BytesRef inputGeometryScratch = new BytesRef(); + position: for (int p = 0; p < positionCount; p++) { + if (inputGeometryBlock.isNull(p)) { + result.appendNull(); + continue position; + } + if (inputGeometryBlock.getValueCount(p) != 1) { + if (inputGeometryBlock.getValueCount(p) > 1) { + warnings().registerException(new IllegalArgumentException("single-value function encountered multi-value")); + } + result.appendNull(); + continue position; + } + BytesRef inputGeometry = inputGeometryBlock.getBytesRef(inputGeometryBlock.getFirstValueIndex(p), inputGeometryScratch); + try { + result.appendBytesRef(StSimplify.processNonFoldableGeoAndConstantTolerance(inputGeometry, this.inputTolerance)); + } catch (IllegalArgumentException e) { + warnings().registerException(e); + result.appendNull(); + } + } + return result.build(); + } + } + + public BytesRefBlock eval(int positionCount, BytesRefVector inputGeometryVector) { + try(BytesRefBlock.Builder result = driverContext.blockFactory().newBytesRefBlockBuilder(positionCount)) { + BytesRef inputGeometryScratch = new BytesRef(); + position: for (int p = 0; p < positionCount; p++) { + BytesRef inputGeometry = inputGeometryVector.getBytesRef(p, inputGeometryScratch); + try { + result.appendBytesRef(StSimplify.processNonFoldableGeoAndConstantTolerance(inputGeometry, this.inputTolerance)); + } catch (IllegalArgumentException e) { + warnings().registerException(e); + result.appendNull(); + } + } + return result.build(); + } + } + + @Override + public String toString() { + return "StSimplifyNonFoldableGeoAndConstantToleranceEvaluator[" + "inputGeometry=" + inputGeometry + ", inputTolerance=" + inputTolerance + "]"; + } + + @Override + public void close() { + Releasables.closeExpectNoException(inputGeometry); + } + + private Warnings warnings() { + if (warnings == null) { + this.warnings = Warnings.createWarnings( + driverContext.warningsMode(), + source.source().getLineNumber(), + source.source().getColumnNumber(), + source.text() + ); + } + return warnings; + } + + static class Factory implements EvalOperator.ExpressionEvaluator.Factory { + private final Source source; + + private final EvalOperator.ExpressionEvaluator.Factory inputGeometry; + + private final double inputTolerance; + + public Factory(Source source, EvalOperator.ExpressionEvaluator.Factory inputGeometry, + double inputTolerance) { + this.source = source; + this.inputGeometry = inputGeometry; + this.inputTolerance = inputTolerance; + } + + @Override + public StSimplifyNonFoldableGeoAndConstantToleranceEvaluator get(DriverContext context) { + return new StSimplifyNonFoldableGeoAndConstantToleranceEvaluator(source, inputGeometry.get(context), inputTolerance, context); + } + + @Override + public String toString() { + return "StSimplifyNonFoldableGeoAndConstantToleranceEvaluator[" + "inputGeometry=" + inputGeometry + ", inputTolerance=" + inputTolerance + "]"; + } + } +} diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplify.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplify.java index 8cd1d4e60ea46..6fd6f0f253a4d 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplify.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplify.java @@ -11,16 +11,9 @@ import org.elasticsearch.common.io.stream.NamedWriteableRegistry; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; -import org.elasticsearch.compute.ann.ConvertEvaluator; import org.elasticsearch.compute.ann.Evaluator; import org.elasticsearch.compute.ann.Fixed; -import org.elasticsearch.compute.data.BytesRefBlock; import org.elasticsearch.compute.operator.EvalOperator; -import org.elasticsearch.geometry.Geometry; -import org.elasticsearch.geometry.utils.GeometryValidator; -import org.elasticsearch.geometry.utils.StandardValidator; -import org.elasticsearch.geometry.utils.WellKnownBinary; -import org.elasticsearch.geometry.utils.WellKnownText; import org.elasticsearch.xpack.esql.core.expression.Expression; import org.elasticsearch.xpack.esql.core.tree.NodeInfo; import org.elasticsearch.xpack.esql.core.tree.Source; @@ -35,10 +28,10 @@ import org.locationtech.jts.simplify.DouglasPeuckerSimplifier; import java.io.IOException; -import java.nio.ByteOrder; import java.util.List; import static org.elasticsearch.xpack.esql.core.type.DataType.GEO_SHAPE; +import static org.elasticsearch.xpack.esql.core.util.SpatialCoordinateTypes.UNSPECIFIED; public class StSimplify extends EsqlScalarFunction { public static final NamedWriteableRegistry.Entry ENTRY = new NamedWriteableRegistry.Entry( @@ -101,18 +94,16 @@ public void writeTo(StreamOutput out) throws IOException { } private static class GeoSimplifier { - static GeometryValidator validator = StandardValidator.instance(true); static WKTReader reader = new WKTReader(); static WKTWriter writer = new WKTWriter(); public static BytesRef geoSourceAndConstantTolerance(BytesRef inputGeometry, @Fixed double inputTolerance) { - String wkt = WellKnownText.fromWKB(inputGeometry.bytes, inputGeometry.offset, inputGeometry.length); + String wkt = UNSPECIFIED.wkbToWkt(inputGeometry); try { org.locationtech.jts.geom.Geometry jtsGeometry = reader.read(wkt); org.locationtech.jts.geom.Geometry simplifiedGeometry = DouglasPeuckerSimplifier.simplify(jtsGeometry, inputTolerance); String simplifiedWkt = writer.write(simplifiedGeometry); - Geometry esGeometryResult = WellKnownText.fromWKT(validator, false, simplifiedWkt); - return new BytesRef(WellKnownBinary.toWKB(esGeometryResult, ByteOrder.LITTLE_ENDIAN)); + return UNSPECIFIED.wktToWkb(simplifiedWkt); } catch (Exception e) { throw new RuntimeException(e); } @@ -136,18 +127,12 @@ public EvalOperator.ExpressionEvaluator.Factory toEvaluator(ToEvaluator toEvalua } @Evaluator(extraName = "NonFoldableGeoAndConstantTolerance", warnExceptions = { IllegalArgumentException.class }) - static BytesRef processNonFoldableGeoAndConstantTolerance( - BytesRef inputGeometry, - @Fixed double inputTolerance - ) { + static BytesRef processNonFoldableGeoAndConstantTolerance(BytesRef inputGeometry, @Fixed double inputTolerance) { return GeoSimplifier.geoSourceAndConstantTolerance(inputGeometry, inputTolerance); } @Evaluator(extraName = "FoldableGeoAndConstantTolerance", warnExceptions = { IllegalArgumentException.class }) - static BytesRef processFoldableGeoAndConstantTolerance( - @Fixed BytesRef inputGeometry, - @Fixed double inputTolerance - ) { + static BytesRef processFoldableGeoAndConstantTolerance(@Fixed BytesRef inputGeometry, @Fixed double inputTolerance) { return GeoSimplifier.geoSourceAndConstantTolerance(inputGeometry, inputTolerance); } } From 0e6e1e23a0c75c49df936b3559f6cda1e0aeff31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nacho=20Cord=C3=B3n?= Date: Thu, 9 Oct 2025 18:23:14 +0200 Subject: [PATCH 08/45] Update docs/changelog/136309.yaml --- docs/changelog/136309.yaml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 docs/changelog/136309.yaml diff --git a/docs/changelog/136309.yaml b/docs/changelog/136309.yaml new file mode 100644 index 0000000000000..316cf300f7933 --- /dev/null +++ b/docs/changelog/136309.yaml @@ -0,0 +1,5 @@ +pr: 136309 +summary: Adds ST_SIMPLIFY spatial funcion +area: ES|QL +type: enhancement +issues: [] From 908632ba3d3f9dd10dae3a397dcf05f530ace2a0 Mon Sep 17 00:00:00 2001 From: ncordon Date: Thu, 9 Oct 2025 18:32:09 +0200 Subject: [PATCH 09/45] Updates changelog --- docs/changelog/136309.yaml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/changelog/136309.yaml b/docs/changelog/136309.yaml index 316cf300f7933..33192872867d1 100644 --- a/docs/changelog/136309.yaml +++ b/docs/changelog/136309.yaml @@ -1,5 +1,6 @@ pr: 136309 -summary: Adds ST_SIMPLIFY spatial funcion +summary: Adds ST_SIMPLIFY geo spatial function area: ES|QL type: enhancement -issues: [] +issues: + - 48521 From dd2933eaf4adde27fde88761d8ae6d0fbea5fe89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nacho=20Cord=C3=B3n?= Date: Thu, 9 Oct 2025 18:32:22 +0200 Subject: [PATCH 10/45] Update docs/changelog/136309.yaml --- docs/changelog/136309.yaml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/changelog/136309.yaml b/docs/changelog/136309.yaml index 33192872867d1..a5a02d4351b68 100644 --- a/docs/changelog/136309.yaml +++ b/docs/changelog/136309.yaml @@ -2,5 +2,4 @@ pr: 136309 summary: Adds ST_SIMPLIFY geo spatial function area: ES|QL type: enhancement -issues: - - 48521 +issues: [] From 6ab6bdcccd0402071a12a74122cad6c3c8c3a791 Mon Sep 17 00:00:00 2001 From: ncordon Date: Fri, 10 Oct 2025 11:36:35 +0200 Subject: [PATCH 11/45] Reshapes the code a bit --- x-pack/plugin/esql-core/build.gradle | 1 + .../licenses/jts-core-LICENSE.txt | 0 .../licenses/jts-core-NOTICE.txt | 0 .../core/util/SpatialCoordinateTypes.java | 16 +++++++++++ x-pack/plugin/esql/build.gradle | 1 - .../function/scalar/spatial/StSimplify.java | 28 +++++++------------ 6 files changed, 27 insertions(+), 19 deletions(-) rename x-pack/plugin/{esql => esql-core}/licenses/jts-core-LICENSE.txt (100%) rename x-pack/plugin/{esql => esql-core}/licenses/jts-core-NOTICE.txt (100%) diff --git a/x-pack/plugin/esql-core/build.gradle b/x-pack/plugin/esql-core/build.gradle index c18c10840d221..eca5ba65ccde5 100644 --- a/x-pack/plugin/esql-core/build.gradle +++ b/x-pack/plugin/esql-core/build.gradle @@ -16,6 +16,7 @@ base { dependencies { api "org.antlr:antlr4-runtime:${versions.antlr4}" api project(path: xpackModule('mapper-version')) + api "org.locationtech.jts:jts-core:${versions.jts}" compileOnly project(path: xpackModule('core')) testApi(project(xpackModule('esql-core:test-fixtures'))) { exclude group: 'org.elasticsearch.plugin', module: 'esql-core' diff --git a/x-pack/plugin/esql/licenses/jts-core-LICENSE.txt b/x-pack/plugin/esql-core/licenses/jts-core-LICENSE.txt similarity index 100% rename from x-pack/plugin/esql/licenses/jts-core-LICENSE.txt rename to x-pack/plugin/esql-core/licenses/jts-core-LICENSE.txt diff --git a/x-pack/plugin/esql/licenses/jts-core-NOTICE.txt b/x-pack/plugin/esql-core/licenses/jts-core-NOTICE.txt similarity index 100% rename from x-pack/plugin/esql/licenses/jts-core-NOTICE.txt rename to x-pack/plugin/esql-core/licenses/jts-core-NOTICE.txt diff --git a/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/util/SpatialCoordinateTypes.java b/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/util/SpatialCoordinateTypes.java index 019aabda5058e..7662da986b238 100644 --- a/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/util/SpatialCoordinateTypes.java +++ b/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/util/SpatialCoordinateTypes.java @@ -16,6 +16,9 @@ import org.elasticsearch.geometry.utils.GeometryValidator; import org.elasticsearch.geometry.utils.WellKnownBinary; import org.elasticsearch.geometry.utils.WellKnownText; +import org.locationtech.jts.io.ParseException; +import org.locationtech.jts.io.WKTReader; +import org.locationtech.jts.io.WKTWriter; import java.nio.ByteOrder; @@ -125,4 +128,17 @@ public String wkbToWkt(BytesRef wkb) { public Geometry wkbToGeometry(BytesRef wkb) { return WellKnownBinary.fromWKB(validator(), false, wkb.bytes, wkb.offset, wkb.length); } + + public org.locationtech.jts.geom.Geometry wkbToJtsGeometry(BytesRef wkb) throws ParseException { + String wkt = wkbToWkt(wkb); + WKTReader reader = new WKTReader(); + return reader.read(wkt); + } + + public BytesRef jtsGeometryToWkb(org.locationtech.jts.geom.Geometry jtsGeometry) { + WKTWriter writer = new WKTWriter(); + String wkt = writer.write(jtsGeometry); + return wktToWkb(wkt); + } + } diff --git a/x-pack/plugin/esql/build.gradle b/x-pack/plugin/esql/build.gradle index 9aeaa789bf9e0..734c0b62eb729 100644 --- a/x-pack/plugin/esql/build.gradle +++ b/x-pack/plugin/esql/build.gradle @@ -37,7 +37,6 @@ dependencies { compileOnly project(xpackModule('ml')) compileOnly project(path: xpackModule('mapper-aggregate-metric')) compileOnly project(path: xpackModule('downsample')) - implementation "org.locationtech.jts:jts-core:${versions.jts}" implementation project(xpackModule('kql')) implementation project('compute') implementation project('compute:ann') diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplify.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplify.java index 6fd6f0f253a4d..8f724531ca6e7 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplify.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplify.java @@ -23,8 +23,7 @@ import org.elasticsearch.xpack.esql.expression.function.Param; import org.elasticsearch.xpack.esql.expression.function.scalar.EsqlScalarFunction; import org.elasticsearch.xpack.esql.io.stream.PlanStreamInput; -import org.locationtech.jts.io.WKTReader; -import org.locationtech.jts.io.WKTWriter; +import org.locationtech.jts.io.ParseException; import org.locationtech.jts.simplify.DouglasPeuckerSimplifier; import java.io.IOException; @@ -93,20 +92,13 @@ public void writeTo(StreamOutput out) throws IOException { out.writeNamedWriteable(tolerance); } - private static class GeoSimplifier { - static WKTReader reader = new WKTReader(); - static WKTWriter writer = new WKTWriter(); - - public static BytesRef geoSourceAndConstantTolerance(BytesRef inputGeometry, @Fixed double inputTolerance) { - String wkt = UNSPECIFIED.wkbToWkt(inputGeometry); - try { - org.locationtech.jts.geom.Geometry jtsGeometry = reader.read(wkt); - org.locationtech.jts.geom.Geometry simplifiedGeometry = DouglasPeuckerSimplifier.simplify(jtsGeometry, inputTolerance); - String simplifiedWkt = writer.write(simplifiedGeometry); - return UNSPECIFIED.wktToWkb(simplifiedWkt); - } catch (Exception e) { - throw new RuntimeException(e); - } + private static BytesRef geoSourceAndConstantTolerance(BytesRef inputGeometry, double inputTolerance) { + try { + org.locationtech.jts.geom.Geometry jtsGeometry = UNSPECIFIED.wkbToJtsGeometry(inputGeometry); + org.locationtech.jts.geom.Geometry simplifiedGeometry = DouglasPeuckerSimplifier.simplify(jtsGeometry, inputTolerance); + return UNSPECIFIED.jtsGeometryToWkb(simplifiedGeometry); + } catch (ParseException e) { + throw new IllegalArgumentException("could not parse the geometry expression: " + e); } } @@ -128,11 +120,11 @@ public EvalOperator.ExpressionEvaluator.Factory toEvaluator(ToEvaluator toEvalua @Evaluator(extraName = "NonFoldableGeoAndConstantTolerance", warnExceptions = { IllegalArgumentException.class }) static BytesRef processNonFoldableGeoAndConstantTolerance(BytesRef inputGeometry, @Fixed double inputTolerance) { - return GeoSimplifier.geoSourceAndConstantTolerance(inputGeometry, inputTolerance); + return geoSourceAndConstantTolerance(inputGeometry, inputTolerance); } @Evaluator(extraName = "FoldableGeoAndConstantTolerance", warnExceptions = { IllegalArgumentException.class }) static BytesRef processFoldableGeoAndConstantTolerance(@Fixed BytesRef inputGeometry, @Fixed double inputTolerance) { - return GeoSimplifier.geoSourceAndConstantTolerance(inputGeometry, inputTolerance); + return geoSourceAndConstantTolerance(inputGeometry, inputTolerance); } } From 4b164d0a3512a6d906159dcf0463b7834868dc57 Mon Sep 17 00:00:00 2001 From: ncordon Date: Mon, 20 Oct 2025 15:41:29 +0200 Subject: [PATCH 12/45] Adds auto-generated tests for st_simplify --- .../functions/description/st_simplify.md | 6 + .../functions/examples/st_simplify.md | 9 ++ .../_snippets/functions/layout/st_simplify.md | 23 ++++ .../functions/parameters/st_simplify.md | 10 ++ .../_snippets/functions/types/st_simplify.md | 9 ++ .../esql/images/functions/st_simplify.svg | 1 + .../definition/functions/st_simplify.json | 49 ++++++++ .../esql/kibana/docs/functions/st_simplify.md | 8 ++ .../core/util/SpatialCoordinateTypes.java | 5 +- ...dableGeoAndConstantToleranceEvaluator.java | 20 +-- .../function/scalar/spatial/StSimplify.java | 12 +- .../scalar/spatial/StSimplifyTests.java | 116 ++++++++++++++++++ 12 files changed, 255 insertions(+), 13 deletions(-) create mode 100644 docs/reference/query-languages/esql/_snippets/functions/description/st_simplify.md create mode 100644 docs/reference/query-languages/esql/_snippets/functions/examples/st_simplify.md create mode 100644 docs/reference/query-languages/esql/_snippets/functions/layout/st_simplify.md create mode 100644 docs/reference/query-languages/esql/_snippets/functions/parameters/st_simplify.md create mode 100644 docs/reference/query-languages/esql/_snippets/functions/types/st_simplify.md create mode 100644 docs/reference/query-languages/esql/images/functions/st_simplify.svg create mode 100644 docs/reference/query-languages/esql/kibana/definition/functions/st_simplify.json create mode 100644 docs/reference/query-languages/esql/kibana/docs/functions/st_simplify.md create mode 100644 x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyTests.java diff --git a/docs/reference/query-languages/esql/_snippets/functions/description/st_simplify.md b/docs/reference/query-languages/esql/_snippets/functions/description/st_simplify.md new file mode 100644 index 0000000000000..fb858e0a475a0 --- /dev/null +++ b/docs/reference/query-languages/esql/_snippets/functions/description/st_simplify.md @@ -0,0 +1,6 @@ +% This is generated by ESQL's AbstractFunctionTestCase. Do not edit it. See ../README.md for how to regenerate it. + +**Description** + +Simplifies the input geometry with a given tolerance. + diff --git a/docs/reference/query-languages/esql/_snippets/functions/examples/st_simplify.md b/docs/reference/query-languages/esql/_snippets/functions/examples/st_simplify.md new file mode 100644 index 0000000000000..b3efac50b7ae7 --- /dev/null +++ b/docs/reference/query-languages/esql/_snippets/functions/examples/st_simplify.md @@ -0,0 +1,9 @@ +% This is generated by ESQL's AbstractFunctionTestCase. Do not edit it. See ../README.md for how to regenerate it. + +**Example** + +```esql +null +``` + + diff --git a/docs/reference/query-languages/esql/_snippets/functions/layout/st_simplify.md b/docs/reference/query-languages/esql/_snippets/functions/layout/st_simplify.md new file mode 100644 index 0000000000000..16ccee40bf53b --- /dev/null +++ b/docs/reference/query-languages/esql/_snippets/functions/layout/st_simplify.md @@ -0,0 +1,23 @@ +% This is generated by ESQL's AbstractFunctionTestCase. Do not edit it. See ../README.md for how to regenerate it. + +## `ST_SIMPLIFY` [esql-st_simplify] + +**Syntax** + +:::{image} ../../../images/functions/st_simplify.svg +:alt: Embedded +:class: text-center +::: + + +:::{include} ../parameters/st_simplify.md +::: + +:::{include} ../description/st_simplify.md +::: + +:::{include} ../types/st_simplify.md +::: + +:::{include} ../examples/st_simplify.md +::: diff --git a/docs/reference/query-languages/esql/_snippets/functions/parameters/st_simplify.md b/docs/reference/query-languages/esql/_snippets/functions/parameters/st_simplify.md new file mode 100644 index 0000000000000..f9a349dee3a32 --- /dev/null +++ b/docs/reference/query-languages/esql/_snippets/functions/parameters/st_simplify.md @@ -0,0 +1,10 @@ +% This is generated by ESQL's AbstractFunctionTestCase. Do not edit it. See ../README.md for how to regenerate it. + +**Parameters** + +`geometry` +: Expression of type `geo_point`, `geo_shape`, `cartesian_point` or `cartesian_shape`. If `null`, the function returns `null`. + +`tolerance` +: Tolerance for the geometry simplification + diff --git a/docs/reference/query-languages/esql/_snippets/functions/types/st_simplify.md b/docs/reference/query-languages/esql/_snippets/functions/types/st_simplify.md new file mode 100644 index 0000000000000..df086f5a7336d --- /dev/null +++ b/docs/reference/query-languages/esql/_snippets/functions/types/st_simplify.md @@ -0,0 +1,9 @@ +% This is generated by ESQL's AbstractFunctionTestCase. Do not edit it. See ../README.md for how to regenerate it. + +**Supported types** + +| geometry | tolerance | result | +| --- | --- | --- | +| cartesian_point | double | geo_shape | +| geo_point | double | geo_shape | + diff --git a/docs/reference/query-languages/esql/images/functions/st_simplify.svg b/docs/reference/query-languages/esql/images/functions/st_simplify.svg new file mode 100644 index 0000000000000..cea940a2f2bd2 --- /dev/null +++ b/docs/reference/query-languages/esql/images/functions/st_simplify.svg @@ -0,0 +1 @@ +ST_SIMPLIFY(geometry,tolerance) \ No newline at end of file diff --git a/docs/reference/query-languages/esql/kibana/definition/functions/st_simplify.json b/docs/reference/query-languages/esql/kibana/definition/functions/st_simplify.json new file mode 100644 index 0000000000000..8303d64821861 --- /dev/null +++ b/docs/reference/query-languages/esql/kibana/definition/functions/st_simplify.json @@ -0,0 +1,49 @@ +{ + "comment" : "This is generated by ESQL's AbstractFunctionTestCase. Do not edit it. See ../README.md for how to regenerate it.", + "type" : "scalar", + "name" : "st_simplify", + "description" : "Simplifies the input geometry with a given tolerance.", + "signatures" : [ + { + "params" : [ + { + "name" : "geometry", + "type" : "cartesian_point", + "optional" : false, + "description" : "Expression of type `geo_point`, `geo_shape`, `cartesian_point` or `cartesian_shape`. If `null`, the function returns `null`." + }, + { + "name" : "tolerance", + "type" : "double", + "optional" : false, + "description" : "Tolerance for the geometry simplification" + } + ], + "variadic" : false, + "returnType" : "geo_shape" + }, + { + "params" : [ + { + "name" : "geometry", + "type" : "geo_point", + "optional" : false, + "description" : "Expression of type `geo_point`, `geo_shape`, `cartesian_point` or `cartesian_shape`. If `null`, the function returns `null`." + }, + { + "name" : "tolerance", + "type" : "double", + "optional" : false, + "description" : "Tolerance for the geometry simplification" + } + ], + "variadic" : false, + "returnType" : "geo_shape" + } + ], + "examples" : [ + null + ], + "preview" : false, + "snapshot_only" : false +} diff --git a/docs/reference/query-languages/esql/kibana/docs/functions/st_simplify.md b/docs/reference/query-languages/esql/kibana/docs/functions/st_simplify.md new file mode 100644 index 0000000000000..9ab0def9b3f08 --- /dev/null +++ b/docs/reference/query-languages/esql/kibana/docs/functions/st_simplify.md @@ -0,0 +1,8 @@ +% This is generated by ESQL's AbstractFunctionTestCase. Do not edit it. See ../README.md for how to regenerate it. + +### ST SIMPLIFY +Simplifies the input geometry with a given tolerance. + +```esql +null +``` diff --git a/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/util/SpatialCoordinateTypes.java b/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/util/SpatialCoordinateTypes.java index 7662da986b238..f894b05d72b20 100644 --- a/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/util/SpatialCoordinateTypes.java +++ b/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/util/SpatialCoordinateTypes.java @@ -129,8 +129,11 @@ public Geometry wkbToGeometry(BytesRef wkb) { return WellKnownBinary.fromWKB(validator(), false, wkb.bytes, wkb.offset, wkb.length); } - public org.locationtech.jts.geom.Geometry wkbToJtsGeometry(BytesRef wkb) throws ParseException { + public org.locationtech.jts.geom.Geometry wkbToJtsGeometry(BytesRef wkb) throws ParseException, IllegalArgumentException { String wkt = wkbToWkt(wkb); + if (wkt.startsWith("BBOX")) { + throw new IllegalArgumentException("Input geometry cannot be a BBOX"); + } WKTReader reader = new WKTReader(); return reader.read(wkt); } diff --git a/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyNonFoldableGeoAndConstantToleranceEvaluator.java b/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyNonFoldableGeoAndConstantToleranceEvaluator.java index 909fc4eb35d83..53ad6a5880eb7 100644 --- a/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyNonFoldableGeoAndConstantToleranceEvaluator.java +++ b/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyNonFoldableGeoAndConstantToleranceEvaluator.java @@ -67,16 +67,16 @@ public BytesRefBlock eval(int positionCount, BytesRefBlock inputGeometryBlock) { try(BytesRefBlock.Builder result = driverContext.blockFactory().newBytesRefBlockBuilder(positionCount)) { BytesRef inputGeometryScratch = new BytesRef(); position: for (int p = 0; p < positionCount; p++) { - if (inputGeometryBlock.isNull(p)) { - result.appendNull(); - continue position; - } - if (inputGeometryBlock.getValueCount(p) != 1) { - if (inputGeometryBlock.getValueCount(p) > 1) { - warnings().registerException(new IllegalArgumentException("single-value function encountered multi-value")); - } - result.appendNull(); - continue position; + switch (inputGeometryBlock.getValueCount(p)) { + case 0: + result.appendNull(); + continue position; + case 1: + break; + default: + warnings().registerException(new IllegalArgumentException("single-value function encountered multi-value")); + result.appendNull(); + continue position; } BytesRef inputGeometry = inputGeometryBlock.getBytesRef(inputGeometryBlock.getFirstValueIndex(p), inputGeometryScratch); try { diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplify.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplify.java index 8f724531ca6e7..6679b5ed90dbb 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplify.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplify.java @@ -43,7 +43,7 @@ public class StSimplify extends EsqlScalarFunction { @FunctionInfo( returnType = "geo_shape", - description = "Simplifies the input geometry with a given tolerance", + description = "Simplifies the input geometry with a given tolerance.", examples = @Example(file = "spatial", tag = "st_simplify") ) public StSimplify( @@ -92,7 +92,10 @@ public void writeTo(StreamOutput out) throws IOException { out.writeNamedWriteable(tolerance); } - private static BytesRef geoSourceAndConstantTolerance(BytesRef inputGeometry, double inputTolerance) { + private static BytesRef geoSourceAndConstantTolerance(BytesRef inputGeometry, Double inputTolerance) { + if (inputGeometry == null || inputTolerance == null) { + return null; + } try { org.locationtech.jts.geom.Geometry jtsGeometry = UNSPECIFIED.wkbToJtsGeometry(inputGeometry); org.locationtech.jts.geom.Geometry simplifiedGeometry = DouglasPeuckerSimplifier.simplify(jtsGeometry, inputTolerance); @@ -127,4 +130,9 @@ static BytesRef processNonFoldableGeoAndConstantTolerance(BytesRef inputGeometry static BytesRef processFoldableGeoAndConstantTolerance(@Fixed BytesRef inputGeometry, @Fixed double inputTolerance) { return geoSourceAndConstantTolerance(inputGeometry, inputTolerance); } + + @Override + public boolean foldable() { + return geometry.foldable() && tolerance.foldable(); + } } diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyTests.java new file mode 100644 index 0000000000000..ab1add8676446 --- /dev/null +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyTests.java @@ -0,0 +1,116 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +package org.elasticsearch.xpack.esql.expression.function.scalar.spatial; + +import com.carrotsearch.randomizedtesting.annotations.Name; +import com.carrotsearch.randomizedtesting.annotations.ParametersFactory; + +import org.apache.lucene.util.BytesRef; +import org.elasticsearch.xpack.esql.core.expression.Expression; +import org.elasticsearch.xpack.esql.core.tree.Source; +import org.elasticsearch.xpack.esql.core.type.DataType; +import org.elasticsearch.xpack.esql.expression.function.AbstractScalarFunctionTestCase; +import org.elasticsearch.xpack.esql.expression.function.FunctionName; +import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier; +import org.hamcrest.Matchers; +import org.junit.AssumptionViolatedException; +import org.locationtech.jts.simplify.DouglasPeuckerSimplifier; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.BiFunction; +import java.util.function.Supplier; + +import static org.elasticsearch.xpack.esql.core.type.DataType.CARTESIAN_POINT; +import static org.elasticsearch.xpack.esql.core.type.DataType.CARTESIAN_SHAPE; +import static org.elasticsearch.xpack.esql.core.type.DataType.DOUBLE; +import static org.elasticsearch.xpack.esql.core.type.DataType.GEO_POINT; +import static org.elasticsearch.xpack.esql.core.type.DataType.GEO_SHAPE; +import static org.elasticsearch.xpack.esql.core.util.SpatialCoordinateTypes.UNSPECIFIED; + +@FunctionName("st_simplify") +public class StSimplifyTests extends AbstractScalarFunctionTestCase { + public StSimplifyTests(@Name("TestCase") Supplier testCaseSupplier) { + this.testCase = testCaseSupplier.get(); + } + + @ParametersFactory + public static Iterable parameters() { + final List suppliers = new ArrayList<>(); + addTestCaseSuppliers(suppliers, new DataType[] { GEO_POINT }, GEO_SHAPE, StSimplifyTests::valueOf); + addTestCaseSuppliers(suppliers, new DataType[] { CARTESIAN_POINT }, GEO_SHAPE, StSimplifyTests::valueOf); + addTestCaseSuppliers(suppliers, new DataType[] { CARTESIAN_SHAPE }, GEO_SHAPE, StSimplifyTests::valueOf); + addTestCaseSuppliers(suppliers, new DataType[] { GEO_SHAPE }, GEO_SHAPE, StSimplifyTests::valueOf); + return parameterSuppliersFromTypedDataWithDefaultChecksNoErrors(true, suppliers); + } + + public static TestCaseSupplier.TypedDataSupplier testCaseSupplier(DataType dataType) { + return switch (dataType) { + case GEO_POINT -> TestCaseSupplier.geoPointCases(() -> false).getFirst(); + case GEO_SHAPE -> TestCaseSupplier.geoShapeCases(() -> false).getFirst(); + case CARTESIAN_POINT -> TestCaseSupplier.cartesianPointCases(() -> false).getFirst(); + case CARTESIAN_SHAPE -> TestCaseSupplier.cartesianShapeCases(() -> false).getFirst(); + default -> throw new IllegalArgumentException("Unsupported datatype for " + functionName() + ": " + dataType); + }; + } + + private static String getFunctionClassName() { + Class testClass = getTestClass(); + String testClassName = testClass.getSimpleName(); + return testClassName.replace("Tests", ""); + } + + protected static void addTestCaseSuppliers( + List suppliers, + DataType[] dataTypes, + DataType gridType, + BiFunction expectedValue + ) { + for (DataType spatialType : dataTypes) { + TestCaseSupplier.TypedDataSupplier geometrySupplier = testCaseSupplier(spatialType); + String testName = spatialType.typeName() + " with tolerance."; + + suppliers.add(new TestCaseSupplier(testName, List.of(spatialType, DOUBLE), () -> { + TestCaseSupplier.TypedData geoTypedData = geometrySupplier.get(); + BytesRef geometry = (BytesRef) geoTypedData.data(); + double tolerance = randomDoubleBetween(0, 100, true); + TestCaseSupplier.TypedData toleranceData = new TestCaseSupplier.TypedData(tolerance, DOUBLE, "tolerance"); + toleranceData = toleranceData.forceLiteral(); + String evaluatorName = "NonFoldableGeoAndConstantToleranceEvaluator[inputGeometry=Attribute[channel=0], inputTolerance=" + + tolerance + + "]"; + var expectedResult = expectedValue.apply(geometry, tolerance); + + return new TestCaseSupplier.TestCase( + List.of(geoTypedData, toleranceData), + getFunctionClassName() + evaluatorName, + gridType, + Matchers.equalTo(expectedResult) + ); + })); + } + } + + private static BytesRef valueOf(BytesRef wkb, double tolerance) { + if (wkb == null) { + return null; + } + try { + org.locationtech.jts.geom.Geometry jtsGeometry = UNSPECIFIED.wkbToJtsGeometry(wkb); + org.locationtech.jts.geom.Geometry simplifiedGeometry = DouglasPeuckerSimplifier.simplify(jtsGeometry, tolerance); + return UNSPECIFIED.jtsGeometryToWkb(simplifiedGeometry); + } catch (Exception e) { + throw new AssumptionViolatedException("Skipping invalid test case"); + } + } + + @Override + protected Expression build(Source source, List args) { + return new StSimplify(source, args.get(0), args.get(1)); + } +} From 8bc10f848e9bf82744cb379333f1659314116154 Mon Sep 17 00:00:00 2001 From: ncordon Date: Mon, 20 Oct 2025 17:24:45 +0200 Subject: [PATCH 13/45] checkpoint --- .../_snippets/functions/types/st_simplify.md | 2 + .../definition/functions/st_simplify.json | 36 ++++ ...leGeoAndFoldableIntToleranceEvaluator.java | 114 +++++++++++++ ...ableGeoAndFoldableToleranceEvaluator.java} | 16 +- ...leGeoAndFoldableIntToleranceEvaluator.java | 155 ++++++++++++++++++ ...ableGeoAndFoldableToleranceEvaluator.java} | 14 +- .../function/scalar/spatial/StSimplify.java | 27 ++- .../scalar/spatial/StSimplifyTests.java | 59 ++++++- 8 files changed, 400 insertions(+), 23 deletions(-) create mode 100644 x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyFoldableGeoAndFoldableIntToleranceEvaluator.java rename x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/{StSimplifyFoldableGeoAndConstantToleranceEvaluator.java => StSimplifyFoldableGeoAndFoldableToleranceEvaluator.java} (87%) create mode 100644 x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyNonFoldableGeoAndFoldableIntToleranceEvaluator.java rename x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/{StSimplifyNonFoldableGeoAndConstantToleranceEvaluator.java => StSimplifyNonFoldableGeoAndFoldableToleranceEvaluator.java} (92%) diff --git a/docs/reference/query-languages/esql/_snippets/functions/types/st_simplify.md b/docs/reference/query-languages/esql/_snippets/functions/types/st_simplify.md index df086f5a7336d..2bce42953a668 100644 --- a/docs/reference/query-languages/esql/_snippets/functions/types/st_simplify.md +++ b/docs/reference/query-languages/esql/_snippets/functions/types/st_simplify.md @@ -5,5 +5,7 @@ | geometry | tolerance | result | | --- | --- | --- | | cartesian_point | double | geo_shape | +| cartesian_shape | double | geo_shape | | geo_point | double | geo_shape | +| geo_shape | double | geo_shape | diff --git a/docs/reference/query-languages/esql/kibana/definition/functions/st_simplify.json b/docs/reference/query-languages/esql/kibana/definition/functions/st_simplify.json index 8303d64821861..291aa3a608f4e 100644 --- a/docs/reference/query-languages/esql/kibana/definition/functions/st_simplify.json +++ b/docs/reference/query-languages/esql/kibana/definition/functions/st_simplify.json @@ -22,6 +22,24 @@ "variadic" : false, "returnType" : "geo_shape" }, + { + "params" : [ + { + "name" : "geometry", + "type" : "cartesian_shape", + "optional" : false, + "description" : "Expression of type `geo_point`, `geo_shape`, `cartesian_point` or `cartesian_shape`. If `null`, the function returns `null`." + }, + { + "name" : "tolerance", + "type" : "double", + "optional" : false, + "description" : "Tolerance for the geometry simplification" + } + ], + "variadic" : false, + "returnType" : "geo_shape" + }, { "params" : [ { @@ -39,6 +57,24 @@ ], "variadic" : false, "returnType" : "geo_shape" + }, + { + "params" : [ + { + "name" : "geometry", + "type" : "geo_shape", + "optional" : false, + "description" : "Expression of type `geo_point`, `geo_shape`, `cartesian_point` or `cartesian_shape`. If `null`, the function returns `null`." + }, + { + "name" : "tolerance", + "type" : "double", + "optional" : false, + "description" : "Tolerance for the geometry simplification" + } + ], + "variadic" : false, + "returnType" : "geo_shape" } ], "examples" : [ diff --git a/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyFoldableGeoAndFoldableIntToleranceEvaluator.java b/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyFoldableGeoAndFoldableIntToleranceEvaluator.java new file mode 100644 index 0000000000000..4019d14902aaa --- /dev/null +++ b/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyFoldableGeoAndFoldableIntToleranceEvaluator.java @@ -0,0 +1,114 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License +// 2.0; you may not use this file except in compliance with the Elastic License +// 2.0. +package org.elasticsearch.xpack.esql.expression.function.scalar.spatial; + +import java.lang.IllegalArgumentException; +import java.lang.Override; +import java.lang.String; +import org.apache.lucene.util.BytesRef; +import org.apache.lucene.util.RamUsageEstimator; +import org.elasticsearch.compute.data.Block; +import org.elasticsearch.compute.data.BytesRefBlock; +import org.elasticsearch.compute.data.Page; +import org.elasticsearch.compute.operator.DriverContext; +import org.elasticsearch.compute.operator.EvalOperator; +import org.elasticsearch.compute.operator.Warnings; +import org.elasticsearch.xpack.esql.core.tree.Source; + +/** + * {@link EvalOperator.ExpressionEvaluator} implementation for {@link StSimplify}. + * This class is generated. Edit {@code EvaluatorImplementer} instead. + */ +public final class StSimplifyFoldableGeoAndFoldableIntToleranceEvaluator implements EvalOperator.ExpressionEvaluator { + private static final long BASE_RAM_BYTES_USED = RamUsageEstimator.shallowSizeOfInstance(StSimplifyFoldableGeoAndFoldableIntToleranceEvaluator.class); + + private final Source source; + + private final BytesRef inputGeometry; + + private final int inputTolerance; + + private final DriverContext driverContext; + + private Warnings warnings; + + public StSimplifyFoldableGeoAndFoldableIntToleranceEvaluator(Source source, + BytesRef inputGeometry, int inputTolerance, DriverContext driverContext) { + this.source = source; + this.inputGeometry = inputGeometry; + this.inputTolerance = inputTolerance; + this.driverContext = driverContext; + } + + @Override + public Block eval(Page page) { + return eval(page.getPositionCount()); + } + + @Override + public long baseRamBytesUsed() { + long baseRamBytesUsed = BASE_RAM_BYTES_USED; + return baseRamBytesUsed; + } + + public BytesRefBlock eval(int positionCount) { + try(BytesRefBlock.Builder result = driverContext.blockFactory().newBytesRefBlockBuilder(positionCount)) { + position: for (int p = 0; p < positionCount; p++) { + try { + result.appendBytesRef(StSimplify.processFoldableGeoAndFoldableTolerance(this.inputGeometry, this.inputTolerance)); + } catch (IllegalArgumentException e) { + warnings().registerException(e); + result.appendNull(); + } + } + return result.build(); + } + } + + @Override + public String toString() { + return "StSimplifyFoldableGeoAndFoldableIntToleranceEvaluator[" + "inputGeometry=" + inputGeometry + ", inputTolerance=" + inputTolerance + "]"; + } + + @Override + public void close() { + } + + private Warnings warnings() { + if (warnings == null) { + this.warnings = Warnings.createWarnings( + driverContext.warningsMode(), + source.source().getLineNumber(), + source.source().getColumnNumber(), + source.text() + ); + } + return warnings; + } + + static class Factory implements EvalOperator.ExpressionEvaluator.Factory { + private final Source source; + + private final BytesRef inputGeometry; + + private final int inputTolerance; + + public Factory(Source source, BytesRef inputGeometry, int inputTolerance) { + this.source = source; + this.inputGeometry = inputGeometry; + this.inputTolerance = inputTolerance; + } + + @Override + public StSimplifyFoldableGeoAndFoldableIntToleranceEvaluator get(DriverContext context) { + return new StSimplifyFoldableGeoAndFoldableIntToleranceEvaluator(source, inputGeometry, inputTolerance, context); + } + + @Override + public String toString() { + return "StSimplifyFoldableGeoAndFoldableIntToleranceEvaluator[" + "inputGeometry=" + inputGeometry + ", inputTolerance=" + inputTolerance + "]"; + } + } +} diff --git a/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyFoldableGeoAndConstantToleranceEvaluator.java b/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyFoldableGeoAndFoldableToleranceEvaluator.java similarity index 87% rename from x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyFoldableGeoAndConstantToleranceEvaluator.java rename to x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyFoldableGeoAndFoldableToleranceEvaluator.java index 5bcde39b51a91..2dbb0b9203311 100644 --- a/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyFoldableGeoAndConstantToleranceEvaluator.java +++ b/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyFoldableGeoAndFoldableToleranceEvaluator.java @@ -21,8 +21,8 @@ * {@link EvalOperator.ExpressionEvaluator} implementation for {@link StSimplify}. * This class is generated. Edit {@code EvaluatorImplementer} instead. */ -public final class StSimplifyFoldableGeoAndConstantToleranceEvaluator implements EvalOperator.ExpressionEvaluator { - private static final long BASE_RAM_BYTES_USED = RamUsageEstimator.shallowSizeOfInstance(StSimplifyFoldableGeoAndConstantToleranceEvaluator.class); +public final class StSimplifyFoldableGeoAndFoldableToleranceEvaluator implements EvalOperator.ExpressionEvaluator { + private static final long BASE_RAM_BYTES_USED = RamUsageEstimator.shallowSizeOfInstance(StSimplifyFoldableGeoAndFoldableToleranceEvaluator.class); private final Source source; @@ -34,7 +34,7 @@ public final class StSimplifyFoldableGeoAndConstantToleranceEvaluator implements private Warnings warnings; - public StSimplifyFoldableGeoAndConstantToleranceEvaluator(Source source, BytesRef inputGeometry, + public StSimplifyFoldableGeoAndFoldableToleranceEvaluator(Source source, BytesRef inputGeometry, double inputTolerance, DriverContext driverContext) { this.source = source; this.inputGeometry = inputGeometry; @@ -57,7 +57,7 @@ public BytesRefBlock eval(int positionCount) { try(BytesRefBlock.Builder result = driverContext.blockFactory().newBytesRefBlockBuilder(positionCount)) { position: for (int p = 0; p < positionCount; p++) { try { - result.appendBytesRef(StSimplify.processFoldableGeoAndConstantTolerance(this.inputGeometry, this.inputTolerance)); + result.appendBytesRef(StSimplify.processFoldableGeoAndFoldableTolerance(this.inputGeometry, this.inputTolerance)); } catch (IllegalArgumentException e) { warnings().registerException(e); result.appendNull(); @@ -69,7 +69,7 @@ public BytesRefBlock eval(int positionCount) { @Override public String toString() { - return "StSimplifyFoldableGeoAndConstantToleranceEvaluator[" + "inputGeometry=" + inputGeometry + ", inputTolerance=" + inputTolerance + "]"; + return "StSimplifyFoldableGeoAndFoldableToleranceEvaluator[" + "inputGeometry=" + inputGeometry + ", inputTolerance=" + inputTolerance + "]"; } @Override @@ -102,13 +102,13 @@ public Factory(Source source, BytesRef inputGeometry, double inputTolerance) { } @Override - public StSimplifyFoldableGeoAndConstantToleranceEvaluator get(DriverContext context) { - return new StSimplifyFoldableGeoAndConstantToleranceEvaluator(source, inputGeometry, inputTolerance, context); + public StSimplifyFoldableGeoAndFoldableToleranceEvaluator get(DriverContext context) { + return new StSimplifyFoldableGeoAndFoldableToleranceEvaluator(source, inputGeometry, inputTolerance, context); } @Override public String toString() { - return "StSimplifyFoldableGeoAndConstantToleranceEvaluator[" + "inputGeometry=" + inputGeometry + ", inputTolerance=" + inputTolerance + "]"; + return "StSimplifyFoldableGeoAndFoldableToleranceEvaluator[" + "inputGeometry=" + inputGeometry + ", inputTolerance=" + inputTolerance + "]"; } } } diff --git a/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyNonFoldableGeoAndFoldableIntToleranceEvaluator.java b/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyNonFoldableGeoAndFoldableIntToleranceEvaluator.java new file mode 100644 index 0000000000000..11d657cac2304 --- /dev/null +++ b/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyNonFoldableGeoAndFoldableIntToleranceEvaluator.java @@ -0,0 +1,155 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License +// 2.0; you may not use this file except in compliance with the Elastic License +// 2.0. +package org.elasticsearch.xpack.esql.expression.function.scalar.spatial; + +import java.lang.IllegalArgumentException; +import java.lang.Override; +import java.lang.String; +import org.apache.lucene.util.BytesRef; +import org.apache.lucene.util.RamUsageEstimator; +import org.elasticsearch.compute.data.Block; +import org.elasticsearch.compute.data.BytesRefBlock; +import org.elasticsearch.compute.data.BytesRefVector; +import org.elasticsearch.compute.data.Page; +import org.elasticsearch.compute.operator.DriverContext; +import org.elasticsearch.compute.operator.EvalOperator; +import org.elasticsearch.compute.operator.Warnings; +import org.elasticsearch.core.Releasables; +import org.elasticsearch.xpack.esql.core.tree.Source; + +/** + * {@link EvalOperator.ExpressionEvaluator} implementation for {@link StSimplify}. + * This class is generated. Edit {@code EvaluatorImplementer} instead. + */ +public final class StSimplifyNonFoldableGeoAndFoldableIntToleranceEvaluator implements EvalOperator.ExpressionEvaluator { + private static final long BASE_RAM_BYTES_USED = RamUsageEstimator.shallowSizeOfInstance(StSimplifyNonFoldableGeoAndFoldableIntToleranceEvaluator.class); + + private final Source source; + + private final EvalOperator.ExpressionEvaluator inputGeometry; + + private final int inputTolerance; + + private final DriverContext driverContext; + + private Warnings warnings; + + public StSimplifyNonFoldableGeoAndFoldableIntToleranceEvaluator(Source source, + EvalOperator.ExpressionEvaluator inputGeometry, int inputTolerance, + DriverContext driverContext) { + this.source = source; + this.inputGeometry = inputGeometry; + this.inputTolerance = inputTolerance; + this.driverContext = driverContext; + } + + @Override + public Block eval(Page page) { + try (BytesRefBlock inputGeometryBlock = (BytesRefBlock) inputGeometry.eval(page)) { + BytesRefVector inputGeometryVector = inputGeometryBlock.asVector(); + if (inputGeometryVector == null) { + return eval(page.getPositionCount(), inputGeometryBlock); + } + return eval(page.getPositionCount(), inputGeometryVector); + } + } + + @Override + public long baseRamBytesUsed() { + long baseRamBytesUsed = BASE_RAM_BYTES_USED; + baseRamBytesUsed += inputGeometry.baseRamBytesUsed(); + return baseRamBytesUsed; + } + + public BytesRefBlock eval(int positionCount, BytesRefBlock inputGeometryBlock) { + try(BytesRefBlock.Builder result = driverContext.blockFactory().newBytesRefBlockBuilder(positionCount)) { + BytesRef inputGeometryScratch = new BytesRef(); + position: for (int p = 0; p < positionCount; p++) { + switch (inputGeometryBlock.getValueCount(p)) { + case 0: + result.appendNull(); + continue position; + case 1: + break; + default: + warnings().registerException(new IllegalArgumentException("single-value function encountered multi-value")); + result.appendNull(); + continue position; + } + BytesRef inputGeometry = inputGeometryBlock.getBytesRef(inputGeometryBlock.getFirstValueIndex(p), inputGeometryScratch); + try { + result.appendBytesRef(StSimplify.processNonFoldableGeoAndConstantTolerance(inputGeometry, this.inputTolerance)); + } catch (IllegalArgumentException e) { + warnings().registerException(e); + result.appendNull(); + } + } + return result.build(); + } + } + + public BytesRefBlock eval(int positionCount, BytesRefVector inputGeometryVector) { + try(BytesRefBlock.Builder result = driverContext.blockFactory().newBytesRefBlockBuilder(positionCount)) { + BytesRef inputGeometryScratch = new BytesRef(); + position: for (int p = 0; p < positionCount; p++) { + BytesRef inputGeometry = inputGeometryVector.getBytesRef(p, inputGeometryScratch); + try { + result.appendBytesRef(StSimplify.processNonFoldableGeoAndConstantTolerance(inputGeometry, this.inputTolerance)); + } catch (IllegalArgumentException e) { + warnings().registerException(e); + result.appendNull(); + } + } + return result.build(); + } + } + + @Override + public String toString() { + return "StSimplifyNonFoldableGeoAndFoldableIntToleranceEvaluator[" + "inputGeometry=" + inputGeometry + ", inputTolerance=" + inputTolerance + "]"; + } + + @Override + public void close() { + Releasables.closeExpectNoException(inputGeometry); + } + + private Warnings warnings() { + if (warnings == null) { + this.warnings = Warnings.createWarnings( + driverContext.warningsMode(), + source.source().getLineNumber(), + source.source().getColumnNumber(), + source.text() + ); + } + return warnings; + } + + static class Factory implements EvalOperator.ExpressionEvaluator.Factory { + private final Source source; + + private final EvalOperator.ExpressionEvaluator.Factory inputGeometry; + + private final int inputTolerance; + + public Factory(Source source, EvalOperator.ExpressionEvaluator.Factory inputGeometry, + int inputTolerance) { + this.source = source; + this.inputGeometry = inputGeometry; + this.inputTolerance = inputTolerance; + } + + @Override + public StSimplifyNonFoldableGeoAndFoldableIntToleranceEvaluator get(DriverContext context) { + return new StSimplifyNonFoldableGeoAndFoldableIntToleranceEvaluator(source, inputGeometry.get(context), inputTolerance, context); + } + + @Override + public String toString() { + return "StSimplifyNonFoldableGeoAndFoldableIntToleranceEvaluator[" + "inputGeometry=" + inputGeometry + ", inputTolerance=" + inputTolerance + "]"; + } + } +} diff --git a/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyNonFoldableGeoAndConstantToleranceEvaluator.java b/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyNonFoldableGeoAndFoldableToleranceEvaluator.java similarity index 92% rename from x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyNonFoldableGeoAndConstantToleranceEvaluator.java rename to x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyNonFoldableGeoAndFoldableToleranceEvaluator.java index 53ad6a5880eb7..81de3cde35e4d 100644 --- a/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyNonFoldableGeoAndConstantToleranceEvaluator.java +++ b/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyNonFoldableGeoAndFoldableToleranceEvaluator.java @@ -23,8 +23,8 @@ * {@link EvalOperator.ExpressionEvaluator} implementation for {@link StSimplify}. * This class is generated. Edit {@code EvaluatorImplementer} instead. */ -public final class StSimplifyNonFoldableGeoAndConstantToleranceEvaluator implements EvalOperator.ExpressionEvaluator { - private static final long BASE_RAM_BYTES_USED = RamUsageEstimator.shallowSizeOfInstance(StSimplifyNonFoldableGeoAndConstantToleranceEvaluator.class); +public final class StSimplifyNonFoldableGeoAndFoldableToleranceEvaluator implements EvalOperator.ExpressionEvaluator { + private static final long BASE_RAM_BYTES_USED = RamUsageEstimator.shallowSizeOfInstance(StSimplifyNonFoldableGeoAndFoldableToleranceEvaluator.class); private final Source source; @@ -36,7 +36,7 @@ public final class StSimplifyNonFoldableGeoAndConstantToleranceEvaluator impleme private Warnings warnings; - public StSimplifyNonFoldableGeoAndConstantToleranceEvaluator(Source source, + public StSimplifyNonFoldableGeoAndFoldableToleranceEvaluator(Source source, EvalOperator.ExpressionEvaluator inputGeometry, double inputTolerance, DriverContext driverContext) { this.source = source; @@ -108,7 +108,7 @@ public BytesRefBlock eval(int positionCount, BytesRefVector inputGeometryVector) @Override public String toString() { - return "StSimplifyNonFoldableGeoAndConstantToleranceEvaluator[" + "inputGeometry=" + inputGeometry + ", inputTolerance=" + inputTolerance + "]"; + return "StSimplifyNonFoldableGeoAndFoldableToleranceEvaluator[" + "inputGeometry=" + inputGeometry + ", inputTolerance=" + inputTolerance + "]"; } @Override @@ -143,13 +143,13 @@ public Factory(Source source, EvalOperator.ExpressionEvaluator.Factory inputGeom } @Override - public StSimplifyNonFoldableGeoAndConstantToleranceEvaluator get(DriverContext context) { - return new StSimplifyNonFoldableGeoAndConstantToleranceEvaluator(source, inputGeometry.get(context), inputTolerance, context); + public StSimplifyNonFoldableGeoAndFoldableToleranceEvaluator get(DriverContext context) { + return new StSimplifyNonFoldableGeoAndFoldableToleranceEvaluator(source, inputGeometry.get(context), inputTolerance, context); } @Override public String toString() { - return "StSimplifyNonFoldableGeoAndConstantToleranceEvaluator[" + "inputGeometry=" + inputGeometry + ", inputTolerance=" + inputTolerance + "]"; + return "StSimplifyNonFoldableGeoAndFoldableToleranceEvaluator[" + "inputGeometry=" + inputGeometry + ", inputTolerance=" + inputTolerance + "]"; } } } diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplify.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplify.java index 6679b5ed90dbb..528fc27235f97 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplify.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplify.java @@ -92,8 +92,8 @@ public void writeTo(StreamOutput out) throws IOException { out.writeNamedWriteable(tolerance); } - private static BytesRef geoSourceAndConstantTolerance(BytesRef inputGeometry, Double inputTolerance) { - if (inputGeometry == null || inputTolerance == null) { + private static BytesRef geoSourceAndConstantTolerance(BytesRef inputGeometry, double inputTolerance) { + if (inputGeometry == null) { return null; } try { @@ -114,20 +114,33 @@ public EvalOperator.ExpressionEvaluator.Factory toEvaluator(ToEvaluator toEvalua } double inputTolerance = (double) tolerance.fold(toEvaluator.foldCtx()); + if (inputTolerance < 0) { + throw new IllegalArgumentException("tolerance must not be negative"); + } if (geometry.foldable()) { BytesRef inputGeometry = (BytesRef) geometry.fold(toEvaluator.foldCtx()); - return new StSimplifyFoldableGeoAndConstantToleranceEvaluator.Factory(source(), inputGeometry, inputTolerance); + return new StSimplifyFoldableGeoAndFoldableToleranceEvaluator.Factory(source(), inputGeometry, inputTolerance); } - return new StSimplifyNonFoldableGeoAndConstantToleranceEvaluator.Factory(source(), geometryEvaluator, inputTolerance); + return new StSimplifyNonFoldableGeoAndFoldableToleranceEvaluator.Factory(source(), geometryEvaluator, inputTolerance); } - @Evaluator(extraName = "NonFoldableGeoAndConstantTolerance", warnExceptions = { IllegalArgumentException.class }) + @Evaluator(extraName = "NonFoldableGeoAndFoldableTolerance", warnExceptions = { IllegalArgumentException.class }) static BytesRef processNonFoldableGeoAndConstantTolerance(BytesRef inputGeometry, @Fixed double inputTolerance) { return geoSourceAndConstantTolerance(inputGeometry, inputTolerance); } - @Evaluator(extraName = "FoldableGeoAndConstantTolerance", warnExceptions = { IllegalArgumentException.class }) - static BytesRef processFoldableGeoAndConstantTolerance(@Fixed BytesRef inputGeometry, @Fixed double inputTolerance) { + @Evaluator(extraName = "FoldableGeoAndFoldableTolerance", warnExceptions = { IllegalArgumentException.class }) + static BytesRef processFoldableGeoAndFoldableTolerance(@Fixed BytesRef inputGeometry, @Fixed double inputTolerance) { + return geoSourceAndConstantTolerance(inputGeometry, inputTolerance); + } + + @Evaluator(extraName = "NonFoldableGeoAndFoldableIntTolerance", warnExceptions = { IllegalArgumentException.class }) + static BytesRef processNonFoldableGeoAndConstantTolerance(BytesRef inputGeometry, @Fixed int inputTolerance) { + return geoSourceAndConstantTolerance(inputGeometry, inputTolerance); + } + + @Evaluator(extraName = "FoldableGeoAndFoldableIntTolerance", warnExceptions = { IllegalArgumentException.class }) + static BytesRef processFoldableGeoAndFoldableTolerance(@Fixed BytesRef inputGeometry, @Fixed int inputTolerance) { return geoSourceAndConstantTolerance(inputGeometry, inputTolerance); } diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyTests.java index ab1add8676446..49adddbf5f281 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyTests.java @@ -11,7 +11,11 @@ import com.carrotsearch.randomizedtesting.annotations.ParametersFactory; import org.apache.lucene.util.BytesRef; +import org.elasticsearch.compute.data.Block; +import org.elasticsearch.compute.data.BytesRefVectorBlock; +import org.elasticsearch.compute.operator.EvalOperator; import org.elasticsearch.xpack.esql.core.expression.Expression; +import org.elasticsearch.xpack.esql.core.expression.Literal; import org.elasticsearch.xpack.esql.core.tree.Source; import org.elasticsearch.xpack.esql.core.type.DataType; import org.elasticsearch.xpack.esql.expression.function.AbstractScalarFunctionTestCase; @@ -32,6 +36,9 @@ import static org.elasticsearch.xpack.esql.core.type.DataType.GEO_POINT; import static org.elasticsearch.xpack.esql.core.type.DataType.GEO_SHAPE; import static org.elasticsearch.xpack.esql.core.util.SpatialCoordinateTypes.UNSPECIFIED; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.equalTo; +import static org.junit.Assume.assumeNotNull; @FunctionName("st_simplify") public class StSimplifyTests extends AbstractScalarFunctionTestCase { @@ -81,7 +88,7 @@ protected static void addTestCaseSuppliers( double tolerance = randomDoubleBetween(0, 100, true); TestCaseSupplier.TypedData toleranceData = new TestCaseSupplier.TypedData(tolerance, DOUBLE, "tolerance"); toleranceData = toleranceData.forceLiteral(); - String evaluatorName = "NonFoldableGeoAndConstantToleranceEvaluator[inputGeometry=Attribute[channel=0], inputTolerance=" + String evaluatorName = "NonFoldableGeoAndFoldableToleranceEvaluator[inputGeometry=Attribute[channel=0], inputTolerance=" + tolerance + "]"; var expectedResult = expectedValue.apply(geometry, tolerance); @@ -113,4 +120,54 @@ private static BytesRef valueOf(BytesRef wkb, double tolerance) { protected Expression build(Source source, List args) { return new StSimplify(source, args.get(0), args.get(1)); } + + protected BytesRef process(Double tolerance) { + Object spatialObj = this.testCase.getDataValues().getFirst(); + assumeNotNull(spatialObj); + assumeTrue("Expected a BytesRef, but got " + spatialObj.getClass(), spatialObj instanceof BytesRef); + BytesRef wkb = (BytesRef) spatialObj; + try ( + EvalOperator.ExpressionEvaluator eval = evaluator( + build(Source.EMPTY, List.of(new Literal(Source.EMPTY, wkb, GEO_POINT), new Literal(Source.EMPTY, tolerance, DOUBLE))) + ).get(driverContext()); + Block block = eval.eval(row(List.of(wkb, tolerance))) + ) { + var result = ((BytesRefVectorBlock) block).asVector().getBytesRef(0, new BytesRef()); + return block.isNull(0) ? null : result; + } + } + + protected String processNonRandom(String wkt, double tolerance) { + BytesRef wkb = UNSPECIFIED.wktToWkb(wkt); + try ( + EvalOperator.ExpressionEvaluator eval = evaluator( + build(Source.EMPTY, List.of(new Literal(Source.EMPTY, wkb, GEO_POINT), new Literal(Source.EMPTY, tolerance, DOUBLE))) + ).get(driverContext()); + Block block = eval.eval(row(List.of(wkb, tolerance))) + ) { + var result = ((BytesRefVectorBlock) block).asVector().getBytesRef(0, new BytesRef()); + return block.isNull(0) ? null : UNSPECIFIED.wkbToWkt(result); + } + } + + public void testInvalidToleranceNonRandom() { + IllegalArgumentException ex = expectThrows(IllegalArgumentException.class, () -> process(-1.0)); + assertThat(ex.getMessage(), containsString("tolerance must not be negative")); + } + + // This should succeed + public void testZeroToleranceNonRandom() { + var result = processNonRandom( "POLYGON((0 0, 1 0.1, 2 0, 2 2, 1 1.9, 0 2, 0 0))", 2.0); + assertThat(result, equalTo("POLYGON EMPTY")); + } + + public void testInvalidTolerance() { + IllegalArgumentException ex = expectThrows(IllegalArgumentException.class, () -> process(-1.0)); + assertThat(ex.getMessage(), containsString("tolerance must not be negative")); + } + + // This should succeed + public void testZeroTolerance() { + process(0.0); + } } From 7cc3afe5635c65a16a01272aadd52640f6dd99c3 Mon Sep 17 00:00:00 2001 From: elasticsearchmachine Date: Mon, 20 Oct 2025 15:43:50 +0000 Subject: [PATCH 14/45] [CI] Auto commit changes from spotless --- .../expression/function/scalar/spatial/StSimplifyTests.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyTests.java index 49adddbf5f281..f4975108e7234 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyTests.java @@ -157,7 +157,7 @@ public void testInvalidToleranceNonRandom() { // This should succeed public void testZeroToleranceNonRandom() { - var result = processNonRandom( "POLYGON((0 0, 1 0.1, 2 0, 2 2, 1 1.9, 0 2, 0 0))", 2.0); + var result = processNonRandom("POLYGON((0 0, 1 0.1, 2 0, 2 2, 1 1.9, 0 2, 0 0))", 2.0); assertThat(result, equalTo("POLYGON EMPTY")); } From c117a1937dfe92eab9aa14a2b078220e1a82e74a Mon Sep 17 00:00:00 2001 From: ncordon Date: Mon, 20 Oct 2025 17:57:55 +0200 Subject: [PATCH 15/45] Makes integers be a valid input for the tolerance --- .../_snippets/functions/types/st_simplify.md | 1 - .../definition/functions/st_simplify.json | 18 ---------- .../src/main/resources/spatial.csv-spec | 12 +++++++ .../function/scalar/spatial/StSimplify.java | 34 +++++++++++-------- .../scalar/spatial/StSimplifyTests.java | 25 -------------- 5 files changed, 31 insertions(+), 59 deletions(-) diff --git a/docs/reference/query-languages/esql/_snippets/functions/types/st_simplify.md b/docs/reference/query-languages/esql/_snippets/functions/types/st_simplify.md index 2bce42953a668..14c546b7b01d8 100644 --- a/docs/reference/query-languages/esql/_snippets/functions/types/st_simplify.md +++ b/docs/reference/query-languages/esql/_snippets/functions/types/st_simplify.md @@ -7,5 +7,4 @@ | cartesian_point | double | geo_shape | | cartesian_shape | double | geo_shape | | geo_point | double | geo_shape | -| geo_shape | double | geo_shape | diff --git a/docs/reference/query-languages/esql/kibana/definition/functions/st_simplify.json b/docs/reference/query-languages/esql/kibana/definition/functions/st_simplify.json index 291aa3a608f4e..95a6056d4ada8 100644 --- a/docs/reference/query-languages/esql/kibana/definition/functions/st_simplify.json +++ b/docs/reference/query-languages/esql/kibana/definition/functions/st_simplify.json @@ -57,24 +57,6 @@ ], "variadic" : false, "returnType" : "geo_shape" - }, - { - "params" : [ - { - "name" : "geometry", - "type" : "geo_shape", - "optional" : false, - "description" : "Expression of type `geo_point`, `geo_shape`, `cartesian_point` or `cartesian_shape`. If `null`, the function returns `null`." - }, - { - "name" : "tolerance", - "type" : "double", - "optional" : false, - "description" : "Tolerance for the geometry simplification" - } - ], - "variadic" : false, - "returnType" : "geo_shape" } ], "examples" : [ diff --git a/x-pack/plugin/esql/qa/testFixtures/src/main/resources/spatial.csv-spec b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/spatial.csv-spec index fd8e19370e957..e37dcec7cd99a 100644 --- a/x-pack/plugin/esql/qa/testFixtures/src/main/resources/spatial.csv-spec +++ b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/spatial.csv-spec @@ -3144,3 +3144,15 @@ result:geo_shape POINT (97.11 75.53) POLYGON ((0.0 0.0, 0.0 2.0, 2.0 2.0, 2.0 0.0, 0.0 0.0)) ; + +stSimplifyWithIntegerTolerance +required_capability: st_simplify + +ROW geo_shape = TO_GEOSHAPE("POLYGON((0 0, 1 0.1, 2 0, 2 2, 1 1.9, 0 2, 0 0))") +| EVAL result = st_simplify(geo_shape, 2) +| KEEP result +; + +result:geo_shape +POLYGON EMPTY +; diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplify.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplify.java index 528fc27235f97..b635810e7bc05 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplify.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplify.java @@ -112,11 +112,8 @@ public EvalOperator.ExpressionEvaluator.Factory toEvaluator(ToEvaluator toEvalua if (tolerance.foldable() == false) { throw new IllegalArgumentException("tolerance must be foldable"); } - double inputTolerance = (double) tolerance.fold(toEvaluator.foldCtx()); - - if (inputTolerance < 0) { - throw new IllegalArgumentException("tolerance must not be negative"); - } + var toleranceExpression = tolerance.fold(toEvaluator.foldCtx()); + double inputTolerance = getInputTolerance(toleranceExpression); if (geometry.foldable()) { BytesRef inputGeometry = (BytesRef) geometry.fold(toEvaluator.foldCtx()); return new StSimplifyFoldableGeoAndFoldableToleranceEvaluator.Factory(source(), inputGeometry, inputTolerance); @@ -124,6 +121,23 @@ public EvalOperator.ExpressionEvaluator.Factory toEvaluator(ToEvaluator toEvalua return new StSimplifyNonFoldableGeoAndFoldableToleranceEvaluator.Factory(source(), geometryEvaluator, inputTolerance); } + private static double getInputTolerance(Object toleranceExpression) { + double inputTolerance; + + if (toleranceExpression instanceof Integer) { + inputTolerance = ((Integer) toleranceExpression).doubleValue(); + } else if (toleranceExpression instanceof Double) { + inputTolerance = (double) toleranceExpression; + } else { + throw new IllegalArgumentException("tolerance for st_simplify must be a non negative integer or double"); + } + + if (inputTolerance < 0) { + throw new IllegalArgumentException("tolerance must not be negative"); + } + return inputTolerance; + } + @Evaluator(extraName = "NonFoldableGeoAndFoldableTolerance", warnExceptions = { IllegalArgumentException.class }) static BytesRef processNonFoldableGeoAndConstantTolerance(BytesRef inputGeometry, @Fixed double inputTolerance) { return geoSourceAndConstantTolerance(inputGeometry, inputTolerance); @@ -134,16 +148,6 @@ static BytesRef processFoldableGeoAndFoldableTolerance(@Fixed BytesRef inputGeom return geoSourceAndConstantTolerance(inputGeometry, inputTolerance); } - @Evaluator(extraName = "NonFoldableGeoAndFoldableIntTolerance", warnExceptions = { IllegalArgumentException.class }) - static BytesRef processNonFoldableGeoAndConstantTolerance(BytesRef inputGeometry, @Fixed int inputTolerance) { - return geoSourceAndConstantTolerance(inputGeometry, inputTolerance); - } - - @Evaluator(extraName = "FoldableGeoAndFoldableIntTolerance", warnExceptions = { IllegalArgumentException.class }) - static BytesRef processFoldableGeoAndFoldableTolerance(@Fixed BytesRef inputGeometry, @Fixed int inputTolerance) { - return geoSourceAndConstantTolerance(inputGeometry, inputTolerance); - } - @Override public boolean foldable() { return geometry.foldable() && tolerance.foldable(); diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyTests.java index f4975108e7234..060f677bea3a5 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyTests.java @@ -37,7 +37,6 @@ import static org.elasticsearch.xpack.esql.core.type.DataType.GEO_SHAPE; import static org.elasticsearch.xpack.esql.core.util.SpatialCoordinateTypes.UNSPECIFIED; import static org.hamcrest.Matchers.containsString; -import static org.hamcrest.Matchers.equalTo; import static org.junit.Assume.assumeNotNull; @FunctionName("st_simplify") @@ -137,30 +136,6 @@ protected BytesRef process(Double tolerance) { } } - protected String processNonRandom(String wkt, double tolerance) { - BytesRef wkb = UNSPECIFIED.wktToWkb(wkt); - try ( - EvalOperator.ExpressionEvaluator eval = evaluator( - build(Source.EMPTY, List.of(new Literal(Source.EMPTY, wkb, GEO_POINT), new Literal(Source.EMPTY, tolerance, DOUBLE))) - ).get(driverContext()); - Block block = eval.eval(row(List.of(wkb, tolerance))) - ) { - var result = ((BytesRefVectorBlock) block).asVector().getBytesRef(0, new BytesRef()); - return block.isNull(0) ? null : UNSPECIFIED.wkbToWkt(result); - } - } - - public void testInvalidToleranceNonRandom() { - IllegalArgumentException ex = expectThrows(IllegalArgumentException.class, () -> process(-1.0)); - assertThat(ex.getMessage(), containsString("tolerance must not be negative")); - } - - // This should succeed - public void testZeroToleranceNonRandom() { - var result = processNonRandom("POLYGON((0 0, 1 0.1, 2 0, 2 2, 1 1.9, 0 2, 0 0))", 2.0); - assertThat(result, equalTo("POLYGON EMPTY")); - } - public void testInvalidTolerance() { IllegalArgumentException ex = expectThrows(IllegalArgumentException.class, () -> process(-1.0)); assertThat(ex.getMessage(), containsString("tolerance must not be negative")); From f216fb9b76598d4b0b8149f336eeea3816f3fb6d Mon Sep 17 00:00:00 2001 From: ncordon Date: Mon, 27 Oct 2025 11:07:44 +0100 Subject: [PATCH 16/45] Fix merge conflicts --- .../_snippets/functions/types/st_simplify.md | 1 + .../definition/functions/st_simplify.json | 18 ++++++++++++++++++ .../scalar/spatial/StSimplifyTests.java | 2 +- 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/docs/reference/query-languages/esql/_snippets/functions/types/st_simplify.md b/docs/reference/query-languages/esql/_snippets/functions/types/st_simplify.md index 14c546b7b01d8..2bce42953a668 100644 --- a/docs/reference/query-languages/esql/_snippets/functions/types/st_simplify.md +++ b/docs/reference/query-languages/esql/_snippets/functions/types/st_simplify.md @@ -7,4 +7,5 @@ | cartesian_point | double | geo_shape | | cartesian_shape | double | geo_shape | | geo_point | double | geo_shape | +| geo_shape | double | geo_shape | diff --git a/docs/reference/query-languages/esql/kibana/definition/functions/st_simplify.json b/docs/reference/query-languages/esql/kibana/definition/functions/st_simplify.json index 95a6056d4ada8..291aa3a608f4e 100644 --- a/docs/reference/query-languages/esql/kibana/definition/functions/st_simplify.json +++ b/docs/reference/query-languages/esql/kibana/definition/functions/st_simplify.json @@ -57,6 +57,24 @@ ], "variadic" : false, "returnType" : "geo_shape" + }, + { + "params" : [ + { + "name" : "geometry", + "type" : "geo_shape", + "optional" : false, + "description" : "Expression of type `geo_point`, `geo_shape`, `cartesian_point` or `cartesian_shape`. If `null`, the function returns `null`." + }, + { + "name" : "tolerance", + "type" : "double", + "optional" : false, + "description" : "Tolerance for the geometry simplification" + } + ], + "variadic" : false, + "returnType" : "geo_shape" } ], "examples" : [ diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyTests.java index 060f677bea3a5..0d3d6ef1b742f 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyTests.java @@ -52,7 +52,7 @@ public static Iterable parameters() { addTestCaseSuppliers(suppliers, new DataType[] { CARTESIAN_POINT }, GEO_SHAPE, StSimplifyTests::valueOf); addTestCaseSuppliers(suppliers, new DataType[] { CARTESIAN_SHAPE }, GEO_SHAPE, StSimplifyTests::valueOf); addTestCaseSuppliers(suppliers, new DataType[] { GEO_SHAPE }, GEO_SHAPE, StSimplifyTests::valueOf); - return parameterSuppliersFromTypedDataWithDefaultChecksNoErrors(true, suppliers); + return parameterSuppliersFromTypedDataWithDefaultChecks(true, suppliers); } public static TestCaseSupplier.TypedDataSupplier testCaseSupplier(DataType dataType) { From dfabf12842edcbc7d6008ad4429266faa1e3e67a Mon Sep 17 00:00:00 2001 From: ncordon Date: Mon, 17 Nov 2025 13:37:05 +0100 Subject: [PATCH 17/45] temp --- .../functions/examples/st_simplify.md | 14 +- .../definition/functions/st_simplify.json | 2 +- .../esql/kibana/docs/functions/st_simplify.md | 6 +- .../src/main/resources/spatial.csv-spec | 20 +++ ...leGeoAndFoldableIntToleranceEvaluator.java | 114 ------------- ...leGeoAndFoldableIntToleranceEvaluator.java | 155 ------------------ .../function/scalar/spatial/StSimplify.java | 5 +- 7 files changed, 41 insertions(+), 275 deletions(-) delete mode 100644 x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyFoldableGeoAndFoldableIntToleranceEvaluator.java delete mode 100644 x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyNonFoldableGeoAndFoldableIntToleranceEvaluator.java diff --git a/docs/reference/query-languages/esql/_snippets/functions/examples/st_simplify.md b/docs/reference/query-languages/esql/_snippets/functions/examples/st_simplify.md index b3efac50b7ae7..baf132dd1e231 100644 --- a/docs/reference/query-languages/esql/_snippets/functions/examples/st_simplify.md +++ b/docs/reference/query-languages/esql/_snippets/functions/examples/st_simplify.md @@ -3,7 +3,19 @@ **Example** ```esql -null +FROM airports +| SORT name +| LIMIT 5 +| EVAL result = st_simplify(location, 0.0) +| KEEP location, result ``` +| location:geo_point | result:geo_shape | +| --- | --- | +| POINT (41.857756722253 9.61267784753569) | POINT (41.857756722253 9.61267784753569) | +| POINT (112.711418617258 -7.92998002840567) | POINT (112.711418617258 -7.92998002840567) | +| POINT (-3.93221929167636 5.2543984451492) | POINT (-3.93221929167636 5.2543984451492) | +| POINT (54.6463293225558 24.4272271529764) | POINT (54.6463293225558 24.4272271529764) | +| POINT (7.27025993974356 9.00437659781094) | POINT (7.27025993974356 9.00437659781094) | + diff --git a/docs/reference/query-languages/esql/kibana/definition/functions/st_simplify.json b/docs/reference/query-languages/esql/kibana/definition/functions/st_simplify.json index 291aa3a608f4e..9ebd0d87c3e09 100644 --- a/docs/reference/query-languages/esql/kibana/definition/functions/st_simplify.json +++ b/docs/reference/query-languages/esql/kibana/definition/functions/st_simplify.json @@ -78,7 +78,7 @@ } ], "examples" : [ - null + "FROM airports\n| SORT name\n| LIMIT 5\n| EVAL result = st_simplify(location, 0.0)\n| KEEP location, result" ], "preview" : false, "snapshot_only" : false diff --git a/docs/reference/query-languages/esql/kibana/docs/functions/st_simplify.md b/docs/reference/query-languages/esql/kibana/docs/functions/st_simplify.md index 9ab0def9b3f08..77ab6fc41b671 100644 --- a/docs/reference/query-languages/esql/kibana/docs/functions/st_simplify.md +++ b/docs/reference/query-languages/esql/kibana/docs/functions/st_simplify.md @@ -4,5 +4,9 @@ Simplifies the input geometry with a given tolerance. ```esql -null +FROM airports +| SORT name +| LIMIT 5 +| EVAL result = st_simplify(location, 0.0) +| KEEP location, result ``` diff --git a/x-pack/plugin/esql/qa/testFixtures/src/main/resources/spatial.csv-spec b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/spatial.csv-spec index e37dcec7cd99a..b7391ad548902 100644 --- a/x-pack/plugin/esql/qa/testFixtures/src/main/resources/spatial.csv-spec +++ b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/spatial.csv-spec @@ -3050,19 +3050,23 @@ Abuja Int'l | POLYGON ((-10.0 -60.0, -10.0 60.0, 120.0 60.0, 120.0 stSimplifyMultiRowWithPoints required_capability: st_simplify +// tag::st_simplify[] FROM airports | SORT name | LIMIT 5 | EVAL result = st_simplify(location, 0.0) | KEEP location, result +// end::st_simplify[] ; +// tag::st_simplify-result[] location:geo_point | result:geo_shape POINT (41.857756722253 9.61267784753569) | POINT (41.857756722253 9.61267784753569) POINT (112.711418617258 -7.92998002840567) | POINT (112.711418617258 -7.92998002840567) POINT (-3.93221929167636 5.2543984451492) | POINT (-3.93221929167636 5.2543984451492) POINT (54.6463293225558 24.4272271529764) | POINT (54.6463293225558 24.4272271529764) POINT (7.27025993974356 9.00437659781094) | POINT (7.27025993974356 9.00437659781094) +// end::st_simplify-result[] ; stSimplifyNoSimplification @@ -3156,3 +3160,19 @@ ROW geo_shape = TO_GEOSHAPE("POLYGON((0 0, 1 0.1, 2 0, 2 2, 1 1.9, 0 2, 0 0))") result:geo_shape POLYGON EMPTY ; + +stSimplifyMultiPoints +required_capability: st_simplify + +ROW geo_point = ["MULTIPOINT(0 0, 2 0)"] +| MV_EXPAND geo_point +| EVAL geo_shape = TO_GEOSHAPE(geo_point) +| EVAL result = st_simplify(geo_shape, 0.2) +| KEEP result +; + +result:geo_shape +POLYGON EMPTY +POLYGON EMPTY +POLYGON EMPTY +; diff --git a/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyFoldableGeoAndFoldableIntToleranceEvaluator.java b/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyFoldableGeoAndFoldableIntToleranceEvaluator.java deleted file mode 100644 index 4019d14902aaa..0000000000000 --- a/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyFoldableGeoAndFoldableIntToleranceEvaluator.java +++ /dev/null @@ -1,114 +0,0 @@ -// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one -// or more contributor license agreements. Licensed under the Elastic License -// 2.0; you may not use this file except in compliance with the Elastic License -// 2.0. -package org.elasticsearch.xpack.esql.expression.function.scalar.spatial; - -import java.lang.IllegalArgumentException; -import java.lang.Override; -import java.lang.String; -import org.apache.lucene.util.BytesRef; -import org.apache.lucene.util.RamUsageEstimator; -import org.elasticsearch.compute.data.Block; -import org.elasticsearch.compute.data.BytesRefBlock; -import org.elasticsearch.compute.data.Page; -import org.elasticsearch.compute.operator.DriverContext; -import org.elasticsearch.compute.operator.EvalOperator; -import org.elasticsearch.compute.operator.Warnings; -import org.elasticsearch.xpack.esql.core.tree.Source; - -/** - * {@link EvalOperator.ExpressionEvaluator} implementation for {@link StSimplify}. - * This class is generated. Edit {@code EvaluatorImplementer} instead. - */ -public final class StSimplifyFoldableGeoAndFoldableIntToleranceEvaluator implements EvalOperator.ExpressionEvaluator { - private static final long BASE_RAM_BYTES_USED = RamUsageEstimator.shallowSizeOfInstance(StSimplifyFoldableGeoAndFoldableIntToleranceEvaluator.class); - - private final Source source; - - private final BytesRef inputGeometry; - - private final int inputTolerance; - - private final DriverContext driverContext; - - private Warnings warnings; - - public StSimplifyFoldableGeoAndFoldableIntToleranceEvaluator(Source source, - BytesRef inputGeometry, int inputTolerance, DriverContext driverContext) { - this.source = source; - this.inputGeometry = inputGeometry; - this.inputTolerance = inputTolerance; - this.driverContext = driverContext; - } - - @Override - public Block eval(Page page) { - return eval(page.getPositionCount()); - } - - @Override - public long baseRamBytesUsed() { - long baseRamBytesUsed = BASE_RAM_BYTES_USED; - return baseRamBytesUsed; - } - - public BytesRefBlock eval(int positionCount) { - try(BytesRefBlock.Builder result = driverContext.blockFactory().newBytesRefBlockBuilder(positionCount)) { - position: for (int p = 0; p < positionCount; p++) { - try { - result.appendBytesRef(StSimplify.processFoldableGeoAndFoldableTolerance(this.inputGeometry, this.inputTolerance)); - } catch (IllegalArgumentException e) { - warnings().registerException(e); - result.appendNull(); - } - } - return result.build(); - } - } - - @Override - public String toString() { - return "StSimplifyFoldableGeoAndFoldableIntToleranceEvaluator[" + "inputGeometry=" + inputGeometry + ", inputTolerance=" + inputTolerance + "]"; - } - - @Override - public void close() { - } - - private Warnings warnings() { - if (warnings == null) { - this.warnings = Warnings.createWarnings( - driverContext.warningsMode(), - source.source().getLineNumber(), - source.source().getColumnNumber(), - source.text() - ); - } - return warnings; - } - - static class Factory implements EvalOperator.ExpressionEvaluator.Factory { - private final Source source; - - private final BytesRef inputGeometry; - - private final int inputTolerance; - - public Factory(Source source, BytesRef inputGeometry, int inputTolerance) { - this.source = source; - this.inputGeometry = inputGeometry; - this.inputTolerance = inputTolerance; - } - - @Override - public StSimplifyFoldableGeoAndFoldableIntToleranceEvaluator get(DriverContext context) { - return new StSimplifyFoldableGeoAndFoldableIntToleranceEvaluator(source, inputGeometry, inputTolerance, context); - } - - @Override - public String toString() { - return "StSimplifyFoldableGeoAndFoldableIntToleranceEvaluator[" + "inputGeometry=" + inputGeometry + ", inputTolerance=" + inputTolerance + "]"; - } - } -} diff --git a/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyNonFoldableGeoAndFoldableIntToleranceEvaluator.java b/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyNonFoldableGeoAndFoldableIntToleranceEvaluator.java deleted file mode 100644 index 11d657cac2304..0000000000000 --- a/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyNonFoldableGeoAndFoldableIntToleranceEvaluator.java +++ /dev/null @@ -1,155 +0,0 @@ -// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one -// or more contributor license agreements. Licensed under the Elastic License -// 2.0; you may not use this file except in compliance with the Elastic License -// 2.0. -package org.elasticsearch.xpack.esql.expression.function.scalar.spatial; - -import java.lang.IllegalArgumentException; -import java.lang.Override; -import java.lang.String; -import org.apache.lucene.util.BytesRef; -import org.apache.lucene.util.RamUsageEstimator; -import org.elasticsearch.compute.data.Block; -import org.elasticsearch.compute.data.BytesRefBlock; -import org.elasticsearch.compute.data.BytesRefVector; -import org.elasticsearch.compute.data.Page; -import org.elasticsearch.compute.operator.DriverContext; -import org.elasticsearch.compute.operator.EvalOperator; -import org.elasticsearch.compute.operator.Warnings; -import org.elasticsearch.core.Releasables; -import org.elasticsearch.xpack.esql.core.tree.Source; - -/** - * {@link EvalOperator.ExpressionEvaluator} implementation for {@link StSimplify}. - * This class is generated. Edit {@code EvaluatorImplementer} instead. - */ -public final class StSimplifyNonFoldableGeoAndFoldableIntToleranceEvaluator implements EvalOperator.ExpressionEvaluator { - private static final long BASE_RAM_BYTES_USED = RamUsageEstimator.shallowSizeOfInstance(StSimplifyNonFoldableGeoAndFoldableIntToleranceEvaluator.class); - - private final Source source; - - private final EvalOperator.ExpressionEvaluator inputGeometry; - - private final int inputTolerance; - - private final DriverContext driverContext; - - private Warnings warnings; - - public StSimplifyNonFoldableGeoAndFoldableIntToleranceEvaluator(Source source, - EvalOperator.ExpressionEvaluator inputGeometry, int inputTolerance, - DriverContext driverContext) { - this.source = source; - this.inputGeometry = inputGeometry; - this.inputTolerance = inputTolerance; - this.driverContext = driverContext; - } - - @Override - public Block eval(Page page) { - try (BytesRefBlock inputGeometryBlock = (BytesRefBlock) inputGeometry.eval(page)) { - BytesRefVector inputGeometryVector = inputGeometryBlock.asVector(); - if (inputGeometryVector == null) { - return eval(page.getPositionCount(), inputGeometryBlock); - } - return eval(page.getPositionCount(), inputGeometryVector); - } - } - - @Override - public long baseRamBytesUsed() { - long baseRamBytesUsed = BASE_RAM_BYTES_USED; - baseRamBytesUsed += inputGeometry.baseRamBytesUsed(); - return baseRamBytesUsed; - } - - public BytesRefBlock eval(int positionCount, BytesRefBlock inputGeometryBlock) { - try(BytesRefBlock.Builder result = driverContext.blockFactory().newBytesRefBlockBuilder(positionCount)) { - BytesRef inputGeometryScratch = new BytesRef(); - position: for (int p = 0; p < positionCount; p++) { - switch (inputGeometryBlock.getValueCount(p)) { - case 0: - result.appendNull(); - continue position; - case 1: - break; - default: - warnings().registerException(new IllegalArgumentException("single-value function encountered multi-value")); - result.appendNull(); - continue position; - } - BytesRef inputGeometry = inputGeometryBlock.getBytesRef(inputGeometryBlock.getFirstValueIndex(p), inputGeometryScratch); - try { - result.appendBytesRef(StSimplify.processNonFoldableGeoAndConstantTolerance(inputGeometry, this.inputTolerance)); - } catch (IllegalArgumentException e) { - warnings().registerException(e); - result.appendNull(); - } - } - return result.build(); - } - } - - public BytesRefBlock eval(int positionCount, BytesRefVector inputGeometryVector) { - try(BytesRefBlock.Builder result = driverContext.blockFactory().newBytesRefBlockBuilder(positionCount)) { - BytesRef inputGeometryScratch = new BytesRef(); - position: for (int p = 0; p < positionCount; p++) { - BytesRef inputGeometry = inputGeometryVector.getBytesRef(p, inputGeometryScratch); - try { - result.appendBytesRef(StSimplify.processNonFoldableGeoAndConstantTolerance(inputGeometry, this.inputTolerance)); - } catch (IllegalArgumentException e) { - warnings().registerException(e); - result.appendNull(); - } - } - return result.build(); - } - } - - @Override - public String toString() { - return "StSimplifyNonFoldableGeoAndFoldableIntToleranceEvaluator[" + "inputGeometry=" + inputGeometry + ", inputTolerance=" + inputTolerance + "]"; - } - - @Override - public void close() { - Releasables.closeExpectNoException(inputGeometry); - } - - private Warnings warnings() { - if (warnings == null) { - this.warnings = Warnings.createWarnings( - driverContext.warningsMode(), - source.source().getLineNumber(), - source.source().getColumnNumber(), - source.text() - ); - } - return warnings; - } - - static class Factory implements EvalOperator.ExpressionEvaluator.Factory { - private final Source source; - - private final EvalOperator.ExpressionEvaluator.Factory inputGeometry; - - private final int inputTolerance; - - public Factory(Source source, EvalOperator.ExpressionEvaluator.Factory inputGeometry, - int inputTolerance) { - this.source = source; - this.inputGeometry = inputGeometry; - this.inputTolerance = inputTolerance; - } - - @Override - public StSimplifyNonFoldableGeoAndFoldableIntToleranceEvaluator get(DriverContext context) { - return new StSimplifyNonFoldableGeoAndFoldableIntToleranceEvaluator(source, inputGeometry.get(context), inputTolerance, context); - } - - @Override - public String toString() { - return "StSimplifyNonFoldableGeoAndFoldableIntToleranceEvaluator[" + "inputGeometry=" + inputGeometry + ", inputTolerance=" + inputTolerance + "]"; - } - } -} diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplify.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplify.java index b635810e7bc05..75c03bbb43f3c 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplify.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplify.java @@ -29,7 +29,6 @@ import java.io.IOException; import java.util.List; -import static org.elasticsearch.xpack.esql.core.type.DataType.GEO_SHAPE; import static org.elasticsearch.xpack.esql.core.util.SpatialCoordinateTypes.UNSPECIFIED; public class StSimplify extends EsqlScalarFunction { @@ -42,7 +41,7 @@ public class StSimplify extends EsqlScalarFunction { Expression tolerance; @FunctionInfo( - returnType = "geo_shape", + returnType = { "geo_point", "geo_shape", "cartesian_point", "cartesian_shape" }, description = "Simplifies the input geometry with a given tolerance.", examples = @Example(file = "spatial", tag = "st_simplify") ) @@ -67,7 +66,7 @@ private StSimplify(StreamInput in) throws IOException { @Override public DataType dataType() { - return GEO_SHAPE; + return geometry.dataType(); } @Override From 1aed14a6a4ba13008e0da2f4e0e4d5a9d67fb042 Mon Sep 17 00:00:00 2001 From: ncordon Date: Tue, 18 Nov 2025 20:22:09 +0100 Subject: [PATCH 18/45] Goes through some pr feedback --- .../core/util/SpatialCoordinateTypes.java | 18 +++++++- .../src/main/resources/spatial.csv-spec | 42 +++++++++---------- ...eometryAndFoldableToleranceEvaluator.java} | 18 ++++---- ...eometryAndFoldableToleranceEvaluator.java} | 18 ++++---- .../function/scalar/spatial/StSimplify.java | 16 +++---- 5 files changed, 63 insertions(+), 49 deletions(-) rename x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/{StSimplifyFoldableGeoAndFoldableToleranceEvaluator.java => StSimplifyFoldableGeometryAndFoldableToleranceEvaluator.java} (74%) rename x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/{StSimplifyNonFoldableGeoAndFoldableToleranceEvaluator.java => StSimplifyNonFoldableGeometryAndFoldableToleranceEvaluator.java} (83%) diff --git a/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/util/SpatialCoordinateTypes.java b/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/util/SpatialCoordinateTypes.java index f894b05d72b20..93f913a4c3c15 100644 --- a/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/util/SpatialCoordinateTypes.java +++ b/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/util/SpatialCoordinateTypes.java @@ -132,7 +132,23 @@ public Geometry wkbToGeometry(BytesRef wkb) { public org.locationtech.jts.geom.Geometry wkbToJtsGeometry(BytesRef wkb) throws ParseException, IllegalArgumentException { String wkt = wkbToWkt(wkb); if (wkt.startsWith("BBOX")) { - throw new IllegalArgumentException("Input geometry cannot be a BBOX"); + String[] bboxValues = wkt.substring(wkt.indexOf('(') + 1, wkt.indexOf(')')).split(","); + if (bboxValues.length != 4) { + throw new IllegalArgumentException("BBOX must have 4 coordinates"); + } + double topX = Double.parseDouble(bboxValues[0].trim()); + double bottomX = Double.parseDouble(bboxValues[1].trim()); + double leftY = Double.parseDouble(bboxValues[2].trim()); + double rightY = Double.parseDouble(bboxValues[3].trim()); + + wkt = String.format( + "POLYGON ((%f %f, %f %f, %f %f, %f %f, %f %f))", + topX, leftY, // upper left + topX, rightY, // upper right + bottomX, rightY, // lower right + bottomX, leftY, // lower left + topX, leftY // close the polygon + ); } WKTReader reader = new WKTReader(); return reader.read(wkt); diff --git a/x-pack/plugin/esql/qa/testFixtures/src/main/resources/spatial.csv-spec b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/spatial.csv-spec index b7391ad548902..522fef36244fa 100644 --- a/x-pack/plugin/esql/qa/testFixtures/src/main/resources/spatial.csv-spec +++ b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/spatial.csv-spec @@ -3035,7 +3035,7 @@ required_capability: st_simplify FROM airports | SORT name | LIMIT 5 -| EVAL result = st_simplify(TO_GEOSHAPE("POLYGON((-10 -60, 120 -60, 120 60, -10 60, -10 -60))"), 1.0) +| EVAL result = ST_SIMPLIFY(TO_GEOSHAPE("POLYGON((-10 -60, 120 -60, 120 60, -10 60, -10 -60))"), 1.0) | KEEP name, result ; @@ -3054,13 +3054,13 @@ required_capability: st_simplify FROM airports | SORT name | LIMIT 5 -| EVAL result = st_simplify(location, 0.0) +| EVAL result = ST_SIMPLIFY(location, 0.0) | KEEP location, result // end::st_simplify[] ; // tag::st_simplify-result[] -location:geo_point | result:geo_shape +location:geo_point | result:geo_point POINT (41.857756722253 9.61267784753569) | POINT (41.857756722253 9.61267784753569) POINT (112.711418617258 -7.92998002840567) | POINT (112.711418617258 -7.92998002840567) POINT (-3.93221929167636 5.2543984451492) | POINT (-3.93221929167636 5.2543984451492) @@ -3073,7 +3073,7 @@ stSimplifyNoSimplification required_capability: st_simplify ROW geo_shape = TO_GEOSHAPE("POLYGON((0 0, 1 0.1, 2 0, 2 2, 1 1.9, 0 2, 0 0))") -| EVAL result = st_simplify(geo_shape, 0.01) +| EVAL result = ST_SIMPLIFY(geo_shape, 0.01) | KEEP result ; @@ -3085,7 +3085,7 @@ stSimplifyWithSimplification required_capability: st_simplify ROW geo_shape = TO_GEOSHAPE("POLYGON((0 0, 1 0.1, 2 0, 2 2, 1 1.9, 0 2, 0 0))") -| EVAL result = st_simplify(geo_shape, 0.2) +| EVAL result = ST_SIMPLIFY(geo_shape, 0.2) | KEEP result ; @@ -3097,7 +3097,7 @@ stSimplifyEmptySimplification required_capability: st_simplify ROW geo_shape = TO_GEOSHAPE("POLYGON((0 0, 1 0.1, 2 0, 2 2, 1 1.9, 0 2, 0 0))") -| EVAL result = st_simplify(geo_shape, 2.0) +| EVAL result = ST_SIMPLIFY(geo_shape, 2.0) | KEEP result ; @@ -3109,11 +3109,11 @@ stSimplifyNull required_capability: st_simplify ROW geo_shape = NULL -| EVAL result = st_simplify(geo_shape, 2.0) +| EVAL result = ST_SIMPLIFY(geo_shape, 2.0) | KEEP result ; -result:geo_shape +result:null NULL ; @@ -3125,11 +3125,11 @@ required_capability: st_simplify ROW wkt = ["POINT(97.11 75.53)", "POINT(80.93 72.77)"] | MV_EXPAND wkt | EVAL pt = TO_CARTESIANPOINT(wkt) -| EVAL result = st_simplify(pt, 2.0) +| EVAL result = ST_SIMPLIFY(pt, 2.0) | KEEP result ; -result:geo_shape +result:cartesian_point POINT (97.11 75.53) POINT (80.93 72.77) ; @@ -3140,11 +3140,11 @@ required_capability: st_simplify ROW wkt = ["POINT(97.11 75.53)", "POLYGON((0 0, 1 0.1, 2 0, 2 2, 1 1.9, 0 2, 0 0))"] | MV_EXPAND wkt | EVAL geom = TO_CARTESIANSHAPE(wkt) -| EVAL result = st_simplify(geom, 0.2) +| EVAL result = ST_SIMPLIFY(geom, 0.2) | KEEP result ; -result:geo_shape +result:cartesian_shape POINT (97.11 75.53) POLYGON ((0.0 0.0, 0.0 2.0, 2.0 2.0, 2.0 0.0, 0.0 0.0)) ; @@ -3153,7 +3153,7 @@ stSimplifyWithIntegerTolerance required_capability: st_simplify ROW geo_shape = TO_GEOSHAPE("POLYGON((0 0, 1 0.1, 2 0, 2 2, 1 1.9, 0 2, 0 0))") -| EVAL result = st_simplify(geo_shape, 2) +| EVAL result = ST_SIMPLIFY(geo_shape, 2) | KEEP result ; @@ -3161,18 +3161,14 @@ result:geo_shape POLYGON EMPTY ; -stSimplifyMultiPoints +stSimplifyBbox required_capability: st_simplify -ROW geo_point = ["MULTIPOINT(0 0, 2 0)"] -| MV_EXPAND geo_point -| EVAL geo_shape = TO_GEOSHAPE(geo_point) -| EVAL result = st_simplify(geo_shape, 0.2) -| KEEP result +ROW geo_shape = TO_GEOSHAPE("BBOX (0, 2, 2, 0)") +| EVAL result1 = ST_SIMPLIFY(geo_shape, 2), result2 = ST_SIMPLIFY(geo_shape, 0.5) +| KEEP result1, result2 ; -result:geo_shape -POLYGON EMPTY -POLYGON EMPTY -POLYGON EMPTY +result1:geo_shape | result2:geo_shape +POLYGON EMPTY | POLYGON ((0.0 2.0, 2.0 2.0, 2.0 0.0, 0.0 0.0, 0.0 2.0)) ; diff --git a/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyFoldableGeoAndFoldableToleranceEvaluator.java b/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyFoldableGeometryAndFoldableToleranceEvaluator.java similarity index 74% rename from x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyFoldableGeoAndFoldableToleranceEvaluator.java rename to x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyFoldableGeometryAndFoldableToleranceEvaluator.java index 2dbb0b9203311..686d402202f55 100644 --- a/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyFoldableGeoAndFoldableToleranceEvaluator.java +++ b/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyFoldableGeometryAndFoldableToleranceEvaluator.java @@ -21,8 +21,8 @@ * {@link EvalOperator.ExpressionEvaluator} implementation for {@link StSimplify}. * This class is generated. Edit {@code EvaluatorImplementer} instead. */ -public final class StSimplifyFoldableGeoAndFoldableToleranceEvaluator implements EvalOperator.ExpressionEvaluator { - private static final long BASE_RAM_BYTES_USED = RamUsageEstimator.shallowSizeOfInstance(StSimplifyFoldableGeoAndFoldableToleranceEvaluator.class); +public final class StSimplifyFoldableGeometryAndFoldableToleranceEvaluator implements EvalOperator.ExpressionEvaluator { + private static final long BASE_RAM_BYTES_USED = RamUsageEstimator.shallowSizeOfInstance(StSimplifyFoldableGeometryAndFoldableToleranceEvaluator.class); private final Source source; @@ -34,8 +34,8 @@ public final class StSimplifyFoldableGeoAndFoldableToleranceEvaluator implements private Warnings warnings; - public StSimplifyFoldableGeoAndFoldableToleranceEvaluator(Source source, BytesRef inputGeometry, - double inputTolerance, DriverContext driverContext) { + public StSimplifyFoldableGeometryAndFoldableToleranceEvaluator(Source source, + BytesRef inputGeometry, double inputTolerance, DriverContext driverContext) { this.source = source; this.inputGeometry = inputGeometry; this.inputTolerance = inputTolerance; @@ -57,7 +57,7 @@ public BytesRefBlock eval(int positionCount) { try(BytesRefBlock.Builder result = driverContext.blockFactory().newBytesRefBlockBuilder(positionCount)) { position: for (int p = 0; p < positionCount; p++) { try { - result.appendBytesRef(StSimplify.processFoldableGeoAndFoldableTolerance(this.inputGeometry, this.inputTolerance)); + result.appendBytesRef(StSimplify.processFoldableGeometryAndFoldableTolerance(this.inputGeometry, this.inputTolerance)); } catch (IllegalArgumentException e) { warnings().registerException(e); result.appendNull(); @@ -69,7 +69,7 @@ public BytesRefBlock eval(int positionCount) { @Override public String toString() { - return "StSimplifyFoldableGeoAndFoldableToleranceEvaluator[" + "inputGeometry=" + inputGeometry + ", inputTolerance=" + inputTolerance + "]"; + return "StSimplifyFoldableGeometryAndFoldableToleranceEvaluator[" + "inputGeometry=" + inputGeometry + ", inputTolerance=" + inputTolerance + "]"; } @Override @@ -102,13 +102,13 @@ public Factory(Source source, BytesRef inputGeometry, double inputTolerance) { } @Override - public StSimplifyFoldableGeoAndFoldableToleranceEvaluator get(DriverContext context) { - return new StSimplifyFoldableGeoAndFoldableToleranceEvaluator(source, inputGeometry, inputTolerance, context); + public StSimplifyFoldableGeometryAndFoldableToleranceEvaluator get(DriverContext context) { + return new StSimplifyFoldableGeometryAndFoldableToleranceEvaluator(source, inputGeometry, inputTolerance, context); } @Override public String toString() { - return "StSimplifyFoldableGeoAndFoldableToleranceEvaluator[" + "inputGeometry=" + inputGeometry + ", inputTolerance=" + inputTolerance + "]"; + return "StSimplifyFoldableGeometryAndFoldableToleranceEvaluator[" + "inputGeometry=" + inputGeometry + ", inputTolerance=" + inputTolerance + "]"; } } } diff --git a/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyNonFoldableGeoAndFoldableToleranceEvaluator.java b/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyNonFoldableGeometryAndFoldableToleranceEvaluator.java similarity index 83% rename from x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyNonFoldableGeoAndFoldableToleranceEvaluator.java rename to x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyNonFoldableGeometryAndFoldableToleranceEvaluator.java index 81de3cde35e4d..e1028d75c59b9 100644 --- a/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyNonFoldableGeoAndFoldableToleranceEvaluator.java +++ b/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyNonFoldableGeometryAndFoldableToleranceEvaluator.java @@ -23,8 +23,8 @@ * {@link EvalOperator.ExpressionEvaluator} implementation for {@link StSimplify}. * This class is generated. Edit {@code EvaluatorImplementer} instead. */ -public final class StSimplifyNonFoldableGeoAndFoldableToleranceEvaluator implements EvalOperator.ExpressionEvaluator { - private static final long BASE_RAM_BYTES_USED = RamUsageEstimator.shallowSizeOfInstance(StSimplifyNonFoldableGeoAndFoldableToleranceEvaluator.class); +public final class StSimplifyNonFoldableGeometryAndFoldableToleranceEvaluator implements EvalOperator.ExpressionEvaluator { + private static final long BASE_RAM_BYTES_USED = RamUsageEstimator.shallowSizeOfInstance(StSimplifyNonFoldableGeometryAndFoldableToleranceEvaluator.class); private final Source source; @@ -36,7 +36,7 @@ public final class StSimplifyNonFoldableGeoAndFoldableToleranceEvaluator impleme private Warnings warnings; - public StSimplifyNonFoldableGeoAndFoldableToleranceEvaluator(Source source, + public StSimplifyNonFoldableGeometryAndFoldableToleranceEvaluator(Source source, EvalOperator.ExpressionEvaluator inputGeometry, double inputTolerance, DriverContext driverContext) { this.source = source; @@ -80,7 +80,7 @@ public BytesRefBlock eval(int positionCount, BytesRefBlock inputGeometryBlock) { } BytesRef inputGeometry = inputGeometryBlock.getBytesRef(inputGeometryBlock.getFirstValueIndex(p), inputGeometryScratch); try { - result.appendBytesRef(StSimplify.processNonFoldableGeoAndConstantTolerance(inputGeometry, this.inputTolerance)); + result.appendBytesRef(StSimplify.processNonFoldableGeometryAndConstantTolerance(inputGeometry, this.inputTolerance)); } catch (IllegalArgumentException e) { warnings().registerException(e); result.appendNull(); @@ -96,7 +96,7 @@ public BytesRefBlock eval(int positionCount, BytesRefVector inputGeometryVector) position: for (int p = 0; p < positionCount; p++) { BytesRef inputGeometry = inputGeometryVector.getBytesRef(p, inputGeometryScratch); try { - result.appendBytesRef(StSimplify.processNonFoldableGeoAndConstantTolerance(inputGeometry, this.inputTolerance)); + result.appendBytesRef(StSimplify.processNonFoldableGeometryAndConstantTolerance(inputGeometry, this.inputTolerance)); } catch (IllegalArgumentException e) { warnings().registerException(e); result.appendNull(); @@ -108,7 +108,7 @@ public BytesRefBlock eval(int positionCount, BytesRefVector inputGeometryVector) @Override public String toString() { - return "StSimplifyNonFoldableGeoAndFoldableToleranceEvaluator[" + "inputGeometry=" + inputGeometry + ", inputTolerance=" + inputTolerance + "]"; + return "StSimplifyNonFoldableGeometryAndFoldableToleranceEvaluator[" + "inputGeometry=" + inputGeometry + ", inputTolerance=" + inputTolerance + "]"; } @Override @@ -143,13 +143,13 @@ public Factory(Source source, EvalOperator.ExpressionEvaluator.Factory inputGeom } @Override - public StSimplifyNonFoldableGeoAndFoldableToleranceEvaluator get(DriverContext context) { - return new StSimplifyNonFoldableGeoAndFoldableToleranceEvaluator(source, inputGeometry.get(context), inputTolerance, context); + public StSimplifyNonFoldableGeometryAndFoldableToleranceEvaluator get(DriverContext context) { + return new StSimplifyNonFoldableGeometryAndFoldableToleranceEvaluator(source, inputGeometry.get(context), inputTolerance, context); } @Override public String toString() { - return "StSimplifyNonFoldableGeoAndFoldableToleranceEvaluator[" + "inputGeometry=" + inputGeometry + ", inputTolerance=" + inputTolerance + "]"; + return "StSimplifyNonFoldableGeometryAndFoldableToleranceEvaluator[" + "inputGeometry=" + inputGeometry + ", inputTolerance=" + inputTolerance + "]"; } } } diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplify.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplify.java index 75c03bbb43f3c..157ab47c70c88 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplify.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplify.java @@ -42,7 +42,9 @@ public class StSimplify extends EsqlScalarFunction { @FunctionInfo( returnType = { "geo_point", "geo_shape", "cartesian_point", "cartesian_shape" }, - description = "Simplifies the input geometry with a given tolerance.", + description = "Simplifies he input geometry by applying the Douglas-Peucker algorithm with a specified tolerance. " + + "Vertices that fall within the tolerance distance from the simplified shape are removed. " + + "Note that the resulting geometry may be invalid, even if the original input was valid.", examples = @Example(file = "spatial", tag = "st_simplify") ) public StSimplify( @@ -115,9 +117,9 @@ public EvalOperator.ExpressionEvaluator.Factory toEvaluator(ToEvaluator toEvalua double inputTolerance = getInputTolerance(toleranceExpression); if (geometry.foldable()) { BytesRef inputGeometry = (BytesRef) geometry.fold(toEvaluator.foldCtx()); - return new StSimplifyFoldableGeoAndFoldableToleranceEvaluator.Factory(source(), inputGeometry, inputTolerance); + return new StSimplifyFoldableGeometryAndFoldableToleranceEvaluator.Factory(source(), inputGeometry, inputTolerance); } - return new StSimplifyNonFoldableGeoAndFoldableToleranceEvaluator.Factory(source(), geometryEvaluator, inputTolerance); + return new StSimplifyNonFoldableGeometryAndFoldableToleranceEvaluator.Factory(source(), geometryEvaluator, inputTolerance); } private static double getInputTolerance(Object toleranceExpression) { @@ -137,13 +139,13 @@ private static double getInputTolerance(Object toleranceExpression) { return inputTolerance; } - @Evaluator(extraName = "NonFoldableGeoAndFoldableTolerance", warnExceptions = { IllegalArgumentException.class }) - static BytesRef processNonFoldableGeoAndConstantTolerance(BytesRef inputGeometry, @Fixed double inputTolerance) { + @Evaluator(extraName = "NonFoldableGeometryAndFoldableTolerance", warnExceptions = { IllegalArgumentException.class }) + static BytesRef processNonFoldableGeometryAndConstantTolerance(BytesRef inputGeometry, @Fixed double inputTolerance) { return geoSourceAndConstantTolerance(inputGeometry, inputTolerance); } - @Evaluator(extraName = "FoldableGeoAndFoldableTolerance", warnExceptions = { IllegalArgumentException.class }) - static BytesRef processFoldableGeoAndFoldableTolerance(@Fixed BytesRef inputGeometry, @Fixed double inputTolerance) { + @Evaluator(extraName = "FoldableGeometryAndFoldableTolerance", warnExceptions = { IllegalArgumentException.class }) + static BytesRef processFoldableGeometryAndFoldableTolerance(@Fixed BytesRef inputGeometry, @Fixed double inputTolerance) { return geoSourceAndConstantTolerance(inputGeometry, inputTolerance); } From 253619e42524344163b59a2dc5f2f762c1f7282b Mon Sep 17 00:00:00 2001 From: elasticsearchmachine Date: Tue, 18 Nov 2025 19:30:08 +0000 Subject: [PATCH 19/45] [CI] Auto commit changes from spotless --- .../esql/core/util/SpatialCoordinateTypes.java | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/util/SpatialCoordinateTypes.java b/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/util/SpatialCoordinateTypes.java index 93f913a4c3c15..6d105e662adea 100644 --- a/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/util/SpatialCoordinateTypes.java +++ b/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/util/SpatialCoordinateTypes.java @@ -143,11 +143,16 @@ public org.locationtech.jts.geom.Geometry wkbToJtsGeometry(BytesRef wkb) throws wkt = String.format( "POLYGON ((%f %f, %f %f, %f %f, %f %f, %f %f))", - topX, leftY, // upper left - topX, rightY, // upper right - bottomX, rightY, // lower right - bottomX, leftY, // lower left - topX, leftY // close the polygon + topX, + leftY, // upper left + topX, + rightY, // upper right + bottomX, + rightY, // lower right + bottomX, + leftY, // lower left + topX, + leftY // close the polygon ); } WKTReader reader = new WKTReader(); From 9465cb0cfd89ab352239a86c5c53801f4ae8a821 Mon Sep 17 00:00:00 2001 From: ncordon Date: Wed, 19 Nov 2025 01:09:43 +0100 Subject: [PATCH 20/45] Addresses more pr feedback --- .../functions/description/st_simplify.md | 2 +- .../functions/examples/st_simplify.md | 4 +- .../_snippets/functions/types/st_simplify.md | 6 +- .../definition/functions/st_simplify.json | 10 +- .../esql/kibana/docs/functions/st_simplify.md | 4 +- .../core/util/SpatialCoordinateTypes.java | 2 + .../src/main/resources/spatial.csv-spec | 6 +- ...GeometryAndFoldableToleranceEvaluator.java | 114 ------------------ .../function/scalar/spatial/StSimplify.java | 20 +-- .../scalar/spatial/StSimplifyTests.java | 64 +++++----- 10 files changed, 61 insertions(+), 171 deletions(-) delete mode 100644 x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyFoldableGeometryAndFoldableToleranceEvaluator.java diff --git a/docs/reference/query-languages/esql/_snippets/functions/description/st_simplify.md b/docs/reference/query-languages/esql/_snippets/functions/description/st_simplify.md index fb858e0a475a0..cf4f8189d1efc 100644 --- a/docs/reference/query-languages/esql/_snippets/functions/description/st_simplify.md +++ b/docs/reference/query-languages/esql/_snippets/functions/description/st_simplify.md @@ -2,5 +2,5 @@ **Description** -Simplifies the input geometry with a given tolerance. +Simplifies he input geometry by applying the Douglas-Peucker algorithm with a specified tolerance. Vertices that fall within the tolerance distance from the simplified shape are removed. Note that the resulting geometry may be invalid, even if the original input was valid. diff --git a/docs/reference/query-languages/esql/_snippets/functions/examples/st_simplify.md b/docs/reference/query-languages/esql/_snippets/functions/examples/st_simplify.md index baf132dd1e231..10d4feee2d2e3 100644 --- a/docs/reference/query-languages/esql/_snippets/functions/examples/st_simplify.md +++ b/docs/reference/query-languages/esql/_snippets/functions/examples/st_simplify.md @@ -6,11 +6,11 @@ FROM airports | SORT name | LIMIT 5 -| EVAL result = st_simplify(location, 0.0) +| EVAL result = ST_SIMPLIFY(location, 0.0) | KEEP location, result ``` -| location:geo_point | result:geo_shape | +| location:geo_point | result:geo_point | | --- | --- | | POINT (41.857756722253 9.61267784753569) | POINT (41.857756722253 9.61267784753569) | | POINT (112.711418617258 -7.92998002840567) | POINT (112.711418617258 -7.92998002840567) | diff --git a/docs/reference/query-languages/esql/_snippets/functions/types/st_simplify.md b/docs/reference/query-languages/esql/_snippets/functions/types/st_simplify.md index 2bce42953a668..4902ee3237cd4 100644 --- a/docs/reference/query-languages/esql/_snippets/functions/types/st_simplify.md +++ b/docs/reference/query-languages/esql/_snippets/functions/types/st_simplify.md @@ -4,8 +4,8 @@ | geometry | tolerance | result | | --- | --- | --- | -| cartesian_point | double | geo_shape | -| cartesian_shape | double | geo_shape | -| geo_point | double | geo_shape | +| cartesian_point | double | cartesian_point | +| cartesian_shape | double | cartesian_shape | +| geo_point | double | geo_point | | geo_shape | double | geo_shape | diff --git a/docs/reference/query-languages/esql/kibana/definition/functions/st_simplify.json b/docs/reference/query-languages/esql/kibana/definition/functions/st_simplify.json index 9ebd0d87c3e09..19abef36181b4 100644 --- a/docs/reference/query-languages/esql/kibana/definition/functions/st_simplify.json +++ b/docs/reference/query-languages/esql/kibana/definition/functions/st_simplify.json @@ -2,7 +2,7 @@ "comment" : "This is generated by ESQL's AbstractFunctionTestCase. Do not edit it. See ../README.md for how to regenerate it.", "type" : "scalar", "name" : "st_simplify", - "description" : "Simplifies the input geometry with a given tolerance.", + "description" : "Simplifies he input geometry by applying the Douglas-Peucker algorithm with a specified tolerance. Vertices that fall within the tolerance distance from the simplified shape are removed. Note that the resulting geometry may be invalid, even if the original input was valid.", "signatures" : [ { "params" : [ @@ -20,7 +20,7 @@ } ], "variadic" : false, - "returnType" : "geo_shape" + "returnType" : "cartesian_point" }, { "params" : [ @@ -38,7 +38,7 @@ } ], "variadic" : false, - "returnType" : "geo_shape" + "returnType" : "cartesian_shape" }, { "params" : [ @@ -56,7 +56,7 @@ } ], "variadic" : false, - "returnType" : "geo_shape" + "returnType" : "geo_point" }, { "params" : [ @@ -78,7 +78,7 @@ } ], "examples" : [ - "FROM airports\n| SORT name\n| LIMIT 5\n| EVAL result = st_simplify(location, 0.0)\n| KEEP location, result" + "FROM airports\n| SORT name\n| LIMIT 5\n| EVAL result = ST_SIMPLIFY(location, 0.0)\n| KEEP location, result" ], "preview" : false, "snapshot_only" : false diff --git a/docs/reference/query-languages/esql/kibana/docs/functions/st_simplify.md b/docs/reference/query-languages/esql/kibana/docs/functions/st_simplify.md index 77ab6fc41b671..803257db24048 100644 --- a/docs/reference/query-languages/esql/kibana/docs/functions/st_simplify.md +++ b/docs/reference/query-languages/esql/kibana/docs/functions/st_simplify.md @@ -1,12 +1,12 @@ % This is generated by ESQL's AbstractFunctionTestCase. Do not edit it. See ../README.md for how to regenerate it. ### ST SIMPLIFY -Simplifies the input geometry with a given tolerance. +Simplifies he input geometry by applying the Douglas-Peucker algorithm with a specified tolerance. Vertices that fall within the tolerance distance from the simplified shape are removed. Note that the resulting geometry may be invalid, even if the original input was valid. ```esql FROM airports | SORT name | LIMIT 5 -| EVAL result = st_simplify(location, 0.0) +| EVAL result = ST_SIMPLIFY(location, 0.0) | KEEP location, result ``` diff --git a/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/util/SpatialCoordinateTypes.java b/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/util/SpatialCoordinateTypes.java index 6d105e662adea..0bc1c283cf1dc 100644 --- a/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/util/SpatialCoordinateTypes.java +++ b/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/util/SpatialCoordinateTypes.java @@ -21,6 +21,7 @@ import org.locationtech.jts.io.WKTWriter; import java.nio.ByteOrder; +import java.util.Locale; import static org.apache.lucene.geo.GeoEncodingUtils.encodeLatitude; import static org.apache.lucene.geo.GeoEncodingUtils.encodeLongitude; @@ -142,6 +143,7 @@ public org.locationtech.jts.geom.Geometry wkbToJtsGeometry(BytesRef wkb) throws double rightY = Double.parseDouble(bboxValues[3].trim()); wkt = String.format( + Locale.ROOT, "POLYGON ((%f %f, %f %f, %f %f, %f %f, %f %f))", topX, leftY, // upper left diff --git a/x-pack/plugin/esql/qa/testFixtures/src/main/resources/spatial.csv-spec b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/spatial.csv-spec index 522fef36244fa..67defa88bfc34 100644 --- a/x-pack/plugin/esql/qa/testFixtures/src/main/resources/spatial.csv-spec +++ b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/spatial.csv-spec @@ -3120,9 +3120,7 @@ NULL stSimplifyCartesianPoint required_capability: st_simplify -# TODO Why cannot we use a latitud outside of -90, 90 when there are tests -# that use the TO_CARTESIANPOINT that are doing it already? -ROW wkt = ["POINT(97.11 75.53)", "POINT(80.93 72.77)"] +ROW wkt = ["POINT(-97.11 95.53)", "POINT(80.93 72.77)"] | MV_EXPAND wkt | EVAL pt = TO_CARTESIANPOINT(wkt) | EVAL result = ST_SIMPLIFY(pt, 2.0) @@ -3130,7 +3128,7 @@ ROW wkt = ["POINT(97.11 75.53)", "POINT(80.93 72.77)"] ; result:cartesian_point -POINT (97.11 75.53) +POINT (-97.11 95.53) POINT (80.93 72.77) ; diff --git a/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyFoldableGeometryAndFoldableToleranceEvaluator.java b/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyFoldableGeometryAndFoldableToleranceEvaluator.java deleted file mode 100644 index 686d402202f55..0000000000000 --- a/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyFoldableGeometryAndFoldableToleranceEvaluator.java +++ /dev/null @@ -1,114 +0,0 @@ -// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one -// or more contributor license agreements. Licensed under the Elastic License -// 2.0; you may not use this file except in compliance with the Elastic License -// 2.0. -package org.elasticsearch.xpack.esql.expression.function.scalar.spatial; - -import java.lang.IllegalArgumentException; -import java.lang.Override; -import java.lang.String; -import org.apache.lucene.util.BytesRef; -import org.apache.lucene.util.RamUsageEstimator; -import org.elasticsearch.compute.data.Block; -import org.elasticsearch.compute.data.BytesRefBlock; -import org.elasticsearch.compute.data.Page; -import org.elasticsearch.compute.operator.DriverContext; -import org.elasticsearch.compute.operator.EvalOperator; -import org.elasticsearch.compute.operator.Warnings; -import org.elasticsearch.xpack.esql.core.tree.Source; - -/** - * {@link EvalOperator.ExpressionEvaluator} implementation for {@link StSimplify}. - * This class is generated. Edit {@code EvaluatorImplementer} instead. - */ -public final class StSimplifyFoldableGeometryAndFoldableToleranceEvaluator implements EvalOperator.ExpressionEvaluator { - private static final long BASE_RAM_BYTES_USED = RamUsageEstimator.shallowSizeOfInstance(StSimplifyFoldableGeometryAndFoldableToleranceEvaluator.class); - - private final Source source; - - private final BytesRef inputGeometry; - - private final double inputTolerance; - - private final DriverContext driverContext; - - private Warnings warnings; - - public StSimplifyFoldableGeometryAndFoldableToleranceEvaluator(Source source, - BytesRef inputGeometry, double inputTolerance, DriverContext driverContext) { - this.source = source; - this.inputGeometry = inputGeometry; - this.inputTolerance = inputTolerance; - this.driverContext = driverContext; - } - - @Override - public Block eval(Page page) { - return eval(page.getPositionCount()); - } - - @Override - public long baseRamBytesUsed() { - long baseRamBytesUsed = BASE_RAM_BYTES_USED; - return baseRamBytesUsed; - } - - public BytesRefBlock eval(int positionCount) { - try(BytesRefBlock.Builder result = driverContext.blockFactory().newBytesRefBlockBuilder(positionCount)) { - position: for (int p = 0; p < positionCount; p++) { - try { - result.appendBytesRef(StSimplify.processFoldableGeometryAndFoldableTolerance(this.inputGeometry, this.inputTolerance)); - } catch (IllegalArgumentException e) { - warnings().registerException(e); - result.appendNull(); - } - } - return result.build(); - } - } - - @Override - public String toString() { - return "StSimplifyFoldableGeometryAndFoldableToleranceEvaluator[" + "inputGeometry=" + inputGeometry + ", inputTolerance=" + inputTolerance + "]"; - } - - @Override - public void close() { - } - - private Warnings warnings() { - if (warnings == null) { - this.warnings = Warnings.createWarnings( - driverContext.warningsMode(), - source.source().getLineNumber(), - source.source().getColumnNumber(), - source.text() - ); - } - return warnings; - } - - static class Factory implements EvalOperator.ExpressionEvaluator.Factory { - private final Source source; - - private final BytesRef inputGeometry; - - private final double inputTolerance; - - public Factory(Source source, BytesRef inputGeometry, double inputTolerance) { - this.source = source; - this.inputGeometry = inputGeometry; - this.inputTolerance = inputTolerance; - } - - @Override - public StSimplifyFoldableGeometryAndFoldableToleranceEvaluator get(DriverContext context) { - return new StSimplifyFoldableGeometryAndFoldableToleranceEvaluator(source, inputGeometry, inputTolerance, context); - } - - @Override - public String toString() { - return "StSimplifyFoldableGeometryAndFoldableToleranceEvaluator[" + "inputGeometry=" + inputGeometry + ", inputTolerance=" + inputTolerance + "]"; - } - } -} diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplify.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplify.java index 157ab47c70c88..a5c202546f298 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplify.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplify.java @@ -15,6 +15,7 @@ import org.elasticsearch.compute.ann.Fixed; import org.elasticsearch.compute.operator.EvalOperator; import org.elasticsearch.xpack.esql.core.expression.Expression; +import org.elasticsearch.xpack.esql.core.expression.FoldContext; import org.elasticsearch.xpack.esql.core.tree.NodeInfo; import org.elasticsearch.xpack.esql.core.tree.Source; import org.elasticsearch.xpack.esql.core.type.DataType; @@ -68,7 +69,7 @@ private StSimplify(StreamInput in) throws IOException { @Override public DataType dataType() { - return geometry.dataType(); + return tolerance.dataType() == DataType.NULL ? DataType.NULL : geometry.dataType(); } @Override @@ -115,10 +116,6 @@ public EvalOperator.ExpressionEvaluator.Factory toEvaluator(ToEvaluator toEvalua } var toleranceExpression = tolerance.fold(toEvaluator.foldCtx()); double inputTolerance = getInputTolerance(toleranceExpression); - if (geometry.foldable()) { - BytesRef inputGeometry = (BytesRef) geometry.fold(toEvaluator.foldCtx()); - return new StSimplifyFoldableGeometryAndFoldableToleranceEvaluator.Factory(source(), inputGeometry, inputTolerance); - } return new StSimplifyNonFoldableGeometryAndFoldableToleranceEvaluator.Factory(source(), geometryEvaluator, inputTolerance); } @@ -144,13 +141,16 @@ static BytesRef processNonFoldableGeometryAndConstantTolerance(BytesRef inputGeo return geoSourceAndConstantTolerance(inputGeometry, inputTolerance); } - @Evaluator(extraName = "FoldableGeometryAndFoldableTolerance", warnExceptions = { IllegalArgumentException.class }) - static BytesRef processFoldableGeometryAndFoldableTolerance(@Fixed BytesRef inputGeometry, @Fixed double inputTolerance) { - return geoSourceAndConstantTolerance(inputGeometry, inputTolerance); - } - @Override public boolean foldable() { return geometry.foldable() && tolerance.foldable(); } + + @Override + public Object fold(FoldContext foldCtx) { + var toleranceExpression = tolerance.fold(foldCtx); + double inputTolerance = getInputTolerance(toleranceExpression); + BytesRef inputGeometry = (BytesRef) geometry.fold(foldCtx); + return geoSourceAndConstantTolerance(inputGeometry, inputTolerance); + } } diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyTests.java index 0d3d6ef1b742f..40ab0fd21209a 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyTests.java @@ -48,11 +48,18 @@ public StSimplifyTests(@Name("TestCase") Supplier tes @ParametersFactory public static Iterable parameters() { final List suppliers = new ArrayList<>(); - addTestCaseSuppliers(suppliers, new DataType[] { GEO_POINT }, GEO_SHAPE, StSimplifyTests::valueOf); - addTestCaseSuppliers(suppliers, new DataType[] { CARTESIAN_POINT }, GEO_SHAPE, StSimplifyTests::valueOf); - addTestCaseSuppliers(suppliers, new DataType[] { CARTESIAN_SHAPE }, GEO_SHAPE, StSimplifyTests::valueOf); - addTestCaseSuppliers(suppliers, new DataType[] { GEO_SHAPE }, GEO_SHAPE, StSimplifyTests::valueOf); - return parameterSuppliersFromTypedDataWithDefaultChecks(true, suppliers); + addTestCaseSuppliers(suppliers, GEO_POINT, StSimplifyTests::valueOf); + addTestCaseSuppliers(suppliers, CARTESIAN_POINT, StSimplifyTests::valueOf); + addTestCaseSuppliers(suppliers, CARTESIAN_SHAPE, StSimplifyTests::valueOf); + addTestCaseSuppliers(suppliers, GEO_SHAPE, StSimplifyTests::valueOf); + + var testSuppliers = anyNullIsNull( + randomizeBytesRefsOffset(suppliers), + (nullPosition, nullValueDataType, original) -> nullValueDataType == DataType.NULL ? DataType.NULL : original.expectedType(), + (nullPosition, nullData, original) -> nullData.isForceLiteral() ? Matchers.equalTo("LiteralsEvaluator[lit=null]") : original + ); + + return parameterSuppliersFromTypedData(testSuppliers); } public static TestCaseSupplier.TypedDataSupplier testCaseSupplier(DataType dataType) { @@ -73,33 +80,30 @@ private static String getFunctionClassName() { protected static void addTestCaseSuppliers( List suppliers, - DataType[] dataTypes, - DataType gridType, + DataType spatialType, BiFunction expectedValue ) { - for (DataType spatialType : dataTypes) { - TestCaseSupplier.TypedDataSupplier geometrySupplier = testCaseSupplier(spatialType); - String testName = spatialType.typeName() + " with tolerance."; - - suppliers.add(new TestCaseSupplier(testName, List.of(spatialType, DOUBLE), () -> { - TestCaseSupplier.TypedData geoTypedData = geometrySupplier.get(); - BytesRef geometry = (BytesRef) geoTypedData.data(); - double tolerance = randomDoubleBetween(0, 100, true); - TestCaseSupplier.TypedData toleranceData = new TestCaseSupplier.TypedData(tolerance, DOUBLE, "tolerance"); - toleranceData = toleranceData.forceLiteral(); - String evaluatorName = "NonFoldableGeoAndFoldableToleranceEvaluator[inputGeometry=Attribute[channel=0], inputTolerance=" - + tolerance - + "]"; - var expectedResult = expectedValue.apply(geometry, tolerance); - - return new TestCaseSupplier.TestCase( - List.of(geoTypedData, toleranceData), - getFunctionClassName() + evaluatorName, - gridType, - Matchers.equalTo(expectedResult) - ); - })); - } + TestCaseSupplier.TypedDataSupplier geometrySupplier = testCaseSupplier(spatialType); + String testName = spatialType.typeName() + " with tolerance."; + + suppliers.add(new TestCaseSupplier(testName, List.of(spatialType, DOUBLE), () -> { + TestCaseSupplier.TypedData geoTypedData = geometrySupplier.get(); + BytesRef geometry = (BytesRef) geoTypedData.data(); + double tolerance = randomDoubleBetween(0, 100, true); + TestCaseSupplier.TypedData toleranceData = new TestCaseSupplier.TypedData(tolerance, DOUBLE, "tolerance"); + toleranceData = toleranceData.forceLiteral(); + String evaluatorName = "NonFoldableGeometryAndFoldableToleranceEvaluator[inputGeometry=Attribute[channel=0], inputTolerance=" + + tolerance + + "]"; + var expectedResult = expectedValue.apply(geometry, tolerance); + + return new TestCaseSupplier.TestCase( + List.of(geoTypedData, toleranceData), + getFunctionClassName() + evaluatorName, + spatialType, + Matchers.equalTo(expectedResult) + ); + })); } private static BytesRef valueOf(BytesRef wkb, double tolerance) { From 90b075d47a5fd5b86370800236dab9c2b40ff876 Mon Sep 17 00:00:00 2001 From: ncordon Date: Thu, 20 Nov 2025 01:35:32 +0100 Subject: [PATCH 21/45] Bumps jts version and addresses more pr feedback --- build-tools-internal/version.properties | 2 +- gradle/verification-metadata.xml | 5 +++ modules/legacy-geo/build.gradle | 2 +- .../core/util/SpatialCoordinateTypes.java | 40 ++++++++----------- .../src/main/resources/spatial.csv-spec | 28 ++++++++----- .../function/scalar/spatial/StSimplify.java | 4 ++ 6 files changed, 47 insertions(+), 34 deletions(-) diff --git a/build-tools-internal/version.properties b/build-tools-internal/version.properties index c37cd2a4782fd..bbc1d9e7ad551 100644 --- a/build-tools-internal/version.properties +++ b/build-tools-internal/version.properties @@ -5,7 +5,7 @@ bundled_jdk_vendor = openjdk bundled_jdk = 25.0.1+8@2fbf10d8c78e40bd87641c434705079d # optional dependencies spatial4j = 0.7 -jts = 1.15.0 +jts = 1.20.0 jackson = 2.15.0 snakeyaml = 2.0 icu4j = 77.1 diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml index 86146db87dbc1..6ebe3acf21012 100644 --- a/gradle/verification-metadata.xml +++ b/gradle/verification-metadata.xml @@ -4483,6 +4483,11 @@ + + + + + diff --git a/modules/legacy-geo/build.gradle b/modules/legacy-geo/build.gradle index f26c828897642..9b1c81b0271fa 100644 --- a/modules/legacy-geo/build.gradle +++ b/modules/legacy-geo/build.gradle @@ -17,7 +17,7 @@ esplugin { dependencies { api "org.apache.lucene:lucene-spatial-extras:${versions.lucene}" api "org.locationtech.spatial4j:spatial4j:${versions.spatial4j}" - api "org.locationtech.jts:jts-core:${versions.jts}" + api "org.locationtech.jts:jts-core:1.15.0" implementation "org.apache.lucene:lucene-spatial3d:${versions.lucene}" implementation "com.fasterxml.jackson.core:jackson-core:${versions.jackson}" implementation "io.sgr:s2-geometry-library-java:1.0.1" diff --git a/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/util/SpatialCoordinateTypes.java b/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/util/SpatialCoordinateTypes.java index 0bc1c283cf1dc..dadb097d13447 100644 --- a/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/util/SpatialCoordinateTypes.java +++ b/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/util/SpatialCoordinateTypes.java @@ -12,16 +12,21 @@ import org.apache.lucene.util.BytesRef; import org.elasticsearch.geometry.Geometry; import org.elasticsearch.geometry.Point; +import org.elasticsearch.geometry.Rectangle; import org.elasticsearch.geometry.utils.GeographyValidator; import org.elasticsearch.geometry.utils.GeometryValidator; import org.elasticsearch.geometry.utils.WellKnownBinary; import org.elasticsearch.geometry.utils.WellKnownText; +import org.locationtech.jts.geom.Coordinate; +import org.locationtech.jts.geom.GeometryFactory; +import org.locationtech.jts.geom.LinearRing; +import org.locationtech.jts.geom.Polygon; +import org.locationtech.jts.geom.impl.CoordinateArraySequence; import org.locationtech.jts.io.ParseException; import org.locationtech.jts.io.WKTReader; import org.locationtech.jts.io.WKTWriter; import java.nio.ByteOrder; -import java.util.Locale; import static org.apache.lucene.geo.GeoEncodingUtils.encodeLatitude; import static org.apache.lucene.geo.GeoEncodingUtils.encodeLongitude; @@ -133,29 +138,18 @@ public Geometry wkbToGeometry(BytesRef wkb) { public org.locationtech.jts.geom.Geometry wkbToJtsGeometry(BytesRef wkb) throws ParseException, IllegalArgumentException { String wkt = wkbToWkt(wkb); if (wkt.startsWith("BBOX")) { - String[] bboxValues = wkt.substring(wkt.indexOf('(') + 1, wkt.indexOf(')')).split(","); - if (bboxValues.length != 4) { - throw new IllegalArgumentException("BBOX must have 4 coordinates"); + Geometry geometry = WellKnownBinary.fromWKB(GeometryValidator.NOOP, true, wkb.bytes); + if (geometry instanceof Rectangle rect) { + var bottomLeft = new Coordinate(rect.getMinX(), rect.getMinY()); + var bottomRight = new Coordinate(rect.getMaxX(), rect.getMinY()); + var topRight = new Coordinate(rect.getMaxX(), rect.getMaxY()); + var topLeft = new Coordinate(rect.getMinX(), rect.getMaxY()); + + var coordinates = new Coordinate[] { bottomLeft, topLeft, topRight, bottomRight, bottomLeft }; + var geomFactory = new GeometryFactory(); + var linearRing = new LinearRing(new CoordinateArraySequence(coordinates), geomFactory); + return new Polygon(linearRing, null, geomFactory); } - double topX = Double.parseDouble(bboxValues[0].trim()); - double bottomX = Double.parseDouble(bboxValues[1].trim()); - double leftY = Double.parseDouble(bboxValues[2].trim()); - double rightY = Double.parseDouble(bboxValues[3].trim()); - - wkt = String.format( - Locale.ROOT, - "POLYGON ((%f %f, %f %f, %f %f, %f %f, %f %f))", - topX, - leftY, // upper left - topX, - rightY, // upper right - bottomX, - rightY, // lower right - bottomX, - leftY, // lower left - topX, - leftY // close the polygon - ); } WKTReader reader = new WKTReader(); return reader.read(wkt); diff --git a/x-pack/plugin/esql/qa/testFixtures/src/main/resources/spatial.csv-spec b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/spatial.csv-spec index e258ba55d2e03..f88f81c190b0d 100644 --- a/x-pack/plugin/esql/qa/testFixtures/src/main/resources/spatial.csv-spec +++ b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/spatial.csv-spec @@ -3109,11 +3109,11 @@ FROM airports ; name:text | result:geo_shape -Aba Tenna D. Yilma Int'l | POLYGON ((-10.0 -60.0, -10.0 60.0, 120.0 60.0, 120.0 -60.0, -10.0 -60.0)) -Abdul Rachman Saleh | POLYGON ((-10.0 -60.0, -10.0 60.0, 120.0 60.0, 120.0 -60.0, -10.0 -60.0)) -Abidjan Port Bouet | POLYGON ((-10.0 -60.0, -10.0 60.0, 120.0 60.0, 120.0 -60.0, -10.0 -60.0)) -Abu Dhabi Int'l | POLYGON ((-10.0 -60.0, -10.0 60.0, 120.0 60.0, 120.0 -60.0, -10.0 -60.0)) -Abuja Int'l | POLYGON ((-10.0 -60.0, -10.0 60.0, 120.0 60.0, 120.0 -60.0, -10.0 -60.0)) +Aba Tenna D. Yilma Int'l | POLYGON ((-10.0 -60.0, 120.0 -60.0, 120.0 60.0, -10.0 60.0, -10.0 -60.0)) +Abdul Rachman Saleh | POLYGON ((-10.0 -60.0, 120.0 -60.0, 120.0 60.0, -10.0 60.0, -10.0 -60.0)) +Abidjan Port Bouet | POLYGON ((-10.0 -60.0, 120.0 -60.0, 120.0 60.0, -10.0 60.0, -10.0 -60.0)) +Abu Dhabi Int'l | POLYGON ((-10.0 -60.0, 120.0 -60.0, 120.0 60.0, -10.0 60.0, -10.0 -60.0)) +Abuja Int'l | POLYGON ((-10.0 -60.0, 120.0 -60.0, 120.0 60.0, -10.0 60.0, -10.0 -60.0)) ; stSimplifyMultiRowWithPoints @@ -3147,7 +3147,7 @@ ROW geo_shape = TO_GEOSHAPE("POLYGON((0 0, 1 0.1, 2 0, 2 2, 1 1.9, 0 2, 0 0))") ; result:geo_shape -POLYGON ((0.0 0.0, 0.0 2.0, 1.0 1.9, 2.0 2.0, 2.0 0.0, 1.0 0.1, 0.0 0.0)) +POLYGON ((0.0 0.0, 1.0 0.1, 2.0 0.0, 2.0 2.0, 1.0 1.9, 0.0 2.0, 0.0 0.0)) ; stSimplifyWithSimplification @@ -3159,7 +3159,17 @@ ROW geo_shape = TO_GEOSHAPE("POLYGON((0 0, 1 0.1, 2 0, 2 2, 1 1.9, 0 2, 0 0))") ; result:geo_shape -POLYGON ((0.0 0.0, 0.0 2.0, 2.0 2.0, 2.0 0.0, 0.0 0.0)) +POLYGON ((0.0 0.0, 2.0 0.0, 2.0 2.0, 0.0 2.0, 0.0 0.0)) +; + +stSimplifyFoldableGeometry +required_capability: st_simplify + +ROW result = ST_SIMPLIFY(TO_GEOSHAPE("POLYGON((0 0, 1 0.1, 2 0, 2 2, 1 1.9, 0 2, 0 0))") , 0.2) +; + +result:geo_shape +POLYGON ((0.0 0.0, 2.0 0.0, 2.0 2.0, 0.0 2.0, 0.0 0.0)) ; stSimplifyEmptySimplification @@ -3213,7 +3223,7 @@ ROW wkt = ["POINT(97.11 75.53)", "POLYGON((0 0, 1 0.1, 2 0, 2 2, 1 1.9, 0 2, 0 0 result:cartesian_shape POINT (97.11 75.53) -POLYGON ((0.0 0.0, 0.0 2.0, 2.0 2.0, 2.0 0.0, 0.0 0.0)) +POLYGON ((0.0 0.0, 2.0 0.0, 2.0 2.0, 0.0 2.0, 0.0 0.0)) ; stSimplifyWithIntegerTolerance @@ -3237,5 +3247,5 @@ ROW geo_shape = TO_GEOSHAPE("BBOX (0, 2, 2, 0)") ; result1:geo_shape | result2:geo_shape -POLYGON EMPTY | POLYGON ((0.0 2.0, 2.0 2.0, 2.0 0.0, 0.0 0.0, 0.0 2.0)) +POLYGON EMPTY | POLYGON ((0.0 0.0, 0.0 2.0, 2.0 2.0, 2.0 0.0, 0.0 0.0)) ; diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplify.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplify.java index a5c202546f298..b67108bd65717 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplify.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplify.java @@ -20,6 +20,8 @@ import org.elasticsearch.xpack.esql.core.tree.Source; import org.elasticsearch.xpack.esql.core.type.DataType; import org.elasticsearch.xpack.esql.expression.function.Example; +import org.elasticsearch.xpack.esql.expression.function.FunctionAppliesTo; +import org.elasticsearch.xpack.esql.expression.function.FunctionAppliesToLifecycle; import org.elasticsearch.xpack.esql.expression.function.FunctionInfo; import org.elasticsearch.xpack.esql.expression.function.Param; import org.elasticsearch.xpack.esql.expression.function.scalar.EsqlScalarFunction; @@ -46,6 +48,8 @@ public class StSimplify extends EsqlScalarFunction { description = "Simplifies he input geometry by applying the Douglas-Peucker algorithm with a specified tolerance. " + "Vertices that fall within the tolerance distance from the simplified shape are removed. " + "Note that the resulting geometry may be invalid, even if the original input was valid.", + preview = true, + appliesTo = { @FunctionAppliesTo(lifeCycle = FunctionAppliesToLifecycle.PREVIEW, version = "9.3.0") }, examples = @Example(file = "spatial", tag = "st_simplify") ) public StSimplify( From 5d9f9366cfa4e593c13a6ee0d040cdcace75d921 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nacho=20Cord=C3=B3n?= Date: Wed, 26 Nov 2025 11:28:12 +0100 Subject: [PATCH 22/45] Update docs/changelog/136309.yaml --- docs/changelog/136309.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/changelog/136309.yaml b/docs/changelog/136309.yaml index a5a02d4351b68..4b317903e3c78 100644 --- a/docs/changelog/136309.yaml +++ b/docs/changelog/136309.yaml @@ -2,4 +2,5 @@ pr: 136309 summary: Adds ST_SIMPLIFY geo spatial function area: ES|QL type: enhancement -issues: [] +issues: + - 44747 From a1e9d29cc153cdf6e580af0f9a121244183a0963 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nacho=20Cord=C3=B3n?= Date: Tue, 25 Nov 2025 17:14:52 +0100 Subject: [PATCH 23/45] Bumps jts version to 1.20.0 (#138351) --- gradle/verification-metadata.xml | 5 ----- modules/legacy-geo/build.gradle | 2 +- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml index acf8c4c382d6b..b49889e698b9c 100644 --- a/gradle/verification-metadata.xml +++ b/gradle/verification-metadata.xml @@ -4483,11 +4483,6 @@ - - - - - diff --git a/modules/legacy-geo/build.gradle b/modules/legacy-geo/build.gradle index 9b1c81b0271fa..f26c828897642 100644 --- a/modules/legacy-geo/build.gradle +++ b/modules/legacy-geo/build.gradle @@ -17,7 +17,7 @@ esplugin { dependencies { api "org.apache.lucene:lucene-spatial-extras:${versions.lucene}" api "org.locationtech.spatial4j:spatial4j:${versions.spatial4j}" - api "org.locationtech.jts:jts-core:1.15.0" + api "org.locationtech.jts:jts-core:${versions.jts}" implementation "org.apache.lucene:lucene-spatial3d:${versions.lucene}" implementation "com.fasterxml.jackson.core:jackson-core:${versions.jackson}" implementation "io.sgr:s2-geometry-library-java:1.0.1" From ed20dd999bd8d65860aa0fa870db0b3a0a8d2e35 Mon Sep 17 00:00:00 2001 From: ncordon Date: Wed, 26 Nov 2025 18:49:05 +0100 Subject: [PATCH 24/45] Adds PostGIS tests and tweaks the tolerance description --- .../src/main/resources/spatial.csv-spec | 72 +++++++++++++++++++ .../function/scalar/spatial/StSimplify.java | 8 ++- 2 files changed, 78 insertions(+), 2 deletions(-) diff --git a/x-pack/plugin/esql/qa/testFixtures/src/main/resources/spatial.csv-spec b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/spatial.csv-spec index f88f81c190b0d..9c7dc4447a82c 100644 --- a/x-pack/plugin/esql/qa/testFixtures/src/main/resources/spatial.csv-spec +++ b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/spatial.csv-spec @@ -3249,3 +3249,75 @@ ROW geo_shape = TO_GEOSHAPE("BBOX (0, 2, 2, 0)") result1:geo_shape | result2:geo_shape POLYGON EMPTY | POLYGON ((0.0 0.0, 0.0 2.0, 2.0 2.0, 2.0 0.0, 0.0 0.0)) ; + +stSimplifyPostGisFirstExample +required_capability: st_simplify + +ROW geom = TO_GEOSHAPE("POLYGON((11 3,10.914448613738104 1.694738077799484,10.659258262890683 0.411809548974793,10.238795325112868 -0.826834323650898,9.660254037844387 -1.999999999999999,8.933533402912353 -3.087614290087205,8.071067811865476 -4.071067811865475,7.087614290087206 -4.933533402912351,6.000000000000001 -5.660254037844386,4.826834323650898 -6.238795325112868,3.58819045102521 -6.659258262890681,2.305261922200517 -6.914448613738104,1 -7,-0.305261922200514 -6.914448613738106,-1.588190451025206 -6.659258262890683,-2.826834323650895 -6.238795325112868,-3.999999999999998 -5.660254037844387,-5.087614290087204 -4.933533402912354,-6.071067811865475 -4.071067811865476,-6.93353340291235 -3.087614290087209,-7.660254037844386 -2.000000000000004,-8.238795325112868 -0.826834323650899,-8.659258262890681 0.41180954897479,-8.914448613738104 1.69473807779948,-9 3,-8.914448613738106 4.305261922200513,-8.659258262890685 5.588190451025204,-8.23879532511287 6.826834323650893,-7.660254037844389 7.999999999999997,-6.933533402912354 9.087614290087203,-6.071067811865479 10.071067811865472,-5.087614290087209 10.93353340291235,-4.000000000000004 11.660254037844384,-2.826834323650903 12.238795325112864,-1.588190451025215 12.659258262890681,-0.305261922200525 12.914448613738102,1 13,2.305261922200513 12.914448613738106,3.588190451025203 12.659258262890685,4.826834323650892 12.23879532511287,5.999999999999993 11.66025403784439,7.087614290087199 10.933533402912357,8.071067811865474 10.071067811865477,8.93353340291235 9.08761429008721,9.660254037844384 8.000000000000004,10.238795325112864 6.826834323650904,10.659258262890681 5.588190451025216,10.914448613738102 4.305261922200526,11 3))") +| EVAL np01_notbadcircle = ST_Simplify(geom, 0.1) +| KEEP np01_notbadcircle +; + +np01_notbadcircle:geo_shape + POLYGON((11.0 3.0,10.914448613738104 1.694738077799484,10.238795325112868 -0.826834323650898,8.933533402912353 -3.087614290087205,8.071067811865476 -4.071067811865475,6.000000000000001 -5.660254037844386,4.826834323650898 -6.238795325112868,2.305261922200517 -6.914448613738104,1.0 -7.0,-0.305261922200514 -6.914448613738106,-2.826834323650895 -6.238795325112868,-5.087614290087204 -4.933533402912354,-6.071067811865475 -4.071067811865476,-7.660254037844386 -2.000000000000004,-8.238795325112868 -0.826834323650899,-8.914448613738104 1.69473807779948,-9.0 3.0,-8.914448613738106 4.305261922200513,-8.23879532511287 6.826834323650893,-7.660254037844389 7.999999999999997,-6.071067811865479 10.071067811865472,-4.000000000000004 11.660254037844384,-2.826834323650903 12.238795325112864,-1.588190451025215 12.659258262890681,1.0 13.0,2.305261922200513 12.914448613738106,4.826834323650892 12.23879532511287,5.999999999999993 11.66025403784439,8.071067811865474 10.071067811865477,8.93353340291235 9.08761429008721,10.238795325112864 6.826834323650904,10.659258262890681 5.588190451025216,11.0 3.0)) +; + +stSimplifyPostGisSecondExample +required_capability: st_simplify + +ROW geom = TO_GEOSHAPE("POLYGON((11 3,10.914448613738104 1.694738077799484,10.659258262890683 0.411809548974793,10.238795325112868 -0.826834323650898,9.660254037844387 -1.999999999999999,8.933533402912353 -3.087614290087205,8.071067811865476 -4.071067811865475,7.087614290087206 -4.933533402912351,6.000000000000001 -5.660254037844386,4.826834323650898 -6.238795325112868,3.58819045102521 -6.659258262890681,2.305261922200517 -6.914448613738104,1 -7,-0.305261922200514 -6.914448613738106,-1.588190451025206 -6.659258262890683,-2.826834323650895 -6.238795325112868,-3.999999999999998 -5.660254037844387,-5.087614290087204 -4.933533402912354,-6.071067811865475 -4.071067811865476,-6.93353340291235 -3.087614290087209,-7.660254037844386 -2.000000000000004,-8.238795325112868 -0.826834323650899,-8.659258262890681 0.41180954897479,-8.914448613738104 1.69473807779948,-9 3,-8.914448613738106 4.305261922200513,-8.659258262890685 5.588190451025204,-8.23879532511287 6.826834323650893,-7.660254037844389 7.999999999999997,-6.933533402912354 9.087614290087203,-6.071067811865479 10.071067811865472,-5.087614290087209 10.93353340291235,-4.000000000000004 11.660254037844384,-2.826834323650903 12.238795325112864,-1.588190451025215 12.659258262890681,-0.305261922200525 12.914448613738102,1 13,2.305261922200513 12.914448613738106,3.588190451025203 12.659258262890685,4.826834323650892 12.23879532511287,5.999999999999993 11.66025403784439,7.087614290087199 10.933533402912357,8.071067811865474 10.071067811865477,8.93353340291235 9.08761429008721,9.660254037844384 8.000000000000004,10.238795325112864 6.826834323650904,10.659258262890681 5.588190451025216,10.914448613738102 4.305261922200526,11 3))") +| EVAL np05_notquitecircle = ST_Simplify(geom, 0.5) +| KEEP np05_notquitecircle +; + +np05_notquitecircle:geo_shape +POLYGON((11 3,10.238795325112868 -0.826834323650898,8.071067811865476 -4.071067811865475,4.826834323650898 -6.238795325112868,1 -7,-2.826834323650895 -6.238795325112868,-6.071067811865475 -4.071067811865476,-8.238795325112868 -0.826834323650899,-9 3,-8.23879532511287 6.826834323650893,-6.071067811865479 10.071067811865472,-2.826834323650903 12.238795325112864,1 13,4.826834323650892 12.23879532511287,8.071067811865474 10.071067811865477,10.238795325112864 6.826834323650904,11 3)) +; + +stSimplifyPostGisThirdExample +required_capability: st_simplify + +ROW geom = TO_GEOSHAPE("POLYGON((11 3,10.914448613738104 1.694738077799484,10.659258262890683 0.411809548974793,10.238795325112868 -0.826834323650898,9.660254037844387 -1.999999999999999,8.933533402912353 -3.087614290087205,8.071067811865476 -4.071067811865475,7.087614290087206 -4.933533402912351,6.000000000000001 -5.660254037844386,4.826834323650898 -6.238795325112868,3.58819045102521 -6.659258262890681,2.305261922200517 -6.914448613738104,1 -7,-0.305261922200514 -6.914448613738106,-1.588190451025206 -6.659258262890683,-2.826834323650895 -6.238795325112868,-3.999999999999998 -5.660254037844387,-5.087614290087204 -4.933533402912354,-6.071067811865475 -4.071067811865476,-6.93353340291235 -3.087614290087209,-7.660254037844386 -2.000000000000004,-8.238795325112868 -0.826834323650899,-8.659258262890681 0.41180954897479,-8.914448613738104 1.69473807779948,-9 3,-8.914448613738106 4.305261922200513,-8.659258262890685 5.588190451025204,-8.23879532511287 6.826834323650893,-7.660254037844389 7.999999999999997,-6.933533402912354 9.087614290087203,-6.071067811865479 10.071067811865472,-5.087614290087209 10.93353340291235,-4.000000000000004 11.660254037844384,-2.826834323650903 12.238795325112864,-1.588190451025215 12.659258262890681,-0.305261922200525 12.914448613738102,1 13,2.305261922200513 12.914448613738106,3.588190451025203 12.659258262890685,4.826834323650892 12.23879532511287,5.999999999999993 11.66025403784439,7.087614290087199 10.933533402912357,8.071067811865474 10.071067811865477,8.93353340291235 9.08761429008721,9.660254037844384 8.000000000000004,10.238795325112864 6.826834323650904,10.659258262890681 5.588190451025216,10.914448613738102 4.305261922200526,11 3))") +| EVAL np1_octagon = ST_Simplify(geom, 1) +| KEEP np1_octagon +; + +np1_octagon:geo_shape +POLYGON((11 3,8.071067811865476 -4.071067811865475,1 -7,-6.071067811865475 -4.071067811865476,-9 3,-6.071067811865479 10.071067811865472,1 13,8.071067811865474 10.071067811865477,11 3)) +; + +stSimplifyPostGisFourthExample +required_capability: st_simplify + +ROW geom = TO_GEOSHAPE("POLYGON((11 3,10.914448613738104 1.694738077799484,10.659258262890683 0.411809548974793,10.238795325112868 -0.826834323650898,9.660254037844387 -1.999999999999999,8.933533402912353 -3.087614290087205,8.071067811865476 -4.071067811865475,7.087614290087206 -4.933533402912351,6.000000000000001 -5.660254037844386,4.826834323650898 -6.238795325112868,3.58819045102521 -6.659258262890681,2.305261922200517 -6.914448613738104,1 -7,-0.305261922200514 -6.914448613738106,-1.588190451025206 -6.659258262890683,-2.826834323650895 -6.238795325112868,-3.999999999999998 -5.660254037844387,-5.087614290087204 -4.933533402912354,-6.071067811865475 -4.071067811865476,-6.93353340291235 -3.087614290087209,-7.660254037844386 -2.000000000000004,-8.238795325112868 -0.826834323650899,-8.659258262890681 0.41180954897479,-8.914448613738104 1.69473807779948,-9 3,-8.914448613738106 4.305261922200513,-8.659258262890685 5.588190451025204,-8.23879532511287 6.826834323650893,-7.660254037844389 7.999999999999997,-6.933533402912354 9.087614290087203,-6.071067811865479 10.071067811865472,-5.087614290087209 10.93353340291235,-4.000000000000004 11.660254037844384,-2.826834323650903 12.238795325112864,-1.588190451025215 12.659258262890681,-0.305261922200525 12.914448613738102,1 13,2.305261922200513 12.914448613738106,3.588190451025203 12.659258262890685,4.826834323650892 12.23879532511287,5.999999999999993 11.66025403784439,7.087614290087199 10.933533402912357,8.071067811865474 10.071067811865477,8.93353340291235 9.08761429008721,9.660254037844384 8.000000000000004,10.238795325112864 6.826834323650904,10.659258262890681 5.588190451025216,10.914448613738102 4.305261922200526,11 3))") +| EVAL np100_geometrygoesaway = ST_Simplify(geom, 100) +| KEEP np100_geometrygoesaway +; + +np100_geometrygoesaway:geo_shape +POLYGON EMPTY +; + +stSimplifyPostGisFifthExample +required_capability: st_simplify + +ROW geom = TO_CARTESIANSHAPE("MULTILINESTRING ((20 180, 20 150, 50 150, 50 100, 110 150, 150 140, 170 120), (20 10, 80 30, 90 120), (90 120, 130 130), (130 130, 130 70, 160 40, 180 60, 180 90, 140 80), (50 40, 70 40, 80 70, 70 60, 60 60, 50 50, 50 40))") +| EVAL result = ST_Simplify(geom, 40) +| KEEP result +; + +result:cartesian_shape +MULTILINESTRING((20 180,50 100,170 120),(20 10,90 120),(90 120,130 130),(130 130,160 40,180 90,140 80),(50 40,80 70,50 40)) +; + +stSimplifyPostGisSixthExample +required_capability: st_simplify + +ROW geom = TO_CARTESIANSHAPE("MULTIPOLYGON (((90 110, 80 180, 50 160, 10 170, 10 140, 20 110, 90 110)), ((40 80, 100 100, 120 160, 170 180, 190 70, 140 10, 110 40, 60 40, 40 80), (180 70, 170 110, 142.5 128.5, 128.5 77.5, 90 60, 180 70)))") +| EVAL result = ST_Simplify(geom, 40) +| KEEP result +; + +result:cartesian_shape +POLYGON ((40.0 80.0, 79.0 110.0, 20.0 110.0, 10.0 170.0, 80.0 180.0, 88.91089108910892 117.62376237623762, 170.0 180.0, 156.93726937269372 105.97785977859779, 142.5 128.5, 90.0 60.0, 150.0 66.66666666666667, 140.0 10.0, 40.0 80.0)) +; diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplify.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplify.java index b67108bd65717..51e665ce3aa1c 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplify.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplify.java @@ -45,7 +45,7 @@ public class StSimplify extends EsqlScalarFunction { @FunctionInfo( returnType = { "geo_point", "geo_shape", "cartesian_point", "cartesian_shape" }, - description = "Simplifies he input geometry by applying the Douglas-Peucker algorithm with a specified tolerance. " + description = "Simplifies the input geometry by applying the Douglas-Peucker algorithm with a specified tolerance. " + "Vertices that fall within the tolerance distance from the simplified shape are removed. " + "Note that the resulting geometry may be invalid, even if the original input was valid.", preview = true, @@ -60,7 +60,11 @@ public StSimplify( description = "Expression of type `geo_point`, `geo_shape`, `cartesian_point` or `cartesian_shape`. " + "If `null`, the function returns `null`." ) Expression geometry, - @Param(name = "tolerance", type = { "double" }, description = "Tolerance for the geometry simplification") Expression tolerance + @Param( + name = "tolerance", + type = { "double" }, + description = "Tolerance for the geometry simplification, in the units of the input SRS" + ) Expression tolerance ) { super(source, List.of(geometry, tolerance)); this.geometry = geometry; From 465cbecdf70011778835705fefbe9cfa699aaf0f Mon Sep 17 00:00:00 2001 From: ncordon Date: Mon, 1 Dec 2025 11:50:17 +0100 Subject: [PATCH 25/45] Fixes automatic tests --- .../functions/description/st_simplify.md | 2 +- .../_snippets/functions/layout/st_simplify.md | 4 ++ .../functions/parameters/st_simplify.md | 2 +- .../_snippets/functions/types/st_simplify.md | 2 - .../definition/functions/st_simplify.json | 44 ++----------------- .../esql/kibana/docs/functions/st_simplify.md | 2 +- .../core/util/SpatialCoordinateTypes.java | 2 +- 7 files changed, 12 insertions(+), 46 deletions(-) diff --git a/docs/reference/query-languages/esql/_snippets/functions/description/st_simplify.md b/docs/reference/query-languages/esql/_snippets/functions/description/st_simplify.md index cf4f8189d1efc..066834132bb4b 100644 --- a/docs/reference/query-languages/esql/_snippets/functions/description/st_simplify.md +++ b/docs/reference/query-languages/esql/_snippets/functions/description/st_simplify.md @@ -2,5 +2,5 @@ **Description** -Simplifies he input geometry by applying the Douglas-Peucker algorithm with a specified tolerance. Vertices that fall within the tolerance distance from the simplified shape are removed. Note that the resulting geometry may be invalid, even if the original input was valid. +Simplifies the input geometry by applying the Douglas-Peucker algorithm with a specified tolerance. Vertices that fall within the tolerance distance from the simplified shape are removed. Note that the resulting geometry may be invalid, even if the original input was valid. diff --git a/docs/reference/query-languages/esql/_snippets/functions/layout/st_simplify.md b/docs/reference/query-languages/esql/_snippets/functions/layout/st_simplify.md index 16ccee40bf53b..d4aa0090bf964 100644 --- a/docs/reference/query-languages/esql/_snippets/functions/layout/st_simplify.md +++ b/docs/reference/query-languages/esql/_snippets/functions/layout/st_simplify.md @@ -1,6 +1,10 @@ % This is generated by ESQL's AbstractFunctionTestCase. Do not edit it. See ../README.md for how to regenerate it. ## `ST_SIMPLIFY` [esql-st_simplify] +```{applies_to} +stack: preview 9.3.0 +serverless: preview +``` **Syntax** diff --git a/docs/reference/query-languages/esql/_snippets/functions/parameters/st_simplify.md b/docs/reference/query-languages/esql/_snippets/functions/parameters/st_simplify.md index f9a349dee3a32..9271c3d03b100 100644 --- a/docs/reference/query-languages/esql/_snippets/functions/parameters/st_simplify.md +++ b/docs/reference/query-languages/esql/_snippets/functions/parameters/st_simplify.md @@ -6,5 +6,5 @@ : Expression of type `geo_point`, `geo_shape`, `cartesian_point` or `cartesian_shape`. If `null`, the function returns `null`. `tolerance` -: Tolerance for the geometry simplification +: Tolerance for the geometry simplification, in the units of the input SRS diff --git a/docs/reference/query-languages/esql/_snippets/functions/types/st_simplify.md b/docs/reference/query-languages/esql/_snippets/functions/types/st_simplify.md index 4902ee3237cd4..f783da8fd033d 100644 --- a/docs/reference/query-languages/esql/_snippets/functions/types/st_simplify.md +++ b/docs/reference/query-languages/esql/_snippets/functions/types/st_simplify.md @@ -5,7 +5,5 @@ | geometry | tolerance | result | | --- | --- | --- | | cartesian_point | double | cartesian_point | -| cartesian_shape | double | cartesian_shape | | geo_point | double | geo_point | -| geo_shape | double | geo_shape | diff --git a/docs/reference/query-languages/esql/kibana/definition/functions/st_simplify.json b/docs/reference/query-languages/esql/kibana/definition/functions/st_simplify.json index 19abef36181b4..8afa9ec19a254 100644 --- a/docs/reference/query-languages/esql/kibana/definition/functions/st_simplify.json +++ b/docs/reference/query-languages/esql/kibana/definition/functions/st_simplify.json @@ -2,7 +2,7 @@ "comment" : "This is generated by ESQL's AbstractFunctionTestCase. Do not edit it. See ../README.md for how to regenerate it.", "type" : "scalar", "name" : "st_simplify", - "description" : "Simplifies he input geometry by applying the Douglas-Peucker algorithm with a specified tolerance. Vertices that fall within the tolerance distance from the simplified shape are removed. Note that the resulting geometry may be invalid, even if the original input was valid.", + "description" : "Simplifies the input geometry by applying the Douglas-Peucker algorithm with a specified tolerance. Vertices that fall within the tolerance distance from the simplified shape are removed. Note that the resulting geometry may be invalid, even if the original input was valid.", "signatures" : [ { "params" : [ @@ -16,30 +16,12 @@ "name" : "tolerance", "type" : "double", "optional" : false, - "description" : "Tolerance for the geometry simplification" + "description" : "Tolerance for the geometry simplification, in the units of the input SRS" } ], "variadic" : false, "returnType" : "cartesian_point" }, - { - "params" : [ - { - "name" : "geometry", - "type" : "cartesian_shape", - "optional" : false, - "description" : "Expression of type `geo_point`, `geo_shape`, `cartesian_point` or `cartesian_shape`. If `null`, the function returns `null`." - }, - { - "name" : "tolerance", - "type" : "double", - "optional" : false, - "description" : "Tolerance for the geometry simplification" - } - ], - "variadic" : false, - "returnType" : "cartesian_shape" - }, { "params" : [ { @@ -52,34 +34,16 @@ "name" : "tolerance", "type" : "double", "optional" : false, - "description" : "Tolerance for the geometry simplification" + "description" : "Tolerance for the geometry simplification, in the units of the input SRS" } ], "variadic" : false, "returnType" : "geo_point" - }, - { - "params" : [ - { - "name" : "geometry", - "type" : "geo_shape", - "optional" : false, - "description" : "Expression of type `geo_point`, `geo_shape`, `cartesian_point` or `cartesian_shape`. If `null`, the function returns `null`." - }, - { - "name" : "tolerance", - "type" : "double", - "optional" : false, - "description" : "Tolerance for the geometry simplification" - } - ], - "variadic" : false, - "returnType" : "geo_shape" } ], "examples" : [ "FROM airports\n| SORT name\n| LIMIT 5\n| EVAL result = ST_SIMPLIFY(location, 0.0)\n| KEEP location, result" ], - "preview" : false, + "preview" : true, "snapshot_only" : false } diff --git a/docs/reference/query-languages/esql/kibana/docs/functions/st_simplify.md b/docs/reference/query-languages/esql/kibana/docs/functions/st_simplify.md index 803257db24048..596a8c83d8774 100644 --- a/docs/reference/query-languages/esql/kibana/docs/functions/st_simplify.md +++ b/docs/reference/query-languages/esql/kibana/docs/functions/st_simplify.md @@ -1,7 +1,7 @@ % This is generated by ESQL's AbstractFunctionTestCase. Do not edit it. See ../README.md for how to regenerate it. ### ST SIMPLIFY -Simplifies he input geometry by applying the Douglas-Peucker algorithm with a specified tolerance. Vertices that fall within the tolerance distance from the simplified shape are removed. Note that the resulting geometry may be invalid, even if the original input was valid. +Simplifies the input geometry by applying the Douglas-Peucker algorithm with a specified tolerance. Vertices that fall within the tolerance distance from the simplified shape are removed. Note that the resulting geometry may be invalid, even if the original input was valid. ```esql FROM airports diff --git a/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/util/SpatialCoordinateTypes.java b/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/util/SpatialCoordinateTypes.java index dadb097d13447..532a24b6825fe 100644 --- a/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/util/SpatialCoordinateTypes.java +++ b/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/util/SpatialCoordinateTypes.java @@ -138,7 +138,7 @@ public Geometry wkbToGeometry(BytesRef wkb) { public org.locationtech.jts.geom.Geometry wkbToJtsGeometry(BytesRef wkb) throws ParseException, IllegalArgumentException { String wkt = wkbToWkt(wkb); if (wkt.startsWith("BBOX")) { - Geometry geometry = WellKnownBinary.fromWKB(GeometryValidator.NOOP, true, wkb.bytes); + Geometry geometry = WellKnownBinary.fromWKB(GeometryValidator.NOOP, true, wkb.bytes, wkb.offset, wkb.length); if (geometry instanceof Rectangle rect) { var bottomLeft = new Coordinate(rect.getMinX(), rect.getMinY()); var bottomRight = new Coordinate(rect.getMaxX(), rect.getMinY()); From 06f0d742ff7d92f64d4c5c5a2417dab94567dd8e Mon Sep 17 00:00:00 2001 From: ncordon Date: Mon, 1 Dec 2025 14:00:18 +0100 Subject: [PATCH 26/45] Adds more unit tests --- .../_snippets/functions/types/st_simplify.md | 2 + .../scalar/spatial/StSimplifyTests.java | 32 --- .../scalar/spatial/StSimplifyUnitTests.java | 193 ++++++++++++++++++ 3 files changed, 195 insertions(+), 32 deletions(-) create mode 100644 x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyUnitTests.java diff --git a/docs/reference/query-languages/esql/_snippets/functions/types/st_simplify.md b/docs/reference/query-languages/esql/_snippets/functions/types/st_simplify.md index f783da8fd033d..4902ee3237cd4 100644 --- a/docs/reference/query-languages/esql/_snippets/functions/types/st_simplify.md +++ b/docs/reference/query-languages/esql/_snippets/functions/types/st_simplify.md @@ -5,5 +5,7 @@ | geometry | tolerance | result | | --- | --- | --- | | cartesian_point | double | cartesian_point | +| cartesian_shape | double | cartesian_shape | | geo_point | double | geo_point | +| geo_shape | double | geo_shape | diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyTests.java index 40ab0fd21209a..fd82191e8bc3f 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyTests.java @@ -11,11 +11,7 @@ import com.carrotsearch.randomizedtesting.annotations.ParametersFactory; import org.apache.lucene.util.BytesRef; -import org.elasticsearch.compute.data.Block; -import org.elasticsearch.compute.data.BytesRefVectorBlock; -import org.elasticsearch.compute.operator.EvalOperator; import org.elasticsearch.xpack.esql.core.expression.Expression; -import org.elasticsearch.xpack.esql.core.expression.Literal; import org.elasticsearch.xpack.esql.core.tree.Source; import org.elasticsearch.xpack.esql.core.type.DataType; import org.elasticsearch.xpack.esql.expression.function.AbstractScalarFunctionTestCase; @@ -36,8 +32,6 @@ import static org.elasticsearch.xpack.esql.core.type.DataType.GEO_POINT; import static org.elasticsearch.xpack.esql.core.type.DataType.GEO_SHAPE; import static org.elasticsearch.xpack.esql.core.util.SpatialCoordinateTypes.UNSPECIFIED; -import static org.hamcrest.Matchers.containsString; -import static org.junit.Assume.assumeNotNull; @FunctionName("st_simplify") public class StSimplifyTests extends AbstractScalarFunctionTestCase { @@ -123,30 +117,4 @@ private static BytesRef valueOf(BytesRef wkb, double tolerance) { protected Expression build(Source source, List args) { return new StSimplify(source, args.get(0), args.get(1)); } - - protected BytesRef process(Double tolerance) { - Object spatialObj = this.testCase.getDataValues().getFirst(); - assumeNotNull(spatialObj); - assumeTrue("Expected a BytesRef, but got " + spatialObj.getClass(), spatialObj instanceof BytesRef); - BytesRef wkb = (BytesRef) spatialObj; - try ( - EvalOperator.ExpressionEvaluator eval = evaluator( - build(Source.EMPTY, List.of(new Literal(Source.EMPTY, wkb, GEO_POINT), new Literal(Source.EMPTY, tolerance, DOUBLE))) - ).get(driverContext()); - Block block = eval.eval(row(List.of(wkb, tolerance))) - ) { - var result = ((BytesRefVectorBlock) block).asVector().getBytesRef(0, new BytesRef()); - return block.isNull(0) ? null : result; - } - } - - public void testInvalidTolerance() { - IllegalArgumentException ex = expectThrows(IllegalArgumentException.class, () -> process(-1.0)); - assertThat(ex.getMessage(), containsString("tolerance must not be negative")); - } - - // This should succeed - public void testZeroTolerance() { - process(0.0); - } } diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyUnitTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyUnitTests.java new file mode 100644 index 0000000000000..f1c659d1530bd --- /dev/null +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyUnitTests.java @@ -0,0 +1,193 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +package org.elasticsearch.xpack.esql.expression.function.scalar.spatial; + +import org.apache.lucene.util.BytesRef; +import org.elasticsearch.common.breaker.CircuitBreaker; +import org.elasticsearch.common.unit.ByteSizeValue; +import org.elasticsearch.common.util.BigArrays; +import org.elasticsearch.common.util.MockBigArrays; +import org.elasticsearch.common.util.PageCacheRecycler; +import org.elasticsearch.compute.data.Block; +import org.elasticsearch.compute.data.BlockFactory; +import org.elasticsearch.compute.data.BlockUtils; +import org.elasticsearch.compute.data.BytesRefVectorBlock; +import org.elasticsearch.compute.data.ElementType; +import org.elasticsearch.compute.data.Page; +import org.elasticsearch.compute.operator.DriverContext; +import org.elasticsearch.compute.operator.EvalOperator; +import org.elasticsearch.compute.test.BlockTestUtils; +import org.elasticsearch.compute.test.TestBlockFactory; +import org.elasticsearch.test.ESTestCase; +import org.elasticsearch.xpack.esql.core.expression.Expression; +import org.elasticsearch.xpack.esql.core.expression.Literal; +import org.elasticsearch.xpack.esql.core.tree.Source; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import static org.elasticsearch.xpack.esql.core.type.DataType.DOUBLE; +import static org.elasticsearch.xpack.esql.core.type.DataType.GEO_POINT; +import static org.elasticsearch.xpack.esql.core.util.SpatialCoordinateTypes.UNSPECIFIED; +import static org.elasticsearch.xpack.esql.expression.function.AbstractFunctionTestCase.evaluator; +import static org.hamcrest.Matchers.containsString; + +public class StSimplifyUnitTests extends ESTestCase { + + protected Expression build(Source source, List args) { + return new StSimplify(source, args.get(0), args.get(1)); + } + + private Page maybeConvertBytesRefsToOrdinals(Page page) { + boolean anyBytesRef = false; + for (int b = 0; b < page.getBlockCount(); b++) { + if (page.getBlock(b).elementType() == ElementType.BYTES_REF) { + anyBytesRef = true; + break; + } + } + return anyBytesRef && randomBoolean() ? BlockTestUtils.convertBytesRefsToOrdinals(page) : page; + } + + private final List breakers = Collections.synchronizedList(new ArrayList<>()); + + protected final DriverContext driverContext() { + BigArrays bigArrays = new MockBigArrays(PageCacheRecycler.NON_RECYCLING_INSTANCE, ByteSizeValue.ofMb(256)).withCircuitBreaking(); + CircuitBreaker breaker = bigArrays.breakerService().getBreaker(CircuitBreaker.REQUEST); + breakers.add(breaker); + return new DriverContext(bigArrays, new BlockFactory(breaker, bigArrays)); + } + + protected final Page row(List values) { + return maybeConvertBytesRefsToOrdinals(new Page(1, BlockUtils.fromListRow(TestBlockFactory.getNonBreakingInstance(), values))); + } + + protected BytesRef process(String wkt, Double tolerance) { + BytesRef wkb = UNSPECIFIED.wktToWkb(wkt); + try ( + EvalOperator.ExpressionEvaluator eval = evaluator( + build(Source.EMPTY, List.of(new Literal(Source.EMPTY, wkb, GEO_POINT), new Literal(Source.EMPTY, tolerance, DOUBLE))) + ).get(driverContext()); + Block block = eval.eval(row(List.of(wkb, tolerance))) + ) { + var result = ((BytesRefVectorBlock) block).asVector().getBytesRef(0, new BytesRef()); + return block.isNull(0) ? null : result; + } + } + + private final String polygonWkt = "POLYGON((0 0, 1 0.1, 2 0, 2 2, 1 1.9, 0 2, 0 0))"; + + public void testInvalidTolerance() { + IllegalArgumentException ex = expectThrows(IllegalArgumentException.class, () -> process(polygonWkt, -1.0)); + assertThat(ex.getMessage(), containsString("tolerance must not be negative")); + } + + // This should succeed + public void testZeroTolerance() { + process(polygonWkt, 0.0); + } + + public void testBowtie() { + /* This is a bowtie + + ******* + * * + * * + * + * * + * * + ******* + */ + var wkb = process("POLYGON((0 0, 2 2, 0 2, 2 0, 0 0))", 0.0); + var result = UNSPECIFIED.wkbToWkt(wkb); + /* And it gets simplified to the lower triangle + * + * * + * * + * * + ********* + */ + assertEquals("POLYGON ((0.0 0.0, 1.0 1.0, 2.0 0.0, 0.0 0.0))", result); + } + + public void testTwoNonIntersectingPolygons() { + /* Polygon with a hole outside the shell + * * + * * + * * * * + * * + * * + * * + * * * * + */ + var wkb = process("POLYGON( (0 0, 4 0, 4 4, 0 4, 0 0), (5 5, 6 5, 6 6, 5 6, 5 5) )", 0.0); + var result = UNSPECIFIED.wkbToWkt(wkb); + /* It gets simplified to the outer shell + + * * * * + * * + * * + * * + * * * * + */ + assertEquals("POLYGON ((0.0 0.0, 0.0 4.0, 4.0 4.0, 4.0 0.0, 0.0 0.0))", result); + } + + public void testTwoTouchingPolygons() { + /* Two polygons with a touching edge + + * * * * + * * * + * * * + * * + * * * * + + */ + var wkb = process("POLYGON((0 0, 4 0, 4 4, 0 4, 0 0), (4 2, 5 2, 5 3, 4 3, 4 2))", 0.0); + var result = UNSPECIFIED.wkbToWkt(wkb); + /* It gets simplified to the biggest square + + * * * * + * * + * * + * * + * * * * + */ + assertEquals("POLYGON ((0.0 0.0, 0.0 4.0, 4.0 4.0, 4.0 3.0, 4.0 2.0, 4.0 0.0, 0.0 0.0))", result); + } + + public void testLineWithRepeatedPoints() { + var wkb = process("LINESTRING(0 0, 1 1, 1 1, 2 2)", 0.0); + var result = UNSPECIFIED.wkbToWkt(wkb); + assertEquals("LINESTRING (0.0 0.0, 2.0 2.0)", result); + } + + public void testEmptyPolygon() { + var wkb = process("POLYGON EMPTY", 0.0); + var result = UNSPECIFIED.wkbToWkt(wkb); + assertEquals("POLYGON EMPTY", result); + } + + public void testBigTolerance() { + /* Polygon with a hole outside the shell + * * + * * + * * * * + * * + * * + * * + * * * * + */ + var wkb = process("POLYGON( (0 0, 4 0, 4 4, 0 4, 0 0), (5 5, 6 5, 6 6, 5 6, 5 5) )", 10.0); + var result = UNSPECIFIED.wkbToWkt(wkb); + /* It gets simplified to an empty polygon + */ + assertEquals("POLYGON EMPTY", result); + } +} From 9c25ce067819929988bdc1a6fa01b0cbecd8ca8f Mon Sep 17 00:00:00 2001 From: ncordon Date: Mon, 1 Dec 2025 17:51:56 +0100 Subject: [PATCH 27/45] Updates docs --- .../definition/functions/st_simplify.json | 36 +++++++++++++++++++ .../scalar/spatial/StSimplifyTests.java | 2 +- .../scalar/spatial/StSimplifyUnitTests.java | 33 +++++++---------- 3 files changed, 49 insertions(+), 22 deletions(-) diff --git a/docs/reference/query-languages/esql/kibana/definition/functions/st_simplify.json b/docs/reference/query-languages/esql/kibana/definition/functions/st_simplify.json index 8afa9ec19a254..7bdecafd4351a 100644 --- a/docs/reference/query-languages/esql/kibana/definition/functions/st_simplify.json +++ b/docs/reference/query-languages/esql/kibana/definition/functions/st_simplify.json @@ -22,6 +22,24 @@ "variadic" : false, "returnType" : "cartesian_point" }, + { + "params" : [ + { + "name" : "geometry", + "type" : "cartesian_shape", + "optional" : false, + "description" : "Expression of type `geo_point`, `geo_shape`, `cartesian_point` or `cartesian_shape`. If `null`, the function returns `null`." + }, + { + "name" : "tolerance", + "type" : "double", + "optional" : false, + "description" : "Tolerance for the geometry simplification, in the units of the input SRS" + } + ], + "variadic" : false, + "returnType" : "cartesian_shape" + }, { "params" : [ { @@ -39,6 +57,24 @@ ], "variadic" : false, "returnType" : "geo_point" + }, + { + "params" : [ + { + "name" : "geometry", + "type" : "geo_shape", + "optional" : false, + "description" : "Expression of type `geo_point`, `geo_shape`, `cartesian_point` or `cartesian_shape`. If `null`, the function returns `null`." + }, + { + "name" : "tolerance", + "type" : "double", + "optional" : false, + "description" : "Tolerance for the geometry simplification, in the units of the input SRS" + } + ], + "variadic" : false, + "returnType" : "geo_shape" } ], "examples" : [ diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyTests.java index fd82191e8bc3f..74f3ad12617cc 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyTests.java @@ -42,9 +42,9 @@ public StSimplifyTests(@Name("TestCase") Supplier tes @ParametersFactory public static Iterable parameters() { final List suppliers = new ArrayList<>(); + addTestCaseSuppliers(suppliers, CARTESIAN_SHAPE, StSimplifyTests::valueOf); addTestCaseSuppliers(suppliers, GEO_POINT, StSimplifyTests::valueOf); addTestCaseSuppliers(suppliers, CARTESIAN_POINT, StSimplifyTests::valueOf); - addTestCaseSuppliers(suppliers, CARTESIAN_SHAPE, StSimplifyTests::valueOf); addTestCaseSuppliers(suppliers, GEO_SHAPE, StSimplifyTests::valueOf); var testSuppliers = anyNullIsNull( diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyUnitTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyUnitTests.java index f1c659d1530bd..ffc9a8bfb5b0d 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyUnitTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyUnitTests.java @@ -142,22 +142,23 @@ public void testTwoNonIntersectingPolygons() { public void testTwoTouchingPolygons() { /* Two polygons with a touching edge - * * * * - * * * - * * * - * * - * * * * + * * * * + * * * + * * * + * * + * * * * */ var wkb = process("POLYGON((0 0, 4 0, 4 4, 0 4, 0 0), (4 2, 5 2, 5 3, 4 3, 4 2))", 0.0); var result = UNSPECIFIED.wkbToWkt(wkb); /* It gets simplified to the biggest square - * * * * - * * - * * - * * - * * * * + * * * * + * * + * * + * * + * * * * + */ assertEquals("POLYGON ((0.0 0.0, 0.0 4.0, 4.0 4.0, 4.0 3.0, 4.0 2.0, 4.0 0.0, 0.0 0.0))", result); } @@ -175,19 +176,9 @@ public void testEmptyPolygon() { } public void testBigTolerance() { - /* Polygon with a hole outside the shell - * * - * * - * * * * - * * - * * - * * - * * * * - */ var wkb = process("POLYGON( (0 0, 4 0, 4 4, 0 4, 0 0), (5 5, 6 5, 6 6, 5 6, 5 5) )", 10.0); var result = UNSPECIFIED.wkbToWkt(wkb); - /* It gets simplified to an empty polygon - */ + // It gets simplified to an empty polygon assertEquals("POLYGON EMPTY", result); } } From 6b5a8feba65616b6a50236564f58b984e7878cc7 Mon Sep 17 00:00:00 2001 From: ncordon Date: Tue, 2 Dec 2025 12:08:28 +0100 Subject: [PATCH 28/45] Corrects bug when generating documentation --- .../function/AbstractFunctionTestCase.java | 22 ++++++++++++------- .../scalar/spatial/StSimplifyTests.java | 2 +- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/AbstractFunctionTestCase.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/AbstractFunctionTestCase.java index 52c541cf7d384..50dd1edd15440 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/AbstractFunctionTestCase.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/AbstractFunctionTestCase.java @@ -59,6 +59,7 @@ import org.hamcrest.Matchers; import org.junit.After; import org.junit.AfterClass; +import org.junit.AssumptionViolatedException; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; @@ -964,15 +965,20 @@ public static Set signatures(Class testClass) { } for (Object p : params) { TestCaseSupplier tcs = (TestCaseSupplier) ((Object[]) p)[0]; - TestCaseSupplier.TestCase tc = tcs.get(); - if (tc.getExpectedTypeError() != null) { - continue; - } - if (tc.getData().stream().anyMatch(t -> t.type() == DataType.NULL)) { - continue; + try { + TestCaseSupplier.TestCase tc = tcs.get(); + if (tc.getExpectedTypeError() != null) { + continue; + } + if (tc.getData().stream().anyMatch(t -> t.type() == DataType.NULL)) { + continue; + } + List sig = tc.getData().stream().map(d -> new DocsV3Support.Param(d.type(), d.appliesTo())).toList(); + signatures.add(new DocsV3Support.TypeSignature(signatureTypes(testClass, sig), tc.expectedType())); + } catch (AssumptionViolatedException ignored) { + // Throwing an AssumptionViolatedException in a test is a valid way of ignoring a test in junit. + // We catch that exception always to keep filling the signatures collection } - List sig = tc.getData().stream().map(d -> new DocsV3Support.Param(d.type(), d.appliesTo())).toList(); - signatures.add(new DocsV3Support.TypeSignature(signatureTypes(testClass, sig), tc.expectedType())); } return signatures; } diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyTests.java index 74f3ad12617cc..bd4605fe9cb24 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyTests.java @@ -42,10 +42,10 @@ public StSimplifyTests(@Name("TestCase") Supplier tes @ParametersFactory public static Iterable parameters() { final List suppliers = new ArrayList<>(); - addTestCaseSuppliers(suppliers, CARTESIAN_SHAPE, StSimplifyTests::valueOf); addTestCaseSuppliers(suppliers, GEO_POINT, StSimplifyTests::valueOf); addTestCaseSuppliers(suppliers, CARTESIAN_POINT, StSimplifyTests::valueOf); addTestCaseSuppliers(suppliers, GEO_SHAPE, StSimplifyTests::valueOf); + addTestCaseSuppliers(suppliers, CARTESIAN_SHAPE, StSimplifyTests::valueOf); var testSuppliers = anyNullIsNull( randomizeBytesRefsOffset(suppliers), From 283f546e096f90a924b41be5465b1436a5005f8e Mon Sep 17 00:00:00 2001 From: Craig Taverner Date: Tue, 9 Dec 2025 17:37:15 +0100 Subject: [PATCH 29/45] Split spatial-jts tests out to separate file --- .../src/main/resources/spatial-jts.csv-spec | 227 +++++++++++++++++ .../src/main/resources/spatial.csv-spec | 228 ------------------ 2 files changed, 227 insertions(+), 228 deletions(-) create mode 100644 x-pack/plugin/esql/qa/testFixtures/src/main/resources/spatial-jts.csv-spec diff --git a/x-pack/plugin/esql/qa/testFixtures/src/main/resources/spatial-jts.csv-spec b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/spatial-jts.csv-spec new file mode 100644 index 0000000000000..0b71a6dee67ed --- /dev/null +++ b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/spatial-jts.csv-spec @@ -0,0 +1,227 @@ +############################################### +# Tests for ST_SIMPLIFY +############################################### + +stSimplifyMultiRow +required_capability: st_simplify + +FROM airports +| SORT name +| LIMIT 5 +| EVAL result = ST_SIMPLIFY(TO_GEOSHAPE("POLYGON((-10 -60, 120 -60, 120 60, -10 60, -10 -60))"), 1.0) +| KEEP name, result +; + +name:text | result:geo_shape +Aba Tenna D. Yilma Int'l | POLYGON ((-10.0 -60.0, 120.0 -60.0, 120.0 60.0, -10.0 60.0, -10.0 -60.0)) +Abdul Rachman Saleh | POLYGON ((-10.0 -60.0, 120.0 -60.0, 120.0 60.0, -10.0 60.0, -10.0 -60.0)) +Abidjan Port Bouet | POLYGON ((-10.0 -60.0, 120.0 -60.0, 120.0 60.0, -10.0 60.0, -10.0 -60.0)) +Abu Dhabi Int'l | POLYGON ((-10.0 -60.0, 120.0 -60.0, 120.0 60.0, -10.0 60.0, -10.0 -60.0)) +Abuja Int'l | POLYGON ((-10.0 -60.0, 120.0 -60.0, 120.0 60.0, -10.0 60.0, -10.0 -60.0)) +; + +stSimplifyMultiRowWithPoints +required_capability: st_simplify + +// tag::st_simplify[] +FROM airports +| SORT name +| LIMIT 5 +| EVAL result = ST_SIMPLIFY(location, 0.0) +| KEEP location, result +// end::st_simplify[] +; + +// tag::st_simplify-result[] +location:geo_point | result:geo_point +POINT (41.857756722253 9.61267784753569) | POINT (41.857756722253 9.61267784753569) +POINT (112.711418617258 -7.92998002840567) | POINT (112.711418617258 -7.92998002840567) +POINT (-3.93221929167636 5.2543984451492) | POINT (-3.93221929167636 5.2543984451492) +POINT (54.6463293225558 24.4272271529764) | POINT (54.6463293225558 24.4272271529764) +POINT (7.27025993974356 9.00437659781094) | POINT (7.27025993974356 9.00437659781094) +// end::st_simplify-result[] +; + +stSimplifyNoSimplification +required_capability: st_simplify + +ROW geo_shape = TO_GEOSHAPE("POLYGON((0 0, 1 0.1, 2 0, 2 2, 1 1.9, 0 2, 0 0))") +| EVAL result = ST_SIMPLIFY(geo_shape, 0.01) +| KEEP result +; + +result:geo_shape +POLYGON ((0.0 0.0, 1.0 0.1, 2.0 0.0, 2.0 2.0, 1.0 1.9, 0.0 2.0, 0.0 0.0)) +; + +stSimplifyWithSimplification +required_capability: st_simplify + +ROW geo_shape = TO_GEOSHAPE("POLYGON((0 0, 1 0.1, 2 0, 2 2, 1 1.9, 0 2, 0 0))") +| EVAL result = ST_SIMPLIFY(geo_shape, 0.2) +| KEEP result +; + +result:geo_shape +POLYGON ((0.0 0.0, 2.0 0.0, 2.0 2.0, 0.0 2.0, 0.0 0.0)) +; + +stSimplifyFoldableGeometry +required_capability: st_simplify + +ROW result = ST_SIMPLIFY(TO_GEOSHAPE("POLYGON((0 0, 1 0.1, 2 0, 2 2, 1 1.9, 0 2, 0 0))") , 0.2) +; + +result:geo_shape +POLYGON ((0.0 0.0, 2.0 0.0, 2.0 2.0, 0.0 2.0, 0.0 0.0)) +; + +stSimplifyEmptySimplification +required_capability: st_simplify + +ROW geo_shape = TO_GEOSHAPE("POLYGON((0 0, 1 0.1, 2 0, 2 2, 1 1.9, 0 2, 0 0))") +| EVAL result = ST_SIMPLIFY(geo_shape, 2.0) +| KEEP result +; + +result:geo_shape +POLYGON EMPTY +; + +stSimplifyNull +required_capability: st_simplify + +ROW geo_shape = NULL +| EVAL result = ST_SIMPLIFY(geo_shape, 2.0) +| KEEP result +; + +result:null +NULL +; + +stSimplifyCartesianPoint +required_capability: st_simplify + +ROW wkt = ["POINT(-97.11 95.53)", "POINT(80.93 72.77)"] +| MV_EXPAND wkt +| EVAL pt = TO_CARTESIANPOINT(wkt) +| EVAL result = ST_SIMPLIFY(pt, 2.0) +| KEEP result +; + +result:cartesian_point +POINT (-97.11 95.53) +POINT (80.93 72.77) +; + +stSimplifyCartesianShape +required_capability: st_simplify + +ROW wkt = ["POINT(97.11 75.53)", "POLYGON((0 0, 1 0.1, 2 0, 2 2, 1 1.9, 0 2, 0 0))"] +| MV_EXPAND wkt +| EVAL geom = TO_CARTESIANSHAPE(wkt) +| EVAL result = ST_SIMPLIFY(geom, 0.2) +| KEEP result +; + +result:cartesian_shape +POINT (97.11 75.53) +POLYGON ((0.0 0.0, 2.0 0.0, 2.0 2.0, 0.0 2.0, 0.0 0.0)) +; + +stSimplifyWithIntegerTolerance +required_capability: st_simplify + +ROW geo_shape = TO_GEOSHAPE("POLYGON((0 0, 1 0.1, 2 0, 2 2, 1 1.9, 0 2, 0 0))") +| EVAL result = ST_SIMPLIFY(geo_shape, 2) +| KEEP result +; + +result:geo_shape +POLYGON EMPTY +; + +stSimplifyBbox +required_capability: st_simplify + +ROW geo_shape = TO_GEOSHAPE("BBOX (0, 2, 2, 0)") +| EVAL result1 = ST_SIMPLIFY(geo_shape, 2), result2 = ST_SIMPLIFY(geo_shape, 0.5) +| KEEP result1, result2 +; + +result1:geo_shape | result2:geo_shape +POLYGON EMPTY | POLYGON ((0.0 0.0, 0.0 2.0, 2.0 2.0, 2.0 0.0, 0.0 0.0)) +; + +stSimplifyPostGisFirstExample +required_capability: st_simplify + +ROW geom = TO_GEOSHAPE("POLYGON((11 3,10.914448613738104 1.694738077799484,10.659258262890683 0.411809548974793,10.238795325112868 -0.826834323650898,9.660254037844387 -1.999999999999999,8.933533402912353 -3.087614290087205,8.071067811865476 -4.071067811865475,7.087614290087206 -4.933533402912351,6.000000000000001 -5.660254037844386,4.826834323650898 -6.238795325112868,3.58819045102521 -6.659258262890681,2.305261922200517 -6.914448613738104,1 -7,-0.305261922200514 -6.914448613738106,-1.588190451025206 -6.659258262890683,-2.826834323650895 -6.238795325112868,-3.999999999999998 -5.660254037844387,-5.087614290087204 -4.933533402912354,-6.071067811865475 -4.071067811865476,-6.93353340291235 -3.087614290087209,-7.660254037844386 -2.000000000000004,-8.238795325112868 -0.826834323650899,-8.659258262890681 0.41180954897479,-8.914448613738104 1.69473807779948,-9 3,-8.914448613738106 4.305261922200513,-8.659258262890685 5.588190451025204,-8.23879532511287 6.826834323650893,-7.660254037844389 7.999999999999997,-6.933533402912354 9.087614290087203,-6.071067811865479 10.071067811865472,-5.087614290087209 10.93353340291235,-4.000000000000004 11.660254037844384,-2.826834323650903 12.238795325112864,-1.588190451025215 12.659258262890681,-0.305261922200525 12.914448613738102,1 13,2.305261922200513 12.914448613738106,3.588190451025203 12.659258262890685,4.826834323650892 12.23879532511287,5.999999999999993 11.66025403784439,7.087614290087199 10.933533402912357,8.071067811865474 10.071067811865477,8.93353340291235 9.08761429008721,9.660254037844384 8.000000000000004,10.238795325112864 6.826834323650904,10.659258262890681 5.588190451025216,10.914448613738102 4.305261922200526,11 3))") +| EVAL np01_notbadcircle = ST_Simplify(geom, 0.1) +| KEEP np01_notbadcircle +; + +np01_notbadcircle:geo_shape + POLYGON((11.0 3.0,10.914448613738104 1.694738077799484,10.238795325112868 -0.826834323650898,8.933533402912353 -3.087614290087205,8.071067811865476 -4.071067811865475,6.000000000000001 -5.660254037844386,4.826834323650898 -6.238795325112868,2.305261922200517 -6.914448613738104,1.0 -7.0,-0.305261922200514 -6.914448613738106,-2.826834323650895 -6.238795325112868,-5.087614290087204 -4.933533402912354,-6.071067811865475 -4.071067811865476,-7.660254037844386 -2.000000000000004,-8.238795325112868 -0.826834323650899,-8.914448613738104 1.69473807779948,-9.0 3.0,-8.914448613738106 4.305261922200513,-8.23879532511287 6.826834323650893,-7.660254037844389 7.999999999999997,-6.071067811865479 10.071067811865472,-4.000000000000004 11.660254037844384,-2.826834323650903 12.238795325112864,-1.588190451025215 12.659258262890681,1.0 13.0,2.305261922200513 12.914448613738106,4.826834323650892 12.23879532511287,5.999999999999993 11.66025403784439,8.071067811865474 10.071067811865477,8.93353340291235 9.08761429008721,10.238795325112864 6.826834323650904,10.659258262890681 5.588190451025216,11.0 3.0)) +; + +stSimplifyPostGisSecondExample +required_capability: st_simplify + +ROW geom = TO_GEOSHAPE("POLYGON((11 3,10.914448613738104 1.694738077799484,10.659258262890683 0.411809548974793,10.238795325112868 -0.826834323650898,9.660254037844387 -1.999999999999999,8.933533402912353 -3.087614290087205,8.071067811865476 -4.071067811865475,7.087614290087206 -4.933533402912351,6.000000000000001 -5.660254037844386,4.826834323650898 -6.238795325112868,3.58819045102521 -6.659258262890681,2.305261922200517 -6.914448613738104,1 -7,-0.305261922200514 -6.914448613738106,-1.588190451025206 -6.659258262890683,-2.826834323650895 -6.238795325112868,-3.999999999999998 -5.660254037844387,-5.087614290087204 -4.933533402912354,-6.071067811865475 -4.071067811865476,-6.93353340291235 -3.087614290087209,-7.660254037844386 -2.000000000000004,-8.238795325112868 -0.826834323650899,-8.659258262890681 0.41180954897479,-8.914448613738104 1.69473807779948,-9 3,-8.914448613738106 4.305261922200513,-8.659258262890685 5.588190451025204,-8.23879532511287 6.826834323650893,-7.660254037844389 7.999999999999997,-6.933533402912354 9.087614290087203,-6.071067811865479 10.071067811865472,-5.087614290087209 10.93353340291235,-4.000000000000004 11.660254037844384,-2.826834323650903 12.238795325112864,-1.588190451025215 12.659258262890681,-0.305261922200525 12.914448613738102,1 13,2.305261922200513 12.914448613738106,3.588190451025203 12.659258262890685,4.826834323650892 12.23879532511287,5.999999999999993 11.66025403784439,7.087614290087199 10.933533402912357,8.071067811865474 10.071067811865477,8.93353340291235 9.08761429008721,9.660254037844384 8.000000000000004,10.238795325112864 6.826834323650904,10.659258262890681 5.588190451025216,10.914448613738102 4.305261922200526,11 3))") +| EVAL np05_notquitecircle = ST_Simplify(geom, 0.5) +| KEEP np05_notquitecircle +; + +np05_notquitecircle:geo_shape +POLYGON((11 3,10.238795325112868 -0.826834323650898,8.071067811865476 -4.071067811865475,4.826834323650898 -6.238795325112868,1 -7,-2.826834323650895 -6.238795325112868,-6.071067811865475 -4.071067811865476,-8.238795325112868 -0.826834323650899,-9 3,-8.23879532511287 6.826834323650893,-6.071067811865479 10.071067811865472,-2.826834323650903 12.238795325112864,1 13,4.826834323650892 12.23879532511287,8.071067811865474 10.071067811865477,10.238795325112864 6.826834323650904,11 3)) +; + +stSimplifyPostGisThirdExample +required_capability: st_simplify + +ROW geom = TO_GEOSHAPE("POLYGON((11 3,10.914448613738104 1.694738077799484,10.659258262890683 0.411809548974793,10.238795325112868 -0.826834323650898,9.660254037844387 -1.999999999999999,8.933533402912353 -3.087614290087205,8.071067811865476 -4.071067811865475,7.087614290087206 -4.933533402912351,6.000000000000001 -5.660254037844386,4.826834323650898 -6.238795325112868,3.58819045102521 -6.659258262890681,2.305261922200517 -6.914448613738104,1 -7,-0.305261922200514 -6.914448613738106,-1.588190451025206 -6.659258262890683,-2.826834323650895 -6.238795325112868,-3.999999999999998 -5.660254037844387,-5.087614290087204 -4.933533402912354,-6.071067811865475 -4.071067811865476,-6.93353340291235 -3.087614290087209,-7.660254037844386 -2.000000000000004,-8.238795325112868 -0.826834323650899,-8.659258262890681 0.41180954897479,-8.914448613738104 1.69473807779948,-9 3,-8.914448613738106 4.305261922200513,-8.659258262890685 5.588190451025204,-8.23879532511287 6.826834323650893,-7.660254037844389 7.999999999999997,-6.933533402912354 9.087614290087203,-6.071067811865479 10.071067811865472,-5.087614290087209 10.93353340291235,-4.000000000000004 11.660254037844384,-2.826834323650903 12.238795325112864,-1.588190451025215 12.659258262890681,-0.305261922200525 12.914448613738102,1 13,2.305261922200513 12.914448613738106,3.588190451025203 12.659258262890685,4.826834323650892 12.23879532511287,5.999999999999993 11.66025403784439,7.087614290087199 10.933533402912357,8.071067811865474 10.071067811865477,8.93353340291235 9.08761429008721,9.660254037844384 8.000000000000004,10.238795325112864 6.826834323650904,10.659258262890681 5.588190451025216,10.914448613738102 4.305261922200526,11 3))") +| EVAL np1_octagon = ST_Simplify(geom, 1) +| KEEP np1_octagon +; + +np1_octagon:geo_shape +POLYGON((11 3,8.071067811865476 -4.071067811865475,1 -7,-6.071067811865475 -4.071067811865476,-9 3,-6.071067811865479 10.071067811865472,1 13,8.071067811865474 10.071067811865477,11 3)) +; + +stSimplifyPostGisFourthExample +required_capability: st_simplify + +ROW geom = TO_GEOSHAPE("POLYGON((11 3,10.914448613738104 1.694738077799484,10.659258262890683 0.411809548974793,10.238795325112868 -0.826834323650898,9.660254037844387 -1.999999999999999,8.933533402912353 -3.087614290087205,8.071067811865476 -4.071067811865475,7.087614290087206 -4.933533402912351,6.000000000000001 -5.660254037844386,4.826834323650898 -6.238795325112868,3.58819045102521 -6.659258262890681,2.305261922200517 -6.914448613738104,1 -7,-0.305261922200514 -6.914448613738106,-1.588190451025206 -6.659258262890683,-2.826834323650895 -6.238795325112868,-3.999999999999998 -5.660254037844387,-5.087614290087204 -4.933533402912354,-6.071067811865475 -4.071067811865476,-6.93353340291235 -3.087614290087209,-7.660254037844386 -2.000000000000004,-8.238795325112868 -0.826834323650899,-8.659258262890681 0.41180954897479,-8.914448613738104 1.69473807779948,-9 3,-8.914448613738106 4.305261922200513,-8.659258262890685 5.588190451025204,-8.23879532511287 6.826834323650893,-7.660254037844389 7.999999999999997,-6.933533402912354 9.087614290087203,-6.071067811865479 10.071067811865472,-5.087614290087209 10.93353340291235,-4.000000000000004 11.660254037844384,-2.826834323650903 12.238795325112864,-1.588190451025215 12.659258262890681,-0.305261922200525 12.914448613738102,1 13,2.305261922200513 12.914448613738106,3.588190451025203 12.659258262890685,4.826834323650892 12.23879532511287,5.999999999999993 11.66025403784439,7.087614290087199 10.933533402912357,8.071067811865474 10.071067811865477,8.93353340291235 9.08761429008721,9.660254037844384 8.000000000000004,10.238795325112864 6.826834323650904,10.659258262890681 5.588190451025216,10.914448613738102 4.305261922200526,11 3))") +| EVAL np100_geometrygoesaway = ST_Simplify(geom, 100) +| KEEP np100_geometrygoesaway +; + +np100_geometrygoesaway:geo_shape +POLYGON EMPTY +; + +stSimplifyPostGisFifthExample +required_capability: st_simplify + +ROW geom = TO_CARTESIANSHAPE("MULTILINESTRING ((20 180, 20 150, 50 150, 50 100, 110 150, 150 140, 170 120), (20 10, 80 30, 90 120), (90 120, 130 130), (130 130, 130 70, 160 40, 180 60, 180 90, 140 80), (50 40, 70 40, 80 70, 70 60, 60 60, 50 50, 50 40))") +| EVAL result = ST_Simplify(geom, 40) +| KEEP result +; + +result:cartesian_shape +MULTILINESTRING((20 180,50 100,170 120),(20 10,90 120),(90 120,130 130),(130 130,160 40,180 90,140 80),(50 40,80 70,50 40)) +; + +stSimplifyPostGisSixthExample +required_capability: st_simplify + +ROW geom = TO_CARTESIANSHAPE("MULTIPOLYGON (((90 110, 80 180, 50 160, 10 170, 10 140, 20 110, 90 110)), ((40 80, 100 100, 120 160, 170 180, 190 70, 140 10, 110 40, 60 40, 40 80), (180 70, 170 110, 142.5 128.5, 128.5 77.5, 90 60, 180 70)))") +| EVAL result = ST_Simplify(geom, 40) +| KEEP result +; + +result:cartesian_shape +POLYGON ((40.0 80.0, 79.0 110.0, 20.0 110.0, 10.0 170.0, 80.0 180.0, 88.91089108910892 117.62376237623762, 170.0 180.0, 156.93726937269372 105.97785977859779, 142.5 128.5, 90.0 60.0, 150.0 66.66666666666667, 140.0 10.0, 40.0 80.0)) +; diff --git a/x-pack/plugin/esql/qa/testFixtures/src/main/resources/spatial.csv-spec b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/spatial.csv-spec index 4277e001bcf57..627f770d55c9e 100644 --- a/x-pack/plugin/esql/qa/testFixtures/src/main/resources/spatial.csv-spec +++ b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/spatial.csv-spec @@ -3104,231 +3104,3 @@ FROM cartesian_multipolygons count:long | extent:cartesian_shape 1 | null ; - -############################################### -# Tests for ST_SIMPLIFY -############################################### - -stSimplifyMultiRow -required_capability: st_simplify - -FROM airports -| SORT name -| LIMIT 5 -| EVAL result = ST_SIMPLIFY(TO_GEOSHAPE("POLYGON((-10 -60, 120 -60, 120 60, -10 60, -10 -60))"), 1.0) -| KEEP name, result -; - -name:text | result:geo_shape -Aba Tenna D. Yilma Int'l | POLYGON ((-10.0 -60.0, 120.0 -60.0, 120.0 60.0, -10.0 60.0, -10.0 -60.0)) -Abdul Rachman Saleh | POLYGON ((-10.0 -60.0, 120.0 -60.0, 120.0 60.0, -10.0 60.0, -10.0 -60.0)) -Abidjan Port Bouet | POLYGON ((-10.0 -60.0, 120.0 -60.0, 120.0 60.0, -10.0 60.0, -10.0 -60.0)) -Abu Dhabi Int'l | POLYGON ((-10.0 -60.0, 120.0 -60.0, 120.0 60.0, -10.0 60.0, -10.0 -60.0)) -Abuja Int'l | POLYGON ((-10.0 -60.0, 120.0 -60.0, 120.0 60.0, -10.0 60.0, -10.0 -60.0)) -; - -stSimplifyMultiRowWithPoints -required_capability: st_simplify - -// tag::st_simplify[] -FROM airports -| SORT name -| LIMIT 5 -| EVAL result = ST_SIMPLIFY(location, 0.0) -| KEEP location, result -// end::st_simplify[] -; - -// tag::st_simplify-result[] -location:geo_point | result:geo_point -POINT (41.857756722253 9.61267784753569) | POINT (41.857756722253 9.61267784753569) -POINT (112.711418617258 -7.92998002840567) | POINT (112.711418617258 -7.92998002840567) -POINT (-3.93221929167636 5.2543984451492) | POINT (-3.93221929167636 5.2543984451492) -POINT (54.6463293225558 24.4272271529764) | POINT (54.6463293225558 24.4272271529764) -POINT (7.27025993974356 9.00437659781094) | POINT (7.27025993974356 9.00437659781094) -// end::st_simplify-result[] -; - -stSimplifyNoSimplification -required_capability: st_simplify - -ROW geo_shape = TO_GEOSHAPE("POLYGON((0 0, 1 0.1, 2 0, 2 2, 1 1.9, 0 2, 0 0))") -| EVAL result = ST_SIMPLIFY(geo_shape, 0.01) -| KEEP result -; - -result:geo_shape -POLYGON ((0.0 0.0, 1.0 0.1, 2.0 0.0, 2.0 2.0, 1.0 1.9, 0.0 2.0, 0.0 0.0)) -; - -stSimplifyWithSimplification -required_capability: st_simplify - -ROW geo_shape = TO_GEOSHAPE("POLYGON((0 0, 1 0.1, 2 0, 2 2, 1 1.9, 0 2, 0 0))") -| EVAL result = ST_SIMPLIFY(geo_shape, 0.2) -| KEEP result -; - -result:geo_shape -POLYGON ((0.0 0.0, 2.0 0.0, 2.0 2.0, 0.0 2.0, 0.0 0.0)) -; - -stSimplifyFoldableGeometry -required_capability: st_simplify - -ROW result = ST_SIMPLIFY(TO_GEOSHAPE("POLYGON((0 0, 1 0.1, 2 0, 2 2, 1 1.9, 0 2, 0 0))") , 0.2) -; - -result:geo_shape -POLYGON ((0.0 0.0, 2.0 0.0, 2.0 2.0, 0.0 2.0, 0.0 0.0)) -; - -stSimplifyEmptySimplification -required_capability: st_simplify - -ROW geo_shape = TO_GEOSHAPE("POLYGON((0 0, 1 0.1, 2 0, 2 2, 1 1.9, 0 2, 0 0))") -| EVAL result = ST_SIMPLIFY(geo_shape, 2.0) -| KEEP result -; - -result:geo_shape -POLYGON EMPTY -; - -stSimplifyNull -required_capability: st_simplify - -ROW geo_shape = NULL -| EVAL result = ST_SIMPLIFY(geo_shape, 2.0) -| KEEP result -; - -result:null -NULL -; - -stSimplifyCartesianPoint -required_capability: st_simplify - -ROW wkt = ["POINT(-97.11 95.53)", "POINT(80.93 72.77)"] -| MV_EXPAND wkt -| EVAL pt = TO_CARTESIANPOINT(wkt) -| EVAL result = ST_SIMPLIFY(pt, 2.0) -| KEEP result -; - -result:cartesian_point -POINT (-97.11 95.53) -POINT (80.93 72.77) -; - -stSimplifyCartesianShape -required_capability: st_simplify - -ROW wkt = ["POINT(97.11 75.53)", "POLYGON((0 0, 1 0.1, 2 0, 2 2, 1 1.9, 0 2, 0 0))"] -| MV_EXPAND wkt -| EVAL geom = TO_CARTESIANSHAPE(wkt) -| EVAL result = ST_SIMPLIFY(geom, 0.2) -| KEEP result -; - -result:cartesian_shape -POINT (97.11 75.53) -POLYGON ((0.0 0.0, 2.0 0.0, 2.0 2.0, 0.0 2.0, 0.0 0.0)) -; - -stSimplifyWithIntegerTolerance -required_capability: st_simplify - -ROW geo_shape = TO_GEOSHAPE("POLYGON((0 0, 1 0.1, 2 0, 2 2, 1 1.9, 0 2, 0 0))") -| EVAL result = ST_SIMPLIFY(geo_shape, 2) -| KEEP result -; - -result:geo_shape -POLYGON EMPTY -; - -stSimplifyBbox -required_capability: st_simplify - -ROW geo_shape = TO_GEOSHAPE("BBOX (0, 2, 2, 0)") -| EVAL result1 = ST_SIMPLIFY(geo_shape, 2), result2 = ST_SIMPLIFY(geo_shape, 0.5) -| KEEP result1, result2 -; - -result1:geo_shape | result2:geo_shape -POLYGON EMPTY | POLYGON ((0.0 0.0, 0.0 2.0, 2.0 2.0, 2.0 0.0, 0.0 0.0)) -; - -stSimplifyPostGisFirstExample -required_capability: st_simplify - -ROW geom = TO_GEOSHAPE("POLYGON((11 3,10.914448613738104 1.694738077799484,10.659258262890683 0.411809548974793,10.238795325112868 -0.826834323650898,9.660254037844387 -1.999999999999999,8.933533402912353 -3.087614290087205,8.071067811865476 -4.071067811865475,7.087614290087206 -4.933533402912351,6.000000000000001 -5.660254037844386,4.826834323650898 -6.238795325112868,3.58819045102521 -6.659258262890681,2.305261922200517 -6.914448613738104,1 -7,-0.305261922200514 -6.914448613738106,-1.588190451025206 -6.659258262890683,-2.826834323650895 -6.238795325112868,-3.999999999999998 -5.660254037844387,-5.087614290087204 -4.933533402912354,-6.071067811865475 -4.071067811865476,-6.93353340291235 -3.087614290087209,-7.660254037844386 -2.000000000000004,-8.238795325112868 -0.826834323650899,-8.659258262890681 0.41180954897479,-8.914448613738104 1.69473807779948,-9 3,-8.914448613738106 4.305261922200513,-8.659258262890685 5.588190451025204,-8.23879532511287 6.826834323650893,-7.660254037844389 7.999999999999997,-6.933533402912354 9.087614290087203,-6.071067811865479 10.071067811865472,-5.087614290087209 10.93353340291235,-4.000000000000004 11.660254037844384,-2.826834323650903 12.238795325112864,-1.588190451025215 12.659258262890681,-0.305261922200525 12.914448613738102,1 13,2.305261922200513 12.914448613738106,3.588190451025203 12.659258262890685,4.826834323650892 12.23879532511287,5.999999999999993 11.66025403784439,7.087614290087199 10.933533402912357,8.071067811865474 10.071067811865477,8.93353340291235 9.08761429008721,9.660254037844384 8.000000000000004,10.238795325112864 6.826834323650904,10.659258262890681 5.588190451025216,10.914448613738102 4.305261922200526,11 3))") -| EVAL np01_notbadcircle = ST_Simplify(geom, 0.1) -| KEEP np01_notbadcircle -; - -np01_notbadcircle:geo_shape - POLYGON((11.0 3.0,10.914448613738104 1.694738077799484,10.238795325112868 -0.826834323650898,8.933533402912353 -3.087614290087205,8.071067811865476 -4.071067811865475,6.000000000000001 -5.660254037844386,4.826834323650898 -6.238795325112868,2.305261922200517 -6.914448613738104,1.0 -7.0,-0.305261922200514 -6.914448613738106,-2.826834323650895 -6.238795325112868,-5.087614290087204 -4.933533402912354,-6.071067811865475 -4.071067811865476,-7.660254037844386 -2.000000000000004,-8.238795325112868 -0.826834323650899,-8.914448613738104 1.69473807779948,-9.0 3.0,-8.914448613738106 4.305261922200513,-8.23879532511287 6.826834323650893,-7.660254037844389 7.999999999999997,-6.071067811865479 10.071067811865472,-4.000000000000004 11.660254037844384,-2.826834323650903 12.238795325112864,-1.588190451025215 12.659258262890681,1.0 13.0,2.305261922200513 12.914448613738106,4.826834323650892 12.23879532511287,5.999999999999993 11.66025403784439,8.071067811865474 10.071067811865477,8.93353340291235 9.08761429008721,10.238795325112864 6.826834323650904,10.659258262890681 5.588190451025216,11.0 3.0)) -; - -stSimplifyPostGisSecondExample -required_capability: st_simplify - -ROW geom = TO_GEOSHAPE("POLYGON((11 3,10.914448613738104 1.694738077799484,10.659258262890683 0.411809548974793,10.238795325112868 -0.826834323650898,9.660254037844387 -1.999999999999999,8.933533402912353 -3.087614290087205,8.071067811865476 -4.071067811865475,7.087614290087206 -4.933533402912351,6.000000000000001 -5.660254037844386,4.826834323650898 -6.238795325112868,3.58819045102521 -6.659258262890681,2.305261922200517 -6.914448613738104,1 -7,-0.305261922200514 -6.914448613738106,-1.588190451025206 -6.659258262890683,-2.826834323650895 -6.238795325112868,-3.999999999999998 -5.660254037844387,-5.087614290087204 -4.933533402912354,-6.071067811865475 -4.071067811865476,-6.93353340291235 -3.087614290087209,-7.660254037844386 -2.000000000000004,-8.238795325112868 -0.826834323650899,-8.659258262890681 0.41180954897479,-8.914448613738104 1.69473807779948,-9 3,-8.914448613738106 4.305261922200513,-8.659258262890685 5.588190451025204,-8.23879532511287 6.826834323650893,-7.660254037844389 7.999999999999997,-6.933533402912354 9.087614290087203,-6.071067811865479 10.071067811865472,-5.087614290087209 10.93353340291235,-4.000000000000004 11.660254037844384,-2.826834323650903 12.238795325112864,-1.588190451025215 12.659258262890681,-0.305261922200525 12.914448613738102,1 13,2.305261922200513 12.914448613738106,3.588190451025203 12.659258262890685,4.826834323650892 12.23879532511287,5.999999999999993 11.66025403784439,7.087614290087199 10.933533402912357,8.071067811865474 10.071067811865477,8.93353340291235 9.08761429008721,9.660254037844384 8.000000000000004,10.238795325112864 6.826834323650904,10.659258262890681 5.588190451025216,10.914448613738102 4.305261922200526,11 3))") -| EVAL np05_notquitecircle = ST_Simplify(geom, 0.5) -| KEEP np05_notquitecircle -; - -np05_notquitecircle:geo_shape -POLYGON((11 3,10.238795325112868 -0.826834323650898,8.071067811865476 -4.071067811865475,4.826834323650898 -6.238795325112868,1 -7,-2.826834323650895 -6.238795325112868,-6.071067811865475 -4.071067811865476,-8.238795325112868 -0.826834323650899,-9 3,-8.23879532511287 6.826834323650893,-6.071067811865479 10.071067811865472,-2.826834323650903 12.238795325112864,1 13,4.826834323650892 12.23879532511287,8.071067811865474 10.071067811865477,10.238795325112864 6.826834323650904,11 3)) -; - -stSimplifyPostGisThirdExample -required_capability: st_simplify - -ROW geom = TO_GEOSHAPE("POLYGON((11 3,10.914448613738104 1.694738077799484,10.659258262890683 0.411809548974793,10.238795325112868 -0.826834323650898,9.660254037844387 -1.999999999999999,8.933533402912353 -3.087614290087205,8.071067811865476 -4.071067811865475,7.087614290087206 -4.933533402912351,6.000000000000001 -5.660254037844386,4.826834323650898 -6.238795325112868,3.58819045102521 -6.659258262890681,2.305261922200517 -6.914448613738104,1 -7,-0.305261922200514 -6.914448613738106,-1.588190451025206 -6.659258262890683,-2.826834323650895 -6.238795325112868,-3.999999999999998 -5.660254037844387,-5.087614290087204 -4.933533402912354,-6.071067811865475 -4.071067811865476,-6.93353340291235 -3.087614290087209,-7.660254037844386 -2.000000000000004,-8.238795325112868 -0.826834323650899,-8.659258262890681 0.41180954897479,-8.914448613738104 1.69473807779948,-9 3,-8.914448613738106 4.305261922200513,-8.659258262890685 5.588190451025204,-8.23879532511287 6.826834323650893,-7.660254037844389 7.999999999999997,-6.933533402912354 9.087614290087203,-6.071067811865479 10.071067811865472,-5.087614290087209 10.93353340291235,-4.000000000000004 11.660254037844384,-2.826834323650903 12.238795325112864,-1.588190451025215 12.659258262890681,-0.305261922200525 12.914448613738102,1 13,2.305261922200513 12.914448613738106,3.588190451025203 12.659258262890685,4.826834323650892 12.23879532511287,5.999999999999993 11.66025403784439,7.087614290087199 10.933533402912357,8.071067811865474 10.071067811865477,8.93353340291235 9.08761429008721,9.660254037844384 8.000000000000004,10.238795325112864 6.826834323650904,10.659258262890681 5.588190451025216,10.914448613738102 4.305261922200526,11 3))") -| EVAL np1_octagon = ST_Simplify(geom, 1) -| KEEP np1_octagon -; - -np1_octagon:geo_shape -POLYGON((11 3,8.071067811865476 -4.071067811865475,1 -7,-6.071067811865475 -4.071067811865476,-9 3,-6.071067811865479 10.071067811865472,1 13,8.071067811865474 10.071067811865477,11 3)) -; - -stSimplifyPostGisFourthExample -required_capability: st_simplify - -ROW geom = TO_GEOSHAPE("POLYGON((11 3,10.914448613738104 1.694738077799484,10.659258262890683 0.411809548974793,10.238795325112868 -0.826834323650898,9.660254037844387 -1.999999999999999,8.933533402912353 -3.087614290087205,8.071067811865476 -4.071067811865475,7.087614290087206 -4.933533402912351,6.000000000000001 -5.660254037844386,4.826834323650898 -6.238795325112868,3.58819045102521 -6.659258262890681,2.305261922200517 -6.914448613738104,1 -7,-0.305261922200514 -6.914448613738106,-1.588190451025206 -6.659258262890683,-2.826834323650895 -6.238795325112868,-3.999999999999998 -5.660254037844387,-5.087614290087204 -4.933533402912354,-6.071067811865475 -4.071067811865476,-6.93353340291235 -3.087614290087209,-7.660254037844386 -2.000000000000004,-8.238795325112868 -0.826834323650899,-8.659258262890681 0.41180954897479,-8.914448613738104 1.69473807779948,-9 3,-8.914448613738106 4.305261922200513,-8.659258262890685 5.588190451025204,-8.23879532511287 6.826834323650893,-7.660254037844389 7.999999999999997,-6.933533402912354 9.087614290087203,-6.071067811865479 10.071067811865472,-5.087614290087209 10.93353340291235,-4.000000000000004 11.660254037844384,-2.826834323650903 12.238795325112864,-1.588190451025215 12.659258262890681,-0.305261922200525 12.914448613738102,1 13,2.305261922200513 12.914448613738106,3.588190451025203 12.659258262890685,4.826834323650892 12.23879532511287,5.999999999999993 11.66025403784439,7.087614290087199 10.933533402912357,8.071067811865474 10.071067811865477,8.93353340291235 9.08761429008721,9.660254037844384 8.000000000000004,10.238795325112864 6.826834323650904,10.659258262890681 5.588190451025216,10.914448613738102 4.305261922200526,11 3))") -| EVAL np100_geometrygoesaway = ST_Simplify(geom, 100) -| KEEP np100_geometrygoesaway -; - -np100_geometrygoesaway:geo_shape -POLYGON EMPTY -; - -stSimplifyPostGisFifthExample -required_capability: st_simplify - -ROW geom = TO_CARTESIANSHAPE("MULTILINESTRING ((20 180, 20 150, 50 150, 50 100, 110 150, 150 140, 170 120), (20 10, 80 30, 90 120), (90 120, 130 130), (130 130, 130 70, 160 40, 180 60, 180 90, 140 80), (50 40, 70 40, 80 70, 70 60, 60 60, 50 50, 50 40))") -| EVAL result = ST_Simplify(geom, 40) -| KEEP result -; - -result:cartesian_shape -MULTILINESTRING((20 180,50 100,170 120),(20 10,90 120),(90 120,130 130),(130 130,160 40,180 90,140 80),(50 40,80 70,50 40)) -; - -stSimplifyPostGisSixthExample -required_capability: st_simplify - -ROW geom = TO_CARTESIANSHAPE("MULTIPOLYGON (((90 110, 80 180, 50 160, 10 170, 10 140, 20 110, 90 110)), ((40 80, 100 100, 120 160, 170 180, 190 70, 140 10, 110 40, 60 40, 40 80), (180 70, 170 110, 142.5 128.5, 128.5 77.5, 90 60, 180 70)))") -| EVAL result = ST_Simplify(geom, 40) -| KEEP result -; - -result:cartesian_shape -POLYGON ((40.0 80.0, 79.0 110.0, 20.0 110.0, 10.0 170.0, 80.0 180.0, 88.91089108910892 117.62376237623762, 170.0 180.0, 156.93726937269372 105.97785977859779, 142.5 128.5, 90.0 60.0, 150.0 66.66666666666667, 140.0 10.0, 40.0 80.0)) -; From c14f942cc14fbbea702b43b8273c017123573645 Mon Sep 17 00:00:00 2001 From: Craig Taverner Date: Tue, 9 Dec 2025 17:37:32 +0100 Subject: [PATCH 30/45] Support any number type for tolerance --- .../function/scalar/spatial/StSimplify.java | 8 +++---- .../scalar/spatial/StSimplifyUnitTests.java | 23 ++++++++++++++++--- 2 files changed, 23 insertions(+), 8 deletions(-) diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplify.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplify.java index 51e665ce3aa1c..95523c8860b2f 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplify.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplify.java @@ -130,12 +130,10 @@ public EvalOperator.ExpressionEvaluator.Factory toEvaluator(ToEvaluator toEvalua private static double getInputTolerance(Object toleranceExpression) { double inputTolerance; - if (toleranceExpression instanceof Integer) { - inputTolerance = ((Integer) toleranceExpression).doubleValue(); - } else if (toleranceExpression instanceof Double) { - inputTolerance = (double) toleranceExpression; + if (toleranceExpression instanceof Number number) { + inputTolerance = number.doubleValue(); } else { - throw new IllegalArgumentException("tolerance for st_simplify must be a non negative integer or double"); + throw new IllegalArgumentException("tolerance for st_simplify must be an integer or floating-point number"); } if (inputTolerance < 0) { diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyUnitTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyUnitTests.java index ffc9a8bfb5b0d..e1ebdab05e95c 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyUnitTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyUnitTests.java @@ -27,12 +27,12 @@ import org.elasticsearch.xpack.esql.core.expression.Expression; import org.elasticsearch.xpack.esql.core.expression.Literal; import org.elasticsearch.xpack.esql.core.tree.Source; +import org.elasticsearch.xpack.esql.core.type.DataType; import java.util.ArrayList; import java.util.Collections; import java.util.List; -import static org.elasticsearch.xpack.esql.core.type.DataType.DOUBLE; import static org.elasticsearch.xpack.esql.core.type.DataType.GEO_POINT; import static org.elasticsearch.xpack.esql.core.util.SpatialCoordinateTypes.UNSPECIFIED; import static org.elasticsearch.xpack.esql.expression.function.AbstractFunctionTestCase.evaluator; @@ -68,11 +68,12 @@ protected final Page row(List values) { return maybeConvertBytesRefsToOrdinals(new Page(1, BlockUtils.fromListRow(TestBlockFactory.getNonBreakingInstance(), values))); } - protected BytesRef process(String wkt, Double tolerance) { + protected BytesRef process(String wkt, Object tolerance) { BytesRef wkb = UNSPECIFIED.wktToWkb(wkt); + DataType toleranceType = DataType.fromJava(tolerance.getClass()); try ( EvalOperator.ExpressionEvaluator eval = evaluator( - build(Source.EMPTY, List.of(new Literal(Source.EMPTY, wkb, GEO_POINT), new Literal(Source.EMPTY, tolerance, DOUBLE))) + build(Source.EMPTY, List.of(new Literal(Source.EMPTY, wkb, GEO_POINT), new Literal(Source.EMPTY, tolerance, toleranceType))) ).get(driverContext()); Block block = eval.eval(row(List.of(wkb, tolerance))) ) { @@ -84,6 +85,11 @@ protected BytesRef process(String wkt, Double tolerance) { private final String polygonWkt = "POLYGON((0 0, 1 0.1, 2 0, 2 2, 1 1.9, 0 2, 0 0))"; public void testInvalidTolerance() { + IllegalArgumentException ex = expectThrows(IllegalArgumentException.class, () -> process(polygonWkt, "invalid")); + assertThat(ex.getMessage(), containsString("tolerance for st_simplify must be an integer or floating-point number")); + } + + public void testINegativeTolerance() { IllegalArgumentException ex = expectThrows(IllegalArgumentException.class, () -> process(polygonWkt, -1.0)); assertThat(ex.getMessage(), containsString("tolerance must not be negative")); } @@ -93,6 +99,17 @@ public void testZeroTolerance() { process(polygonWkt, 0.0); } + public void testValidTolerance() { + // float + process(polygonWkt, 1.0f); + // double + process(polygonWkt, 1.0d); + // int + process(polygonWkt, 1); + // long + process(polygonWkt, 1L); + } + public void testBowtie() { /* This is a bowtie From 47e211f1dee8c2fd8b614a1fdfc21e17fd0039ba Mon Sep 17 00:00:00 2001 From: Craig Taverner Date: Tue, 9 Dec 2025 19:29:35 +0100 Subject: [PATCH 31/45] Some fixes (added more literal tests, support all number types for tolerance, mv-fold) mv-field not working yet --- .../functions/examples/st_simplify.md | 14 +--- .../definition/functions/st_simplify.json | 2 +- .../esql/kibana/docs/functions/st_simplify.md | 6 +- .../src/main/resources/spatial-jts.csv-spec | 81 +++++++++++++------ .../function/scalar/spatial/StSimplify.java | 19 ++++- 5 files changed, 76 insertions(+), 46 deletions(-) diff --git a/docs/reference/query-languages/esql/_snippets/functions/examples/st_simplify.md b/docs/reference/query-languages/esql/_snippets/functions/examples/st_simplify.md index 10d4feee2d2e3..b3efac50b7ae7 100644 --- a/docs/reference/query-languages/esql/_snippets/functions/examples/st_simplify.md +++ b/docs/reference/query-languages/esql/_snippets/functions/examples/st_simplify.md @@ -3,19 +3,7 @@ **Example** ```esql -FROM airports -| SORT name -| LIMIT 5 -| EVAL result = ST_SIMPLIFY(location, 0.0) -| KEEP location, result +null ``` -| location:geo_point | result:geo_point | -| --- | --- | -| POINT (41.857756722253 9.61267784753569) | POINT (41.857756722253 9.61267784753569) | -| POINT (112.711418617258 -7.92998002840567) | POINT (112.711418617258 -7.92998002840567) | -| POINT (-3.93221929167636 5.2543984451492) | POINT (-3.93221929167636 5.2543984451492) | -| POINT (54.6463293225558 24.4272271529764) | POINT (54.6463293225558 24.4272271529764) | -| POINT (7.27025993974356 9.00437659781094) | POINT (7.27025993974356 9.00437659781094) | - diff --git a/docs/reference/query-languages/esql/kibana/definition/functions/st_simplify.json b/docs/reference/query-languages/esql/kibana/definition/functions/st_simplify.json index 7bdecafd4351a..e33094160336c 100644 --- a/docs/reference/query-languages/esql/kibana/definition/functions/st_simplify.json +++ b/docs/reference/query-languages/esql/kibana/definition/functions/st_simplify.json @@ -78,7 +78,7 @@ } ], "examples" : [ - "FROM airports\n| SORT name\n| LIMIT 5\n| EVAL result = ST_SIMPLIFY(location, 0.0)\n| KEEP location, result" + null ], "preview" : true, "snapshot_only" : false diff --git a/docs/reference/query-languages/esql/kibana/docs/functions/st_simplify.md b/docs/reference/query-languages/esql/kibana/docs/functions/st_simplify.md index 596a8c83d8774..5d3f4263d9265 100644 --- a/docs/reference/query-languages/esql/kibana/docs/functions/st_simplify.md +++ b/docs/reference/query-languages/esql/kibana/docs/functions/st_simplify.md @@ -4,9 +4,5 @@ Simplifies the input geometry by applying the Douglas-Peucker algorithm with a specified tolerance. Vertices that fall within the tolerance distance from the simplified shape are removed. Note that the resulting geometry may be invalid, even if the original input was valid. ```esql -FROM airports -| SORT name -| LIMIT 5 -| EVAL result = ST_SIMPLIFY(location, 0.0) -| KEEP location, result +null ``` diff --git a/x-pack/plugin/esql/qa/testFixtures/src/main/resources/spatial-jts.csv-spec b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/spatial-jts.csv-spec index 0b71a6dee67ed..e979ad0950052 100644 --- a/x-pack/plugin/esql/qa/testFixtures/src/main/resources/spatial-jts.csv-spec +++ b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/spatial-jts.csv-spec @@ -2,44 +2,75 @@ # Tests for ST_SIMPLIFY ############################################### -stSimplifyMultiRow +stSimplifyLiteralPolygon required_capability: st_simplify -FROM airports -| SORT name -| LIMIT 5 -| EVAL result = ST_SIMPLIFY(TO_GEOSHAPE("POLYGON((-10 -60, 120 -60, 120 60, -10 60, -10 -60))"), 1.0) -| KEEP name, result +// tag::st_simplify[] +ROW wkt = "POLYGON ((7.998 53.827, 9.470 53.068, 15.754 53.801, 16.523 57.160, 11.162 57.868, 8.064 57.445, 6.219 55.317, 7.998 53.827))" +| EVAL simplified = ST_SIMPLIFY(TO_GEOSHAPE(wkt), 0.7) +// end::st_simplify[] +; + +// tag::st_simplify-result[] +wkt:keyword | simplified:geo_shape +POLYGON ((7.998 53.827, 9.470 53.068, 15.754 53.801, 16.523 57.160, 11.162 57.868, 8.064 57.445, 6.219 55.317, 7.998 53.827)) | POLYGON ((9.47 53.068, 15.754 53.801, 16.523 57.16, 8.064 57.445, 6.219 55.317, 9.47 53.068)) +// end::st_simplify-result[] +; + +stSimplifyLiteralPoints +required_capability: st_simplify + +// tag::st_simplify_points[] +ROW wkt = ["POINT (7.998 53.827)", "POINT (9.470 53.068)", "POINT (15.754 53.801)", "POINT (16.523 57.160)", "POINT (11.162 57.868)", "POINT (8.064 57.445)", "POINT (6.219 55.317)", "POINT (7.998 53.827)"] +| EVAL simplified = ST_SIMPLIFY(TO_GEOPOINT(wkt), 0.1) +// end::st_simplify_points[] +; + +// tag::st_simplify_points-result[] +wkt:keyword | simplified:geo_point +[POINT (7.998 53.827), POINT (9.470 53.068), POINT (15.754 53.801), POINT (16.523 57.160), POINT (11.162 57.868), POINT (8.064 57.445), POINT (6.219 55.317), POINT (7.998 53.827)] | [POINT (7.998 53.827), POINT (9.470 53.068), POINT (15.754 53.801), POINT (16.523 57.160), POINT (11.162 57.868), POINT (8.064 57.445), POINT (6.219 55.317), POINT (7.998 53.827)] +// end::st_simplify_points-result[] ; -name:text | result:geo_shape -Aba Tenna D. Yilma Int'l | POLYGON ((-10.0 -60.0, 120.0 -60.0, 120.0 60.0, -10.0 60.0, -10.0 -60.0)) -Abdul Rachman Saleh | POLYGON ((-10.0 -60.0, 120.0 -60.0, 120.0 60.0, -10.0 60.0, -10.0 -60.0)) -Abidjan Port Bouet | POLYGON ((-10.0 -60.0, 120.0 -60.0, 120.0 60.0, -10.0 60.0, -10.0 -60.0)) -Abu Dhabi Int'l | POLYGON ((-10.0 -60.0, 120.0 -60.0, 120.0 60.0, -10.0 60.0, -10.0 -60.0)) -Abuja Int'l | POLYGON ((-10.0 -60.0, 120.0 -60.0, 120.0 60.0, -10.0 60.0, -10.0 -60.0)) +stSimplifyCityBoundaries +required_capability: st_intersects +required_capability: st_simplify + +// tag::st_simplify_city_boundaries[] +FROM airport_city_boundaries +| WHERE ST_INTERSECTS(city_location, TO_GEOSHAPE("POLYGON ((7.998 53.827, 9.470 53.068, 15.754 53.801, 16.523 57.160, 11.162 57.868, 8.064 57.445, 6.219 55.317, 7.998 53.827))")) +| EVAL o_len = LENGTH(TO_STRING(city_boundary)) +| EVAL city_boundary = ST_SIMPLIFY(city_boundary, 0.05) +| EVAL s_len = LENGTH(TO_STRING(city_boundary)) +| KEEP city, o_len, s_len, city_boundary +// end::st_simplify_city_boundaries[] +; + +// tag::st_simplify_city_boundaries-result[] +city:keyword | o_len:long | s_len:long | city_boundary:geo_shape +Copenhagen | 265 | 76 | POLYGON ((12.453 55.7122, 12.5332 55.6318, 12.6398 55.7224, 12.453 55.7122)) +Gothenburg | 112 | 78 | POLYGON ((11.8941 57.6889, 12.0885 57.6823, 12.0541 57.7338, 11.8941 57.6889)) +Norderstedt | 221 | 75 | POLYGON ((9.9348 53.6785, 10.0729 53.7097, 9.9833 53.7595, 9.9348 53.6785)) +// end::st_simplify_city_boundaries-result[] ; stSimplifyMultiRowWithPoints required_capability: st_simplify +required_capability: st_simplify_multivalue -// tag::st_simplify[] +// tag::st_simplify_multipoint[] FROM airports | SORT name -| LIMIT 5 -| EVAL result = ST_SIMPLIFY(location, 0.0) -| KEEP location, result -// end::st_simplify[] +| LIMIT 50 +| STATS points = VALUES(location) +| EVAL result = ST_SIMPLIFY(points, 1) +// end::st_simplify_multipoint[] ; -// tag::st_simplify-result[] -location:geo_point | result:geo_point -POINT (41.857756722253 9.61267784753569) | POINT (41.857756722253 9.61267784753569) -POINT (112.711418617258 -7.92998002840567) | POINT (112.711418617258 -7.92998002840567) -POINT (-3.93221929167636 5.2543984451492) | POINT (-3.93221929167636 5.2543984451492) -POINT (54.6463293225558 24.4272271529764) | POINT (54.6463293225558 24.4272271529764) -POINT (7.27025993974356 9.00437659781094) | POINT (7.27025993974356 9.00437659781094) -// end::st_simplify-result[] +// tag::st_simplify_multipoint-result[] +points:geo_point +POINT (41.857756722253 9.61267784753569) +// end::st_simplify_multipoint-result[] ; stSimplifyNoSimplification diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplify.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplify.java index 95523c8860b2f..4c22ff7d4a712 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplify.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplify.java @@ -30,6 +30,7 @@ import org.locationtech.jts.simplify.DouglasPeuckerSimplifier; import java.io.IOException; +import java.util.ArrayList; import java.util.List; import static org.elasticsearch.xpack.esql.core.util.SpatialCoordinateTypes.UNSPECIFIED; @@ -156,7 +157,21 @@ public boolean foldable() { public Object fold(FoldContext foldCtx) { var toleranceExpression = tolerance.fold(foldCtx); double inputTolerance = getInputTolerance(toleranceExpression); - BytesRef inputGeometry = (BytesRef) geometry.fold(foldCtx); - return geoSourceAndConstantTolerance(inputGeometry, inputTolerance); + Object input = geometry.fold(foldCtx); + if (input instanceof List list) { + ArrayList results = new ArrayList<>(list.size()); + for (Object o : list) { + if (o instanceof BytesRef inputGeometry) { + results.add(geoSourceAndConstantTolerance(inputGeometry, inputTolerance)); + } else { + throw new IllegalArgumentException("unsupported list element type: " + o.getClass().getSimpleName()); + } + } + return results; + } else if (input instanceof BytesRef inputGeometry) { + return geoSourceAndConstantTolerance(inputGeometry, inputTolerance); + } else { + throw new IllegalArgumentException("unsupported block type: " + input.getClass().getSimpleName()); + } } } From 89ac8ccb1bb5094ae9b9814b509869c674f5c651 Mon Sep 17 00:00:00 2001 From: ncordon Date: Wed, 10 Dec 2025 11:26:51 +0100 Subject: [PATCH 32/45] Addresses more pr feedback --- .../esql/_snippets/functions/examples/st_simplify.md | 7 ++++++- .../esql/kibana/definition/functions/st_simplify.json | 2 +- .../esql/kibana/docs/functions/st_simplify.md | 3 ++- .../xpack/esql/core/util/SpatialCoordinateTypes.java | 2 +- .../src/main/resources/spatial-jts.csv-spec | 10 +++++----- .../expression/function/scalar/spatial/StSimplify.java | 2 +- .../function/scalar/spatial/StSimplifyTests.java | 10 ++-------- .../function/scalar/spatial/StSimplifyUnitTests.java | 4 +++- 8 files changed, 21 insertions(+), 19 deletions(-) diff --git a/docs/reference/query-languages/esql/_snippets/functions/examples/st_simplify.md b/docs/reference/query-languages/esql/_snippets/functions/examples/st_simplify.md index b3efac50b7ae7..52ba8652b850d 100644 --- a/docs/reference/query-languages/esql/_snippets/functions/examples/st_simplify.md +++ b/docs/reference/query-languages/esql/_snippets/functions/examples/st_simplify.md @@ -3,7 +3,12 @@ **Example** ```esql -null +ROW wkt = "POLYGON ((7.998 53.827, 9.470 53.068, 15.754 53.801, 16.523 57.160, 11.162 57.868, 8.064 57.445, 6.219 55.317, 7.998 53.827))" +| EVAL simplified = ST_SIMPLIFY(TO_GEOSHAPE(wkt), 0.7) ``` +| wkt:keyword | simplified:geo_shape | +| --- | --- | +| POLYGON ((7.998 53.827, 9.470 53.068, 15.754 53.801, 16.523 57.160, 11.162 57.868, 8.064 57.445, 6.219 55.317, 7.998 53.827)) | POLYGON ((9.47 53.068, 15.754 53.801, 16.523 57.16, 8.064 57.445, 6.219 55.317, 9.47 53.068)) | + diff --git a/docs/reference/query-languages/esql/kibana/definition/functions/st_simplify.json b/docs/reference/query-languages/esql/kibana/definition/functions/st_simplify.json index e33094160336c..89ea74fa57aac 100644 --- a/docs/reference/query-languages/esql/kibana/definition/functions/st_simplify.json +++ b/docs/reference/query-languages/esql/kibana/definition/functions/st_simplify.json @@ -78,7 +78,7 @@ } ], "examples" : [ - null + "ROW wkt = \"POLYGON ((7.998 53.827, 9.470 53.068, 15.754 53.801, 16.523 57.160, 11.162 57.868, 8.064 57.445, 6.219 55.317, 7.998 53.827))\"\n| EVAL simplified = ST_SIMPLIFY(TO_GEOSHAPE(wkt), 0.7)" ], "preview" : true, "snapshot_only" : false diff --git a/docs/reference/query-languages/esql/kibana/docs/functions/st_simplify.md b/docs/reference/query-languages/esql/kibana/docs/functions/st_simplify.md index 5d3f4263d9265..1e46dc4642693 100644 --- a/docs/reference/query-languages/esql/kibana/docs/functions/st_simplify.md +++ b/docs/reference/query-languages/esql/kibana/docs/functions/st_simplify.md @@ -4,5 +4,6 @@ Simplifies the input geometry by applying the Douglas-Peucker algorithm with a specified tolerance. Vertices that fall within the tolerance distance from the simplified shape are removed. Note that the resulting geometry may be invalid, even if the original input was valid. ```esql -null +ROW wkt = "POLYGON ((7.998 53.827, 9.470 53.068, 15.754 53.801, 16.523 57.160, 11.162 57.868, 8.064 57.445, 6.219 55.317, 7.998 53.827))" +| EVAL simplified = ST_SIMPLIFY(TO_GEOSHAPE(wkt), 0.7) ``` diff --git a/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/util/SpatialCoordinateTypes.java b/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/util/SpatialCoordinateTypes.java index 532a24b6825fe..e0ed0549518aa 100644 --- a/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/util/SpatialCoordinateTypes.java +++ b/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/util/SpatialCoordinateTypes.java @@ -145,7 +145,7 @@ public org.locationtech.jts.geom.Geometry wkbToJtsGeometry(BytesRef wkb) throws var topRight = new Coordinate(rect.getMaxX(), rect.getMaxY()); var topLeft = new Coordinate(rect.getMinX(), rect.getMaxY()); - var coordinates = new Coordinate[] { bottomLeft, topLeft, topRight, bottomRight, bottomLeft }; + var coordinates = new Coordinate[] { bottomLeft, bottomRight, topRight, topLeft, bottomLeft }; var geomFactory = new GeometryFactory(); var linearRing = new LinearRing(new CoordinateArraySequence(coordinates), geomFactory); return new Polygon(linearRing, null, geomFactory); diff --git a/x-pack/plugin/esql/qa/testFixtures/src/main/resources/spatial-jts.csv-spec b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/spatial-jts.csv-spec index e979ad0950052..1bbd93866096c 100644 --- a/x-pack/plugin/esql/qa/testFixtures/src/main/resources/spatial-jts.csv-spec +++ b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/spatial-jts.csv-spec @@ -47,10 +47,10 @@ FROM airport_city_boundaries ; // tag::st_simplify_city_boundaries-result[] -city:keyword | o_len:long | s_len:long | city_boundary:geo_shape -Copenhagen | 265 | 76 | POLYGON ((12.453 55.7122, 12.5332 55.6318, 12.6398 55.7224, 12.453 55.7122)) -Gothenburg | 112 | 78 | POLYGON ((11.8941 57.6889, 12.0885 57.6823, 12.0541 57.7338, 11.8941 57.6889)) -Norderstedt | 221 | 75 | POLYGON ((9.9348 53.6785, 10.0729 53.7097, 9.9833 53.7595, 9.9348 53.6785)) +city:keyword | o_len:integer | s_len:integer | city_boundary:geo_shape +Copenhagen | 265 | 76 | POLYGON ((12.453 55.7122, 12.5332 55.6318, 12.6398 55.7224, 12.453 55.7122)) +Gothenburg | 112 | 78 | POLYGON ((11.8941 57.6889, 12.0885 57.6823, 12.0541 57.7338, 11.8941 57.6889)) +Norderstedt | 221 | 75 | POLYGON ((9.9348 53.6785, 10.0729 53.7097, 9.9833 53.7595, 9.9348 53.6785)) // end::st_simplify_city_boundaries-result[] ; @@ -182,7 +182,7 @@ ROW geo_shape = TO_GEOSHAPE("BBOX (0, 2, 2, 0)") ; result1:geo_shape | result2:geo_shape -POLYGON EMPTY | POLYGON ((0.0 0.0, 0.0 2.0, 2.0 2.0, 2.0 0.0, 0.0 0.0)) +POLYGON EMPTY | POLYGON ((0.0 0.0, 2.0 0.0, 2.0 2.0, 0.0 2.0, 0.0 0.0)) ; stSimplifyPostGisFirstExample diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplify.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplify.java index 4c22ff7d4a712..7b7250fdc0525 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplify.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplify.java @@ -51,7 +51,7 @@ public class StSimplify extends EsqlScalarFunction { + "Note that the resulting geometry may be invalid, even if the original input was valid.", preview = true, appliesTo = { @FunctionAppliesTo(lifeCycle = FunctionAppliesToLifecycle.PREVIEW, version = "9.3.0") }, - examples = @Example(file = "spatial", tag = "st_simplify") + examples = @Example(file = "spatial-jts", tag = "st_simplify") ) public StSimplify( Source source, diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyTests.java index bd4605fe9cb24..5499fbe2780d7 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyTests.java @@ -66,12 +66,6 @@ public static TestCaseSupplier.TypedDataSupplier testCaseSupplier(DataType dataT }; } - private static String getFunctionClassName() { - Class testClass = getTestClass(); - String testClassName = testClass.getSimpleName(); - return testClassName.replace("Tests", ""); - } - protected static void addTestCaseSuppliers( List suppliers, DataType spatialType, @@ -86,14 +80,14 @@ protected static void addTestCaseSuppliers( double tolerance = randomDoubleBetween(0, 100, true); TestCaseSupplier.TypedData toleranceData = new TestCaseSupplier.TypedData(tolerance, DOUBLE, "tolerance"); toleranceData = toleranceData.forceLiteral(); - String evaluatorName = "NonFoldableGeometryAndFoldableToleranceEvaluator[inputGeometry=Attribute[channel=0], inputTolerance=" + String evaluatorName = "StSimplifyNonFoldableGeometryAndFoldableToleranceEvaluator[inputGeometry=Attribute[channel=0], inputTolerance=" + tolerance + "]"; var expectedResult = expectedValue.apply(geometry, tolerance); return new TestCaseSupplier.TestCase( List.of(geoTypedData, toleranceData), - getFunctionClassName() + evaluatorName, + evaluatorName, spatialType, Matchers.equalTo(expectedResult) ); diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyUnitTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyUnitTests.java index e1ebdab05e95c..10aa2aa6192de 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyUnitTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyUnitTests.java @@ -89,7 +89,7 @@ public void testInvalidTolerance() { assertThat(ex.getMessage(), containsString("tolerance for st_simplify must be an integer or floating-point number")); } - public void testINegativeTolerance() { + public void testNegativeTolerance() { IllegalArgumentException ex = expectThrows(IllegalArgumentException.class, () -> process(polygonWkt, -1.0)); assertThat(ex.getMessage(), containsString("tolerance must not be negative")); } @@ -198,4 +198,6 @@ public void testBigTolerance() { // It gets simplified to an empty polygon assertEquals("POLYGON EMPTY", result); } + + } From bbfa066698a61c911b72d416061e3f9ce6b7e7f2 Mon Sep 17 00:00:00 2001 From: elasticsearchmachine Date: Wed, 10 Dec 2025 10:35:45 +0000 Subject: [PATCH 33/45] [CI] Auto commit changes from spotless --- .../function/scalar/spatial/StSimplifyTests.java | 7 ++++--- .../function/scalar/spatial/StSimplifyUnitTests.java | 1 - 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyTests.java index 5499fbe2780d7..2f05df6417874 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyTests.java @@ -80,9 +80,10 @@ protected static void addTestCaseSuppliers( double tolerance = randomDoubleBetween(0, 100, true); TestCaseSupplier.TypedData toleranceData = new TestCaseSupplier.TypedData(tolerance, DOUBLE, "tolerance"); toleranceData = toleranceData.forceLiteral(); - String evaluatorName = "StSimplifyNonFoldableGeometryAndFoldableToleranceEvaluator[inputGeometry=Attribute[channel=0], inputTolerance=" - + tolerance - + "]"; + String evaluatorName = + "StSimplifyNonFoldableGeometryAndFoldableToleranceEvaluator[inputGeometry=Attribute[channel=0], inputTolerance=" + + tolerance + + "]"; var expectedResult = expectedValue.apply(geometry, tolerance); return new TestCaseSupplier.TestCase( diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyUnitTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyUnitTests.java index 10aa2aa6192de..51964882e55b0 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyUnitTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyUnitTests.java @@ -199,5 +199,4 @@ public void testBigTolerance() { assertEquals("POLYGON EMPTY", result); } - } From 5d4531f6380476dbeb1f6b4afe206139237d82aa Mon Sep 17 00:00:00 2001 From: Craig Taverner Date: Wed, 10 Dec 2025 12:57:37 +0100 Subject: [PATCH 34/45] Added support for multi-valued fields and initial doc-values evaluators --- .../src/main/resources/spatial-jts.csv-spec | 22 +-- ...ocValuesAndFoldableToleranceEvaluator.java | 129 +++++++++++++ ...ocValuesAndFoldableToleranceEvaluator.java | 130 +++++++++++++ ...GeometryAndFoldableToleranceEvaluator.java | 78 +++----- .../function/scalar/spatial/StSimplify.java | 179 ++++++++++++++---- .../scalar/spatial/StSimplifyTests.java | 2 +- 6 files changed, 438 insertions(+), 102 deletions(-) create mode 100644 x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyNonFoldableCartesianPointDocValuesAndFoldableToleranceEvaluator.java create mode 100644 x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyNonFoldableGeoPointDocValuesAndFoldableToleranceEvaluator.java diff --git a/x-pack/plugin/esql/qa/testFixtures/src/main/resources/spatial-jts.csv-spec b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/spatial-jts.csv-spec index e979ad0950052..3cf5cf15a76c7 100644 --- a/x-pack/plugin/esql/qa/testFixtures/src/main/resources/spatial-jts.csv-spec +++ b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/spatial-jts.csv-spec @@ -47,30 +47,26 @@ FROM airport_city_boundaries ; // tag::st_simplify_city_boundaries-result[] -city:keyword | o_len:long | s_len:long | city_boundary:geo_shape -Copenhagen | 265 | 76 | POLYGON ((12.453 55.7122, 12.5332 55.6318, 12.6398 55.7224, 12.453 55.7122)) -Gothenburg | 112 | 78 | POLYGON ((11.8941 57.6889, 12.0885 57.6823, 12.0541 57.7338, 11.8941 57.6889)) -Norderstedt | 221 | 75 | POLYGON ((9.9348 53.6785, 10.0729 53.7097, 9.9833 53.7595, 9.9348 53.6785)) +city:keyword | o_len:i | s_len:i | city_boundary:geo_shape +Copenhagen | 265 | 76 | POLYGON ((12.453 55.7122, 12.5332 55.6318, 12.6398 55.7224, 12.453 55.7122)) +Gothenburg | 112 | 78 | POLYGON ((11.8941 57.6889, 12.0885 57.6823, 12.0541 57.7338, 11.8941 57.6889)) +Norderstedt | 221 | 75 | POLYGON ((9.9348 53.6785, 10.0729 53.7097, 9.9833 53.7595, 9.9348 53.6785)) // end::st_simplify_city_boundaries-result[] ; stSimplifyMultiRowWithPoints required_capability: st_simplify -required_capability: st_simplify_multivalue -// tag::st_simplify_multipoint[] FROM airports | SORT name -| LIMIT 50 -| STATS points = VALUES(location) +| STATS count = COUNT(location), points = VALUES(location) | EVAL result = ST_SIMPLIFY(points, 1) -// end::st_simplify_multipoint[] +| EVAL result_length = LENGTH(TO_STRING(result)) +| KEEP count, result_length ; -// tag::st_simplify_multipoint-result[] -points:geo_point -POINT (41.857756722253 9.61267784753569) -// end::st_simplify_multipoint-result[] +count:long | result_length:i +891 | 37442 ; stSimplifyNoSimplification diff --git a/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyNonFoldableCartesianPointDocValuesAndFoldableToleranceEvaluator.java b/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyNonFoldableCartesianPointDocValuesAndFoldableToleranceEvaluator.java new file mode 100644 index 0000000000000..f78e806d5de8b --- /dev/null +++ b/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyNonFoldableCartesianPointDocValuesAndFoldableToleranceEvaluator.java @@ -0,0 +1,129 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License +// 2.0; you may not use this file except in compliance with the Elastic License +// 2.0. +package org.elasticsearch.xpack.esql.expression.function.scalar.spatial; + +import java.io.IOException; +import java.lang.IllegalArgumentException; +import java.lang.Override; +import java.lang.String; +import org.apache.lucene.util.RamUsageEstimator; +import org.elasticsearch.compute.data.Block; +import org.elasticsearch.compute.data.BytesRefBlock; +import org.elasticsearch.compute.data.LongBlock; +import org.elasticsearch.compute.data.Page; +import org.elasticsearch.compute.operator.DriverContext; +import org.elasticsearch.compute.operator.EvalOperator; +import org.elasticsearch.compute.operator.Warnings; +import org.elasticsearch.core.Releasables; +import org.elasticsearch.xpack.esql.core.tree.Source; + +/** + * {@link EvalOperator.ExpressionEvaluator} implementation for {@link StSimplify}. + * This class is generated. Edit {@code EvaluatorImplementer} instead. + */ +public final class StSimplifyNonFoldableCartesianPointDocValuesAndFoldableToleranceEvaluator implements EvalOperator.ExpressionEvaluator { + private static final long BASE_RAM_BYTES_USED = RamUsageEstimator.shallowSizeOfInstance(StSimplifyNonFoldableCartesianPointDocValuesAndFoldableToleranceEvaluator.class); + + private final Source source; + + private final EvalOperator.ExpressionEvaluator left; + + private final double tolerance; + + private final DriverContext driverContext; + + private Warnings warnings; + + public StSimplifyNonFoldableCartesianPointDocValuesAndFoldableToleranceEvaluator(Source source, + EvalOperator.ExpressionEvaluator left, double tolerance, DriverContext driverContext) { + this.source = source; + this.left = left; + this.tolerance = tolerance; + this.driverContext = driverContext; + } + + @Override + public Block eval(Page page) { + try (LongBlock leftBlock = (LongBlock) left.eval(page)) { + return eval(page.getPositionCount(), leftBlock); + } + } + + @Override + public long baseRamBytesUsed() { + long baseRamBytesUsed = BASE_RAM_BYTES_USED; + baseRamBytesUsed += left.baseRamBytesUsed(); + return baseRamBytesUsed; + } + + public BytesRefBlock eval(int positionCount, LongBlock leftBlock) { + try(BytesRefBlock.Builder result = driverContext.blockFactory().newBytesRefBlockBuilder(positionCount)) { + position: for (int p = 0; p < positionCount; p++) { + boolean allBlocksAreNulls = true; + if (!leftBlock.isNull(p)) { + allBlocksAreNulls = false; + } + if (allBlocksAreNulls) { + result.appendNull(); + continue position; + } + try { + StSimplify.processCartesianPointDocValuesAndConstantTolerance(result, p, leftBlock, this.tolerance); + } catch (IllegalArgumentException | IOException e) { + warnings().registerException(e); + result.appendNull(); + } + } + return result.build(); + } + } + + @Override + public String toString() { + return "StSimplifyNonFoldableCartesianPointDocValuesAndFoldableToleranceEvaluator[" + "left=" + left + ", tolerance=" + tolerance + "]"; + } + + @Override + public void close() { + Releasables.closeExpectNoException(left); + } + + private Warnings warnings() { + if (warnings == null) { + this.warnings = Warnings.createWarnings( + driverContext.warningsMode(), + source.source().getLineNumber(), + source.source().getColumnNumber(), + source.text() + ); + } + return warnings; + } + + static class Factory implements EvalOperator.ExpressionEvaluator.Factory { + private final Source source; + + private final EvalOperator.ExpressionEvaluator.Factory left; + + private final double tolerance; + + public Factory(Source source, EvalOperator.ExpressionEvaluator.Factory left, double tolerance) { + this.source = source; + this.left = left; + this.tolerance = tolerance; + } + + @Override + public StSimplifyNonFoldableCartesianPointDocValuesAndFoldableToleranceEvaluator get( + DriverContext context) { + return new StSimplifyNonFoldableCartesianPointDocValuesAndFoldableToleranceEvaluator(source, left.get(context), tolerance, context); + } + + @Override + public String toString() { + return "StSimplifyNonFoldableCartesianPointDocValuesAndFoldableToleranceEvaluator[" + "left=" + left + ", tolerance=" + tolerance + "]"; + } + } +} diff --git a/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyNonFoldableGeoPointDocValuesAndFoldableToleranceEvaluator.java b/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyNonFoldableGeoPointDocValuesAndFoldableToleranceEvaluator.java new file mode 100644 index 0000000000000..1608f4cc0fa1c --- /dev/null +++ b/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyNonFoldableGeoPointDocValuesAndFoldableToleranceEvaluator.java @@ -0,0 +1,130 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License +// 2.0; you may not use this file except in compliance with the Elastic License +// 2.0. +package org.elasticsearch.xpack.esql.expression.function.scalar.spatial; + +import java.io.IOException; +import java.lang.IllegalArgumentException; +import java.lang.Override; +import java.lang.String; +import org.apache.lucene.util.RamUsageEstimator; +import org.elasticsearch.compute.data.Block; +import org.elasticsearch.compute.data.BytesRefBlock; +import org.elasticsearch.compute.data.LongBlock; +import org.elasticsearch.compute.data.Page; +import org.elasticsearch.compute.operator.DriverContext; +import org.elasticsearch.compute.operator.EvalOperator; +import org.elasticsearch.compute.operator.Warnings; +import org.elasticsearch.core.Releasables; +import org.elasticsearch.xpack.esql.core.tree.Source; + +/** + * {@link EvalOperator.ExpressionEvaluator} implementation for {@link StSimplify}. + * This class is generated. Edit {@code EvaluatorImplementer} instead. + */ +public final class StSimplifyNonFoldableGeoPointDocValuesAndFoldableToleranceEvaluator implements EvalOperator.ExpressionEvaluator { + private static final long BASE_RAM_BYTES_USED = RamUsageEstimator.shallowSizeOfInstance(StSimplifyNonFoldableGeoPointDocValuesAndFoldableToleranceEvaluator.class); + + private final Source source; + + private final EvalOperator.ExpressionEvaluator point; + + private final double tolerance; + + private final DriverContext driverContext; + + private Warnings warnings; + + public StSimplifyNonFoldableGeoPointDocValuesAndFoldableToleranceEvaluator(Source source, + EvalOperator.ExpressionEvaluator point, double tolerance, DriverContext driverContext) { + this.source = source; + this.point = point; + this.tolerance = tolerance; + this.driverContext = driverContext; + } + + @Override + public Block eval(Page page) { + try (LongBlock pointBlock = (LongBlock) point.eval(page)) { + return eval(page.getPositionCount(), pointBlock); + } + } + + @Override + public long baseRamBytesUsed() { + long baseRamBytesUsed = BASE_RAM_BYTES_USED; + baseRamBytesUsed += point.baseRamBytesUsed(); + return baseRamBytesUsed; + } + + public BytesRefBlock eval(int positionCount, LongBlock pointBlock) { + try(BytesRefBlock.Builder result = driverContext.blockFactory().newBytesRefBlockBuilder(positionCount)) { + position: for (int p = 0; p < positionCount; p++) { + boolean allBlocksAreNulls = true; + if (!pointBlock.isNull(p)) { + allBlocksAreNulls = false; + } + if (allBlocksAreNulls) { + result.appendNull(); + continue position; + } + try { + StSimplify.processGeoPointDocValuesAndConstantTolerance(result, p, pointBlock, this.tolerance); + } catch (IllegalArgumentException | IOException e) { + warnings().registerException(e); + result.appendNull(); + } + } + return result.build(); + } + } + + @Override + public String toString() { + return "StSimplifyNonFoldableGeoPointDocValuesAndFoldableToleranceEvaluator[" + "point=" + point + ", tolerance=" + tolerance + "]"; + } + + @Override + public void close() { + Releasables.closeExpectNoException(point); + } + + private Warnings warnings() { + if (warnings == null) { + this.warnings = Warnings.createWarnings( + driverContext.warningsMode(), + source.source().getLineNumber(), + source.source().getColumnNumber(), + source.text() + ); + } + return warnings; + } + + static class Factory implements EvalOperator.ExpressionEvaluator.Factory { + private final Source source; + + private final EvalOperator.ExpressionEvaluator.Factory point; + + private final double tolerance; + + public Factory(Source source, EvalOperator.ExpressionEvaluator.Factory point, + double tolerance) { + this.source = source; + this.point = point; + this.tolerance = tolerance; + } + + @Override + public StSimplifyNonFoldableGeoPointDocValuesAndFoldableToleranceEvaluator get( + DriverContext context) { + return new StSimplifyNonFoldableGeoPointDocValuesAndFoldableToleranceEvaluator(source, point.get(context), tolerance, context); + } + + @Override + public String toString() { + return "StSimplifyNonFoldableGeoPointDocValuesAndFoldableToleranceEvaluator[" + "point=" + point + ", tolerance=" + tolerance + "]"; + } + } +} diff --git a/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyNonFoldableGeometryAndFoldableToleranceEvaluator.java b/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyNonFoldableGeometryAndFoldableToleranceEvaluator.java index e1028d75c59b9..72fcae95c4930 100644 --- a/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyNonFoldableGeometryAndFoldableToleranceEvaluator.java +++ b/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyNonFoldableGeometryAndFoldableToleranceEvaluator.java @@ -7,11 +7,9 @@ import java.lang.IllegalArgumentException; import java.lang.Override; import java.lang.String; -import org.apache.lucene.util.BytesRef; import org.apache.lucene.util.RamUsageEstimator; import org.elasticsearch.compute.data.Block; import org.elasticsearch.compute.data.BytesRefBlock; -import org.elasticsearch.compute.data.BytesRefVector; import org.elasticsearch.compute.data.Page; import org.elasticsearch.compute.operator.DriverContext; import org.elasticsearch.compute.operator.EvalOperator; @@ -28,75 +26,49 @@ public final class StSimplifyNonFoldableGeometryAndFoldableToleranceEvaluator im private final Source source; - private final EvalOperator.ExpressionEvaluator inputGeometry; + private final EvalOperator.ExpressionEvaluator geometry; - private final double inputTolerance; + private final double tolerance; private final DriverContext driverContext; private Warnings warnings; public StSimplifyNonFoldableGeometryAndFoldableToleranceEvaluator(Source source, - EvalOperator.ExpressionEvaluator inputGeometry, double inputTolerance, - DriverContext driverContext) { + EvalOperator.ExpressionEvaluator geometry, double tolerance, DriverContext driverContext) { this.source = source; - this.inputGeometry = inputGeometry; - this.inputTolerance = inputTolerance; + this.geometry = geometry; + this.tolerance = tolerance; this.driverContext = driverContext; } @Override public Block eval(Page page) { - try (BytesRefBlock inputGeometryBlock = (BytesRefBlock) inputGeometry.eval(page)) { - BytesRefVector inputGeometryVector = inputGeometryBlock.asVector(); - if (inputGeometryVector == null) { - return eval(page.getPositionCount(), inputGeometryBlock); - } - return eval(page.getPositionCount(), inputGeometryVector); + try (BytesRefBlock geometryBlock = (BytesRefBlock) geometry.eval(page)) { + return eval(page.getPositionCount(), geometryBlock); } } @Override public long baseRamBytesUsed() { long baseRamBytesUsed = BASE_RAM_BYTES_USED; - baseRamBytesUsed += inputGeometry.baseRamBytesUsed(); + baseRamBytesUsed += geometry.baseRamBytesUsed(); return baseRamBytesUsed; } - public BytesRefBlock eval(int positionCount, BytesRefBlock inputGeometryBlock) { + public BytesRefBlock eval(int positionCount, BytesRefBlock geometryBlock) { try(BytesRefBlock.Builder result = driverContext.blockFactory().newBytesRefBlockBuilder(positionCount)) { - BytesRef inputGeometryScratch = new BytesRef(); position: for (int p = 0; p < positionCount; p++) { - switch (inputGeometryBlock.getValueCount(p)) { - case 0: - result.appendNull(); - continue position; - case 1: - break; - default: - warnings().registerException(new IllegalArgumentException("single-value function encountered multi-value")); - result.appendNull(); - continue position; + boolean allBlocksAreNulls = true; + if (!geometryBlock.isNull(p)) { + allBlocksAreNulls = false; } - BytesRef inputGeometry = inputGeometryBlock.getBytesRef(inputGeometryBlock.getFirstValueIndex(p), inputGeometryScratch); - try { - result.appendBytesRef(StSimplify.processNonFoldableGeometryAndConstantTolerance(inputGeometry, this.inputTolerance)); - } catch (IllegalArgumentException e) { - warnings().registerException(e); + if (allBlocksAreNulls) { result.appendNull(); + continue position; } - } - return result.build(); - } - } - - public BytesRefBlock eval(int positionCount, BytesRefVector inputGeometryVector) { - try(BytesRefBlock.Builder result = driverContext.blockFactory().newBytesRefBlockBuilder(positionCount)) { - BytesRef inputGeometryScratch = new BytesRef(); - position: for (int p = 0; p < positionCount; p++) { - BytesRef inputGeometry = inputGeometryVector.getBytesRef(p, inputGeometryScratch); try { - result.appendBytesRef(StSimplify.processNonFoldableGeometryAndConstantTolerance(inputGeometry, this.inputTolerance)); + StSimplify.processNonFoldableGeometryAndConstantTolerance(result, p, geometryBlock, this.tolerance); } catch (IllegalArgumentException e) { warnings().registerException(e); result.appendNull(); @@ -108,12 +80,12 @@ public BytesRefBlock eval(int positionCount, BytesRefVector inputGeometryVector) @Override public String toString() { - return "StSimplifyNonFoldableGeometryAndFoldableToleranceEvaluator[" + "inputGeometry=" + inputGeometry + ", inputTolerance=" + inputTolerance + "]"; + return "StSimplifyNonFoldableGeometryAndFoldableToleranceEvaluator[" + "geometry=" + geometry + ", tolerance=" + tolerance + "]"; } @Override public void close() { - Releasables.closeExpectNoException(inputGeometry); + Releasables.closeExpectNoException(geometry); } private Warnings warnings() { @@ -131,25 +103,25 @@ private Warnings warnings() { static class Factory implements EvalOperator.ExpressionEvaluator.Factory { private final Source source; - private final EvalOperator.ExpressionEvaluator.Factory inputGeometry; + private final EvalOperator.ExpressionEvaluator.Factory geometry; - private final double inputTolerance; + private final double tolerance; - public Factory(Source source, EvalOperator.ExpressionEvaluator.Factory inputGeometry, - double inputTolerance) { + public Factory(Source source, EvalOperator.ExpressionEvaluator.Factory geometry, + double tolerance) { this.source = source; - this.inputGeometry = inputGeometry; - this.inputTolerance = inputTolerance; + this.geometry = geometry; + this.tolerance = tolerance; } @Override public StSimplifyNonFoldableGeometryAndFoldableToleranceEvaluator get(DriverContext context) { - return new StSimplifyNonFoldableGeometryAndFoldableToleranceEvaluator(source, inputGeometry.get(context), inputTolerance, context); + return new StSimplifyNonFoldableGeometryAndFoldableToleranceEvaluator(source, geometry.get(context), tolerance, context); } @Override public String toString() { - return "StSimplifyNonFoldableGeometryAndFoldableToleranceEvaluator[" + "inputGeometry=" + inputGeometry + ", inputTolerance=" + inputTolerance + "]"; + return "StSimplifyNonFoldableGeometryAndFoldableToleranceEvaluator[" + "geometry=" + geometry + ", tolerance=" + tolerance + "]"; } } } diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplify.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplify.java index 4c22ff7d4a712..ada29e7d75cf7 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplify.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplify.java @@ -13,12 +13,17 @@ import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.compute.ann.Evaluator; import org.elasticsearch.compute.ann.Fixed; +import org.elasticsearch.compute.ann.Position; +import org.elasticsearch.compute.data.BytesRefBlock; +import org.elasticsearch.compute.data.LongBlock; import org.elasticsearch.compute.operator.EvalOperator; +import org.elasticsearch.geometry.Point; import org.elasticsearch.xpack.esql.core.expression.Expression; import org.elasticsearch.xpack.esql.core.expression.FoldContext; import org.elasticsearch.xpack.esql.core.tree.NodeInfo; import org.elasticsearch.xpack.esql.core.tree.Source; import org.elasticsearch.xpack.esql.core.type.DataType; +import org.elasticsearch.xpack.esql.core.util.SpatialCoordinateTypes; import org.elasticsearch.xpack.esql.expression.function.Example; import org.elasticsearch.xpack.esql.expression.function.FunctionAppliesTo; import org.elasticsearch.xpack.esql.expression.function.FunctionAppliesToLifecycle; @@ -26,13 +31,19 @@ import org.elasticsearch.xpack.esql.expression.function.Param; import org.elasticsearch.xpack.esql.expression.function.scalar.EsqlScalarFunction; import org.elasticsearch.xpack.esql.io.stream.PlanStreamInput; +import org.locationtech.jts.geom.Coordinate; +import org.locationtech.jts.geom.Geometry; +import org.locationtech.jts.geom.GeometryFactory; import org.locationtech.jts.io.ParseException; import org.locationtech.jts.simplify.DouglasPeuckerSimplifier; import java.io.IOException; import java.util.ArrayList; import java.util.List; +import java.util.function.Function; +import static org.elasticsearch.xpack.esql.core.util.SpatialCoordinateTypes.CARTESIAN; +import static org.elasticsearch.xpack.esql.core.util.SpatialCoordinateTypes.GEO; import static org.elasticsearch.xpack.esql.core.util.SpatialCoordinateTypes.UNSPECIFIED; public class StSimplify extends EsqlScalarFunction { @@ -41,8 +52,11 @@ public class StSimplify extends EsqlScalarFunction { "StSimplify", StSimplify::new ); - Expression geometry; - Expression tolerance; + private static final BlockProcessor processor = new BlockProcessor(UNSPECIFIED); + private static final BlockProcessor geoProcessor = new BlockProcessor(GEO); + private static final BlockProcessor cartesianProcessor = new BlockProcessor(CARTESIAN); + private final Expression geometry; + private final Expression tolerance; @FunctionInfo( returnType = { "geo_point", "geo_shape", "cartesian_point", "cartesian_shape" }, @@ -103,19 +117,6 @@ public void writeTo(StreamOutput out) throws IOException { out.writeNamedWriteable(tolerance); } - private static BytesRef geoSourceAndConstantTolerance(BytesRef inputGeometry, double inputTolerance) { - if (inputGeometry == null) { - return null; - } - try { - org.locationtech.jts.geom.Geometry jtsGeometry = UNSPECIFIED.wkbToJtsGeometry(inputGeometry); - org.locationtech.jts.geom.Geometry simplifiedGeometry = DouglasPeuckerSimplifier.simplify(jtsGeometry, inputTolerance); - return UNSPECIFIED.jtsGeometryToWkb(simplifiedGeometry); - } catch (ParseException e) { - throw new IllegalArgumentException("could not parse the geometry expression: " + e); - } - } - @Override public EvalOperator.ExpressionEvaluator.Factory toEvaluator(ToEvaluator toEvaluator) { EvalOperator.ExpressionEvaluator.Factory geometryEvaluator = toEvaluator.apply(geometry); @@ -128,26 +129,6 @@ public EvalOperator.ExpressionEvaluator.Factory toEvaluator(ToEvaluator toEvalua return new StSimplifyNonFoldableGeometryAndFoldableToleranceEvaluator.Factory(source(), geometryEvaluator, inputTolerance); } - private static double getInputTolerance(Object toleranceExpression) { - double inputTolerance; - - if (toleranceExpression instanceof Number number) { - inputTolerance = number.doubleValue(); - } else { - throw new IllegalArgumentException("tolerance for st_simplify must be an integer or floating-point number"); - } - - if (inputTolerance < 0) { - throw new IllegalArgumentException("tolerance must not be negative"); - } - return inputTolerance; - } - - @Evaluator(extraName = "NonFoldableGeometryAndFoldableTolerance", warnExceptions = { IllegalArgumentException.class }) - static BytesRef processNonFoldableGeometryAndConstantTolerance(BytesRef inputGeometry, @Fixed double inputTolerance) { - return geoSourceAndConstantTolerance(inputGeometry, inputTolerance); - } - @Override public boolean foldable() { return geometry.foldable() && tolerance.foldable(); @@ -159,6 +140,7 @@ public Object fold(FoldContext foldCtx) { double inputTolerance = getInputTolerance(toleranceExpression); Object input = geometry.fold(foldCtx); if (input instanceof List list) { + // TODO: Consider if this should compact to a GeometryCollection instead, which is what we do for fields ArrayList results = new ArrayList<>(list.size()); for (Object o : list) { if (o instanceof BytesRef inputGeometry) { @@ -174,4 +156,131 @@ public Object fold(FoldContext foldCtx) { throw new IllegalArgumentException("unsupported block type: " + input.getClass().getSimpleName()); } } + + @Evaluator(extraName = "NonFoldableGeometryAndFoldableTolerance", warnExceptions = { IllegalArgumentException.class }) + static void processNonFoldableGeometryAndConstantTolerance( + BytesRefBlock.Builder builder, + @Position int p, + BytesRefBlock geometry, + @Fixed double tolerance + ) { + processor.processGeometries(builder, p, geometry, tolerance); + } + + @Evaluator( + extraName = "NonFoldableGeoPointDocValuesAndFoldableTolerance", + warnExceptions = { IllegalArgumentException.class, IOException.class } + ) + static void processGeoPointDocValuesAndConstantTolerance( + BytesRefBlock.Builder builder, + @Position int p, + LongBlock point, + @Fixed double tolerance + ) throws IOException { + geoProcessor.processPoints(builder, p, point, tolerance); + } + + private static BytesRef geoSourceAndConstantTolerance(BytesRef inputGeometry, double inputTolerance) { + if (inputGeometry == null) { + return null; + } + try { + Geometry jtsGeometry = UNSPECIFIED.wkbToJtsGeometry(inputGeometry); + Geometry simplifiedGeometry = DouglasPeuckerSimplifier.simplify(jtsGeometry, inputTolerance); + return UNSPECIFIED.jtsGeometryToWkb(simplifiedGeometry); + } catch (ParseException e) { + throw new IllegalArgumentException("could not parse the geometry expression: " + e); + } + } + + private static double getInputTolerance(Object toleranceExpression) { + double inputTolerance; + + if (toleranceExpression instanceof Number number) { + inputTolerance = number.doubleValue(); + } else { + throw new IllegalArgumentException("tolerance for st_simplify must be an integer or floating-point number"); + } + + if (inputTolerance < 0) { + throw new IllegalArgumentException("tolerance must not be negative"); + } + return inputTolerance; + } + + @Evaluator( + extraName = "NonFoldableCartesianPointDocValuesAndFoldableTolerance", + warnExceptions = { IllegalArgumentException.class, IOException.class } + ) + static void processCartesianPointDocValuesAndConstantTolerance( + BytesRefBlock.Builder builder, + @Position int p, + LongBlock left, + @Fixed double tolerance + ) throws IOException { + cartesianProcessor.processPoints(builder, p, left, tolerance); + } + + private static class BlockProcessor { + private final SpatialCoordinateTypes spatialCoordinateType; + private final GeometryFactory geometryFactory = new GeometryFactory(); + + BlockProcessor(SpatialCoordinateTypes spatialCoordinateType) { + this.spatialCoordinateType = spatialCoordinateType; + } + + void processPoints(BytesRefBlock.Builder builder, int p, LongBlock left, double tolerance) throws IOException { + if (left.getValueCount(p) < 1) { + builder.appendNull(); + } else { + final Geometry jtsGeometry = asJtsMultiPoint(left, p, spatialCoordinateType::longAsPoint); + Geometry simplifiedGeometry = DouglasPeuckerSimplifier.simplify(jtsGeometry, tolerance); + builder.appendBytesRef(UNSPECIFIED.jtsGeometryToWkb(simplifiedGeometry)); + } + } + + void processGeometries(BytesRefBlock.Builder builder, int p, BytesRefBlock left, double tolerance) { + if (left.getValueCount(p) < 1) { + builder.appendNull(); + } else { + final Geometry jtsGeometry = asJtsGeometry(left, p); + Geometry simplifiedGeometry = DouglasPeuckerSimplifier.simplify(jtsGeometry, tolerance); + builder.appendBytesRef(UNSPECIFIED.jtsGeometryToWkb(simplifiedGeometry)); + } + } + + Geometry asJtsMultiPoint(LongBlock valueBlock, int position, Function decoder) { + final int firstValueIndex = valueBlock.getFirstValueIndex(position); + final int valueCount = valueBlock.getValueCount(position); + if (valueCount == 1) { + Point point = decoder.apply(valueBlock.getLong(firstValueIndex)); + return geometryFactory.createPoint(new Coordinate(point.getX(), point.getY())); + } + final Coordinate[] coordinates = new Coordinate[valueCount]; + for (int i = 0; i < valueCount; i++) { + Point point = decoder.apply(valueBlock.getLong(firstValueIndex + i)); + coordinates[i] = new Coordinate(point.getX(), point.getY()); + } + return geometryFactory.createMultiPointFromCoords(coordinates); + } + + Geometry asJtsGeometry(BytesRefBlock valueBlock, int position) { + try { + final int firstValueIndex = valueBlock.getFirstValueIndex(position); + final int valueCount = valueBlock.getValueCount(position); + BytesRef scratch = new BytesRef(); + if (valueCount == 1) { + return UNSPECIFIED.wkbToJtsGeometry(valueBlock.getBytesRef(firstValueIndex, scratch)); + } + final Geometry[] geometries = new Geometry[valueCount]; + for (int i = 0; i < valueCount; i++) { + BytesRef wkb = valueBlock.getBytesRef(firstValueIndex + i, scratch); + geometries[i] = UNSPECIFIED.wkbToJtsGeometry(valueBlock.getBytesRef(firstValueIndex, scratch)); + } + return geometryFactory.createGeometryCollection(geometries); + } catch (ParseException e) { + throw new IllegalArgumentException("could not parse the geometry expression: " + e); + } + } + } } diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyTests.java index bd4605fe9cb24..941d10a377f07 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyTests.java @@ -86,7 +86,7 @@ protected static void addTestCaseSuppliers( double tolerance = randomDoubleBetween(0, 100, true); TestCaseSupplier.TypedData toleranceData = new TestCaseSupplier.TypedData(tolerance, DOUBLE, "tolerance"); toleranceData = toleranceData.forceLiteral(); - String evaluatorName = "NonFoldableGeometryAndFoldableToleranceEvaluator[inputGeometry=Attribute[channel=0], inputTolerance=" + String evaluatorName = "NonFoldableGeometryAndFoldableToleranceEvaluator[geometry=Attribute[channel=0], tolerance=" + tolerance + "]"; var expectedResult = expectedValue.apply(geometry, tolerance); From 15c06d32f9f89b0eae036be4b4aab7897f1ac78d Mon Sep 17 00:00:00 2001 From: Craig Taverner Date: Wed, 10 Dec 2025 18:31:07 +0100 Subject: [PATCH 35/45] Add st_simplify to the docs --- .../query-languages/esql/_snippets/lists/spatial-functions.md | 1 + .../esql/functions-operators/spatial-functions.md | 3 +++ 2 files changed, 4 insertions(+) diff --git a/docs/reference/query-languages/esql/_snippets/lists/spatial-functions.md b/docs/reference/query-languages/esql/_snippets/lists/spatial-functions.md index 8ffdebed1a899..4812a5df8dd44 100644 --- a/docs/reference/query-languages/esql/_snippets/lists/spatial-functions.md +++ b/docs/reference/query-languages/esql/_snippets/lists/spatial-functions.md @@ -13,3 +13,4 @@ * [`ST_GEOTILE`](../../functions-operators/spatial-functions.md#esql-st_geotile) {applies_to}`stack: preview` {applies_to}`serverless: preview` * [`ST_GEOHEX`](../../functions-operators/spatial-functions.md#esql-st_geohex) {applies_to}`stack: preview` {applies_to}`serverless: preview` * [`ST_GEOHASH`](../../functions-operators/spatial-functions.md#esql-st_geohash) {applies_to}`stack: preview` {applies_to}`serverless: preview` +* [`ST_SIMPLIFY`](../../functions-operators/spatial-functions.md#esql-st_simplify) {applies_to}`stack: preview` {applies_to}`serverless: preview` diff --git a/docs/reference/query-languages/esql/functions-operators/spatial-functions.md b/docs/reference/query-languages/esql/functions-operators/spatial-functions.md index df91b73e257a9..09a2fcc8bd450 100644 --- a/docs/reference/query-languages/esql/functions-operators/spatial-functions.md +++ b/docs/reference/query-languages/esql/functions-operators/spatial-functions.md @@ -59,3 +59,6 @@ mapped_pages: :::{include} ../_snippets/functions/layout/st_geohash.md ::: + +:::{include} ../_snippets/functions/layout/st_simplify.md +::: From 5e443a3845fb3751108ba619adc29617f84d54d6 Mon Sep 17 00:00:00 2001 From: ncordon Date: Sun, 14 Dec 2025 19:38:01 +0100 Subject: [PATCH 36/45] Last nits --- docs/changelog/136309.yaml | 2 +- .../_snippets/functions/layout/st_simplify.md | 2 +- .../spatial/SpatialDocValuesFunction.java | 4 +- .../function/scalar/spatial/StSimplify.java | 32 ++++++------ .../optimizer/PhysicalPlanOptimizerTests.java | 51 ++++++++++++++++++- 5 files changed, 69 insertions(+), 22 deletions(-) diff --git a/docs/changelog/136309.yaml b/docs/changelog/136309.yaml index 4b317903e3c78..f27dcca302bc9 100644 --- a/docs/changelog/136309.yaml +++ b/docs/changelog/136309.yaml @@ -1,5 +1,5 @@ pr: 136309 -summary: Adds ST_SIMPLIFY geo spatial function +summary: Adds ST_SIMPLIFY geospatial function area: ES|QL type: enhancement issues: diff --git a/docs/reference/query-languages/esql/_snippets/functions/layout/st_simplify.md b/docs/reference/query-languages/esql/_snippets/functions/layout/st_simplify.md index d4aa0090bf964..8ec47b30e9c2a 100644 --- a/docs/reference/query-languages/esql/_snippets/functions/layout/st_simplify.md +++ b/docs/reference/query-languages/esql/_snippets/functions/layout/st_simplify.md @@ -2,7 +2,7 @@ ## `ST_SIMPLIFY` [esql-st_simplify] ```{applies_to} -stack: preview 9.3.0 +stack: preview serverless: preview ``` diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/SpatialDocValuesFunction.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/SpatialDocValuesFunction.java index 3f2a5f1ab8ed7..407d7bb0cf5cc 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/SpatialDocValuesFunction.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/SpatialDocValuesFunction.java @@ -15,8 +15,8 @@ import java.util.Objects; /** - * Spatial functions that take one spatial argument, one parameter and one optional bounds can inherit from this class. - * Obvious choices are: StGeohash, StGeotile and StGeohex. + * Spatial functions that can take doc values as an argument can inherit from this class. + * Examples: StGeohash, StGeotile, StGeohex and StSimplify */ public abstract class SpatialDocValuesFunction extends EsqlScalarFunction { protected final boolean spatialDocValues; diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplify.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplify.java index 26d6f0ec4cae7..232b1cdb1032a 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplify.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplify.java @@ -63,7 +63,7 @@ public class StSimplify extends SpatialDocValuesFunction { + "Vertices that fall within the tolerance distance from the simplified shape are removed. " + "Note that the resulting geometry may be invalid, even if the original input was valid.", preview = true, - appliesTo = { @FunctionAppliesTo(lifeCycle = FunctionAppliesToLifecycle.PREVIEW, version = "9.3.0") }, + appliesTo = { @FunctionAppliesTo(lifeCycle = FunctionAppliesToLifecycle.PREVIEW) }, examples = @Example(file = "spatial-jts", tag = "st_simplify") ) public StSimplify( @@ -170,14 +170,14 @@ public Object fold(FoldContext foldCtx) { ArrayList results = new ArrayList<>(list.size()); for (Object o : list) { if (o instanceof BytesRef inputGeometry) { - results.add(geoSourceAndConstantTolerance(inputGeometry, inputTolerance)); + results.add(BlockProcessor.geoSourceAndConstantTolerance(inputGeometry, inputTolerance)); } else { throw new IllegalArgumentException("unsupported list element type: " + o.getClass().getSimpleName()); } } return results; } else if (input instanceof BytesRef inputGeometry) { - return geoSourceAndConstantTolerance(inputGeometry, inputTolerance); + return BlockProcessor.geoSourceAndConstantTolerance(inputGeometry, inputTolerance); } else { throw new IllegalArgumentException("unsupported block type: " + input.getClass().getSimpleName()); } @@ -206,19 +206,6 @@ static void processGeoPointDocValuesAndConstantTolerance( geoProcessor.processPoints(builder, p, point, tolerance); } - private static BytesRef geoSourceAndConstantTolerance(BytesRef inputGeometry, double inputTolerance) { - if (inputGeometry == null) { - return null; - } - try { - Geometry jtsGeometry = UNSPECIFIED.wkbToJtsGeometry(inputGeometry); - Geometry simplifiedGeometry = DouglasPeuckerSimplifier.simplify(jtsGeometry, inputTolerance); - return UNSPECIFIED.jtsGeometryToWkb(simplifiedGeometry); - } catch (ParseException e) { - throw new IllegalArgumentException("could not parse the geometry expression: " + e); - } - } - private static double getInputTolerance(Object toleranceExpression) { double inputTolerance; @@ -255,6 +242,19 @@ private static class BlockProcessor { this.spatialCoordinateType = spatialCoordinateType; } + private static BytesRef geoSourceAndConstantTolerance(BytesRef inputGeometry, double inputTolerance) { + if (inputGeometry == null) { + return null; + } + try { + Geometry jtsGeometry = UNSPECIFIED.wkbToJtsGeometry(inputGeometry); + Geometry simplifiedGeometry = DouglasPeuckerSimplifier.simplify(jtsGeometry, inputTolerance); + return UNSPECIFIED.jtsGeometryToWkb(simplifiedGeometry); + } catch (ParseException e) { + throw new IllegalArgumentException("could not parse the geometry expression: " + e); + } + } + void processPoints(BytesRefBlock.Builder builder, int p, LongBlock left, double tolerance) throws IOException { if (left.getValueCount(p) < 1) { builder.appendNull(); diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/PhysicalPlanOptimizerTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/PhysicalPlanOptimizerTests.java index bed7762751003..d0e1609109371 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/PhysicalPlanOptimizerTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/PhysicalPlanOptimizerTests.java @@ -3855,6 +3855,21 @@ public void testSpatialTypesAndStatsExtentAndCentroidUseDocValues() { } /** + * Before local optimizations: + * + * LimitExec[1000[INTEGER],null] + * \_AggregateExec[[grid{r}#5],[SPATIALCENTROID(location{f}#14,true[BOOLEAN],PT0S[TIME_DURATION]) AS centroid#9, grid{r}#5],FINAL,[g + * rid{r}#5, $$centroid$xVal{r}#18, $$centroid$xDel{r}#19, $$centroid$yVal{r}#20, $$centroid$yDel{r}#21, $$centroid$count{r}#22], + * null] + * \_ExchangeExec[[grid{r}#5, $$centroid$xVal{r}#18, $$centroid$xDel{r}#19, $$centroid$yVal{r}#20, $$centroid$yDel{r}#21, $$cent + * roid$count{r}#22],true] + * \_FragmentExec[filter=null, estimatedRowSize=0, reducer=[], fragment=[ + * Aggregate[[grid{r}#5],[SPATIALCENTROID(location{f}#14,true[BOOLEAN],PT0S[TIME_DURATION]) AS centroid#9, grid{r}#5]] + * \_Eval[[STGEOHASH(location{f}#14,2[INTEGER]) AS grid#5]] + * \_EsRelation[airports-no-doc-values][abbrev{f}#10, city{f}#16, city_location{f}#17, coun..]] + * ] + * + * * After local optimizations: * * LimitExec[1000[INTEGER],29] @@ -3878,7 +3893,7 @@ public void testSpatialTypesAndStatsExtentAndCentroidUseDocValues() { * \_EsQueryExec[airports], indexMode[standard], [_doc{f}#28], limit[], sort[] estimatedRowSize[33] * queryBuilderAndTags [[QueryBuilderAndTags[query=null, tags=[]]]] * - * Note the FieldExtractExec has 'location' set for stats: FieldExtractExec[location{f}#9][location{f}#9] + * Note the FieldExtractExec has 'location' set for stats: FieldExtractExec[location{f}#14][location{f}#14] *

* Also note that the type converting function is removed when it does not actually convert the type, * ensuring that ReferenceAttributes are not created for the same field, and the optimization can still work. @@ -3919,7 +3934,39 @@ public void testSpatialTypesAndStatsCentroidByGeoGridUseDocValues() { } /** - * Note the FieldExtractExec has 'location' set for stats: FieldExtractExec[location{f}#9][location{f}#9] + * Before local optimizations: + * LimitExec[1000[INTEGER],null] + * \_AggregateExec[[simplified{r}#5],[SPATIALEXTENT(location{f}#14,true[BOOLEAN],PT0S[TIME_DURATION]) AS extent#9, simplified{r}#5 + * ],FINAL,[simplified{r}#5, $$extent$top{r}#18, $$extent$bottom{r}#19, $$extent$negLeft{r}#20, $$extent$negRight{r + * }#21, $$extent$posLeft{r}#22, $$extent$posRight{r}#23],null] + * \_ExchangeExec[[simplified{r}#5, $$extent$top{r}#18, $$extent$bottom{r}#19, $$extent$negLeft{r}#20, $$extent$negRight{r}#21, + * $$extent$posLeft{r}#22, $$extent$posRight{r}#23],true] + * \_FragmentExec[filter=null, estimatedRowSize=0, reducer=[], fragment=[ + * Aggregate[[simplified{r}#5],[SPATIALEXTENT(location{f}#14,true[BOOLEAN],PT0S[TIME_DURATION]) AS extent#9, simplified{r}#5] + * ] + * \_Eval[[STSIMPLIFY(location{f}#14,0.05[DOUBLE]) AS simplified#5]] + * \_EsRelation[airports-no-doc-values][abbrev{f}#10, city{f}#16, city_location{f}#17, coun..]]] + * + * After local optimizations: + * + * LimitExec[1000[INTEGER],221] + * \_AggregateExec[[simplified{r}#5],[SPATIALEXTENT(location{f}#14,true[BOOLEAN],PT0S[TIME_DURATION]) AS extent#9, simplified{r}#5 + * ],FINAL,[simplified{r}#5, $$extent$top{r}#18, $$extent$bottom{r}#19, $$extent$negLeft{r}#20, $$extent$negRight{r + * }#21, $$extent$posLeft{r}#22, $$extent$posRight{r}#23],221] + * \_ExchangeExec[[simplified{r}#5, $$extent$top{r}#18, $$extent$bottom{r}#19, $$extent$negLeft{r}#20, $$extent$negRight{r}#21, + * $$extent$posLeft{r}#22, $$extent$posRight{r}#23],true] + * \_AggregateExec[[simplified{r}#5],[SPATIALEXTENT(location{f}#14,true[BOOLEAN],PT0S[TIME_DURATION]) AS extent#9, simplified{r}#5 + * ],INITIAL,[simplified{r}#5, $$extent$top{r}#24, $$extent$bottom{r}#25, $$extent$negLeft{r}#26, $$extent$negRight + * {r}#27, $$extent$posLeft{r}#28, $$extent$posRight{r}#29],221] + * \_EvalExec[[STSIMPLIFY(location{f}#14,0.05[DOUBLE]) AS simplified#5]] + * \_FieldExtractExec[location{f}#14][] + * \_EsQueryExec[airports-no-doc-values], indexMode[standard], [_doc{f}#30], limit[], + * sort[] estimatedRowSize[46] queryBuilderAndTags [[QueryBuilderAndTags[query=null, tags=[]]]] + * + * Note the FieldExtractExec has 'location' set for stats: FieldExtractExec[location{f}#14][location{f}#14] + *

+ * Also note that the type converting function is removed when it does not actually convert the type, + * ensuring that ReferenceAttributes are not created for the same field, and the optimization can still work. */ public void testSpatialSimplifyAndStatsExtentUseDocValues() { String query = """ From cc55c034d67621ced380cb3034c2ad1304abca90 Mon Sep 17 00:00:00 2001 From: ncordon Date: Mon, 15 Dec 2025 14:19:04 +0100 Subject: [PATCH 37/45] Fixes flaky test --- .../qa/testFixtures/src/main/resources/spatial-jts.csv-spec | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/x-pack/plugin/esql/qa/testFixtures/src/main/resources/spatial-jts.csv-spec b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/spatial-jts.csv-spec index b29a3f623871f..05f3ba80d57ba 100644 --- a/x-pack/plugin/esql/qa/testFixtures/src/main/resources/spatial-jts.csv-spec +++ b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/spatial-jts.csv-spec @@ -59,14 +59,14 @@ required_capability: st_simplify FROM airports | SORT name -| STATS count = COUNT(location), points = VALUES(location) +| STATS count = COUNT(location), points = MV_SORT(VALUES(location)) | EVAL result = ST_SIMPLIFY(points, 1) | EVAL result_length = LENGTH(TO_STRING(result)) | KEEP count, result_length ; count:long | result_length:i -891 | 37442 +891 | 22295 ; stSimplifyNoSimplification From 844b5f83729e75ee73e45353494ebca9bf7eea76 Mon Sep 17 00:00:00 2001 From: elasticsearchmachine Date: Mon, 15 Dec 2025 22:58:35 +0000 Subject: [PATCH 38/45] [CI] Auto commit changes from spotless --- .../rules/physical/local/SpatialDocValuesExtraction.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/optimizer/rules/physical/local/SpatialDocValuesExtraction.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/optimizer/rules/physical/local/SpatialDocValuesExtraction.java index 08949e222f74d..efc09e10f0f31 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/optimizer/rules/physical/local/SpatialDocValuesExtraction.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/optimizer/rules/physical/local/SpatialDocValuesExtraction.java @@ -161,8 +161,7 @@ && allowedForDocValues(fieldAttribute, ctx.searchStats(), agg, foundAttributes)) // Search for spatial grid functions in EVALs exec.forEachDown(EvalExec.class, evalExec -> { for (Alias field : evalExec.fields()) { - field.forEachDown( - SpatialGridFunction.class, spatialAggFunc -> { + field.forEachDown(SpatialGridFunction.class, spatialAggFunc -> { if (spatialAggFunc.spatialField() instanceof FieldAttribute fieldAttribute && allowedForDocValues(fieldAttribute, ctx.searchStats(), exec, foundAttributes)) { foundAttributes.add(fieldAttribute); From d838ace0796327da031c6c09b61b218aca9fd8ad Mon Sep 17 00:00:00 2001 From: ncordon Date: Tue, 16 Dec 2025 10:02:58 +0100 Subject: [PATCH 39/45] Adds test for St Simplify and doc values --- .../local/SpatialDocValuesExtraction.java | 5 +- .../optimizer/PhysicalPlanOptimizerTests.java | 49 +++++++++++++++++++ 2 files changed, 51 insertions(+), 3 deletions(-) diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/optimizer/rules/physical/local/SpatialDocValuesExtraction.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/optimizer/rules/physical/local/SpatialDocValuesExtraction.java index efc09e10f0f31..c2f1e7ba44e60 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/optimizer/rules/physical/local/SpatialDocValuesExtraction.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/optimizer/rules/physical/local/SpatialDocValuesExtraction.java @@ -17,7 +17,6 @@ import org.elasticsearch.xpack.esql.expression.function.aggregate.SpatialAggregateFunction; import org.elasticsearch.xpack.esql.expression.function.scalar.spatial.BinarySpatialFunction; import org.elasticsearch.xpack.esql.expression.function.scalar.spatial.SpatialDocValuesFunction; -import org.elasticsearch.xpack.esql.expression.function.scalar.spatial.SpatialGridFunction; import org.elasticsearch.xpack.esql.expression.function.scalar.spatial.SpatialRelatesFunction; import org.elasticsearch.xpack.esql.optimizer.LocalPhysicalOptimizerContext; import org.elasticsearch.xpack.esql.optimizer.PhysicalOptimizerRules; @@ -158,10 +157,10 @@ && allowedForDocValues(fieldAttribute, ctx.searchStats(), agg, foundAttributes)) } } }); - // Search for spatial grid functions in EVALs + // Search in EVALs for functions that can take doc values, namely St_Simplify, St_Geotile, St_Geohex, St_Geohash exec.forEachDown(EvalExec.class, evalExec -> { for (Alias field : evalExec.fields()) { - field.forEachDown(SpatialGridFunction.class, spatialAggFunc -> { + field.forEachDown(SpatialDocValuesFunction.class, spatialAggFunc -> { if (spatialAggFunc.spatialField() instanceof FieldAttribute fieldAttribute && allowedForDocValues(fieldAttribute, ctx.searchStats(), exec, foundAttributes)) { foundAttributes.add(fieldAttribute); diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/PhysicalPlanOptimizerTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/PhysicalPlanOptimizerTests.java index 23f5fc4c34ef7..0bc68ca4cd1a6 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/PhysicalPlanOptimizerTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/PhysicalPlanOptimizerTests.java @@ -87,6 +87,7 @@ import org.elasticsearch.xpack.esql.expression.function.scalar.spatial.SpatialRelatesFunction; import org.elasticsearch.xpack.esql.expression.function.scalar.spatial.SpatialWithin; import org.elasticsearch.xpack.esql.expression.function.scalar.spatial.StDistance; +import org.elasticsearch.xpack.esql.expression.function.scalar.spatial.StSimplify; import org.elasticsearch.xpack.esql.expression.function.scalar.string.ToLower; import org.elasticsearch.xpack.esql.expression.function.scalar.string.ToUpper; import org.elasticsearch.xpack.esql.expression.function.scalar.string.regex.WildcardLike; @@ -4246,6 +4247,54 @@ public void testSpatialTypesAndSortGeoGridUseDocValues2() { } } + public void testStSimplifyUsesDocValues() { + for (boolean keepLocation : new boolean[] { false, true }) { + String query = """ + FROM airport_city_boundaries + | EVAL simplified_city_location = ST_SIMPLIFY(city_location, 0.05) + | SORT airport + """ + (keepLocation + ? "| KEEP airport, simplified_city_location, city_location" + : "| KEEP airport, simplified_city_location"); + for (boolean withDocValues : new boolean[] { true, false }) { + withDocValues &= keepLocation == false; // if we keep location, we cannot use doc-values + var fieldExtractPreference = withDocValues ? FieldExtractPreference.DOC_VALUES : FieldExtractPreference.NONE; + var testData = withDocValues ? airportsCityBoundaries : airportsCityBoundariesNoDocValues; + var plan = physicalPlan(query.replace("airport_city_boundaries", testData.index.name()), testData); + var optimized = optimizedPlan(plan, testData.stats); + var project = as(optimized, ProjectExec.class); + var topNExec = as(project.child(), TopNExec.class); + var exchange = as(topNExec.child(), ExchangeExec.class); + project = as(exchange.child(), ProjectExec.class); + if (keepLocation) { + assertThat(Expressions.names(project.projections()), hasItems("airport", "simplified_city_location", "city_location")); + } else { + assertThat( + Expressions.names(project.projections()), + allOf(hasItems("airport", "simplified_city_location"), not(hasItems("city_location"))) + ); + } + + var topNExecDataNode = as(project.child(), TopNExec.class); + var fieldExtract = as(topNExecDataNode.child(), FieldExtractExec.class); + assertThat( + Expressions.names(fieldExtract.attributesToExtract()), + allOf(hasItems("airport"), not(hasItems("city_location"))) + ); + var evalExec = as(fieldExtract.child(), EvalExec.class); + var alias = as(evalExec.fields().getLast(), Alias.class); + assertThat(alias.name(), equalTo("simplified_city_location")); + var stSimplifyFunction = as(alias.child(), StSimplify.class); + var spatialField = as(stSimplifyFunction.spatialField(), FieldAttribute.class); + assertThat(spatialField.name(), equalTo("city_location")); + assertThat(spatialField.dataType(), equalTo(GEO_POINT)); + fieldExtract = as(evalExec.child(), FieldExtractExec.class); + assertThat(Expressions.names(fieldExtract.attributesToExtract()), is(List.of("city_location"))); + assertChildIsGeoPointExtract(evalExec, fieldExtractPreference); + } + } + } + /** * * LimitExec[1000[INTEGER]] From 2fd8acb1876fd86c01f7daa6978728e3d514b7b5 Mon Sep 17 00:00:00 2001 From: ncordon Date: Tue, 16 Dec 2025 09:58:43 +0100 Subject: [PATCH 40/45] Tweaks comment on test --- .../optimizer/PhysicalPlanOptimizerTests.java | 35 +++++++++++++++++-- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/PhysicalPlanOptimizerTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/PhysicalPlanOptimizerTests.java index 0bc68ca4cd1a6..62dc7423000c6 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/PhysicalPlanOptimizerTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/PhysicalPlanOptimizerTests.java @@ -4010,7 +4010,8 @@ public void testSpatialSimplifyAndStatsExtentUseDocValues() { * \_TopNExec[[Order[abbrev{f}#9,ASC,LAST]],1000[INTEGER],null] * \_ExchangeExec[[],false] * \_FragmentExec[filter=null, estimatedRowSize=0, reducer=[], fragment=[ - * TopN[[Order[abbrev{f}#9,ASC,LAST]],1000[INTEGER],false] + * TopN[[Order[abbrev{f}#9,ASC,LAST]],1000[INTEGER],false + * ] * \_Eval[[STGEOHASH(location{f}#13,2[INTEGER]) AS grid#5]] * \_EsRelation[airports][abbrev{f}#9, city{f}#15, city_location{f}#16, count..]]] * @@ -4163,7 +4164,8 @@ public void testSpatialTypesAndStatsGeoGridUseDocValues() { * \_TopNExec[[Order[abbrev{f}#13,ASC,LAST]],1000[INTEGER],null] * \_ExchangeExec[[],false] * \_FragmentExec[filter=null, estimatedRowSize=0, reducer=[], fragment=[ - * TopN[[Order[abbrev{f}#13,ASC,LAST]],1000[INTEGER],false] + * TopN[[Order[abbrev{f}#13,ASC,LAST]],1000[INTEGER],false + * ] * \_Eval[[TOSTRING(STGEOHASH(location{f}#17,1[INTEGER])) AS gridString#9]] * \_Filter[TOSTRING(grid{r}#5) == 8108bffffffffff[KEYWORD]] * \_Eval[[STGEOHASH(location{f}#17,1[INTEGER],[1 12 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 28 40 0 0 0 0 0 0 4e 40 0 0 0 0 0 @@ -4247,7 +4249,34 @@ public void testSpatialTypesAndSortGeoGridUseDocValues2() { } } - public void testStSimplifyUsesDocValues() { + /** + * Before local optimizations: + * + * ProjectExec[[airport{f}#10, simplified_city_location{r}#5]] + * \_TopNExec[[Order[airport{f}#10,ASC,LAST]],1000[INTEGER],null] + * \_ExchangeExec[[],false] + * \_FragmentExec[filter=null, estimatedRowSize=0, reducer=[], fragment=[ + * TopN[[Order[airport{f}#10,ASC,LAST]],1000[INTEGER],false + * ] + * \_Eval[[STSIMPLIFY(city_location{f}#13,0.05[DOUBLE]) AS simplified_city_location#5]] + * \_EsRelation[airports_city_boundaries][abbrev{f}#9, airport{f}#10, city{f}#12, city_bounda..][]]] + * + * + * After local optimizations: + * + * ProjectExec[[airport{f}#10, simplified_city_location{r}#5]] + * \_TopNExec[[Order[airport{f}#10,ASC,LAST]],1000[INTEGER],1045] + * \_ExchangeExec[[airport{f}#10, simplified_city_location{r}#5],false] + * \_ProjectExec[[airport{f}#10, simplified_city_location{r}#5]] + * \_TopNExec[[Order[airport{f}#10,ASC,LAST]],1000[INTEGER],1086] + * \_FieldExtractExec[airport{f}#10][] + * \_EvalExec[[STSIMPLIFY(city_location{f}#13,0.05[DOUBLE]) AS simplified_city_location#5]] + * \_FieldExtractExec[city_location{f}#13][[city_location{f}#13],[]] + * \_EsQueryExec[airports_city_boundaries], indexMode[standard], [_doc{f}#15], limit[], + * sort[] estimatedRowSize[1070] queryBuilderAndTags [[QueryBuilderAndTags[query=null, tags=[]]]] + * + */ + public void testSpatialSimplifyUsesDocValues() { for (boolean keepLocation : new boolean[] { false, true }) { String query = """ FROM airport_city_boundaries From 556dbca270c8a7ff034a766526ec14f76e703029 Mon Sep 17 00:00:00 2001 From: ncordon Date: Tue, 16 Dec 2025 16:46:04 +0100 Subject: [PATCH 41/45] Adds harcoded test cases so we always have typed data to test --- .../scalar/spatial/StSimplifyTests.java | 41 +++++++++++++++---- 1 file changed, 33 insertions(+), 8 deletions(-) diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyTests.java index aff99751fa4ae..982c1fa11cf8c 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplifyTests.java @@ -23,7 +23,6 @@ import java.util.ArrayList; import java.util.List; -import java.util.function.BiFunction; import java.util.function.Supplier; import static org.elasticsearch.xpack.esql.core.type.DataType.CARTESIAN_POINT; @@ -41,11 +40,38 @@ public StSimplifyTests(@Name("TestCase") Supplier tes @ParametersFactory public static Iterable parameters() { + var geoPoint = new TestCaseSupplier.TypedDataSupplier( + "geo point", + () -> UNSPECIFIED.wktToWkb("POINT(3.141592 -3.141592)"), + GEO_POINT + ); + var cartesianPoint = new TestCaseSupplier.TypedDataSupplier( + "geo point", + () -> UNSPECIFIED.wktToWkb("POINT(3.141592 500)"), + CARTESIAN_POINT + ); + var geoShape = new TestCaseSupplier.TypedDataSupplier( + "geo shape", + () -> UNSPECIFIED.wktToWkb("POLYGON ((-73.97 40.78, -73.98 40.75, -73.95 40.74, -73.93 40.76, -73.97 40.78))"), + GEO_SHAPE + ); + var cartesianShape = new TestCaseSupplier.TypedDataSupplier( + "cartesian shape", + () -> UNSPECIFIED.wktToWkb("POLYGON ((2 3, 4 8, 7 6, 6 2, 2 3))"), + CARTESIAN_SHAPE + ); + final List suppliers = new ArrayList<>(); - addTestCaseSuppliers(suppliers, GEO_POINT, StSimplifyTests::valueOf); - addTestCaseSuppliers(suppliers, CARTESIAN_POINT, StSimplifyTests::valueOf); - addTestCaseSuppliers(suppliers, GEO_SHAPE, StSimplifyTests::valueOf); - addTestCaseSuppliers(suppliers, CARTESIAN_SHAPE, StSimplifyTests::valueOf); + // Random test case suppliers + addTestCaseSuppliers(suppliers, GEO_POINT, testCaseSupplier(GEO_POINT)); + addTestCaseSuppliers(suppliers, CARTESIAN_POINT, testCaseSupplier(CARTESIAN_POINT)); + addTestCaseSuppliers(suppliers, GEO_SHAPE, testCaseSupplier(GEO_SHAPE)); + addTestCaseSuppliers(suppliers, CARTESIAN_SHAPE, testCaseSupplier(CARTESIAN_SHAPE)); + // Adds hardcoded test cases so we avoid failing if the above none of the test cases were valid for a specific typed data + addTestCaseSuppliers(suppliers, GEO_POINT, geoPoint); + addTestCaseSuppliers(suppliers, CARTESIAN_POINT, cartesianPoint); + addTestCaseSuppliers(suppliers, GEO_SHAPE, geoShape); + addTestCaseSuppliers(suppliers, CARTESIAN_SHAPE, cartesianShape); var testSuppliers = anyNullIsNull( randomizeBytesRefsOffset(suppliers), @@ -69,9 +95,8 @@ public static TestCaseSupplier.TypedDataSupplier testCaseSupplier(DataType dataT protected static void addTestCaseSuppliers( List suppliers, DataType spatialType, - BiFunction expectedValue + TestCaseSupplier.TypedDataSupplier geometrySupplier ) { - TestCaseSupplier.TypedDataSupplier geometrySupplier = testCaseSupplier(spatialType); String testName = spatialType.typeName() + " with tolerance."; suppliers.add(new TestCaseSupplier(testName, List.of(spatialType, DOUBLE), () -> { @@ -83,7 +108,7 @@ protected static void addTestCaseSuppliers( String evaluatorName = "StSimplifyNonFoldableGeometryAndFoldableToleranceEvaluator[geometry=Attribute[channel=0], tolerance=" + tolerance + "]"; - var expectedResult = expectedValue.apply(geometry, tolerance); + var expectedResult = valueOf(geometry, tolerance); return new TestCaseSupplier.TestCase( List.of(geoTypedData, toleranceData), From 90d17a7be5afa9b530863eb59426ea4ee9716984 Mon Sep 17 00:00:00 2001 From: Craig Taverner Date: Wed, 17 Dec 2025 19:07:15 +0100 Subject: [PATCH 42/45] Have StSimplify.fold() combine geometries, as we do for multi-value fields --- .../function/scalar/spatial/StSimplify.java | 50 +++++++++++-------- 1 file changed, 29 insertions(+), 21 deletions(-) diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplify.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplify.java index 232b1cdb1032a..fe3f0e0e5c777 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplify.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplify.java @@ -37,7 +37,6 @@ import org.locationtech.jts.simplify.DouglasPeuckerSimplifier; import java.io.IOException; -import java.util.ArrayList; import java.util.List; import java.util.function.Function; @@ -166,18 +165,9 @@ public Object fold(FoldContext foldCtx) { double inputTolerance = getInputTolerance(toleranceExpression); Object input = geometry.fold(foldCtx); if (input instanceof List list) { - // TODO: Consider if this should compact to a GeometryCollection instead, which is what we do for fields - ArrayList results = new ArrayList<>(list.size()); - for (Object o : list) { - if (o instanceof BytesRef inputGeometry) { - results.add(BlockProcessor.geoSourceAndConstantTolerance(inputGeometry, inputTolerance)); - } else { - throw new IllegalArgumentException("unsupported list element type: " + o.getClass().getSimpleName()); - } - } - return results; + return processor.processSingleGeometry(processor.asJtsGeometry(list), inputTolerance); } else if (input instanceof BytesRef inputGeometry) { - return BlockProcessor.geoSourceAndConstantTolerance(inputGeometry, inputTolerance); + return processor.processSingleGeometry(inputGeometry, inputTolerance); } else { throw new IllegalArgumentException("unsupported block type: " + input.getClass().getSimpleName()); } @@ -242,20 +232,23 @@ private static class BlockProcessor { this.spatialCoordinateType = spatialCoordinateType; } - private static BytesRef geoSourceAndConstantTolerance(BytesRef inputGeometry, double inputTolerance) { + private BytesRef processSingleGeometry(BytesRef inputGeometry, double inputTolerance) { if (inputGeometry == null) { return null; } try { - Geometry jtsGeometry = UNSPECIFIED.wkbToJtsGeometry(inputGeometry); - Geometry simplifiedGeometry = DouglasPeuckerSimplifier.simplify(jtsGeometry, inputTolerance); - return UNSPECIFIED.jtsGeometryToWkb(simplifiedGeometry); + return processSingleGeometry(UNSPECIFIED.wkbToJtsGeometry(inputGeometry), inputTolerance); } catch (ParseException e) { throw new IllegalArgumentException("could not parse the geometry expression: " + e); } } - void processPoints(BytesRefBlock.Builder builder, int p, LongBlock left, double tolerance) throws IOException { + private BytesRef processSingleGeometry(Geometry jtsGeometry, double inputTolerance) { + Geometry simplifiedGeometry = DouglasPeuckerSimplifier.simplify(jtsGeometry, inputTolerance); + return UNSPECIFIED.jtsGeometryToWkb(simplifiedGeometry); + } + + private void processPoints(BytesRefBlock.Builder builder, int p, LongBlock left, double tolerance) throws IOException { if (left.getValueCount(p) < 1) { builder.appendNull(); } else { @@ -265,7 +258,7 @@ void processPoints(BytesRefBlock.Builder builder, int p, LongBlock left, double } } - void processGeometries(BytesRefBlock.Builder builder, int p, BytesRefBlock left, double tolerance) { + private void processGeometries(BytesRefBlock.Builder builder, int p, BytesRefBlock left, double tolerance) { if (left.getValueCount(p) < 1) { builder.appendNull(); } else { @@ -275,7 +268,7 @@ void processGeometries(BytesRefBlock.Builder builder, int p, BytesRefBlock left, } } - Geometry asJtsMultiPoint(LongBlock valueBlock, int position, Function decoder) { + private Geometry asJtsMultiPoint(LongBlock valueBlock, int position, Function decoder) { final int firstValueIndex = valueBlock.getFirstValueIndex(position); final int valueCount = valueBlock.getValueCount(position); if (valueCount == 1) { @@ -290,7 +283,7 @@ Geometry asJtsMultiPoint(LongBlock valueBlock, int position, Function values) { + try { + final Geometry[] geometries = new Geometry[values.size()]; + for (int i = 0; i < values.size(); i++) { + if (values.get(i) instanceof BytesRef inputGeometry) { + geometries[i] = UNSPECIFIED.wkbToJtsGeometry(inputGeometry); + } else { + throw new IllegalArgumentException("unsupported list element type: " + values.get(i).getClass().getSimpleName()); + } + } + return geometryFactory.createGeometryCollection(geometries); + } catch (ParseException e) { + throw new IllegalArgumentException("could not parse the geometry expression: " + e); + } + } } } From 82bcbec15dbb36075cd0b5b25adf589f29a04761 Mon Sep 17 00:00:00 2001 From: ncordon Date: Thu, 18 Dec 2025 11:46:55 +0100 Subject: [PATCH 43/45] Changes to the test --- .../qa/testFixtures/src/main/resources/spatial-jts.csv-spec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugin/esql/qa/testFixtures/src/main/resources/spatial-jts.csv-spec b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/spatial-jts.csv-spec index f0a8f16915662..762d72cbaeeba 100644 --- a/x-pack/plugin/esql/qa/testFixtures/src/main/resources/spatial-jts.csv-spec +++ b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/spatial-jts.csv-spec @@ -28,7 +28,7 @@ ROW wkt = ["POINT (7.998 53.827)", "POINT (9.470 53.068)", "POINT (15.754 53.801 // tag::st_simplify_points-result[] wkt:keyword | simplified:geo_point -[POINT (7.998 53.827), POINT (9.470 53.068), POINT (15.754 53.801), POINT (16.523 57.160), POINT (11.162 57.868), POINT (8.064 57.445), POINT (6.219 55.317), POINT (7.998 53.827)] | [POINT (7.998 53.827), POINT (9.470 53.068), POINT (15.754 53.801), POINT (16.523 57.160), POINT (11.162 57.868), POINT (8.064 57.445), POINT (6.219 55.317), POINT (7.998 53.827)] +[POINT (7.998 53.827), POINT (9.470 53.068), POINT (15.754 53.801), POINT (16.523 57.160), POINT (11.162 57.868), POINT (8.064 57.445), POINT (6.219 55.317), POINT (7.998 53.827)] | GEOMETRYCOLLECTION (POINT (7.998 53.827),POINT (9.47 53.068),POINT (15.754 53.801),POINT (16.523 57.16),POINT (11.162 57.868),POINT (8.064 57.445),POINT (6.219 55.317),POINT (7.998 53.827)) // end::st_simplify_points-result[] ; From cd6e24fac79ad5a16f0249b4f84cd13a4e112609 Mon Sep 17 00:00:00 2001 From: ncordon Date: Thu, 18 Dec 2025 16:40:58 +0100 Subject: [PATCH 44/45] Unflakes test --- .../esql/qa/testFixtures/src/main/resources/spatial-jts.csv-spec | 1 + 1 file changed, 1 insertion(+) diff --git a/x-pack/plugin/esql/qa/testFixtures/src/main/resources/spatial-jts.csv-spec b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/spatial-jts.csv-spec index 762d72cbaeeba..e9c383e1072bb 100644 --- a/x-pack/plugin/esql/qa/testFixtures/src/main/resources/spatial-jts.csv-spec +++ b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/spatial-jts.csv-spec @@ -42,6 +42,7 @@ FROM airport_city_boundaries | EVAL o_len = LENGTH(TO_STRING(city_boundary)) | EVAL city_boundary = ST_SIMPLIFY(city_boundary, 0.05) | EVAL s_len = LENGTH(TO_STRING(city_boundary)) +| SORT city | KEEP city, o_len, s_len, city_boundary // end::st_simplify_city_boundaries[] ; From a1c27fe4378f0393fbde6cb98d150cf3649aa593 Mon Sep 17 00:00:00 2001 From: ncordon Date: Thu, 18 Dec 2025 17:05:51 +0100 Subject: [PATCH 45/45] Adds back version to docs --- .../esql/_snippets/functions/layout/st_simplify.md | 2 +- .../esql/expression/function/scalar/spatial/StSimplify.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/reference/query-languages/esql/_snippets/functions/layout/st_simplify.md b/docs/reference/query-languages/esql/_snippets/functions/layout/st_simplify.md index 8ec47b30e9c2a..1e31764f27628 100644 --- a/docs/reference/query-languages/esql/_snippets/functions/layout/st_simplify.md +++ b/docs/reference/query-languages/esql/_snippets/functions/layout/st_simplify.md @@ -2,7 +2,7 @@ ## `ST_SIMPLIFY` [esql-st_simplify] ```{applies_to} -stack: preview +stack: preview 9.4.0 serverless: preview ``` diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplify.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplify.java index fe3f0e0e5c777..22cf41657c627 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplify.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StSimplify.java @@ -62,7 +62,7 @@ public class StSimplify extends SpatialDocValuesFunction { + "Vertices that fall within the tolerance distance from the simplified shape are removed. " + "Note that the resulting geometry may be invalid, even if the original input was valid.", preview = true, - appliesTo = { @FunctionAppliesTo(lifeCycle = FunctionAppliesToLifecycle.PREVIEW) }, + appliesTo = { @FunctionAppliesTo(lifeCycle = FunctionAppliesToLifecycle.PREVIEW, version = "9.4.0") }, examples = @Example(file = "spatial-jts", tag = "st_simplify") ) public StSimplify(