This is an automated email from the ASF dual-hosted git repository.

bchapuis pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/calcite.git


The following commit(s) were added to refs/heads/main by this push:
     new c28d1dcbc3 [CALCITE-5367] Implement spatial type functions
c28d1dcbc3 is described below

commit c28d1dcbc34e748b7bea9712ef6bcf43793a91e8
Author: Bertil Chapuis <[email protected]>
AuthorDate: Fri Feb 3 20:59:47 2023 +0100

    [CALCITE-5367] Implement spatial type functions
    
    - Test ST_LineFromWKB
    - Test ST_PointFromWKB
    - Test ST_PolyFromWKB
    - Improve and test ST_OrderingEquals
    - Add and test ST_FlipCoordinates
    - Add and test ST_Reverse
    - Add and test ST_Normalize
    - Add and test ST_Holes
    - Add and test ST_RemoveHole
    - Add and test ST_ReducePrecision and ST_Densify
    - Replace roundGeom with ST_ReducePrecision
    - Add and test ST_AddPoint and ST_RemovePoint
    - Remove unused method
    - Add ST_Buffer and ST_OffsetCurve functions
    - Test ST_RemoveRepeatedPoints
    - Add ST_AddZ
    - Add and test ST_Split
    - Add and test ST_Delaunay
    - Add and test ST_ConstrainedDelaunay
    - Add missing documentation
---
 .../org/apache/calcite/runtime/AccumOperation.java |  41 ++
 .../apache/calcite/runtime/AddPointOperation.java  |  51 +++
 .../apache/calcite/runtime/AddZTransformer.java    |  48 +++
 .../org/apache/calcite/runtime/BufferStyle.java    | 123 ++++++
 .../apache/calcite/runtime/CollectOperation.java   |  44 ++
 .../runtime/FlipCoordinatesTransformer.java        |  41 ++
 .../calcite/runtime/RemoveHoleTransformer.java     |  41 ++
 .../calcite/runtime/RemovePointOperation.java      |  47 +++
 .../runtime/RemoveRepeatedPointsTransformer.java   |  66 +++
 .../calcite/runtime/SpatialTypeFunctions.java      | 346 ++++++++++++----
 .../org/apache/calcite/runtime/SplitOperation.java | 172 ++++++++
 .../org/apache/calcite/runtime/UnionOperation.java |  42 ++
 .../calcite/sql/SqlSpatialTypeOperatorTable.java   |   9 +-
 core/src/test/resources/sql/spatial.iq             | 441 +++++++++++++++++++--
 site/_docs/reference.md                            |  52 ++-
 .../org/apache/calcite/test/CalciteAssert.java     |  14 +-
 .../java/org/apache/calcite/util/TestUtil.java     |  63 ---
 .../java/org/apache/calcite/util/TestUtilTest.java |  20 -
 18 files changed, 1438 insertions(+), 223 deletions(-)

diff --git a/core/src/main/java/org/apache/calcite/runtime/AccumOperation.java 
b/core/src/main/java/org/apache/calcite/runtime/AccumOperation.java
new file mode 100644
index 0000000000..aa2f7c638a
--- /dev/null
+++ b/core/src/main/java/org/apache/calcite/runtime/AccumOperation.java
@@ -0,0 +1,41 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.calcite.runtime;
+
+import org.locationtech.jts.geom.Geometry;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Used at run time by the ST_Accum function.
+ */
+public class AccumOperation {
+
+  public List<Geometry> init() {
+    return new ArrayList<>();
+  }
+
+  public List<Geometry> add(List<Geometry> accumulator, Geometry geometry) {
+    accumulator.add(geometry);
+    return accumulator;
+  }
+
+  public List<Geometry> result(List<Geometry> accumulator) {
+    return accumulator;
+  }
+}
diff --git 
a/core/src/main/java/org/apache/calcite/runtime/AddPointOperation.java 
b/core/src/main/java/org/apache/calcite/runtime/AddPointOperation.java
new file mode 100644
index 0000000000..940ba4c26c
--- /dev/null
+++ b/core/src/main/java/org/apache/calcite/runtime/AddPointOperation.java
@@ -0,0 +1,51 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.calcite.runtime;
+
+import org.locationtech.jts.geom.Coordinate;
+import org.locationtech.jts.geom.Geometry;
+import org.locationtech.jts.geom.util.GeometryEditor;
+
+/**
+ * Geometry editor operation that adds a point to a geometry.
+ */
+public class AddPointOperation extends GeometryEditor.CoordinateOperation {
+
+  private final Geometry point;
+
+  private final int index;
+
+  public AddPointOperation(Geometry point, int index) {
+    this.point = point;
+    this.index = index;
+  }
+
+  @Override public Coordinate[] edit(Coordinate[] coordinates, Geometry 
geometry) {
+    if (index < 0 || index > coordinates.length) {
+      throw new IllegalArgumentException("Invalid index: " + index);
+    }
+    Coordinate[] newCoordinates = new Coordinate[coordinates.length + 1];
+    for (int i = 0; i < index; i++) {
+      newCoordinates[i] = (Coordinate) coordinates[i].clone();
+    }
+    newCoordinates[index] = point.getCoordinate();
+    for (int i = index; i < coordinates.length; i++) {
+      newCoordinates[i + 1] = (Coordinate) coordinates[i].clone();
+    }
+    return newCoordinates;
+  }
+}
diff --git a/core/src/main/java/org/apache/calcite/runtime/AddZTransformer.java 
b/core/src/main/java/org/apache/calcite/runtime/AddZTransformer.java
new file mode 100644
index 0000000000..db3b02bb2f
--- /dev/null
+++ b/core/src/main/java/org/apache/calcite/runtime/AddZTransformer.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.calcite.runtime;
+
+import org.locationtech.jts.geom.Coordinate;
+import org.locationtech.jts.geom.CoordinateSequence;
+import org.locationtech.jts.geom.Geometry;
+import org.locationtech.jts.geom.impl.CoordinateArraySequence;
+import org.locationtech.jts.geom.util.GeometryTransformer;
+
+/**
+ * Operation that adds a z value to a geometry.
+ */
+public class AddZTransformer extends GeometryTransformer {
+
+  private final double zToAdd;
+
+  public AddZTransformer(double zToAdd) {
+    this.zToAdd = zToAdd;
+  }
+
+  @Override protected CoordinateSequence transformCoordinates(
+      CoordinateSequence coordinates, Geometry parent) {
+    Coordinate[] newCoordinates = new Coordinate[coordinates.size()];
+    for (int i = 0; i < coordinates.size(); i++) {
+      Coordinate current = coordinates.getCoordinate(i);
+      if (!Double.isNaN(current.z)) {
+        newCoordinates[i] = new Coordinate(current.x, current.y, current.z + 
zToAdd);
+      }
+    }
+    return new CoordinateArraySequence(newCoordinates);
+  }
+
+}
diff --git a/core/src/main/java/org/apache/calcite/runtime/BufferStyle.java 
b/core/src/main/java/org/apache/calcite/runtime/BufferStyle.java
new file mode 100644
index 0000000000..314a6bd250
--- /dev/null
+++ b/core/src/main/java/org/apache/calcite/runtime/BufferStyle.java
@@ -0,0 +1,123 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.calcite.runtime;
+
+import org.locationtech.jts.operation.buffer.BufferParameters;
+
+import java.util.Locale;
+
+/**
+ * A parser for buffer styles as defined by PostGIS.
+ */
+public class BufferStyle {
+
+  private int quadrantSegments = BufferParameters.DEFAULT_QUADRANT_SEGMENTS;
+
+  private int endCapStyle = BufferParameters.CAP_ROUND;
+
+  private int joinStyle = BufferParameters.JOIN_ROUND;
+
+  private int side = 0;
+
+  public BufferStyle(String style) {
+    String[] parameters = style.toLowerCase(Locale.ROOT).split(" ");
+    for (String parameter : parameters) {
+      if (parameter == null || parameter.isEmpty()) {
+        continue;
+      }
+      String[] keyValue = parameter.split("=");
+      if (keyValue.length != 2) {
+        throw new IllegalArgumentException("Invalid buffer style: " + style);
+      }
+      String key = keyValue[0];
+      String value = keyValue[1];
+      switch (key) {
+      case "quad_segs":
+        try {
+          quadrantSegments = Integer.parseInt(value);
+          break;
+        } catch (NumberFormatException e) {
+          throw new IllegalArgumentException("Invalid buffer style: " + style);
+        }
+      case "endcap":
+        switch (value) {
+        case "round":
+          endCapStyle = BufferParameters.CAP_ROUND;
+          break;
+        case "flat":
+          endCapStyle = BufferParameters.CAP_FLAT;
+          break;
+        case "square":
+          endCapStyle = BufferParameters.CAP_SQUARE;
+          break;
+        default:
+          throw new IllegalArgumentException("Invalid buffer style: " + style);
+        }
+        break;
+      case "join":
+        switch (value) {
+        case "round":
+          joinStyle = BufferParameters.JOIN_ROUND;
+          break;
+        case "mitre":
+          joinStyle = BufferParameters.JOIN_MITRE;
+          break;
+        case "bevel":
+          joinStyle = BufferParameters.JOIN_BEVEL;
+          break;
+        default:
+          throw new IllegalArgumentException("Invalid buffer style: " + style);
+        }
+        break;
+      case "side":
+        switch (value) {
+        case "left":
+          side += 1;
+          break;
+        case "right":
+          side -= 1;
+          break;
+        case "both":
+          side = 0;
+          break;
+        default:
+          throw new IllegalArgumentException("Invalid buffer style: " + style);
+        }
+        break;
+      }
+    }
+  }
+
+  /**
+   * Returns a sided distance.
+   */
+  public double asSidedDistance(double distance) {
+    return side != 0 ? distance * side : distance;
+  }
+
+  /**
+   * Returns buffer parameters.
+   */
+  public BufferParameters asBufferParameters() {
+    BufferParameters params = new BufferParameters();
+    params.setQuadrantSegments(quadrantSegments);
+    params.setEndCapStyle(endCapStyle);
+    params.setJoinStyle(joinStyle);
+    params.setSingleSided(side != 0);
+    return params;
+  }
+}
diff --git 
a/core/src/main/java/org/apache/calcite/runtime/CollectOperation.java 
b/core/src/main/java/org/apache/calcite/runtime/CollectOperation.java
new file mode 100644
index 0000000000..fc4ab27d8d
--- /dev/null
+++ b/core/src/main/java/org/apache/calcite/runtime/CollectOperation.java
@@ -0,0 +1,44 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.calcite.runtime;
+
+import org.locationtech.jts.geom.Geometry;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.apache.calcite.runtime.SpatialTypeUtils.GEOMETRY_FACTORY;
+
+/**
+ * Used at run time by the ST_Collect function.
+ */
+public class CollectOperation {
+
+  public List<Geometry> init() {
+    return new ArrayList<>();
+  }
+
+  public List<Geometry> add(List<Geometry> accumulator, Geometry geometry) {
+    accumulator.add(geometry);
+    return accumulator;
+  }
+
+  public Geometry result(List<Geometry> accumulator) {
+    Geometry[] array = accumulator.toArray(new Geometry[accumulator.size()]);
+    return GEOMETRY_FACTORY.createGeometryCollection(array);
+  }
+}
diff --git 
a/core/src/main/java/org/apache/calcite/runtime/FlipCoordinatesTransformer.java 
b/core/src/main/java/org/apache/calcite/runtime/FlipCoordinatesTransformer.java
new file mode 100644
index 0000000000..a3f250c50a
--- /dev/null
+++ 
b/core/src/main/java/org/apache/calcite/runtime/FlipCoordinatesTransformer.java
@@ -0,0 +1,41 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.calcite.runtime;
+
+import org.locationtech.jts.geom.Coordinate;
+import org.locationtech.jts.geom.CoordinateSequence;
+import org.locationtech.jts.geom.Geometry;
+import org.locationtech.jts.geom.impl.CoordinateArraySequence;
+import org.locationtech.jts.geom.util.GeometryTransformer;
+
+import java.util.stream.Stream;
+
+/**
+ * Flips the coordinates of a geometry.
+ */
+public class FlipCoordinatesTransformer extends GeometryTransformer {
+
+  @Override protected CoordinateSequence transformCoordinates(
+      CoordinateSequence coordinateSequence, Geometry parent) {
+    Coordinate[] coordinateArray =
+        Stream.of(coordinateSequence.toCoordinateArray())
+            .map(c -> new Coordinate(c.y, c.x))
+            .toArray(Coordinate[]::new);
+    return new CoordinateArraySequence(coordinateArray);
+  }
+
+}
diff --git 
a/core/src/main/java/org/apache/calcite/runtime/RemoveHoleTransformer.java 
b/core/src/main/java/org/apache/calcite/runtime/RemoveHoleTransformer.java
new file mode 100644
index 0000000000..db188c3b45
--- /dev/null
+++ b/core/src/main/java/org/apache/calcite/runtime/RemoveHoleTransformer.java
@@ -0,0 +1,41 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.calcite.runtime;
+
+import org.locationtech.jts.geom.Geometry;
+import org.locationtech.jts.geom.LinearRing;
+import org.locationtech.jts.geom.Polygon;
+import org.locationtech.jts.geom.util.GeometryTransformer;
+
+/**
+ * Removes the holes of a geometry.
+ */
+public class RemoveHoleTransformer extends GeometryTransformer {
+
+  @Override protected Geometry transformPolygon(Polygon geom, Geometry parent) 
{
+    if (geom == null || geom.isEmpty()) {
+      return factory.createPolygon();
+    }
+
+    LinearRing exteriorRing = geom.getExteriorRing();
+    if (exteriorRing == null || exteriorRing.isEmpty()) {
+      return factory.createPolygon();
+    }
+
+    return factory.createPolygon(exteriorRing);
+  }
+}
diff --git 
a/core/src/main/java/org/apache/calcite/runtime/RemovePointOperation.java 
b/core/src/main/java/org/apache/calcite/runtime/RemovePointOperation.java
new file mode 100644
index 0000000000..0dbab29922
--- /dev/null
+++ b/core/src/main/java/org/apache/calcite/runtime/RemovePointOperation.java
@@ -0,0 +1,47 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.calcite.runtime;
+
+import org.locationtech.jts.geom.Coordinate;
+import org.locationtech.jts.geom.Geometry;
+import org.locationtech.jts.geom.util.GeometryEditor;
+
+/**
+ * Geometry editor operation that removes a point to a geometry.
+ */
+public class RemovePointOperation extends GeometryEditor.CoordinateOperation {
+
+  private final int index;
+
+  public RemovePointOperation(int index) {
+    this.index = index;
+  }
+
+  @Override public Coordinate[] edit(Coordinate[] coordinates, Geometry 
geometry) {
+    if (index < 0 || index > coordinates.length - 1) {
+      throw new IllegalArgumentException("Invalid index: " + index);
+    }
+    Coordinate[] newCoordinates = new Coordinate[coordinates.length - 1];
+    for (int i = 0; i < index; i++) {
+      newCoordinates[i] = (Coordinate) coordinates[i].clone();
+    }
+    for (int i = index + 1; i < coordinates.length; i++) {
+      newCoordinates[i - 1] = (Coordinate) coordinates[i].clone();
+    }
+    return newCoordinates;
+  }
+}
diff --git 
a/core/src/main/java/org/apache/calcite/runtime/RemoveRepeatedPointsTransformer.java
 
b/core/src/main/java/org/apache/calcite/runtime/RemoveRepeatedPointsTransformer.java
new file mode 100644
index 0000000000..dd89882203
--- /dev/null
+++ 
b/core/src/main/java/org/apache/calcite/runtime/RemoveRepeatedPointsTransformer.java
@@ -0,0 +1,66 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.calcite.runtime;
+
+import org.locationtech.jts.geom.Coordinate;
+import org.locationtech.jts.geom.CoordinateSequence;
+import org.locationtech.jts.geom.Geometry;
+import org.locationtech.jts.geom.impl.CoordinateArraySequence;
+import org.locationtech.jts.geom.util.GeometryTransformer;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Removes repeated points from a geometry.
+ */
+public class RemoveRepeatedPointsTransformer extends GeometryTransformer {
+
+  private double tolerance = 0;
+
+  public RemoveRepeatedPointsTransformer() {
+    super();
+  }
+
+  public RemoveRepeatedPointsTransformer(double tolerance) {
+    super();
+    this.tolerance = tolerance;
+  }
+
+  @Override protected CoordinateSequence transformCoordinates(
+      CoordinateSequence coordinates, Geometry parent) {
+    List<Coordinate> list = new ArrayList<>();
+    Coordinate previous = coordinates.getCoordinate(0);
+    list.add(previous);
+    for (int i = 1; i < coordinates.size(); i++) {
+      Coordinate current = coordinates.getCoordinate(i);
+      double distance = current.distance(previous);
+      if (distance > tolerance) {
+        list.add(current);
+        previous = current;
+      }
+    }
+    Coordinate last = coordinates.getCoordinate(coordinates.size() - 1);
+    double distance = last.distance(previous);
+    if (distance <= tolerance) {
+      list.set(list.size() - 1, last);
+    }
+    Coordinate[] array = list.toArray(new Coordinate[list.size()]);
+    return new CoordinateArraySequence(array);
+  }
+
+}
diff --git 
a/core/src/main/java/org/apache/calcite/runtime/SpatialTypeFunctions.java 
b/core/src/main/java/org/apache/calcite/runtime/SpatialTypeFunctions.java
index f5be8d9e1f..5587711d91 100644
--- a/core/src/main/java/org/apache/calcite/runtime/SpatialTypeFunctions.java
+++ b/core/src/main/java/org/apache/calcite/runtime/SpatialTypeFunctions.java
@@ -30,6 +30,7 @@ import org.checkerframework.checker.nullness.qual.Nullable;
 import org.locationtech.jts.algorithm.InteriorPoint;
 import org.locationtech.jts.algorithm.MinimumBoundingCircle;
 import org.locationtech.jts.algorithm.MinimumDiameter;
+import org.locationtech.jts.densify.Densifier;
 import org.locationtech.jts.geom.Coordinate;
 import org.locationtech.jts.geom.CoordinateSequence;
 import org.locationtech.jts.geom.Envelope;
@@ -49,21 +50,29 @@ import org.locationtech.jts.geom.Point;
 import org.locationtech.jts.geom.Polygon;
 import org.locationtech.jts.geom.PrecisionModel;
 import org.locationtech.jts.geom.util.AffineTransformation;
+import org.locationtech.jts.geom.util.GeometryEditor;
 import org.locationtech.jts.geom.util.GeometryFixer;
 import org.locationtech.jts.linearref.LengthIndexedLine;
+import org.locationtech.jts.operation.buffer.BufferOp;
+import org.locationtech.jts.operation.buffer.BufferParameters;
+import org.locationtech.jts.operation.buffer.OffsetCurve;
 import org.locationtech.jts.operation.distance.DistanceOp;
 import org.locationtech.jts.operation.linemerge.LineMerger;
 import org.locationtech.jts.operation.overlay.snap.GeometrySnapper;
 import org.locationtech.jts.operation.polygonize.Polygonizer;
-import org.locationtech.jts.operation.union.UnaryUnionOp;
 import org.locationtech.jts.precision.GeometryPrecisionReducer;
 import org.locationtech.jts.simplify.DouglasPeuckerSimplifier;
 import org.locationtech.jts.simplify.TopologyPreservingSimplifier;
+import org.locationtech.jts.triangulate.DelaunayTriangulationBuilder;
+import 
org.locationtech.jts.triangulate.polygon.ConstrainedDelaunayTriangulator;
+import org.locationtech.jts.triangulate.quadedge.QuadEdgeSubdivision;
+import org.locationtech.jts.triangulate.tri.Tri;
 import org.locationtech.jts.util.GeometricShapeFactory;
 
 import java.math.BigDecimal;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Objects;
 import java.util.function.Function;
@@ -293,6 +302,16 @@ public class SpatialTypeFunctions {
     return geometry instanceof Polygon ? geometry : null;
   }
 
+  /**
+   * Reduces the precision of a {@code geom} to the provided {@code gridSize}.
+   */
+  public static Geometry ST_ReducePrecision(Geometry geom, BigDecimal 
gridSize) {
+    PrecisionModel precisionModel = new PrecisionModel(1 / 
gridSize.doubleValue());
+    GeometryPrecisionReducer reducer = new 
GeometryPrecisionReducer(precisionModel);
+    reducer.setPointwise(true);
+    return reducer.reduce(geom);
+  }
+
   /**
    * Converts the coordinates of a {@code geom} into a MULTIPOINT.
    */
@@ -698,7 +717,7 @@ public class SpatialTypeFunctions {
     if (!(geom instanceof GeometryCollection)) {
       return null;
     }
-    return geom.getGeometryN(n);
+    return geom.getGeometryN(n - 1);
   }
 
   /**
@@ -913,7 +932,6 @@ public class SpatialTypeFunctions {
     } else {
       return Double.NaN;
     }
-
   }
 
   /**
@@ -1083,7 +1101,17 @@ public class SpatialTypeFunctions {
    * Geometries are listed in the same order.
    */
   public static boolean ST_OrderingEquals(Geometry geom1, Geometry geom2) {
-    return geom1.equals(geom2);
+    if (!geom1.equals(geom2)) {
+      return false;
+    }
+    Coordinate[] coordinates1 = geom1.getCoordinates();
+    Coordinate[] coordinates2 = geom2.getCoordinates();
+    for (int i = 0; i < coordinates1.length; i++) {
+      if (!coordinates1[i].equals(coordinates2[i])) {
+        return false;
+      }
+    }
+    return true;
   }
 
   /**
@@ -1118,6 +1146,17 @@ public class SpatialTypeFunctions {
 
   // Geometry operators (2D and 3D) ===========================================
 
+  /**
+   * Computes a buffer around {@code geom}.
+   */
+  public static Geometry ST_Buffer(Geometry geom, double distance, String 
bufferStyle) {
+    BufferStyle style = new BufferStyle(bufferStyle);
+    BufferParameters params = style.asBufferParameters();
+    double sidedDistance = style.asSidedDistance(distance);
+    Geometry result = new BufferOp(geom, 
params).getResultGeometry(sidedDistance);
+    return result;
+  }
+
   /**
    * Computes a buffer around {@code geom}.
    */
@@ -1167,6 +1206,21 @@ public class SpatialTypeFunctions {
     return geom1.intersection(geom2);
   }
 
+  /**
+   * Computes an offset line for {@code linestring}.
+   */
+  public static Geometry ST_OffsetCurve(Geometry linestring, double distance, 
String bufferStyle) {
+    if (!(linestring instanceof LineString)) {
+      throw new IllegalArgumentException("ST_OffsetCurve only accepts 
LineString");
+    }
+    BufferStyle style = new BufferStyle(bufferStyle);
+    BufferParameters params = style.asBufferParameters();
+    double sidedDistance = style.asSidedDistance(distance);
+    Coordinate[] coordinates =
+        OffsetCurve.rawOffset((LineString) linestring, sidedDistance, params);
+    return GEOMETRY_FACTORY.createLineString(coordinates);
+  }
+
   /**
    * Returns the DE-9IM intersection matrix for geom1 and geom2.
    */
@@ -1191,7 +1245,8 @@ public class SpatialTypeFunctions {
   /**
    * Computes the union of the geometries in {@code geomCollection}.
    */
-  @SemiStrict public static Geometry ST_UnaryUnion(Geometry geomCollection) {
+  @SemiStrict
+  public static Geometry ST_UnaryUnion(Geometry geomCollection) {
     return geomCollection.union();
   }
 
@@ -1284,6 +1339,14 @@ public class SpatialTypeFunctions {
     return snapper.snapTo(geom2, snapTolerance.doubleValue());
   }
 
+  /**
+   * Splits {@code geom} by {@code blade}.
+   */
+  public static Geometry ST_Split(Geometry geom, Geometry blade) {
+    return new SplitOperation(geom, blade).split();
+  }
+
+
   // Affine transformation functions (3D and 2D)
 
   /**
@@ -1333,7 +1396,138 @@ public class SpatialTypeFunctions {
   public static Geometry ST_Translate(Geometry geom, BigDecimal x, BigDecimal 
y) {
     AffineTransformation transformation = new AffineTransformation();
     transformation.translate(x.doubleValue(), y.doubleValue());
-    return transformation.transform(geom);
+    Geometry translated = transformation.transform(geom);
+    return translated;
+  }
+
+  // Geometry editing functions (2D)
+
+  /**
+   * Adds {@code point} to {@code linestring} at the end.
+   */
+  public static Geometry ST_AddPoint(Geometry linestring, Geometry point) {
+    if (!(linestring instanceof LineString)) {
+      throw new RuntimeException("Only supports LINESTRING.");
+    }
+    if (!(point instanceof Point)) {
+      throw new RuntimeException("Only supports POINT.");
+    }
+    LineString lineString = (LineString) linestring;
+    int numPoints = lineString.getNumPoints();
+    return new GeometryEditor().edit(linestring, new AddPointOperation(point, 
numPoints));
+  }
+
+  /**
+   * Adds {@code point} to {@code linestring} at a given {@code index}.
+   */
+  public static Geometry ST_AddPoint(Geometry linestring, Geometry point, int 
index) {
+    if (!(linestring instanceof LineString)) {
+      throw new RuntimeException("Only supports LINESTRING.");
+    }
+    if (!(point instanceof Point)) {
+      throw new RuntimeException("Only supports POINT.");
+    }
+    return new GeometryEditor().edit(linestring, new AddPointOperation(point, 
index));
+  }
+
+  /**
+   * Densifies a {@code geom} by inserting extra vertices along the line 
segments.
+   */
+  public static Geometry ST_Densify(Geometry geom, BigDecimal tolerance) {
+    return Densifier.densify(geom, tolerance.doubleValue());
+  }
+
+  /**
+   * Flips the X and Y coordinates of the {@code geom}.
+   */
+  public static Geometry ST_FlipCoordinates(Geometry geom) {
+    FlipCoordinatesTransformer transformer = new FlipCoordinatesTransformer();
+    return transformer.transform(geom);
+  }
+
+  /**
+   * Returns the holes in the {@code geom} (which may be a GEOMETRYCOLLECTION).
+   */
+  public static Geometry ST_Holes(Geometry geom) {
+    List<Geometry> acc = new ArrayList<>();
+    extractGeometryHoles(geom, acc);
+    Geometry[] array = acc.toArray(new Geometry[acc.size()]);
+    return GEOMETRY_FACTORY.createGeometryCollection(array);
+  }
+
+  private static void extractGeometryHoles(Geometry geom, List<Geometry> acc) {
+    if (geom instanceof GeometryCollection) {
+      GeometryCollection geometryCollection = (GeometryCollection) geom;
+      for (int i = 0; i < geometryCollection.getNumGeometries(); i++) {
+        Geometry child = geometryCollection.getGeometryN(i);
+        extractGeometryHoles(child, acc);
+      }
+    } else if (geom instanceof Polygon) {
+      Polygon polygon = (Polygon) geom;
+      extractPolygonHoles(polygon, acc);
+    }
+  }
+
+  private static void extractPolygonHoles(Polygon polygon, List<Geometry> acc) 
{
+    int size = polygon.getNumInteriorRing();
+    for (int i = 0; i < size; i++) {
+      acc.add(polygon.getInteriorRingN(i));
+    }
+  }
+
+  /**
+   * Converts the {@code geom} to normal form.
+   */
+  public static Geometry ST_Normalize(Geometry geom) {
+    return geom.norm();
+  }
+
+  /**
+   * Removes duplicated coordinates from the {@code geom}.
+   */
+  public static Geometry ST_RemoveRepeatedPoints(Geometry geom) {
+    return new RemoveRepeatedPointsTransformer().transform(geom);
+  }
+
+  /**
+   * Removes duplicated coordinates from the {@code geom}.
+   */
+  public static Geometry ST_RemoveRepeatedPoints(Geometry geom, BigDecimal 
tolerance) {
+    return new 
RemoveRepeatedPointsTransformer(tolerance.doubleValue()).transform(geom);
+  }
+
+  /**
+   * Removes the holes of the {@code geom}.
+   */
+  public static Geometry ST_RemoveHoles(Geometry geom) {
+    RemoveHoleTransformer transformer = new RemoveHoleTransformer();
+    return transformer.transform(geom);
+  }
+
+  /**
+   * Remove {@code point} at given {@code index} in {@code linestring}.
+   */
+  public static Geometry ST_RemovePoint(Geometry linestring, int index) {
+    if (!(linestring instanceof LineString)) {
+      throw new RuntimeException("Only supports LINESTRING.");
+    }
+    return new GeometryEditor().edit(linestring, new 
RemovePointOperation(index));
+  }
+
+  /**
+   * Reverses the order of the coordinates of the {@code geom}.
+   */
+  public static Geometry ST_Reverse(Geometry geom) {
+    return geom.reverse();
+  }
+
+  // Geometry editing functions (3D)
+
+  /**
+   * Adds {@code zToAdd} to the z-coordinate of the {@code geom}.
+   */
+  public static Geometry ST_AddZ(Geometry geom, BigDecimal zToAdd) {
+    return new AddZTransformer(zToAdd.doubleValue()).transform(geom);
   }
 
   // Geometry measurement functions
@@ -1423,8 +1617,8 @@ public class SpatialTypeFunctions {
         LineSegment lineSegment = new LineSegment(c1, c2);
         coordinates.add(
             lineSegment.pointAlongOffset(
-            segmentLengthFraction.doubleValue(),
-            offsetDistance.doubleValue()));
+                segmentLengthFraction.doubleValue(),
+                offsetDistance.doubleValue()));
       }
     }
     Coordinate[] coordinateArray = coordinates.toArray(new Coordinate[0]);
@@ -1452,7 +1646,7 @@ public class SpatialTypeFunctions {
     if (c1 == null || c2 == null) {
       return null;
     }
-    return GEOMETRY_FACTORY.createLineString(new Coordinate[] {c1, c2});
+    return GEOMETRY_FACTORY.createLineString(new Coordinate[]{c1, c2});
   }
 
   /**
@@ -1498,6 +1692,82 @@ public class SpatialTypeFunctions {
     return GEOMETRY_FACTORY.createPoint(projectedCoordinate);
   }
 
+  // Triangulation functions
+
+  /**
+   * Computes a constrained Delaunay triangulation based on points in {@code 
geom}.
+   */
+  public static Geometry ST_ConstrainedDelaunay(Geometry geom) {
+    return ST_ConstrainedDelaunay(geom, 0);
+  }
+
+  /**
+   * Computes a constrained Delaunay triangulation based on points in {@code 
geom}.
+   */
+  public static Geometry ST_ConstrainedDelaunay(Geometry geom, int flag) {
+    GeometryFactory factory = geom.getFactory();
+    ConstrainedDelaunayTriangulator cdt = new 
ConstrainedDelaunayTriangulator(geom);
+    List<Tri> tris = cdt.getTriangles();
+    Polygon[] polygons = new Polygon[tris.size()];
+    int i = 0;
+    for (Tri tri : tris) {
+      polygons[i++] = tri.toPolygon(factory);
+    }
+    MultiPolygon multiPolygon = factory.createMultiPolygon(polygons);
+    if (flag == 0) {
+      return multiPolygon;
+    } else {
+      return asTriangleEdges(multiPolygon);
+    }
+  }
+
+  /**
+   * Computes a Delaunay triangulation based on points in {@code geom}.
+   */
+  public static Geometry ST_Delaunay(Geometry geom) {
+    return ST_Delaunay(geom, 0);
+  }
+
+  /**
+   * Computes a Delaunay triangulation based on points in {@code geom}.
+   */
+  public static Geometry ST_Delaunay(Geometry geom, int flag) {
+    GeometryFactory factory = geom.getFactory();
+    DelaunayTriangulationBuilder builder = new DelaunayTriangulationBuilder();
+    builder.setSites(geom);
+    QuadEdgeSubdivision subdivision = builder.getSubdivision();
+    List triPtsList = subdivision.getTriangleCoordinates(false);
+    Polygon[] tris = new Polygon[triPtsList.size()];
+    int i = 0;
+    for (Iterator it = triPtsList.iterator(); it.hasNext();) {
+      Coordinate[] triPt = (Coordinate[]) it.next();
+      tris[i++] = factory.createPolygon(factory.createLinearRing(triPt));
+    }
+    MultiPolygon multiPolygon = factory.createMultiPolygon(tris);
+    if (flag == 0) {
+      return multiPolygon;
+    } else {
+      return asTriangleEdges(multiPolygon);
+    }
+  }
+
+  private static Geometry asTriangleEdges(MultiPolygon multiPolygon) {
+    GeometryFactory factory = multiPolygon.getFactory();
+    List<LineString> edges = new ArrayList<>();
+    for (int i = 0; i < multiPolygon.getNumGeometries(); i++) {
+      Polygon polygon = (Polygon) multiPolygon.getGeometryN(i);
+      Coordinate[] coordinates = polygon.getCoordinates();
+      for (int j = 1; j < coordinates.length; j++) {
+        Coordinate c1 = coordinates[j - 1].copy();
+        Coordinate c2 = coordinates[j].copy();
+        LineString line = factory.createLineString(new Coordinate[]{c1, c2});
+        edges.add(line);
+      }
+    }
+    Geometry geometry = factory.createMultiLineString(edges.toArray(new 
LineString[0]));
+    return geometry.union().norm();
+  }
+
   // Space-filling curves
 
   /**
@@ -1608,62 +1878,4 @@ public class SpatialTypeFunctions {
       };
     }
   }
-
-  /**
-   * Used at run time by the ST_Union function.
-   */
-  public static class Union {
-
-    public List<Geometry> init() {
-      return new ArrayList<>();
-    }
-
-    public List<Geometry> add(List<Geometry> accumulator, Geometry geometry) {
-      accumulator.add(geometry);
-      return accumulator;
-    }
-
-    public Geometry result(List<Geometry> accumulator) {
-      return new UnaryUnionOp(accumulator).union();
-    }
-  }
-
-  /**
-   * Used at run time by the ST_Accum function.
-   */
-  public static class Accum {
-
-    public List<Geometry> init() {
-      return new ArrayList<>();
-    }
-
-    public List<Geometry> add(List<Geometry> accumulator, Geometry geometry) {
-      accumulator.add(geometry);
-      return accumulator;
-    }
-
-    public List<Geometry> result(List<Geometry> accumulator) {
-      return accumulator;
-    }
-  }
-
-  /**
-   * Used at run time by the ST_Collect function.
-   */
-  public static class Collect {
-
-    public List<Geometry> init() {
-      return new ArrayList<>();
-    }
-
-    public List<Geometry> add(List<Geometry> accumulator, Geometry geometry) {
-      accumulator.add(geometry);
-      return accumulator;
-    }
-
-    public Geometry result(List<Geometry> accumulator) {
-      Geometry[] array = accumulator.toArray(new Geometry[accumulator.size()]);
-      return GEOMETRY_FACTORY.createGeometryCollection(array);
-    }
-  }
 }
diff --git a/core/src/main/java/org/apache/calcite/runtime/SplitOperation.java 
b/core/src/main/java/org/apache/calcite/runtime/SplitOperation.java
new file mode 100644
index 0000000000..ffd6660bd8
--- /dev/null
+++ b/core/src/main/java/org/apache/calcite/runtime/SplitOperation.java
@@ -0,0 +1,172 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.calcite.runtime;
+
+import org.locationtech.jts.algorithm.LineIntersector;
+import org.locationtech.jts.algorithm.RobustLineIntersector;
+import org.locationtech.jts.geom.Coordinate;
+import org.locationtech.jts.geom.Geometry;
+import org.locationtech.jts.geom.GeometryCollection;
+import org.locationtech.jts.geom.GeometryFactory;
+import org.locationtech.jts.geom.LineString;
+import org.locationtech.jts.geom.MultiLineString;
+import org.locationtech.jts.geom.MultiPolygon;
+import org.locationtech.jts.geom.Point;
+import org.locationtech.jts.geom.Polygon;
+import org.locationtech.jts.geom.util.LineStringExtracter;
+import org.locationtech.jts.operation.polygonize.Polygonizer;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Splits {@code geom} by {@code blade}.
+ */
+public class SplitOperation {
+
+  private final Geometry geom;
+
+  private final Geometry blade;
+
+  public SplitOperation(Geometry geom, Geometry blade) {
+    this.geom = geom;
+    this.blade = blade;
+  }
+
+  public Geometry split() {
+    if (geom instanceof LineString && blade instanceof Point) {
+      return split((LineString) geom, (Point) blade);
+
+    } else if (geom instanceof LineString && blade instanceof LineString) {
+      return split((LineString) geom, (LineString) blade);
+
+    } else if (geom instanceof MultiLineString && blade instanceof Point) {
+      return split((MultiLineString) geom, (Point) blade);
+
+    } else if (geom instanceof MultiLineString && blade instanceof LineString) 
{
+      return split((MultiLineString) geom, (LineString) blade);
+
+    } else if (geom instanceof Polygon && blade instanceof LineString) {
+      return split((Polygon) geom, (LineString) blade);
+
+    } else if (geom instanceof MultiPolygon && blade instanceof LineString) {
+      return split((MultiPolygon) geom, (LineString) blade);
+
+    } else {
+      throw new UnsupportedOperationException(
+          "Split operation not supported for "
+              + geom.getGeometryType() + " and "
+              + blade.getGeometryType());
+    }
+  }
+
+  private static Geometry split(LineString geometry, Point blade) {
+    GeometryFactory factory = geometry.getFactory();
+
+    Coordinate[] coordinates = geometry.getCoordinates();
+    LineIntersector intersector = new RobustLineIntersector();
+    Coordinate p = blade.getCoordinate();
+
+    List<Coordinate> accumulator = new ArrayList<>();
+    List<LineString> lines = new ArrayList<>();
+
+    for (int i = 1; i < coordinates.length; i++) {
+      Coordinate p1 = coordinates[i - 1];
+      Coordinate p2 = coordinates[i];
+
+      accumulator.add(p1.copy());
+
+      intersector.computeIntersection(p, p1, p2);
+      if (intersector.hasIntersection()) {
+        accumulator.add(p.copy());
+        LineString line =
+            factory.createLineString(accumulator.toArray(new Coordinate[0]));
+        lines.add(line);
+
+        accumulator.clear();
+        accumulator.add(p.copy());
+      }
+    }
+
+    accumulator.add(coordinates[coordinates.length - 1].copy());
+    LineString line =
+        factory.createLineString(accumulator.toArray(new Coordinate[0]));
+    lines.add(line);
+
+    if (lines.size() == 1) {
+      return lines.get(0);
+    } else {
+      return factory.buildGeometry(lines);
+    }
+  }
+
+  private static Geometry split(MultiLineString geometry, Point blade) {
+    GeometryFactory factory = geometry.getFactory();
+    List<Geometry> geometries = new ArrayList<>();
+    for (int i = 0; i < geometry.getNumGeometries(); i++) {
+      Geometry split = split((LineString) geometry.getGeometryN(i), blade);
+      if (split instanceof GeometryCollection) {
+        for (int j = 0; j < split.getNumGeometries(); j++) {
+          geometries.add(split.getGeometryN(j));
+        }
+      } else {
+        geometries.add(split);
+      }
+    }
+    return factory.buildGeometry(geometries);
+  }
+
+  private static Geometry split(LineString geometry, LineString blade) {
+    return geometry.difference(blade);
+  }
+
+  private static Geometry split(MultiLineString geometry, LineString blade) {
+    return geometry.difference(blade);
+  }
+
+  private static Geometry split(Polygon geometry, LineString blade) {
+    GeometryFactory factory = geometry.getFactory();
+    List<Polygon> polygons = new ArrayList<>();
+    Geometry union = geometry.getBoundary().union(blade);
+    Polygonizer polygonizer = new Polygonizer();
+    polygonizer.add(LineStringExtracter.getLines(union));
+    for (Polygon p : 
GeometryFactory.toPolygonArray(polygonizer.getPolygons())) {
+      if (geometry.contains(geometry.getInteriorPoint())) {
+        p.normalize();
+        polygons.add(p);
+      }
+    }
+    return factory.buildGeometry(polygons);
+  }
+
+  private Geometry split(MultiPolygon geometry, LineString blade) {
+    GeometryFactory factory = geometry.getFactory();
+    List<Geometry> geometries = new ArrayList<>();
+    for (int i = 0; i < geometry.getNumGeometries(); i++) {
+      Geometry split = split((Polygon) geometry.getGeometryN(i), blade);
+      if (split instanceof GeometryCollection) {
+        for (int j = 0; j < split.getNumGeometries(); j++) {
+          geometries.add(split.getGeometryN(j));
+        }
+      } else {
+        geometries.add(split);
+      }
+    }
+    return factory.buildGeometry(geometries);
+  }
+
+}
diff --git a/core/src/main/java/org/apache/calcite/runtime/UnionOperation.java 
b/core/src/main/java/org/apache/calcite/runtime/UnionOperation.java
new file mode 100644
index 0000000000..6585771ced
--- /dev/null
+++ b/core/src/main/java/org/apache/calcite/runtime/UnionOperation.java
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.calcite.runtime;
+
+import org.locationtech.jts.geom.Geometry;
+import org.locationtech.jts.operation.union.UnaryUnionOp;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Used at run time by the ST_Union function.
+ */
+public class UnionOperation {
+
+  public List<Geometry> init() {
+    return new ArrayList<>();
+  }
+
+  public List<Geometry> add(List<Geometry> accumulator, Geometry geometry) {
+    accumulator.add(geometry);
+    return accumulator;
+  }
+
+  public Geometry result(List<Geometry> accumulator) {
+    return new UnaryUnionOp(accumulator).union();
+  }
+}
diff --git 
a/core/src/main/java/org/apache/calcite/sql/SqlSpatialTypeOperatorTable.java 
b/core/src/main/java/org/apache/calcite/sql/SqlSpatialTypeOperatorTable.java
index 9b960590d9..8ab7dc7d88 100644
--- a/core/src/main/java/org/apache/calcite/sql/SqlSpatialTypeOperatorTable.java
+++ b/core/src/main/java/org/apache/calcite/sql/SqlSpatialTypeOperatorTable.java
@@ -21,7 +21,10 @@ import org.apache.calcite.jdbc.CalciteSchema;
 import org.apache.calcite.jdbc.JavaTypeFactoryImpl;
 import org.apache.calcite.model.ModelHandler;
 import org.apache.calcite.prepare.CalciteCatalogReader;
+import org.apache.calcite.runtime.AccumOperation;
+import org.apache.calcite.runtime.CollectOperation;
 import org.apache.calcite.runtime.SpatialTypeFunctions;
+import org.apache.calcite.runtime.UnionOperation;
 import org.apache.calcite.schema.SchemaPlus;
 import org.apache.calcite.schema.impl.AggregateFunctionImpl;
 import org.apache.calcite.sql.fun.SqlSpatialTypeFunctions;
@@ -59,13 +62,13 @@ public class SqlSpatialTypeOperatorTable implements 
SqlOperatorTable {
     // Register the spatial aggregate functions.
     schema.add(
         "ST_UNION", Objects.requireNonNull(
-        AggregateFunctionImpl.create(SpatialTypeFunctions.Union.class)));
+        AggregateFunctionImpl.create(UnionOperation.class)));
     schema.add(
         "ST_ACCUM", Objects.requireNonNull(
-        AggregateFunctionImpl.create(SpatialTypeFunctions.Accum.class)));
+        AggregateFunctionImpl.create(AccumOperation.class)));
     schema.add(
         "ST_COLLECT", Objects.requireNonNull(
-        AggregateFunctionImpl.create(SpatialTypeFunctions.Collect.class)));
+        AggregateFunctionImpl.create(CollectOperation.class)));
 
     // Create a catalog reader to retrieve the operators.
     CalciteCatalogReader catalogReader =
diff --git a/core/src/test/resources/sql/spatial.iq 
b/core/src/test/resources/sql/spatial.iq
index fe12e9d1fc..8f983e5557 100644
--- a/core/src/test/resources/sql/spatial.iq
+++ b/core/src/test/resources/sql/spatial.iq
@@ -236,7 +236,20 @@ LINESTRING (1 2, 3 4), null
 !ok
 
 # ST_LineFromWKB(wkb [, srid ]) Well Known Binary to LINESTRING
-# Not implemented
+SELECT ST_LineFromWKB(ST_AsWKB('LINESTRING (4 3, 6 5, 8 12)'));
+EXPR$0
+LINESTRING (4 3, 6 5, 8 12)
+!ok
+
+SELECT ST_LineFromWKB(ST_AsWKB('LINESTRING (5 5, 1 2, 3 4, 99 3)'));
+EXPR$0
+LINESTRING (5 5, 1 2, 3 4, 99 3)
+!ok
+
+SELECT ST_LineFromWKB(ST_AsWKB('POINT(2 3)'));
+EXPR$0
+null
+!ok
 
 # ST_MLineFromText(wkt [, srid ]) Well Known Text to MULTILINESTRING
 SELECT ST_MLineFromText('MULTILINESTRING((1 2, 3 4), (4 5, 6 7))');
@@ -282,7 +295,20 @@ srid:4326;POINT (-71.064544 42.28787)
 !ok
 
 # ST_PointFromWKB(wkb [, srid ]) Well Known Binary to POINT
-# Not implemented
+SELECT ST_PointFromWKB(ST_AsWKB('POINT (1 1)'));
+EXPR$0
+POINT (1 1)
+!ok
+
+SELECT ST_PointFromWKB(ST_AsWKB('POINT (5 5)'));
+EXPR$0
+POINT (5 5)
+!ok
+
+SELECT ST_PointFromWKB(ST_AsWKB('LINESTRING (4 3, 6 5, 8 12)'));
+EXPR$0
+null
+!ok
 
 # ST_PolyFromText(wkt [, srid ]) Well Known Text to POLYGON
 SELECT ST_AsWKT(ST_PolyFromText('POLYGON Z((0 0 1,20 0 1,20 20 1,0 20 1,0 0 
1))'));
@@ -301,7 +327,36 @@ POLYGON ((0 0, 0 1, 1 1, 0 0))
 !ok
 
 # ST_PolyFromWKB(wkb [, srid ]) Well Known Binary to POLYGON
-# Not implemented
+SELECT ST_PolyFromWKB(ST_AsWKB('POLYGON ((49 30, 50 28, 53 28, 53 32, 50 32, 
49 30))'));
+EXPR$0
+POLYGON ((49 30, 50 28, 53 28, 53 32, 50 32, 49 30))
+!ok
+
+SELECT ST_PolyFromWKB(ST_AsWKB('POLYGON((0 0, 0 1, 1 1, 1 0, 0 0))'));
+EXPR$0
+POLYGON ((0 0, 0 1, 1 1, 1 0, 0 0))
+!ok
+
+SELECT ST_PolyFromWKB(ST_AsWKB('POINT (1 1)'));
+EXPR$0
+null
+!ok
+
+# ST_ReducedPrecision(geom, gridSize) Reduces the precision of a geom to the 
provided gridSize
+SELECT ST_ReducePrecision('POINT(1.412 19.323)', 0.1);
+EXPR$0
+POINT (1.4 19.3)
+!ok
+
+SELECT ST_ReducePrecision('POINT(1.412 19.323)', 1.0);
+EXPR$0
+POINT (1 19)
+!ok
+
+SELECT ST_ReducePrecision('POINT(1.412 19.323)', 10);
+EXPR$0
+POINT (0 20)
+!ok
 
 # ST_ToMultiLine(geom) Converts the coordinates of *geom* (which may be a 
geometry-collection) into a multi-line-string
 
@@ -372,19 +427,19 @@ LINESTRING Z(-10 10 0, 10 10 0)
 
 # ST_BoundingCircle(geom) Returns the minimum bounding circle of *geom*
 
-SELECT roundGeom(ST_asText(ST_BoundingCircle('POLYGON((1 1, 1 4, 4 4, 4 1, 1 
1))')), 2);
+SELECT ST_ReducePrecision(ST_asText(ST_BoundingCircle('POLYGON((1 1, 1 4, 4 4, 
4 1, 1 1))')), 0.01);
 EXPR$0
-POLYGON ((4.63 2.5, 4.59 2.09, 4.46 1.69, 4.27 1.33, 4 1.01, 3.68 0.74, 3.32 
0.55, 2.92 0.42, 2.5 0.38, 2.09 0.42, 1.69 0.55, 1.33 0.74, 1.01 1, 0.74 1.33, 
0.55 1.69, 0.42 2.09, 0.38 2.50, 0.42 2.92, 0.55 3.32, 0.74 3.68, 1.00 4, 1.33 
4.27, 1.69 4.46, 2.09 4.59, 2.50 4.63, 2.92 4.59, 3.32 4.46, 3.68 4.27, 4.00 4, 
4.27 3.68, 4.46 3.32, 4.59 2.92, 4.63 2.5))
+POLYGON ((4.62 2.5, 4.58 2.09, 4.46 1.69, 4.26 1.32, 4 1, 3.68 0.74, 3.31 
0.54, 2.91 0.42, 2.5 0.38, 2.09 0.42, 1.69 0.54, 1.32 0.74, 1 1, 0.74 1.32, 
0.54 1.69, 0.42 2.09, 0.38 2.5, 0.42 2.91, 0.54 3.31, 0.74 3.68, 1 4, 1.32 
4.26, 1.69 4.46, 2.09 4.58, 2.5 4.62, 2.91 4.58, 3.31 4.46, 3.68 4.26, 4 4, 
4.26 3.68, 4.46 3.31, 4.58 2.91, 4.62 2.5))
 !ok
 
-SELECT roundGeom(ST_asText(ST_BoundingCircle('MULTIPOINT((1 1), (4 2))')), 2);
+SELECT ST_ReducePrecision(ST_asText(ST_BoundingCircle('MULTIPOINT((1 1), (4 
2))')), 0.01);
 EXPR$0
-POLYGON ((4.09 1.5, 4.06 1.20, 3.97 0.90, 3.82 0.63, 3.62 0.39, 3.38 0.19, 
3.11 0.04, 2.81 -0.06, 2.5 -0.09, 2.20 -0.06, 1.90 0.04, 1.63 0.19, 1.39 0.39, 
1.19 0.63, 1.04 0.90, 0.95 1.20, 0.92 1.50, 0.95 1.81, 1.04 2.11, 1.19 2.38, 
1.39 2.62, 1.63 2.82, 1.90 2.97, 2.20 3.06, 2.50 3.09, 2.81 3.06, 3.11 2.97, 
3.38 2.82, 3.62 2.62, 3.82 2.38, 3.97 2.11, 4.06 1.81, 4.09 1.5))
+POLYGON ((4.08 1.5, 4.05 1.19, 3.96 0.89, 3.81 0.62, 3.62 0.38, 3.38 0.19, 
3.11 0.04, 2.81 -0.05, 2.5 -0.08, 2.19 -0.05, 1.89 0.04, 1.62 0.19, 1.38 0.38, 
1.19 0.62, 1.04 0.89, 0.95 1.19, 0.92 1.5, 0.95 1.81, 1.04 2.11, 1.19 2.38, 
1.38 2.62, 1.62 2.81, 1.89 2.96, 2.19 3.05, 2.5 3.08, 2.81 3.05, 3.11 2.96, 
3.38 2.81, 3.62 2.62, 3.81 2.38, 3.96 2.11, 4.05 1.81, 4.08 1.5))
 !ok
 
-SELECT roundGeom(ST_asText(ST_BoundingCircle('LINESTRING(1 1, 4 5, 3 2)')), 2);
+SELECT ST_ReducePrecision(ST_asText(ST_BoundingCircle('LINESTRING(1 1, 4 5, 3 
2)')), 0.01);
 EXPR$0
-POLYGON ((5 3, 4.96 2.52, 4.81 2.05, 4.58 1.62, 4.27 1.24, 3.89 0.93, 3.46 
0.70, 2.99 0.55, 2.5 0.5, 2.02 0.55, 1.55 0.70, 1.12 0.93, 0.74 1.24, 0.43 
1.62, 0.20 2.05, 0.05 2.52, 0 3.00, 0.05 3.49, 0.20 3.96, 0.43 4.39, 0.74 4.77, 
1.12 5.08, 1.55 5.31, 2.02 5.46, 2.50 5.5, 2.99 5.46, 3.46 5.31, 3.89 5.08, 
4.27 4.77, 4.58 4.39, 4.81 3.96, 4.96 3.49, 5 3))
+POLYGON ((5 3, 4.95 2.51, 4.81 2.04, 4.58 1.61, 4.27 1.23, 3.89 0.92, 3.46 
0.69, 2.99 0.55, 2.5 0.5, 2.01 0.55, 1.54 0.69, 1.11 0.92, 0.73 1.23, 0.42 
1.61, 0.19 2.04, 0.05 2.51, 0 3, 0.05 3.49, 0.19 3.96, 0.42 4.39, 0.73 4.77, 
1.11 5.08, 1.54 5.31, 2.01 5.45, 2.5 5.5, 2.99 5.45, 3.46 5.31, 3.89 5.08, 4.27 
4.77, 4.58 4.39, 4.81 3.96, 4.95 3.49, 5 3))
 !ok
 
 # ST_Expand(geom, distance) Expands *geom*'s envelope
@@ -408,9 +463,9 @@ POLYGON ((0 1, 0 8, 9 8, 9 1, 0 1))
 
 # ST_MakeEllipse(point, width, height) Constructs an ellipse
 
-SELECT roundGeom(ST_AsText(ST_MakeEllipse('POINT(5 5)', 2, 5)), 2);
+SELECT ST_ReducePrecision(ST_AsText(ST_MakeEllipse('POINT(5 5)', 2, 5)), 0.01);
 EXPR$0
-POLYGON ((6 5, 6.00 5.16, 6.00 5.32, 5.99 5.47, 5.97 5.63, 5.96 5.78, 5.93 
5.93, 5.91 6.07, 5.88 6.21, 5.85 6.34, 5.81 6.47, 5.78 6.60, 5.73 6.72, 5.69 
6.83, 5.64 6.93, 5.59 7.03, 5.54 7.12, 5.49 7.20, 5.43 7.27, 5.37 7.33, 5.31 
7.38, 5.25 7.43, 5.19 7.46, 5.13 7.49, 5.07 7.50, 5 7.5, 4.94 7.50, 4.88 7.49, 
4.82 7.46, 4.76 7.43, 4.70 7.38, 4.64 7.33, 4.58 7.27, 4.52 7.20, 4.47 7.12, 
4.42 7.03, 4.37 6.93, 4.32 6.83, 4.28 6.72, 4.23 6.60, 4.20 6.47, 4.16 6.34, 
4.13 6.21, 4.10 6.07, 4.08 5.9 [...]
+POLYGON ((6 5, 6 5.16, 5.99 5.31, 5.98 5.47, 5.97 5.62, 5.95 5.77, 5.93 5.92, 
5.9 6.06, 5.88 6.2, 5.84 6.34, 5.81 6.47, 5.77 6.59, 5.73 6.71, 5.68 6.82, 5.64 
6.93, 5.59 7.02, 5.54 7.11, 5.48 7.19, 5.43 7.26, 5.37 7.32, 5.31 7.38, 5.25 
7.42, 5.19 7.46, 5.13 7.48, 5.06 7.5, 5 7.5, 4.94 7.5, 4.87 7.48, 4.81 7.46, 
4.75 7.42, 4.69 7.38, 4.63 7.32, 4.57 7.26, 4.52 7.19, 4.46 7.11, 4.41 7.02, 
4.36 6.93, 4.32 6.82, 4.27 6.71, 4.23 6.59, 4.19 6.47, 4.16 6.34, 4.12 6.2, 4.1 
6.06, 4.07 5.92, 4.05 5 [...]
 !ok
 
 # ST_MakeEnvelope(xMin, yMin, xMax, yMax  [, srid ]) Creates a rectangular 
Polygon
@@ -812,7 +867,7 @@ null
 
 # ST_GeometryType(geom) Returns the type of *geom*
 
-SELECT ST_GeometryN('MULTIPOLYGON(((0 0, 3 -1, 1.5 2, 0 0)), ((1 2, 4 2, 4 6, 
1 6, 1 2)))', 0);
+SELECT ST_GeometryN('MULTIPOLYGON(((0 0, 3 -1, 1.5 2, 0 0)), ((1 2, 4 2, 4 6, 
1 6, 1 2)))', 1);
 EXPR$0
 POLYGON ((0 0, 3 -1, 1.5 2, 0 0))
 !ok
@@ -960,7 +1015,7 @@ false
 # Not implemented
 
 # ST_NPoints(geom) Returns the number of points in *geom*
-# Not implemented
+# Alias: ST_NumPoints
 
 # ST_NumGeometries(geom) Returns the number of geometries in *geom* (1 if it 
is not a geometry-collection)
 
@@ -1356,7 +1411,20 @@ true
 !ok
 
 # ST_OrderingEquals(geom1, geom2) Returns whether *geom1* equals *geom2* and 
their coordinates and component Geometries are listed in the same order
-# Not implemented
+SELECT ST_OrderingEquals('LINESTRING(0 0 1, 0 0, 10 10 3)', 'LINESTRING(0 0 1, 
0 0, 10 10 3)');
+EXPR$0
+true
+!ok
+
+SELECT ST_OrderingEquals('LINESTRING(0 0, 10 10)', 'LINESTRING(0 0, 5 5, 10 
10)');
+EXPR$0
+false
+!ok
+
+SELECT ST_OrderingEquals('POLYGON ((0 0, 10 10, 10 5, 0 0))', 'POLYGON ((0 0, 
10 5, 10 10, 0 0))');
+EXPR$0
+false
+!ok
 
 # ST_Overlaps(geom1, geom2) Returns whether *geom1* overlaps *geom2*
 
@@ -1412,22 +1480,22 @@ false
 
 # ST_Buffer(geom, bufferSize [, quadSegs | style ]) Computes a buffer around 
*geom*
 
-SELECT roundGeom(ST_AsWKT(ST_Buffer('POINT(100 90)', 50)), 10);
+SELECT ST_ReducePrecision(ST_AsWKT(ST_Buffer('POINT(100 90)', 50)), 0.01);
 EXPR$0
-POLYGON ((150 90, 149.0392640202 80.2454838992, 146.1939766256 70.8658283818, 
141.5734806152 62.2214883491, 135.3553390594 54.6446609407, 127.7785116510 
48.4265193849, 119.1341716183 43.8060233745, 109.7545161009 40.9607359799, 100 
40, 90.2454838992 40.9607359799, 80.8658283818 43.8060233745, 72.2214883491 
48.4265193849, 64.6446609407 54.6446609407, 58.4265193849 62.2214883491, 
53.8060233745 70.8658283818, 50.9607359799 80.2454838992, 50 90, 50.9607359799 
99.7545161009, 53.8060233745 109 [...]
+POLYGON ((150 90, 149.04 80.25, 146.19 70.87, 141.57 62.22, 135.36 54.64, 
127.78 48.43, 119.13 43.81, 109.75 40.96, 100 40, 90.25 40.96, 80.87 43.81, 
72.22 48.43, 64.64 54.64, 58.43 62.22, 53.81 70.87, 50.96 80.25, 50 90, 50.96 
99.75, 53.81 109.13, 58.43 117.78, 64.64 125.36, 72.22 131.57, 80.87 136.19, 
90.25 139.04, 100 140, 109.75 139.04, 119.13 136.19, 127.78 131.57, 135.36 
125.36, 141.57 117.78, 146.19 109.13, 149.04 99.75, 150 90))
 !ok
 
-SELECT roundGeom(ST_AsWKT(ST_Buffer('LINESTRING(10 10,30 10)', 5)), 10);
+SELECT ST_ReducePrecision(ST_AsWKT(ST_Buffer('LINESTRING(10 10,30 10)', 5)), 
0.01);
 EXPR$0
-POLYGON ((30 15, 30.9754516101 14.9039264021, 31.9134171619 14.6193976626, 
32.7778511651 14.1573480616, 33.5355339060 13.5355339060, 34.1573480616 
12.7778511651, 34.6193976626 11.9134171619, 34.9039264021 10.9754516101, 35 10, 
34.9039264021 9.0245483900, 34.6193976626 8.0865828382, 34.1573480616 
7.2221488350, 33.5355339060 6.4644660941, 32.7778511651 5.8426519385, 
31.9134171619 5.3806023375, 30.9754516101 5.0960735980, 30 5, 10 5, 
9.0245483900 5.0960735980, 8.0865828382 5.3806023375, 7.2 [...]
+POLYGON ((30 15, 30.98 14.9, 31.91 14.62, 32.78 14.16, 33.54 13.54, 34.16 
12.78, 34.62 11.91, 34.9 10.98, 35 10, 34.9 9.02, 34.62 8.09, 34.16 7.22, 33.54 
6.46, 32.78 5.84, 31.91 5.38, 30.98 5.1, 30 5, 10 5, 9.02 5.1, 8.09 5.38, 7.22 
5.84, 6.46 6.46, 5.84 7.22, 5.38 8.09, 5.1 9.02, 5 10, 5.1 10.98, 5.38 11.91, 
5.84 12.78, 6.46 13.54, 7.22 14.16, 8.09 14.62, 9.02 14.9, 10 15, 30 15))
 !ok
 
-SELECT roundGeom(ST_AsWKT(ST_Buffer(
+SELECT ST_ReducePrecision(ST_AsWKT(ST_Buffer(
   'POLYGON((-71.1776585052917 42.3902909739571,-71.1776820268866 
42.3903701743239,
     -71.1776063012595 42.3903825660754,-71.1775826583081 
42.3903033653531,-71.1776585052917 42.3902909739571))',
- 50)), 10);
+ 50)), 0.01);
 EXPR$0
-POLYGON ((-63.1158577185 -6.9555028496, -73.3944111820 -7.5605449307, 
-83.5789612644 -6.0473771149, -93.2376230056 -2.4801666374, -101.9608122109 
2.9898157492, -109.3786142359 10.1306105467, -115.1764705475 18.6394059614, 
-119.1085178604 28.1553789096, -119.1085413820 28.1554581099, -120.9273691116 
37.3935076607, -120.9820790302 46.8087453442, -119.2707311309 56.0673079853, 
-115.8540096006 64.8408880867, -110.8530709662 72.8183755726, -104.4452478994 
79.7168897137, -96.8577610211 85.2918 [...]
+POLYGON ((-63.12 -6.96, -73.39 -7.56, -83.58 -6.05, -93.24 -2.48, -101.96 
2.99, -109.38 10.13, -115.18 18.64, -119.11 28.16, -119.11 28.16, -120.93 
37.39, -120.98 46.81, -119.27 56.07, -115.85 64.84, -110.85 72.82, -104.45 
79.72, -96.86 85.29, -88.36 89.35, -79.25 91.73, -79.25 91.73, -68.98 92.34, 
-58.8 90.84, -49.15 87.28, -40.43 81.82, -33.01 74.69, -27.21 66.2, -23.27 
56.69, -23.27 56.69, -21.43 47.45, -21.37 38.02, -23.07 28.75, -26.49 19.97, 
-31.49 11.98, -37.9 5.07, -45.49 -0.51,  [...]
 !ok
 
 # Negative buffer size makes the polgyon smaller
@@ -1436,6 +1504,61 @@ EXPR$0
 POLYGON ((11 11, 11 19, 19 19, 19 11, 11 11))
 !ok
 
+SELECT ST_PrecisionReducer(ST_Buffer('POINT(100 90)', 50, 'quad_segs=8'), 3);
+EXPR$0
+POLYGON ((149.039 80.245, 146.194 70.866, 141.573 62.221, 135.355 54.645, 
127.779 48.427, 119.134 43.806, 109.755 40.961, 100 40, 90.245 40.961, 80.866 
43.806, 72.221 48.427, 64.645 54.645, 58.427 62.221, 53.806 70.866, 50.961 
80.245, 50 90, 50.961 99.755, 53.806 109.134, 58.427 117.779, 64.645 125.355, 
72.221 131.573, 80.866 136.194, 90.245 139.039, 100 140, 109.755 139.039, 
119.134 136.194, 127.779 131.573, 135.355 125.355, 141.573 117.779, 146.194 
109.134, 149.039 99.755, 150 90, 149. [...]
+!ok
+
+SELECT ST_PrecisionReducer(ST_Buffer('POINT(100 90)', 50, 'quad_segs=2'), 3);
+EXPR$0
+POLYGON ((135.355 54.645, 100 40, 64.645 54.645, 50 90, 64.645 125.355, 100 
140, 135.355 125.355, 150 90, 135.355 54.645))
+!ok
+
+SELECT ST_PrecisionReducer(ST_Buffer('LINESTRING(50 50,150 150,150 50)', 10, 
'endcap=round join=round'), 3);
+EXPR$0
+POLYGON ((144.444 158.315, 146.173 159.239, 148.049 159.808, 150 160, 151.951 
159.808, 153.827 159.239, 155.556 158.315, 157.071 157.071, 158.315 155.556, 
159.239 153.827, 159.808 151.951, 160 150, 160 50, 159.808 48.049, 159.239 
46.173, 158.315 44.444, 157.071 42.929, 155.556 41.685, 153.827 40.761, 151.951 
40.192, 150 40, 148.049 40.192, 146.173 40.761, 144.444 41.685, 142.929 42.929, 
141.685 44.444, 140.761 46.173, 140.192 48.049, 140 50, 140 125.858, 57.071 
42.929, 55.556 41.685, 53. [...]
+!ok
+
+SELECT ST_PrecisionReducer(ST_Buffer('LINESTRING(50 50,150 150,150 50)', 10, 
'endcap=square join=round'), 3);
+EXPR$0
+POLYGON ((144.444 158.315, 146.173 159.239, 148.049 159.808, 150 160, 151.951 
159.808, 153.827 159.239, 155.556 158.315, 157.071 157.071, 158.315 155.556, 
159.239 153.827, 159.808 151.951, 160 150, 160 50, 160 40, 140 40, 140 125.858, 
57.071 42.929, 50 35.858, 35.858 50, 142.929 157.071, 144.444 158.315))
+!ok
+
+SELECT ST_PrecisionReducer(ST_Buffer('LINESTRING(50 50,150 150,150 50)', 10, 
'endcap=flat join=round'), 3);
+EXPR$0
+POLYGON ((144.444 158.315, 146.173 159.239, 148.049 159.808, 150 160, 151.951 
159.808, 153.827 159.239, 155.556 158.315, 157.071 157.071, 158.315 155.556, 
159.239 153.827, 159.808 151.951, 160 150, 160 50, 140 50, 140 125.858, 57.071 
42.929, 42.929 57.071, 142.929 157.071, 144.444 158.315))
+!ok
+
+SELECT ST_PrecisionReducer(ST_Buffer('LINESTRING(50 50,150 150,150 50)', 10, 
'join=bevel'), 3);
+EXPR$0
+POLYGON ((160 150, 160 50, 159.808 48.049, 159.239 46.173, 158.315 44.444, 
157.071 42.929, 155.556 41.685, 153.827 40.761, 151.951 40.192, 150 40, 148.049 
40.192, 146.173 40.761, 144.444 41.685, 142.929 42.929, 141.685 44.444, 140.761 
46.173, 140.192 48.049, 140 50, 140 125.858, 57.071 42.929, 55.556 41.685, 
53.827 40.761, 51.951 40.192, 50 40, 48.049 40.192, 46.173 40.761, 44.444 
41.685, 42.929 42.929, 41.685 44.444, 40.761 46.173, 40.192 48.049, 40 50, 
40.192 51.951, 40.761 53.827, 41. [...]
+!ok
+
+SELECT ST_PrecisionReducer(ST_Buffer('LINESTRING(50 50,150 150,150 50)', 10, 
'join=mitre mitre_limit=5.0'), 3);
+EXPR$0
+POLYGON ((160 50, 159.808 48.049, 159.239 46.173, 158.315 44.444, 157.071 
42.929, 155.556 41.685, 153.827 40.761, 151.951 40.192, 150 40, 148.049 40.192, 
146.173 40.761, 144.444 41.685, 142.929 42.929, 141.685 44.444, 140.761 46.173, 
140.192 48.049, 140 50, 140 125.858, 57.071 42.929, 55.556 41.685, 53.827 
40.761, 51.951 40.192, 50 40, 48.049 40.192, 46.173 40.761, 44.444 41.685, 
42.929 42.929, 41.685 44.444, 40.761 46.173, 40.192 48.049, 40 50, 40.192 
51.951, 40.761 53.827, 41.685 55.55 [...]
+!ok
+
+SELECT ST_PrecisionReducer(ST_Buffer('LINESTRING(50 50,150 150,150 50)', 10, 
'join=mitre mitre_limit=1.0'), 3);
+EXPR$0
+POLYGON ((160 50, 159.808 48.049, 159.239 46.173, 158.315 44.444, 157.071 
42.929, 155.556 41.685, 153.827 40.761, 151.951 40.192, 150 40, 148.049 40.192, 
146.173 40.761, 144.444 41.685, 142.929 42.929, 141.685 44.444, 140.761 46.173, 
140.192 48.049, 140 50, 140 125.858, 57.071 42.929, 55.556 41.685, 53.827 
40.761, 51.951 40.192, 50 40, 48.049 40.192, 46.173 40.761, 44.444 41.685, 
42.929 42.929, 41.685 44.444, 40.761 46.173, 40.192 48.049, 40 50, 40.192 
51.951, 40.761 53.827, 41.685 55.55 [...]
+!ok
+
+SELECT ST_PrecisionReducer(ST_Buffer('LINESTRING(50 50,150 150,150 50)', 10, 
'side=left'), 3);
+EXPR$0
+POLYGON ((150 150, 50 50, 42.929 57.071, 142.929 157.071, 144.444 158.315, 
146.173 159.239, 148.049 159.808, 150 160, 151.951 159.808, 153.827 159.239, 
155.556 158.315, 157.071 157.071, 158.315 155.556, 159.239 153.827, 159.808 
151.951, 160 150, 160 50, 150 50, 150 150))
+!ok
+
+SELECT ST_PrecisionReducer(ST_Buffer('LINESTRING(50 50,150 150,150 50)', 10, 
'side=right'), 3);
+EXPR$0
+POLYGON ((150 150, 150 50, 140 50, 140 125.858, 57.071 42.929, 50 50, 150 150))
+!ok
+
+SELECT ST_PrecisionReducer(ST_Buffer('LINESTRING(50 50,150 150,150 50)', 10, 
'side=left join=mitre'), 3);
+EXPR$0
+POLYGON ((150 150, 50 50, 42.929 57.071, 160 174.142, 160 50, 150 50, 150 150))
+!ok
+
 !if (fixed.calcite2539) {
 # ST_BUFFER(geom, bufferSize, style) variant - not implemented
 SELECT ST_Buffer('POINT(100 90)', 50, 'quad_segs=8');
@@ -1498,6 +1621,16 @@ EXPR$0
 POINT (3 5)
 !ok
 
+# ST_OffsetCurve(geom, distance, bufferStyle) Computes an offset line for 
*linestring*.
+SELECT ST_ReducePrecision(ST_OffsetCurve('LINESTRING(0 0, 10 0, 10 10)', 2, 
''), 0.01);
+EXPR$0
+LINESTRING (0 2, 8 2, 8 10)
+!ok
+
+SELECT ST_ReducePrecision(ST_OffsetCurve('LINESTRING(0 0, 10 0, 10 10)', -2, 
''), 0.01);
+EXPR$0
+LINESTRING (0 -2, 10 -2, 10.39 -1.96, 10.77 -1.85, 11.11 -1.66, 11.41 -1.41, 
11.66 -1.11, 11.85 -0.77, 11.96 -0.39, 12 0, 12 10)
+!ok
 
 # ST_SymDifference(geom1, geom2) Computes the symmetric difference between two 
geometries
 
@@ -1603,46 +1736,225 @@ EXPR$0
 GEOMETRYCOLLECTION (POLYGON ((-1 1, 2 6, 5 7, -1 8, -1 1)), MULTIPOINT ((-1 
2), (1 3), (0 4)))
 !ok
 
+SELECT ST_AsText(ST_Translate('POINT Z(1 2 3)', 10, 20));
+EXPR$0
+POINT Z(11 22 3)
+!ok
+
+SELECT ST_AsText(ST_Translate('LINESTRING Z(0 0 0, 1 0 1)', 1, 2));
+EXPR$0
+LINESTRING Z(1 2 0, 2 2 1)
+!ok
 
 #### Geometry editing functions (2D)
 
-# ST_AddPoint(geom, point [, tolerance ]) Adds *point* to *geom* with a given 
*tolerance* (default 0)
-# Not implemented
+# ST_AddPoint(linestring, point [, index ]) Adds *point* to *linestring* at a 
given *index* (or at the end if *index* is not specified)
+SELECT ST_AddPoint('LINESTRING(0 0, 1 1)', 'POINT(2 2)');
+EXPR$0
+LINESTRING (0 0, 1 1, 2 2)
+!ok
+
+SELECT ST_AddPoint('LINESTRING(0 0, 1 1, 3 3)', 'POINT(2 2)', 2);
+EXPR$0
+LINESTRING (0 0, 1 1, 2 2, 3 3)
+!ok
 
 # ST_CollectionExtract(geom, dimension) Filters *geom*, returning a 
multi-geometry of those members with a given *dimension* (1 = point, 2 = 
line-string, 3 = polygon)
 # Not implemented
 
-# ST_Densify(geom, tolerance) Inserts extra vertices every *tolerance* along 
the line segments of *geom*
-# Not implemented
+# ST_Densify(geom, tolerance) Densifies a *geom* by inserting extra vertices 
along the line segments.
+SELECT ST_Densify('POINT(14 2)', 10);
+EXPR$0
+POINT (14 2)
+!ok
+
+SELECT ST_ReducePrecision(ST_Densify('LINESTRING(1 11, 8 1)', 2), 0.01);
+EXPR$0
+LINESTRING (1 11, 2 9.57, 3 8.14, 4 6.71, 5 5.29, 6 3.86, 7 2.43, 8 1)
+!ok
+
+SELECT ST_Densify('LINESTRING(1 11, 8 1)', 10);
+EXPR$0
+LINESTRING (1 11, 4.5 6, 8 1)
+!ok
+
+SELECT ST_Densify('POLYGON((2 0, 2 8, 4 8, 4 0, 2 0))', 4.5);
+EXPR$0
+POLYGON ((2 0, 2 4, 2 8, 4 8, 4 4, 4 0, 2 0))
+!ok
 
 # ST_FlipCoordinates(geom) Flips the X and Y coordinates of *geom*
-# Not implemented
+SELECT ST_FlipCoordinates('POINT(1 2)');
+EXPR$0
+POINT (2 1)
+!ok
+
+SELECT ST_FlipCoordinates('LINESTRING(1 2, 3 3, 5 9)');
+EXPR$0
+LINESTRING (2 1, 3 3, 9 5)
+!ok
+
+SELECT ST_FlipCoordinates('POLYGON ((0 1, 2 3, 3 2, 2 1, 0 1))');
+EXPR$0
+POLYGON ((1 0, 3 2, 2 3, 1 2, 1 0))
+!ok
 
 # ST_Holes(geom) Returns the holes in *geom* (which may be a 
geometry-collection)
-# Not implemented
+SELECT ST_Holes('POLYGON((0 0, 10 0, 10 5, 0 5, 0 0))');
+EXPR$0
+GEOMETRYCOLLECTION EMPTY
+!ok
+
+SELECT ST_Holes('POLYGON((0 0, 10 0, 10 5, 0 5, 0 0), (1 1, 2 1, 2 4, 1 4, 1 
1))');
+EXPR$0
+GEOMETRYCOLLECTION (LINEARRING (1 1, 2 1, 2 4, 1 4, 1 1))
+!ok
+
+SELECT ST_Holes('GEOMETRYCOLLECTION(POLYGON((0 0, 10 0, 10 5, 0 5, 0 0), (1 1, 
2 1, 2 4, 1 4, 1 1)), POLYGON((11 6, 14 6, 14 9, 11 9, 11 6), (12 7, 14 7, 14 
8, 12 8, 12 7)))');
+EXPR$0
+GEOMETRYCOLLECTION (LINEARRING (1 1, 2 1, 2 4, 1 4, 1 1), LINEARRING (12 7, 14 
7, 14 8, 12 8, 12 7))
+!ok
+
+SELECT ST_Holes('LINESTRING(5 5, 1 2, 3 4, 9 3)');
+EXPR$0
+GEOMETRYCOLLECTION EMPTY
+!ok
 
 # ST_Normalize(geom) Converts *geom* to normal form
-# Not implemented
+SELECT ST_Normalize('POLYGON((2 4, 1 3, 2 1, 6 1, 6 3, 4 4, 2 4))');
+EXPR$0
+POLYGON ((1 3, 2 4, 4 4, 6 3, 6 1, 2 1, 1 3))
+!ok
 
-# ST_RemoveDuplicatedCoordinates(geom) Removes duplicated coordinates from 
*geom*
-# Not implemented
+SELECT ST_Normalize('MULTIPOINT((2 2), (2 5), (10 3), (7 1), (5 1), (5 3))');
+EXPR$0
+MULTIPOINT ((2 2), (2 5), (5 1), (5 3), (7 1), (10 3))
+!ok
+
+SELECT ST_Normalize('LINESTRING(3 1, 6 1, 6 3, 3 3, 1 1)');
+EXPR$0
+LINESTRING (1 1, 3 3, 6 3, 6 1, 3 1)
+!ok
+
+# ST_RemoveRepeatedPoints(geom) Removes duplicated coordinates from *geom*
+SELECT ST_RemoveRepeatedPoints( 'LINESTRING (0 0, 0 0, 1 1, 5 5, 1 1, 2 2)', 
2);
+EXPR$0
+LINESTRING (0 0, 5 5, 2 2)
+!ok
+
+SELECT ST_RemoveRepeatedPoints('LINESTRING(0 0, 0 0, 1 1, 0 0, 1 1, 2 2)');
+EXPR$0
+LINESTRING (0 0, 1 1, 0 0, 1 1, 2 2)
+!ok
+
+SELECT ST_RemoveRepeatedPoints('LINESTRING(1 1, 2 2, 2 2, 1 3, 1 3, 3 3, 3 3, 
5 2, 5 2, 5 1)');
+EXPR$0
+LINESTRING (1 1, 2 2, 1 3, 3 3, 5 2, 5 1)
+!ok
+
+SELECT ST_RemoveRepeatedPoints('LINESTRING(1 1, 1 3, 2 3, 2 6, 3 8)', 2);
+EXPR$0
+LINESTRING (1 1, 2 3, 2 6, 3 8)
+!ok
+
+SELECT ST_RemoveRepeatedPoints('POLYGON((2 4, 1 3, 2 1, 2 1, 6 1, 6 3, 4 4, 4 
4, 2 4))');
+EXPR$0
+POLYGON ((2 4, 1 3, 2 1, 6 1, 6 3, 4 4, 2 4))
+!ok
+
+SELECT ST_RemoveRepeatedPoints('GEOMETRYCOLLECTION(POLYGON((1 2, 4 2, 4 6, 1 
6, 1 6, 1 2)), MULTIPOINT((4 4), (1 1), (1 0), (0 3)))');
+EXPR$0
+GEOMETRYCOLLECTION (POLYGON ((1 2, 4 2, 4 6, 1 6, 1 2)), MULTIPOINT ((4 4), (1 
1), (1 0), (0 3)))
+!ok
+
+SELECT ST_RemoveRepeatedPoints('MULTIPOINT((4 4), (1 1), (1 0), (0 3), (4 
4))');
+EXPR$0
+MULTIPOINT ((4 4), (1 1), (1 0), (0 3), (4 4))
+!ok
 
 # ST_RemoveHoles(geom) Removes a *geom*'s holes
-# Not implemented
+SELECT ST_RemoveHoles('POLYGON((1 5, 0 4, 0 1, 1 0, 4 0, 4 2, 5 4, 5 4, 1 5), 
(1 3, 1 4, 2 4, 2 3, 1 3), (2 2, 1 1, 2 1, 2 2))');
+EXPR$0
+POLYGON ((1 5, 0 4, 0 1, 1 0, 4 0, 4 2, 5 4, 5 4, 1 5))
+!ok
 
-# ST_RemovePoints(geom, poly) Removes all coordinates of *geom* located within 
*poly*; null if all coordinates are removed
-# Not implemented
+SELECT ST_RemoveHoles('POLYGON((1 5, 0 4, 0 1, 1 0, 4 0, 4 2, 5 4, 5 4, 1 
5))');
+EXPR$0
+POLYGON ((1 5, 0 4, 0 1, 1 0, 4 0, 4 2, 5 4, 5 4, 1 5))
+!ok
+
+SELECT ST_RemoveHoles('POINT(1 5)');
+EXPR$0
+POINT (1 5)
+!ok
+
+# ST_RemovePoint(linestring, index) Remove *point* at given *index* in 
*linestring*
+SELECT ST_RemovePoint('LINESTRING(0 0, 1 1, 2 2, 3 3)', 2);
+EXPR$0
+LINESTRING (0 0, 1 1, 3 3)
+!ok
+
+SELECT ST_RemovePoint('LINESTRING(0 0, 1 1, 2 2, 3 3)', 0);
+EXPR$0
+LINESTRING (1 1, 2 2, 3 3)
+!ok
+
+SELECT ST_RemovePoint('LINESTRING(0 0, 1 1, 2 2, 3 3)', 3);
+EXPR$0
+LINESTRING (0 0, 1 1, 2 2)
+!ok
 
 # ST_RemoveRepeatedPoints(geom, tolerance) Removes from *geom* all repeated 
points (or points within *tolerance* of another point)
 # Not implemented
 
 # ST_Reverse(geom) Reverses the vertex order of *geom*
-# Not implemented
+SELECT ST_Reverse('LINESTRING(1 1, 2 2, 1 3, 3 3, 5 2, 5 1)');
+EXPR$0
+LINESTRING (5 1, 5 2, 3 3, 1 3, 2 2, 1 1)
+!ok
+
+SELECT ST_Reverse('MULTILINESTRING((10 260, 150 290, 186 406, 286 286), (120 
120, 130 125, 142 129, 360 160, 357 170, 380 340), (1 1, 5 5))');
+EXPR$0
+MULTILINESTRING ((286 286, 186 406, 150 290, 10 260), (380 340, 357 170, 360 
160, 142 129, 130 125, 120 120), (5 5, 1 1))
+!ok
+
+SELECT ST_Reverse('POLYGON((2 4, 1 3, 2 1, 6 1, 6 3, 4 4, 2 4))');
+EXPR$0
+POLYGON ((2 4, 4 4, 6 3, 6 1, 2 1, 1 3, 2 4))
+!ok
+
+SELECT ST_Reverse('MULTIPOLYGON(((2 4, 1 3, 2 1, 6 1, 6 3, 4 4, 2 4)), ((1 6, 
6 6, 6 5, 1 5, 1 6)), ((0 1, 1 1, 1 0, 0 0, 0 1)))');
+EXPR$0
+MULTIPOLYGON (((2 4, 4 4, 6 3, 6 1, 2 1, 1 3, 2 4)), ((1 6, 1 5, 6 5, 6 6, 1 
6)), ((0 1, 0 0, 1 0, 1 1, 0 1)))
+!ok
+
+SELECT ST_Reverse('GEOMETRYCOLLECTION(POLYGON((1 2, 4 2, 4 6, 1 6, 1 2)), 
LINESTRING(2 6, 6 2))');
+EXPR$0
+GEOMETRYCOLLECTION (POLYGON ((1 2, 1 6, 4 6, 4 2, 1 2)), LINESTRING (6 2, 2 6))
+!ok
 
 #### Geometry editing functions (3D)
 
 # ST_AddZ(geom, zToAdd) Adds *zToAdd* to the z-coordinate of *geom*
-# Not implemented
+SELECT ST_AsText(ST_AddZ('MULTIPOINT((190 300 1), (10 11 0))', 10));
+EXPR$0
+MULTIPOINT Z((190 300 11), (10 11 10))
+!ok
+
+SELECT ST_Z(ST_GeometryN(ST_AddZ('MULTIPOINT((190 300 1), (10 11))', 10), 1));
+EXPR$0
+NaN
+!ok
+
+SELECT ST_AsText(ST_AddZ('MULTIPOINT Z((190 300 10), (10 11 5))', -10));
+EXPR$0
+MULTIPOINT Z((190 300 0), (10 11 -5))
+!ok
+
+SELECT ST_AsText(ST_AddZ('POLYGON((1 1 5, 1 7 10, 7 7 -1, 7 1 -1, 1 1 5))', 
-10));
+EXPR$0
+POLYGON Z((1 1 -5, 1 7 0, 7 7 -11, 7 1 -11, 1 1 -5))
+!ok
 
 # ST_Interpolate3DLine(geom) Returns *geom* with a interpolation of z values, 
or null if it is not a line-string or multi-line-string
 # Not implemented
@@ -2026,8 +2338,37 @@ EXPR$0
 POLYGON ((3 3, 1 1, 1 1, 1 1, -2 1, -1 7, 1 7, 3 6, 4 8, 7 7, 7 7, 9 6, 7 1, 7 
1, 3 3))
 !ok
 
-# ST_Split(geom1, geom2 [, tolerance]) Splits *geom1* by *geom2* using 
*tolerance* (default 1E-6) to determine where the point splits the line
-# Not implemented
+# ST_Split(geom, blade) Splits *geom* by *blade*
+
+SELECT ST_Split('LINESTRING(0 0, 10 0)', 'POINT(5 0)');
+EXPR$0
+MULTILINESTRING ((0 0, 5 0), (5 0, 10 0))
+!ok
+
+SELECT ST_Split('MULTILINESTRING ((5 0, 5 10), (0 5, 10 5))', 'POINT(5 5)');
+EXPR$0
+MULTILINESTRING ((5 0, 5 5), (5 5, 5 10), (0 5, 5 5), (5 5, 10 5))
+!ok
+
+SELECT ST_Split('LINESTRING(0 0, 10 0)', 'LINESTRING(5 -1, 5 1)');
+EXPR$0
+MULTILINESTRING ((0 0, 5 0), (5 0, 10 0))
+!ok
+
+SELECT ST_Split('MULTILINESTRING ((1 0, 1 10), (2 0, 2 10))', 'LINESTRING(0 5, 
4 5))');
+EXPR$0
+MULTILINESTRING ((1 0, 1 5), (1 5, 1 10), (2 0, 2 5), (2 5, 2 10))
+!ok
+
+SELECT ST_Split('POLYGON((0 0, 5 0, 5 5, 0 5, 0 0))', 'LINESTRING(2 0, 2 5)');
+EXPR$0
+MULTIPOLYGON (((0 0, 0 5, 2 5, 2 0, 0 0)), ((2 0, 2 5, 5 5, 5 0, 2 0)))
+!ok
+
+SELECT ST_Split('MULTIPOLYGON (((0 0, 0 5, 2 5, 2 0, 0 0)), ((2 0, 2 5, 5 5, 5 
0, 2 0)))', 'LINESTRING(0 2, 5 2)');
+EXPR$0
+MULTIPOLYGON (((0 0, 0 2, 2 2, 2 0, 0 0)), ((0 2, 0 5, 2 5, 2 2, 0 2)), ((2 0, 
2 2, 5 2, 5 0, 2 0)), ((2 2, 2 5, 5 5, 5 2, 2 2)))
+!ok
 
 #### Geometry projection functions
 
@@ -2044,10 +2385,10 @@ srid:9804;POINT (-13732990.875349075 6178458.964254234)
 !ok
 
 # ST_Transform(geom, srid) Transforms *geom* from one coordinate reference 
system (CRS) to the CRS specified by *srid*
-SELECT roundGeom(ST_AsText(ST_Transform(ST_GeomFromText('POLYGON((743238 
2967416,743238 2967450,
-  743265 2967450,743265.625 2967416,743238 2967416))',2249),4326)), 10) As 
wgs_geom;
+SELECT 
ST_ReducePrecision(ST_AsText(ST_Transform(ST_GeomFromText('POLYGON((743238 
2967416,743238 2967450,
+  743265 2967450,743265.625 2967416,743238 2967416))',2249),4326)), 0.01) As 
wgs_geom;
 WGS_GEOM
-POLYGON ((-71.1776848523 42.3902896513, -71.1776843767 42.3903829479, 
-71.1775844306 42.3903826678, -71.1775825928 42.3902893648, -71.1776848523 
42.3902896513))
+POLYGON ((-71.18 42.39, -71.18 42.39, -71.18 42.39, -71.18 42.39, -71.18 
42.39))
 !ok
 
 #### Trigonometry functions
@@ -2075,10 +2416,26 @@ POLYGON ((-71.1776848523 42.3902896513, -71.1776843767 
42.3903829479, -71.177584
 #### Triangulation functions
 
 # ST_ConstrainedDelaunay(geom [, flag [, quality ]]) Computes a constrained 
Delaunay triangulation based on *geom*
-# Not implemented
+SELECT ST_ConstrainedDelaunay('POLYGON((0 0, 0 10, 10 10, 10 0, 0 0))');
+EXPR$0
+MULTIPOLYGON (((0 0, 0 10, 10 10, 0 0)), ((10 10, 10 0, 0 0, 10 10)))
+!ok
+
+SELECT ST_ConstrainedDelaunay('POLYGON((0 0, 0 10, 10 10, 10 0, 0 0))', 1);
+EXPR$0
+MULTILINESTRING ((0 0, 0 10), (0 0, 10 0), (0 0, 10 10), (0 10, 10 10), (10 0, 
10 10))
+!ok
 
 # ST_Delaunay(geom [, flag [, quality ]]) Computes a Delaunay triangulation 
based on points
-# Not implemented
+SELECT ST_Delaunay('MULTIPOINT((1 1), (0 4), (3 2), (3 7), (4 5), (5 2), (7 
1), (7 6), (8 4), (5 8), (1 8), (4 0))');
+EXPR$0
+MULTIPOLYGON (((1 8, 0 4, 3 7, 1 8)), ((1 8, 3 7, 5 8, 1 8)), ((5 8, 3 7, 4 5, 
5 8)), ((5 8, 4 5, 7 6, 5 8)), ((7 6, 4 5, 8 4, 7 6)), ((8 4, 4 5, 5 2, 8 4)), 
((8 4, 5 2, 7 1, 8 4)), ((4 0, 7 1, 5 2, 4 0)), ((4 0, 5 2, 3 2, 4 0)), ((4 0, 
3 2, 1 1, 4 0)), ((1 1, 3 2, 0 4, 1 1)), ((0 4, 3 2, 4 5, 0 4)), ((0 4, 4 5, 3 
7, 0 4)), ((4 5, 3 2, 5 2, 4 5)))
+!ok
+
+SELECT ST_Delaunay('MULTIPOINT((1 1), (0 4), (3 2), (3 7), (4 5), (5 2), (7 
1), (7 6), (8 4), (5 8), (1 8), (4 0))', 1);
+EXPR$0
+MULTILINESTRING ((0 4, 1 1), (0 4, 1 8), (0 4, 3 2), (0 4, 3 7), (0 4, 4 5), 
(1 1, 3 2), (1 1, 4 0), (1 8, 3 7), (1 8, 5 8), (3 2, 4 0), (3 2, 4 5), (3 2, 5 
2), (3 7, 4 5), (3 7, 5 8), (4 0, 5 2), (4 0, 7 1), (4 5, 5 2), (4 5, 5 8), (4 
5, 7 6), (4 5, 8 4), (5 2, 7 1), (5 2, 8 4), (5 8, 7 6), (7 1, 8 4), (7 6, 8 4))
+!ok
 
 # ST_Tessellate(polygon) Tessellates *polygon* (may be multi-polygon) with 
adaptive triangles
 # Not implemented
diff --git a/site/_docs/reference.md b/site/_docs/reference.md
index efa1fb5349..0b4f67af3e 100644
--- a/site/_docs/reference.md
+++ b/site/_docs/reference.md
@@ -2222,6 +2222,7 @@ implements the OpenGIS Simple Features Implementation 
Specification for SQL,
 | o | ST_PointFromWKB(wkt [, srid ]) | Converts WKB → POINT
 | o | ST_PolyFromText(wkt [, srid ]) | Converts WKT → POLYGON
 | o | ST_PolyFromWKB(wkt [, srid ]) | Converts WKB → POLYGON
+| p | ST_ReducePrecision(geom, gridSize) | Reduces the precision of a *geom* 
to the provided *gridSize*
 | h | ST_ToMultiPoint(geom) | Converts the coordinates of *geom* (which may be 
a GEOMETRYCOLLECTION) into a MULTIPOINT
 | h | ST_ToMultiLine(geom) | Converts the coordinates of *geom* (which may be 
a GEOMETRYCOLLECTION) into a MULTILINESTRING
 | h | ST_ToMultiSegments(geom) | Converts *geom* (which may be a 
GEOMETRYCOLLECTION) into a set of distinct segments stored in a MULTILINESTRING
@@ -2354,11 +2355,13 @@ The following functions combine 2D geometries.
 
 | C | Operator syntax      | Description
 |:- |:-------------------- |:-----------
-| o | ST_Buffer(geom, distance [, quadSegs \| style ]) | Computes a buffer 
around *geom*
+| p | ST_Buffer(geom, distance [, quadSegs, endCapStyle ]) | Computes a buffer 
around *geom*
+| p | ST_Buffer(geom, distance [, bufferStyle ]) | Computes a buffer around 
*geom*
 | o | ST_ConvexHull(geom) | Computes the smallest convex polygon that contains 
all the points in *geom*
 | o | ST_Difference(geom1, geom2) | Computes the difference between two 
geometries
 | o | ST_SymDifference(geom1, geom2) | Computes the symmetric difference 
between two geometries
 | o | ST_Intersection(geom1, geom2) | Computes the intersection of *geom1* and 
*geom2*
+| p | ST_OffsetCurve(geom, distance, bufferStyle) | Computes an offset line 
for *linestring*
 | o | ST_Union(geom1, geom2) | Computes the union of *geom1* and *geom2*
 | o | ST_Union(geomCollection) | Computes the union of the geometries in 
*geomCollection*
 
@@ -2383,27 +2386,33 @@ Not implemented:
 
 The following functions modify 2D geometries.
 
+| C | Operator syntax      | Description
+|:- |:-------------------- |:-----------
+| p | ST_AddPoint(linestring, point [, index]) | Adds *point* to *linestring* 
at a given *index* (or at the end if *index* is not specified)
+| h | ST_Densify(geom, tolerance) | Densifies a *geom* by inserting extra 
vertices along the line segments
+| h | ST_FlipCoordinates(geom) | Flips the X and Y coordinates of the *geom*
+| h | ST_Holes(geom) | Returns the holes in the *geom* (which may be a 
GEOMETRYCOLLECTION)
+| h | ST_Normalize(geom) | Converts the *geom* to normal form
+| p | ST_RemoveRepeatedPoints(geom [, tolerance]) | Removes duplicated 
coordinates from the *geom*
+| h | ST_RemoveHoles(geom) | Removes the holes of the *geom*
+| p | ST_RemovePoint(linestring, index) | Remove *point* at given *index* in 
*linestring*
+| h | ST_Reverse(geom) | Reverses the order of the coordinates of the *geom*
+
 Not implemented:
 
-* ST_AddPoint(geom, point [, tolerance ]) Adds *point* to *geom* with a given 
*tolerance* (default 0)
 * ST_CollectionExtract(geom, dimension) Filters *geom*, returning a 
multi-geometry of those members with a given *dimension* (1 = point, 2 = 
line-string, 3 = polygon)
-* ST_Densify(geom, tolerance) Inserts extra vertices every *tolerance* along 
the line segments of *geom*
-* ST_FlipCoordinates(geom) Flips the X and Y coordinates of *geom*
-* ST_Holes(geom) Returns the holes in *geom* (which may be a 
GEOMETRYCOLLECTION)
-* ST_Normalize(geom) Converts *geom* to normal form
-* ST_RemoveDuplicatedCoordinates(geom) Removes duplicated coordinates from 
*geom*
-* ST_RemoveHoles(geom) Removes a *geom*'s holes
-* ST_RemovePoints(geom, poly) Removes all coordinates of *geom* located within 
*poly*; null if all coordinates are removed
-* ST_RemoveRepeatedPoints(geom, tolerance) Removes from *geom* all repeated 
points (or points within *tolerance* of another point)
-* ST_Reverse(geom) Reverses the vertex order of *geom*
 
 #### Geometry editing functions (3D)
 
 The following functions modify 3D geometries.
 
+
+| C | Operator syntax      | Description
+|:- |:-------------------- |:-----------
+| h | ST_AddZ(geom, zToAdd) | Adds *zToAdd* to the z-coordinate of *geom*
+
 Not implemented:
 
-* ST_AddZ(geom, zToAdd) Adds *zToAdd* to the z-coordinate of *geom*
 * ST_Interpolate3DLine(geom) Returns *geom* with an interpolation of z values, 
or null if it is not a line-string or MULTILINESTRING
 * ST_MultiplyZ(geom, zFactor) Returns *geom* with its z-values multiplied by 
*zFactor*
 * ST_Reverse3DLine(geom [, sortOrder ]) Potentially reverses *geom* according 
to the z-values of its first and last coordinates
@@ -2449,6 +2458,7 @@ The following functions process geometries.
 | o | ST_Simplify(geom, distance)  | Simplifies *geom* using the 
[Douglas-Peuker 
algorithm](https://en.wikipedia.org/wiki/Ramer%E2%80%93Douglas%E2%80%93Peucker_algorithm)
 with a *distance* tolerance
 | o | ST_SimplifyPreserveTopology(geom, distance) | Simplifies *geom*, 
preserving its topology
 | o | ST_Snap(geom1, geom2, tolerance) | Snaps *geom1* and *geom2* together
+| p | ST_Split(geom, blade) | Splits *geom* by *blade*
 
 Not implemented:
 
@@ -2457,7 +2467,6 @@ Not implemented:
 * ST_MakeValid(geom [, preserveGeomDim [, preserveDuplicateCoord [, 
preserveCoordDim]]]) Makes *geom* valid
 * ST_RingSideBuffer(geom, distance, bufferCount [, endCapStyle [, 
doDifference]]) Computes a ring buffer on one side
 * ST_SideBuffer(geom, distance [, bufferStyle ]) Compute a single buffer on 
one side
-* ST_Split(geom1, geom2 [, tolerance]) Splits *geom1* by *geom2* using 
*tolerance* (default 1E-6) to determine where the point splits the line
 
 #### Geometry projection functions
 
@@ -2489,19 +2498,22 @@ Not implemented:
 
 #### Triangulation functions
 
+| C | Operator syntax      | Description
+|:- |:-------------------- |:-----------
+| h | ST_ConstrainedDelaunay(geom [, flag]) | Computes a constrained Delaunay 
triangulation based on *geom*
+| h | ST_Delaunay(geom [, flag]) | Computes a Delaunay triangulation based on 
points in *geom*
+
 Not implemented:
 
-* ST_ConstrainedDelaunay(geom [, flag [, quality ]]) Computes a constrained 
Delaunay triangulation based on *geom*
-* ST_Delaunay(geom [, flag [, quality ]]) Computes a Delaunay triangulation 
based on points
 * ST_Tessellate(polygon) Tessellates *polygon* (may be MULTIPOLYGON) with 
adaptive triangles
 
 #### Geometry aggregate functions
 
-Not implemented:
-
-* ST_Accum(geom) Accumulates *geom* into a GEOMETRYCOLLECTION (or MULTIPOINT, 
MULTILINESTRING or MULTIPOLYGON if possible)
-* ST_Collect(geom) Synonym for `ST_Accum`
-* ST_Union(geom) Computes the union of geometries
+| C | Operator syntax      | Description
+|:- |:-------------------- |:-----------
+| h | ST_Accum(geom) | Accumulates *geom* into an array
+| h | ST_Collect(geom) | Collects *geom* into a GeometryCollection
+| h | ST_Union(geom) | Computes the union of the geometries in *geom*
 
 ### JSON Functions
 
diff --git a/testkit/src/main/java/org/apache/calcite/test/CalciteAssert.java 
b/testkit/src/main/java/org/apache/calcite/test/CalciteAssert.java
index 9ca33819f4..b5a0adaf57 100644
--- a/testkit/src/main/java/org/apache/calcite/test/CalciteAssert.java
+++ b/testkit/src/main/java/org/apache/calcite/test/CalciteAssert.java
@@ -41,12 +41,12 @@ import org.apache.calcite.rel.type.RelDataType;
 import org.apache.calcite.rel.type.RelDataTypeFactory;
 import org.apache.calcite.rel.type.RelDataTypeImpl;
 import org.apache.calcite.rel.type.RelProtoDataType;
+import org.apache.calcite.runtime.AccumOperation;
 import org.apache.calcite.runtime.CalciteException;
+import org.apache.calcite.runtime.CollectOperation;
 import org.apache.calcite.runtime.Hook;
 import org.apache.calcite.runtime.SpatialTypeFunctions;
-import org.apache.calcite.runtime.SpatialTypeFunctions.Accum;
-import org.apache.calcite.runtime.SpatialTypeFunctions.Collect;
-import org.apache.calcite.runtime.SpatialTypeFunctions.Union;
+import org.apache.calcite.runtime.UnionOperation;
 import org.apache.calcite.schema.Schema;
 import org.apache.calcite.schema.SchemaPlus;
 import org.apache.calcite.schema.SchemaVersion;
@@ -827,11 +827,9 @@ public class CalciteAssert {
           SpatialTypeFunctions.class.getName(), "*", true);
       ModelHandler.addFunctions(rootSchema, null, emptyPath,
           SqlSpatialTypeFunctions.class.getName(), "*", true);
-      rootSchema.add("ST_UNION", AggregateFunctionImpl.create(Union.class));
-      rootSchema.add("ST_ACCUM", AggregateFunctionImpl.create(Accum.class));
-      rootSchema.add("ST_COLLECT", 
AggregateFunctionImpl.create(Collect.class));
-      ModelHandler.addFunctions(rootSchema, "roundGeom", emptyPath,
-          TestUtil.class.getName(), "roundGeom", true);
+      rootSchema.add("ST_UNION", 
AggregateFunctionImpl.create(UnionOperation.class));
+      rootSchema.add("ST_ACCUM", 
AggregateFunctionImpl.create(AccumOperation.class));
+      rootSchema.add("ST_COLLECT", 
AggregateFunctionImpl.create(CollectOperation.class));
       final SchemaPlus s =
           rootSchema.add(schema.schemaName, new AbstractSchema());
       ModelHandler.addFunctions(s, "countries", emptyPath,
diff --git a/testkit/src/main/java/org/apache/calcite/util/TestUtil.java 
b/testkit/src/main/java/org/apache/calcite/util/TestUtil.java
index 7e7870df7e..15c332c54e 100644
--- a/testkit/src/main/java/org/apache/calcite/util/TestUtil.java
+++ b/testkit/src/main/java/org/apache/calcite/util/TestUtil.java
@@ -248,69 +248,6 @@ public abstract class TestUtil {
     return s;
   }
 
-  /** Rounds all decimal fractions inside a string to a given number of decimal
-   * places.
-   *
-   * <p>For example,
-   * {@code round("POINT(-1.23456, 9.87654)", 3)}
-   * returns "POINT(-1.235, 9.877)". */
-  public static String roundGeom(String s, int precision) {
-    final StringBuilder b = new StringBuilder();
-    boolean carried = false;
-    int end = -1;
-    for (int i = 0; i < s.length(); i++) {
-      char c = s.charAt(i);
-      switch (c) {
-      case '.':
-        // Entering the fractional part of a number
-        end = i + precision + 1;
-        carried = false;
-        break;
-      case '0': case '1': case '2': case '3': case '4':
-      case '5': case '6': case '7': case '8': case '9':
-        if (end < 0) {
-          break; // We've not seen a '.'
-        }
-        if (i < end) {
-          break; // Have seen a '.' but not enough digits yet
-        }
-        if (c > '5' || i > end && c > '0') {
-          if (!carried) {
-            carry(b);
-            carried = true;
-          }
-        }
-        continue;
-      default:
-        end = -1; // no longer in a number
-      }
-      b.append(c);
-    }
-    return b.toString();
-  }
-
-  /** Increments the last digit of a decimal number in a StringBuilder, and if
-   * that digit was a '9', carries on going. */
-  private static void carry(StringBuilder b) {
-    for (int i = b.length() - 1; i >= 0; i--) {
-      char c = b.charAt(i);
-      switch (c) {
-      case '.':
-        continue; // continue to the left of decimal point
-      case '0': case '1': case '2': case '3': case '4':
-      case '5': case '6': case '7': case '8':
-        b.setCharAt(i, (char) (c + 1)); // carry, and we're done
-        return;
-      case '9':
-        b.setCharAt(i, '0'); // '9' becomes '0', and continue carrying
-        continue;
-      default:
-        b.insert(i + 1, '1');
-        return;
-      }
-    }
-  }
-
   /**
    * Returns the Java major version: 7 for JDK 1.7, 8 for JDK 8, 10 for
    * JDK 10, etc. depending on current system property {@code java.version}.
diff --git a/testkit/src/test/java/org/apache/calcite/util/TestUtilTest.java 
b/testkit/src/test/java/org/apache/calcite/util/TestUtilTest.java
index e5bf1477fa..79056be35d 100644
--- a/testkit/src/test/java/org/apache/calcite/util/TestUtilTest.java
+++ b/testkit/src/test/java/org/apache/calcite/util/TestUtilTest.java
@@ -28,8 +28,6 @@ import java.util.List;
 import java.util.SortedSet;
 import java.util.stream.Collectors;
 
-import static org.apache.calcite.util.TestUtil.roundGeom;
-
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.core.Is.is;
 import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -40,24 +38,6 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
  */
 class TestUtilTest {
 
-  /** Tests {@link TestUtil#roundGeom}. */
-  @Test void testRoundGeom() {
-    assertThat(roundGeom("1", 3), is("1"));
-    assertThat(roundGeom("1.28", 3), is("1.28"));
-    assertThat(roundGeom("1.278", 3), is("1.278"));
-    assertThat(roundGeom("1.2784", 3), is("1.278")); // < 5 rounds down
-    assertThat(roundGeom("1.2785", 3), is("1.278")); // = 5 rounds down
-    assertThat(roundGeom("1.27850001", 3), is("1.279")); // > 5 rounds up
-    assertThat(roundGeom("1.27950001", 3), is("1.280"));
-    assertThat(roundGeom("1.29950001", 3), is("1.300"));
-    assertThat(roundGeom("19.99950001", 3), is("20.000"));
-    assertThat(roundGeom("2 9.99950001", 3), is("2 10.000"));
-    assertThat(roundGeom("23 9.99950001", 3), is("23 10.000"));
-    assertThat(roundGeom("234 9.99950001", 3), is("234 10.000"));
-    assertThat(roundGeom("POINT(-1.23456, 9.87654)", 3),
-        is("POINT(-1.235, 9.877)"));
-  }
-
   @Test void javaMajorVersionExceeds6() {
     // shouldn't throw any exceptions (for current JDK)
     int majorVersion = TestUtil.getJavaMajorVersion();


Reply via email to