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

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

commit 262492527fbd450892df36cda38080d447fc6498
Author: Bertil Chapuis <bchap...@gmail.com>
AuthorDate: Sat Sep 3 14:02:53 2022 +0200

    [CALCITE-5262] Add many spatial functions, including support for WKB 
(well-known binary) and GeoJSON
    
    Add functions:
     * ST_AsBinary
     * ST_AsEWKT and ST_GeomFromEWKT
     * ST_AsGeoJSON and ST_GeomFromGeoJSON
     * ST_AsGML and ST_GeomFromGML
     * ST_AsWKB and ST_GeomFromWKB
     * ST_Centroid
     * ST_ConvexHull
     * ST_CoordDim
     * ST_Covers
     * ST_Difference and ST_SymDifference
     * ST_Dimension and ST_BoundingCircle
     * ST_Expand
     * ST_Extent
     * ST_ExteriorRing and ST_InteriorRing
     * ST_Force2D and ST_Force3D
     * ST_GeometryN
     * ST_Intersection
     * ST_IsClosed, ST_IsRectangle, ST_IsRing, ST_IsSimple, ST_IsValid, 
ST_IsEmpty
     * ST_LineMerge
     * ST_MakeEllipse and ST_MakePolygon
     * ST_MakeValid
     * ST_MinimumDiameter
     * ST_MinimumRectangle
     * ST_NumGeometries
     * ST_NumInteriorRing and ST_NumInteriorRings
     * ST_NumPoints
     * ST_OctagonalEnvelope
     * ST_PointN
     * ST_PointOnSurface
     * ST_Polygonize
     * ST_PrecisionReducer
     * ST_Relate
     * ST_Rotate
     * ST_Scale
     * ST_Simplify and ST_SimplifyPreserveTopology
     * ST_Snap
     * ST_SRID
     * ST_StartPoint and ST_EndPoint
     * ST_ToMultiLine and ST_ToMultiSegments
     * ST_ToMultiPoint
     * ST_Translate
     * ST_XMax, ST_XMin, ST_YMax, ST_YMin, ST_ZMin, ST_ZMax
    
    Improve documentation, aliases, and variable names.
    
    Set the extractOnlyPolygonal option to true in ST_Polygonize.
    
    Replace Esri by JTS in the acknowledgements.
    
    Close apache/calcite#2893
---
 .../calcite/runtime/CoordinateTransformer.java     |   51 +
 .../calcite/runtime/SpatialTypeFunctions.java      | 1115 +++++++++++++++++---
 .../apache/calcite/runtime/SpatialTypeUtils.java   |   44 +-
 core/src/test/resources/sql/spatial.iq             |  991 +++++++++++++++--
 site/_docs/reference.md                            |  165 +--
 site/_docs/spatial.md                              |    2 +-
 6 files changed, 2066 insertions(+), 302 deletions(-)

diff --git 
a/core/src/main/java/org/apache/calcite/runtime/CoordinateTransformer.java 
b/core/src/main/java/org/apache/calcite/runtime/CoordinateTransformer.java
new file mode 100644
index 0000000000..b82efd4b6f
--- /dev/null
+++ b/core/src/main/java/org/apache/calcite/runtime/CoordinateTransformer.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.CoordinateSequence;
+import org.locationtech.jts.geom.Geometry;
+import org.locationtech.jts.geom.impl.CoordinateArraySequence;
+import org.locationtech.jts.geom.util.GeometryTransformer;
+
+import java.util.function.Function;
+import java.util.stream.Stream;
+
+/**
+ * Transforms the coordinates of a geometry.
+ */
+public class CoordinateTransformer extends GeometryTransformer {
+
+  private final Function<Coordinate, Coordinate> transform;
+
+  /**
+   * Creates a transformer that applies the {@code transform} to all 
coordinates.
+   */
+  public CoordinateTransformer(Function<Coordinate, Coordinate> transform) {
+    this.transform = transform;
+  }
+
+  @Override protected CoordinateSequence transformCoordinates(
+      CoordinateSequence coordinateSequence, Geometry parent) {
+    Coordinate[] coordinateArray =
+        Stream.of(coordinateSequence.toCoordinateArray())
+            .map(transform)
+            .toArray(Coordinate[]::new);
+    return new CoordinateArraySequence(coordinateArray);
+  }
+
+}
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 d7808a475f..bba3e13edf 100644
--- a/core/src/main/java/org/apache/calcite/runtime/SpatialTypeFunctions.java
+++ b/core/src/main/java/org/apache/calcite/runtime/SpatialTypeFunctions.java
@@ -16,6 +16,7 @@
  */
 package org.apache.calcite.runtime;
 
+import org.apache.calcite.avatica.util.ByteString;
 import org.apache.calcite.linq4j.AbstractEnumerable;
 import org.apache.calcite.linq4j.Enumerator;
 import org.apache.calcite.linq4j.function.Deterministic;
@@ -26,20 +27,54 @@ import org.apache.calcite.linq4j.function.Strict;
 import org.apache.calcite.runtime.SpatialTypeUtils.SpatialType;
 
 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.geom.Coordinate;
+import org.locationtech.jts.geom.CoordinateSequence;
 import org.locationtech.jts.geom.Envelope;
 import org.locationtech.jts.geom.Geometry;
+import org.locationtech.jts.geom.GeometryCollection;
+import org.locationtech.jts.geom.GeometryComponentFilter;
+import org.locationtech.jts.geom.GeometryFactory;
+import org.locationtech.jts.geom.GeometryFilter;
+import org.locationtech.jts.geom.LineString;
 import org.locationtech.jts.geom.LinearRing;
+import org.locationtech.jts.geom.MultiLineString;
+import org.locationtech.jts.geom.MultiPoint;
+import org.locationtech.jts.geom.MultiPolygon;
+import org.locationtech.jts.geom.OctagonalEnvelope;
 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.GeometryFixer;
+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.precision.GeometryPrecisionReducer;
+import org.locationtech.jts.simplify.DouglasPeuckerSimplifier;
+import org.locationtech.jts.simplify.TopologyPreservingSimplifier;
+import org.locationtech.jts.util.GeometricShapeFactory;
 
 import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Objects;
+import java.util.function.Function;
+import java.util.stream.Stream;
 
 import static org.apache.calcite.runtime.SpatialTypeUtils.GEOMETRY_FACTORY;
 import static org.apache.calcite.runtime.SpatialTypeUtils.NO_SRID;
 import static org.apache.calcite.runtime.SpatialTypeUtils.asEwkt;
+import static org.apache.calcite.runtime.SpatialTypeUtils.asGeoJson;
+import static org.apache.calcite.runtime.SpatialTypeUtils.asGml;
+import static org.apache.calcite.runtime.SpatialTypeUtils.asWkb;
 import static org.apache.calcite.runtime.SpatialTypeUtils.asWkt;
+import static org.apache.calcite.runtime.SpatialTypeUtils.fromEwkt;
+import static org.apache.calcite.runtime.SpatialTypeUtils.fromGeoJson;
+import static org.apache.calcite.runtime.SpatialTypeUtils.fromGml;
+import static org.apache.calcite.runtime.SpatialTypeUtils.fromWkb;
 import static org.apache.calcite.runtime.SpatialTypeUtils.fromWkt;
 
 /**
@@ -68,95 +103,256 @@ import static 
org.apache.calcite.runtime.SpatialTypeUtils.fromWkt;
 @Experimental
 public class SpatialTypeFunctions {
 
-  private SpatialTypeFunctions() {}
+  private SpatialTypeFunctions() {
+  }
 
-  // Geometry conversion functions (2D and 3D) ================================
+  // Geometry conversion functions (2D)
 
-  public static @Nullable String ST_AsEWKT(Geometry g) {
-    return asEwkt(g);
+  public static @Nullable ByteString ST_AsBinary(Geometry geometry) {
+    return ST_AsWKB(geometry);
   }
 
-  public static @Nullable String ST_AsText(Geometry g) {
-    return asWkt(g);
+  public static @Nullable String ST_AsEWKT(Geometry geometry) {
+    return asEwkt(geometry);
   }
 
-  public static @Nullable String ST_AsWKT(Geometry g) {
-    return asWkt(g);
+  public static @Nullable String ST_AsGeoJSON(Geometry geometry) {
+    return asGeoJson(geometry);
   }
 
-  public static @Nullable Geometry ST_GeomFromText(String s) {
-    return fromWkt(s);
+  public static @Nullable String ST_AsGML(Geometry geometry) {
+    return asGml(geometry);
   }
 
-  public static @Nullable Geometry ST_GeomFromText(String s, int srid) {
-    final Geometry g = fromWkt(s);
-    g.setSRID(srid);
-    return g;
+  public static @Nullable String ST_AsText(Geometry geometry) {
+    return ST_AsWKT(geometry);
+  }
+
+  public static @Nullable ByteString ST_AsEWKB(Geometry geometry) {
+    return ST_AsWKB(geometry);
+  }
+
+  public static @Nullable ByteString ST_AsWKB(Geometry geometry) {
+    return asWkb(geometry);
+  }
+
+  public static @Nullable String ST_AsWKT(Geometry geometry) {
+    return asWkt(geometry);
   }
 
-  public static @Nullable Geometry ST_LineFromText(String s) {
-    return ST_GeomFromText(s, NO_SRID);
+  public static @Nullable Geometry ST_Force2D(Geometry geometry) {
+    Function<Coordinate, Coordinate> transform =
+        coordinate -> new Coordinate(coordinate.getX(), coordinate.getY());
+    CoordinateTransformer transformer = new CoordinateTransformer(transform);
+    return transformer.transform(geometry);
+  }
+
+  public static @Nullable Geometry ST_GeomFromEWKB(ByteString ewkb) {
+    return ST_GeomFromWKB(ewkb);
+  }
+
+  public static @Nullable Geometry ST_GeomFromEWKT(String ewkt) {
+    return fromEwkt(ewkt);
+  }
+
+  public static @Nullable Geometry ST_GeomFromGeoJSON(String geojson) {
+    return fromGeoJson(geojson);
+  }
+
+  public static @Nullable Geometry ST_GeomFromGML(String gml) {
+    return ST_GeomFromGML(gml, NO_SRID);
+  }
+
+  public static @Nullable Geometry ST_GeomFromGML(String gml, int srid) {
+    Geometry geometry = fromGml(gml);
+    geometry.setSRID(srid);
+    return geometry;
+  }
+
+  public static @Nullable Geometry ST_GeomFromText(String wkt) {
+    return ST_GeomFromWKT(wkt);
+  }
+
+  public static @Nullable Geometry ST_GeomFromText(String wkt, int srid) {
+    return ST_GeomFromWKT(wkt, srid);
+  }
+
+  public static @Nullable Geometry ST_GeomFromWKB(ByteString wkb) {
+    return fromWkb(wkb);
+  }
+
+  public static @Nullable Geometry ST_GeomFromWKB(ByteString wkb, int srid) {
+    Geometry geometry = fromWkb(wkb);
+    geometry.setSRID(srid);
+    return geometry;
+  }
+
+  public static @Nullable Geometry ST_GeomFromWKT(String wkt) {
+    return ST_GeomFromWKT(wkt, NO_SRID);
+  }
+
+  public static @Nullable Geometry ST_GeomFromWKT(String wkt, int srid) {
+    Geometry geometry = fromWkt(wkt);
+    geometry.setSRID(srid);
+    return geometry;
+  }
+
+  public static @Nullable Geometry ST_LineFromText(String wkt) {
+    Geometry geometry = ST_GeomFromWKT(wkt);
+    return geometry instanceof LineString ? geometry : null;
   }
 
   public static @Nullable Geometry ST_LineFromText(String wkt, int srid) {
-    final Geometry g = fromWkt(wkt);
-    g.setSRID(srid);
-    return g == null ? null : g;
+    Geometry geometry = ST_GeomFromWKT(wkt, srid);
+    return geometry instanceof LineString ? geometry : null;
+  }
+
+  public static @Nullable Geometry ST_LineFromWKB(ByteString wkb) {
+    Geometry geometry = ST_GeomFromWKB(wkb);
+    return geometry instanceof LineString ? geometry : null;
+  }
+
+  public static @Nullable Geometry ST_LineFromWKB(ByteString wkt, int srid) {
+    Geometry geometry = ST_GeomFromWKB(wkt, srid);
+    return geometry instanceof LineString ? geometry : null;
   }
 
-  public static @Nullable Geometry ST_MPointFromText(String s) {
-    return ST_GeomFromText(s, NO_SRID);
+  public static @Nullable Geometry ST_MLineFromText(String wkt) {
+    Geometry geometry = ST_GeomFromWKT(wkt);
+    return geometry instanceof MultiLineString ? geometry : null;
+  }
+
+  public static @Nullable Geometry ST_MLineFromText(String wkt, int srid) {
+    Geometry geometry = ST_GeomFromWKT(wkt, srid);
+    return geometry instanceof MultiLineString ? geometry : null;
+  }
+
+  public static @Nullable Geometry ST_MPointFromText(String wkt) {
+    Geometry geometry = ST_GeomFromWKT(wkt);
+    return geometry instanceof MultiPoint ? geometry : null;
   }
 
   public static @Nullable Geometry ST_MPointFromText(String wkt, int srid) {
-    final Geometry g = fromWkt(wkt);
-    g.setSRID(srid);
-    return g == null ? null : g;
+    Geometry geometry = ST_GeomFromWKT(wkt, srid);
+    return geometry instanceof MultiPoint ? geometry : null;
+  }
+
+  public static @Nullable Geometry ST_MPolyFromText(String wkt) {
+    Geometry geometry = ST_GeomFromWKT(wkt);
+    return geometry instanceof MultiPolygon ? geometry : null;
   }
 
-  public static @Nullable Geometry ST_PointFromText(String s) {
-    return ST_GeomFromText(s, NO_SRID);
+  public static @Nullable Geometry ST_MPolyFromText(String wkt, int srid) {
+    Geometry geometry = ST_GeomFromWKT(wkt, srid);
+    return geometry instanceof MultiPolygon ? geometry : null;
+  }
+
+  public static @Nullable Geometry ST_PointFromText(String wkt) {
+    Geometry geometry = ST_GeomFromWKT(wkt);
+    return geometry instanceof Point ? geometry : null;
   }
 
   public static @Nullable Geometry ST_PointFromText(String wkt, int srid) {
-    final Geometry g = fromWkt(wkt);
-    g.setSRID(srid);
-    return g == null ? null : g;
+    Geometry geometry = ST_GeomFromWKT(wkt, srid);
+    return geometry instanceof Point ? geometry : null;
+  }
+
+  public static @Nullable Geometry ST_PointFromWKB(ByteString wkb) {
+    Geometry geometry = ST_GeomFromWKB(wkb);
+    return geometry instanceof Point ? geometry : null;
+  }
+
+  public static @Nullable Geometry ST_PointFromWKB(ByteString wkb, int srid) {
+    Geometry geometry = ST_GeomFromWKB(wkb, srid);
+    return geometry instanceof Point ? geometry : null;
   }
 
-  public static @Nullable Geometry ST_PolyFromText(String s) {
-    return ST_GeomFromText(s, NO_SRID);
+  public static @Nullable Geometry ST_PolyFromText(String wkt) {
+    Geometry geometry = ST_GeomFromWKT(wkt);
+    return geometry instanceof Polygon ? geometry : null;
   }
 
   public static @Nullable Geometry ST_PolyFromText(String wkt, int srid) {
-    final Geometry g = fromWkt(wkt);
-    g.setSRID(srid);
-    return g == null ? null : g;
+    Geometry geometry = ST_GeomFromWKT(wkt, srid);
+    return geometry instanceof Polygon ? geometry : null;
   }
 
-  public static @Nullable Geometry ST_MLineFromText(String s) {
-    return ST_GeomFromText(s, NO_SRID);
+  public static @Nullable Geometry ST_PolyFromWKB(ByteString wkb) {
+    Geometry geometry = ST_GeomFromWKB(wkb);
+    return geometry instanceof Polygon ? geometry : null;
   }
 
-  public static @Nullable Geometry ST_MLineFromText(String wkt, int srid) {
-    final Geometry g = fromWkt(wkt);
-    g.setSRID(srid);
-    return g == null ? null : g;
+  public static @Nullable Geometry ST_PolyFromWKB(ByteString wkb, int srid) {
+    Geometry geometry = ST_GeomFromWKB(wkb, srid);
+    return geometry instanceof Polygon ? geometry : null;
   }
 
-  public static @Nullable Geometry ST_MPolyFromText(String s) {
-    return ST_GeomFromText(s, NO_SRID);
+  /**
+   * Converts the coordinates of a {@code geom} into a MULTIPOINT.
+   */
+  public static Geometry ST_ToMultiPoint(Geometry geom) {
+    CoordinateSequence coordinateSequence = GEOMETRY_FACTORY
+        .getCoordinateSequenceFactory().create(geom.getCoordinates());
+    return GEOMETRY_FACTORY.createMultiPoint(coordinateSequence);
   }
 
-  public static @Nullable Geometry ST_MPolyFromText(String wkt, int srid) {
-    final Geometry g = fromWkt(wkt);
-    g.setSRID(srid);
-    return g == null ? null : g;
+  /**
+   * Converts the a {@code geom} into a MULTILINESTRING.
+   */
+  public static Geometry ST_ToMultiLine(Geometry geom) {
+    GeometryFactory factory = geom.getFactory();
+    ArrayList<LineString> lines = new ArrayList<>();
+    geom.apply((GeometryComponentFilter) inputGeom -> {
+      if (inputGeom instanceof LineString) {
+        lines.add(factory.createLineString(inputGeom.getCoordinates()));
+      }
+    });
+    if (lines.isEmpty()) {
+      return factory.createMultiLineString();
+    } else {
+      return factory.createMultiLineString(lines.toArray(new 
LineString[lines.size()]));
+    }
+  }
+
+  /**
+   * Converts a {@code geom} into a set of distinct segments stored in a 
MULTILINESTRING.
+   */
+  public static Geometry ST_ToMultiSegments(Geometry geom) {
+    GeometryFactory factory = geom.getFactory();
+    ArrayList<LineString> lines = new ArrayList<>();
+    geom.apply((GeometryComponentFilter) inputGeom -> {
+      if (inputGeom instanceof LineString) {
+        Coordinate[] coordinates = inputGeom.getCoordinates();
+        for (int i = 1; i < coordinates.length; i++) {
+          Coordinate[] pair = new Coordinate[]{coordinates[i - 1], 
coordinates[i]};
+          lines.add(factory.createLineString(pair));
+        }
+      }
+    });
+    if (lines.isEmpty()) {
+      return factory.createMultiLineString();
+    } else {
+      return factory.createMultiLineString(lines.toArray(new 
LineString[lines.size()]));
+    }
+  }
+
+  // Geometry conversion functions (3D)
+
+  public static @Nullable Geometry ST_Force3D(Geometry geometry) {
+    Function<Coordinate, Coordinate> transform =
+        coordinate -> new Coordinate(
+            coordinate.getX(),
+            coordinate.getY(),
+            Double.isNaN(coordinate.getZ()) ? 0d : coordinate.getZ());
+    CoordinateTransformer transformer = new CoordinateTransformer(transform);
+    return transformer.transform(geometry);
   }
 
   // Geometry creation functions ==============================================
 
-  /** Calculates a regular grid of polygons based on {@code geom}. */
+  /**
+   * Calculates a regular grid of polygons based on {@code geom}.
+   */
   private static void ST_MakeGrid(final Geometry geom,
       final BigDecimal deltaX, final BigDecimal deltaY) {
     // This is a dummy function. We cannot include table functions in this
@@ -164,7 +360,9 @@ public class SpatialTypeFunctions {
     // in SqlSpatialTypeFunctions.
   }
 
-  /** Calculates a regular grid of points based on {@code geom}. */
+  /**
+   * Calculates a regular grid of points based on {@code geom}.
+   */
   private static void ST_MakeGridPoints(final Geometry geom,
       final BigDecimal deltaX, final BigDecimal deltaY) {
     // This is a dummy function. We cannot include table functions in this
@@ -172,7 +370,200 @@ public class SpatialTypeFunctions {
     // in SqlSpatialTypeFunctions.
   }
 
-  /** Creates a rectangular Polygon. */
+  // Geometry creation functions (2D)
+
+  /**
+   * Returns the minimum bounding circle of {@code geom}.
+   */
+  public static Geometry ST_BoundingCircle(Geometry geom) {
+    return new MinimumBoundingCircle(geom).getCircle();
+  }
+
+  /**
+   * Expands {@code geom}'s envelope.
+   */
+  public static Geometry ST_Expand(Geometry geom, BigDecimal distance) {
+    Envelope envelope = geom.getEnvelopeInternal().copy();
+    envelope.expandBy(distance.doubleValue());
+    return geom.getFactory().toGeometry(envelope);
+  }
+
+  /**
+   * Makes an ellipse.
+   */
+  public static @Nullable Geometry ST_MakeEllipse(Geometry point, BigDecimal 
width,
+      BigDecimal height) {
+    if (!(point instanceof Point)) {
+      return null;
+    }
+    GeometricShapeFactory factory = new 
GeometricShapeFactory(point.getFactory());
+    factory.setCentre(point.getCoordinate());
+    factory.setWidth(width.doubleValue());
+    factory.setHeight(height.doubleValue());
+    return factory.createEllipse();
+  }
+
+  /**
+   * Makes a polygon.
+   */
+  public static @Nullable Geometry ST_MakePolygon(Geometry shell) {
+    return makePolygon(shell);
+  }
+
+  /**
+   * Makes a polygon.
+   */
+  public static @Nullable Geometry ST_MakePolygon(Geometry shell, Geometry 
hole0) {
+    return makePolygon(shell, hole0);
+  }
+
+  /**
+   * Makes a polygon.
+   */
+  public static @Nullable Geometry ST_MakePolygon(Geometry shell,
+      Geometry hole0, Geometry hole1) {
+    return makePolygon(shell,
+        hole0, hole1);
+  }
+
+  /**
+   * Makes a polygon.
+   */
+  public static @Nullable Geometry ST_MakePolygon(Geometry shell,
+      Geometry hole0, Geometry hole1, Geometry hole2) {
+    return makePolygon(shell,
+        hole0, hole1, hole2);
+  }
+
+  /**
+   * Makes a polygon.
+   */
+  public static @Nullable Geometry ST_MakePolygon(Geometry shell,
+      Geometry hole0, Geometry hole1, Geometry hole2, Geometry hole3) {
+    return makePolygon(shell,
+        hole0, hole1, hole2, hole3);
+  }
+
+  /**
+   * Makes a polygon.
+   */
+  public static @Nullable Geometry ST_MakePolygon(Geometry shell,
+      Geometry hole0, Geometry hole1, Geometry hole2, Geometry hole3, Geometry 
hole4) {
+    return makePolygon(shell,
+        hole0, hole1, hole2, hole3, hole4);
+  }
+
+  /**
+   * Makes a polygon.
+   */
+  public static @Nullable Geometry ST_MakePolygon(Geometry shell,
+      Geometry hole0, Geometry hole1, Geometry hole2, Geometry hole3, Geometry 
hole4,
+      Geometry hole5) {
+    return makePolygon(shell,
+        hole0, hole1, hole2, hole3, hole4,
+        hole5);
+  }
+
+  /**
+   * Makes a polygon.
+   */
+  public static @Nullable Geometry ST_MakePolygon(Geometry shell,
+      Geometry hole0, Geometry hole1, Geometry hole2, Geometry hole3, Geometry 
hole4,
+      Geometry hole5, Geometry hole6) {
+    return makePolygon(shell,
+        hole0, hole1, hole2, hole3, hole4,
+        hole5, hole6);
+  }
+
+  /**
+   * Makes a polygon.
+   */
+  public static @Nullable Geometry ST_MakePolygon(Geometry shell,
+      Geometry hole0, Geometry hole1, Geometry hole2, Geometry hole3, Geometry 
hole4,
+      Geometry hole5, Geometry hole6, Geometry hole7) {
+    return makePolygon(shell,
+        hole0, hole1, hole2, hole3, hole4,
+        hole5, hole6, hole7);
+  }
+
+  /**
+   * Makes a polygon.
+   */
+  public static @Nullable Geometry ST_MakePolygon(Geometry shell,
+      Geometry hole0, Geometry hole1, Geometry hole2, Geometry hole3, Geometry 
hole4,
+      Geometry hole5, Geometry hole6, Geometry hole7, Geometry hole8) {
+    return makePolygon(shell,
+        hole0, hole1, hole2, hole3, hole4,
+        hole5, hole6, hole7, hole8);
+  }
+
+  /**
+   * Makes a polygon.
+   */
+  public static @Nullable Geometry ST_MakePolygon(Geometry shell,
+      Geometry hole0, Geometry hole1, Geometry hole2, Geometry hole3, Geometry 
hole4,
+      Geometry hole5, Geometry hole6, Geometry hole7, Geometry hole8, Geometry 
hole9) {
+    return makePolygon(shell,
+        hole0, hole1, hole2, hole3, hole4,
+        hole5, hole6, hole7, hole8, hole9);
+  }
+
+  private static @Nullable Geometry makePolygon(Geometry shell, Geometry... 
holes) {
+    if (!(shell instanceof LineString)) {
+      throw new RuntimeException("Only supports LINESTRINGs.");
+    }
+    if (!((LineString) shell).isClosed()) {
+      throw new RuntimeException("The LINESTRING must be closed.");
+    }
+    for (Geometry hole : holes) {
+      if (!(hole instanceof LineString)) {
+        throw new RuntimeException("Only supports LINESTRINGs.");
+      }
+      if (!((LineString) hole).isClosed()) {
+        throw new RuntimeException("The LINESTRING must be closed.");
+      }
+    }
+    LinearRing shellRing = 
shell.getFactory().createLinearRing(shell.getCoordinates());
+    LinearRing[] holeRings = new LinearRing[holes.length];
+    for (int i = 0; i < holes.length; i++) {
+      holeRings[i] = 
holes[i].getFactory().createLinearRing(holes[i].getCoordinates());
+    }
+    return shell.getFactory().createPolygon(shellRing, holeRings);
+  }
+
+  /**
+   * Returns the minimum diameter of {@code geom}.
+   */
+  public static @Nullable Geometry ST_MinimumDiameter(Geometry geom) {
+    return new MinimumDiameter(geom).getDiameter();
+  }
+
+  /**
+   * Returns the minimum rectangle enclosing {@code geom}.
+   */
+  public static @Nullable Geometry ST_MinimumRectangle(Geometry geom) {
+    return new MinimumDiameter(geom).getMinimumRectangle();
+  }
+
+  /**
+   * Returns the octagonal envelope of {@code geom}.
+   */
+  public static @Nullable Geometry ST_OctagonalEnvelope(Geometry geom) {
+    return new OctagonalEnvelope(geom).toGeometry(geom.getFactory());
+  }
+
+  /**
+   * Expands {@code geom}'s envelope.
+   */
+  public static Geometry ST_Expand(Geometry geom, BigDecimal deltaX, 
BigDecimal deltaY) {
+    Envelope envelope = geom.getEnvelopeInternal().copy();
+    envelope.expandBy(deltaX.doubleValue(), deltaY.doubleValue());
+    return geom.getFactory().toGeometry(envelope);
+  }
+
+  /**
+   * Creates a rectangular Polygon.
+   */
   public static Geometry ST_MakeEnvelope(BigDecimal xMin, BigDecimal yMin,
       BigDecimal xMax, BigDecimal yMax, int srid) {
     Geometry geom = ST_GeomFromText("POLYGON(("
@@ -184,16 +575,20 @@ public class SpatialTypeFunctions {
     return Objects.requireNonNull(geom, "geom");
   }
 
-  /** Creates a rectangular Polygon. */
+  /**
+   * Creates a rectangular Polygon.
+   */
   public static Geometry ST_MakeEnvelope(BigDecimal xMin, BigDecimal yMin,
       BigDecimal xMax, BigDecimal yMax) {
     return ST_MakeEnvelope(xMin, yMin, xMax, yMax, NO_SRID);
   }
 
-  /** Creates a line-string from the given POINTs (or MULTIPOINTs). */
+  /**
+   * Creates a line-string from the given POINTs (or MULTIPOINTs).
+   */
   @Hints({"SqlKind:ST_MAKE_LINE"})
   public static Geometry ST_MakeLine(Geometry geom1, Geometry geom2) {
-    return GEOMETRY_FACTORY.createLineString(new Coordinate[] {
+    return GEOMETRY_FACTORY.createLineString(new Coordinate[]{
         geom1.getCoordinate(),
         geom2.getCoordinate(),
     });
@@ -201,7 +596,7 @@ public class SpatialTypeFunctions {
 
   @Hints({"SqlKind:ST_MAKE_LINE"})
   public static Geometry ST_MakeLine(Geometry geom1, Geometry geom2, Geometry 
geom3) {
-    return GEOMETRY_FACTORY.createLineString(new Coordinate[] {
+    return GEOMETRY_FACTORY.createLineString(new Coordinate[]{
         geom1.getCoordinate(),
         geom2.getCoordinate(),
         geom3.getCoordinate(),
@@ -211,7 +606,7 @@ public class SpatialTypeFunctions {
   @Hints({"SqlKind:ST_MAKE_LINE"})
   public static Geometry ST_MakeLine(Geometry geom1, Geometry geom2, Geometry 
geom3,
       Geometry geom4) {
-    return GEOMETRY_FACTORY.createLineString(new Coordinate[] {
+    return GEOMETRY_FACTORY.createLineString(new Coordinate[]{
         geom1.getCoordinate(),
         geom2.getCoordinate(),
         geom3.getCoordinate(),
@@ -222,7 +617,7 @@ public class SpatialTypeFunctions {
   @Hints({"SqlKind:ST_MAKE_LINE"})
   public static Geometry ST_MakeLine(Geometry geom1, Geometry geom2, Geometry 
geom3,
       Geometry geom4, Geometry geom5) {
-    return GEOMETRY_FACTORY.createLineString(new Coordinate[] {
+    return GEOMETRY_FACTORY.createLineString(new Coordinate[]{
         geom1.getCoordinate(),
         geom2.getCoordinate(),
         geom3.getCoordinate(),
@@ -234,7 +629,7 @@ public class SpatialTypeFunctions {
   @Hints({"SqlKind:ST_MAKE_LINE"})
   public static Geometry ST_MakeLine(Geometry geom1, Geometry geom2, Geometry 
geom3,
       Geometry geom4, Geometry geom5, Geometry geom6) {
-    return GEOMETRY_FACTORY.createLineString(new Coordinate[] {
+    return GEOMETRY_FACTORY.createLineString(new Coordinate[]{
         geom1.getCoordinate(),
         geom2.getCoordinate(),
         geom3.getCoordinate(),
@@ -244,160 +639,455 @@ public class SpatialTypeFunctions {
     });
   }
 
-  /** Alias for {@link #ST_Point(BigDecimal, BigDecimal)}. */
+  /**
+   * Alias for {@link #ST_Point(BigDecimal, BigDecimal)}.
+   */
   @Hints({"SqlKind:ST_POINT"})
   public static Geometry ST_MakePoint(BigDecimal x, BigDecimal y) {
     return ST_Point(x, y);
   }
 
-  /** Alias for {@link #ST_Point(BigDecimal, BigDecimal, BigDecimal)}. */
+  /**
+   * Alias for {@link #ST_Point(BigDecimal, BigDecimal, BigDecimal)}.
+   */
   @Hints({"SqlKind:ST_POINT3"})
   public static Geometry ST_MakePoint(BigDecimal x, BigDecimal y, BigDecimal 
z) {
     return ST_Point(x, y, z);
   }
 
-  /** Constructs a 2D point from coordinates. */
+  /**
+   * Constructs a 2D point from coordinates.
+   */
   @Hints({"SqlKind:ST_POINT"})
   public static Geometry ST_Point(BigDecimal x, BigDecimal y) {
     // NOTE: Combine the double and BigDecimal variants of this function
     return GEOMETRY_FACTORY.createPoint(new Coordinate(x.doubleValue(), 
y.doubleValue()));
   }
 
-  /** Constructs a 3D point from coordinates. */
+  /**
+   * Constructs a 3D point from coordinates.
+   */
   @Hints({"SqlKind:ST_POINT3"})
   public static Geometry ST_Point(BigDecimal x, BigDecimal y, BigDecimal z) {
     final Geometry g = GEOMETRY_FACTORY.createPoint(
         new Coordinate(x.doubleValue(), y.doubleValue(),
-        z.doubleValue()));
+            z.doubleValue()));
     return g;
   }
 
-  // Geometry properties (2D and 3D) ==========================================
+  // Geometry properties (2D)
+
+  /**
+   * Returns the minimum bounding box that encloses geom as a Geometry.
+   */
+  public static @Nullable Geometry ST_Extent(Geometry geom) {
+    // Note: check whether the extent and the envelope are the same.
+    return geom.getEnvelope();
+  }
+
+  /**
+   * Returns the nth geometry of a geometry collection.
+   */
+  public static @Nullable Geometry ST_GeometryN(Geometry geom, int n) {
+    if (!(geom instanceof GeometryCollection)) {
+      return null;
+    }
+    return geom.getGeometryN(n);
+  }
+
+  /**
+   * Returns the exterior ring of {@code geom}, or null if {@code geom} is not 
a polygon.
+   */
+  public static @Nullable Geometry ST_ExteriorRing(Geometry geom) {
+    if (geom instanceof Polygon) {
+      return ((Polygon) geom).getExteriorRing();
+    }
+    return null;
+  }
 
-  /** Returns whether {@code geom} has at least one z-coordinate. */
+  /**
+   * Returns the first point of {@code geom}.
+   */
+  public static Geometry ST_EndPoint(Geometry geom) {
+    return ST_PointN(geom, -1);
+  }
+
+  /**
+   * Returns the nth interior ring of {@code geom}, or null if {@code geom} is 
not a polygon.
+   */
+  public static @Nullable Geometry ST_InteriorRing(Geometry geom, int n) {
+    if (geom instanceof Polygon) {
+      return ((Polygon) geom).getInteriorRingN(n);
+    }
+    return null;
+  }
+
+  /**
+   * Returns whether {@code geom} is a closed LINESTRING or MULTILINESTRING.
+   */
+  public static boolean ST_IsClosed(Geometry geom) {
+    if (geom instanceof LineString) {
+      return ((LineString) geom).isClosed();
+    }
+    if (geom instanceof MultiLineString) {
+      return ((MultiLineString) geom).isClosed();
+    }
+    return false;
+  }
+
+  /**
+   * Returns whether {@code geom} has at least one z-coordinate.
+   */
   public static boolean ST_Is3D(Geometry geom) {
-    for (Coordinate coordinate : geom.getCoordinates()) {
-      if (!Double.isNaN(coordinate.getZ())) {
-        return true;
-      }
+    return ST_CoordDim(geom) == 3;
+  }
+
+  /**
+   * Returns true if geom is empty.
+   */
+  public static boolean ST_IsEmpty(Geometry geom) {
+    return geom.isEmpty();
+  }
+
+  /**
+   * Returns true if geom is rectangle.
+   */
+  public static boolean ST_IsRectangle(Geometry geom) {
+    return geom.isRectangle();
+  }
+
+  /**
+   * Returns whether {@code geom} is a closed and simple linestring or 
multi-linestring.
+   */
+  public static boolean ST_IsRing(Geometry geom) {
+    if (geom instanceof LineString) {
+      return ((LineString) geom).isClosed() && geom.isSimple();
+    }
+    if (geom instanceof MultiLineString) {
+      return ((MultiLineString) geom).isClosed() && geom.isSimple();
     }
     return false;
   }
 
-  /** Returns the x-value of the first coordinate of {@code geom}. */
+  /**
+   * Returns true if geom is simple.
+   */
+  public static boolean ST_IsSimple(Geometry geom) {
+    return geom.isSimple();
+  }
+
+  /**
+   * Returns true if geom is valid.
+   */
+  public static boolean ST_IsValid(Geometry geom) {
+    return geom.isValid();
+  }
+
+  /**
+   * Returns the number of points in {@code geom}.
+   */
+  public static int ST_NPoints(Geometry geom) {
+    return ST_NumPoints(geom);
+  }
+
+  /**
+   * Returns the number of geometries in {@code geom} (1 if it is not a 
GEOMETRYCOLLECTION).
+   */
+  public static int ST_NumGeometries(Geometry geom) {
+    return geom.getNumGeometries();
+  }
+
+  /**
+   * Returns the number of interior rings of {@code geom}.
+   */
+  public static int ST_NumInteriorRing(Geometry geom) {
+    return ST_NumInteriorRings(geom);
+  }
+
+  /**
+   * Returns the number of interior rings of {@code geom}.
+   */
+  public static int ST_NumInteriorRings(Geometry geom) {
+    int[] num = new int[]{0};
+    geom.apply(new GeometryFilter() {
+      @Override public void filter(Geometry geom) {
+        if (geom instanceof Polygon) {
+          num[0] += ((Polygon) geom).getNumInteriorRing();
+        }
+      }
+    });
+    return num[0];
+  }
+
+  /**
+   * Returns the number of points in {@code geom}.
+   */
+  public static int ST_NumPoints(Geometry geom) {
+    return geom.getCoordinates().length;
+  }
+
+  /**
+   * Returns the nth point of a {@code geom}.
+   */
+  public static Geometry ST_PointN(Geometry geom, int n) {
+    Coordinate[] coordinates = geom.getCoordinates();
+    int i = (coordinates.length + (n % coordinates.length)) % 
coordinates.length;
+    return geom.getFactory().createPoint(coordinates[i]);
+  }
+
+  /**
+   * Returns an interior or boundary point of {@code geom}.
+   */
+  public static Geometry ST_PointOnSurface(Geometry geom) {
+    return geom.getFactory().createPoint(InteriorPoint.getInteriorPoint(geom));
+  }
+
+  /**
+   * Returns SRID value or 0 if input Geometry does not have one.
+   */
+  public static int ST_SRID(Geometry geom) {
+    return geom.getSRID();
+  }
+
+  /**
+   * Returns the first point of {@code geom}.
+   */
+  public static Geometry ST_StartPoint(Geometry geom) {
+    return ST_PointN(geom, 0);
+  }
+
+  /**
+   * Return the X coordinate of the point, or NULL if not available. Input 
must be a point..
+   */
   public static @Nullable Double ST_X(Geometry geom) {
     return geom instanceof Point ? ((Point) geom).getX() : null;
   }
 
-  /** Returns the y-value of the first coordinate of {@code geom}. */
+  /**
+   * Returns the X maxima of a 2D or 3D bounding box or a geometry.
+   */
+  public static @Nullable Double ST_XMax(Geometry geom) {
+    return geom.getEnvelopeInternal().getMaxX();
+  }
+
+  /**
+   * Returns the X minima of a 2D or 3D bounding box or a geometry.
+   */
+  public static @Nullable Double ST_XMin(Geometry geom) {
+    return geom.getEnvelopeInternal().getMinX();
+  }
+
+  /**
+   * Returns the y-value of the first coordinate of {@code geom}.
+   */
   public static @Nullable Double ST_Y(Geometry geom) {
     return geom instanceof Point ? ((Point) geom).getY() : null;
   }
 
-  /** Returns the z-value of the first coordinate of {@code geom}. */
-  public static @Nullable Double ST_Z(Geometry geom) {
-    return geom instanceof Point
-        && !Double.isNaN(geom.getCoordinate().getZ())
-        ? geom.getCoordinate().getZ() : null;
+  /**
+   * Returns the Y maxima of a 2D or 3D bounding box or a geometry.
+   */
+  public static @Nullable Double ST_YMax(Geometry geom) {
+    return geom.getEnvelopeInternal().getMaxY();
+  }
+
+  /**
+   * Returns the Y minima of a 2D or 3D bounding box or a geometry.
+   */
+  public static @Nullable Double ST_YMin(Geometry geom) {
+    return geom.getEnvelopeInternal().getMinY();
   }
 
-  /** Returns the boundary of {@code geom}. */
+  /**
+   * Returns the z-value of the first coordinate of {@code geom}.
+   */
+  public static Double ST_Z(Geometry geom) {
+    if (geom.getCoordinate() != null) {
+      return geom.getCoordinate().getZ();
+    } else {
+      return Double.NaN;
+    }
+
+  }
+
+  /**
+   * Returns the maximum z-value of {@code geom}.
+   */
+  public static Double ST_ZMax(Geometry geom) {
+    return Arrays.stream(geom.getCoordinates())
+        .filter(c -> !Double.isNaN(c.getZ()))
+        .map(c -> c.getZ())
+        .max(Double::compareTo)
+        .orElse(Double.NaN);
+  }
+
+  /**
+   * Returns the minimum z-value of {@code geom}.
+   */
+  public static Double ST_ZMin(Geometry geom) {
+    return Arrays.stream(geom.getCoordinates())
+        .filter(c -> !Double.isNaN(c.getZ()))
+        .map(c -> c.getZ())
+        .min(Double::compareTo)
+        .orElse(Double.NaN);
+  }
+
+  // Geometry properties (2D)
+
+  /**
+   * Returns the boundary of {@code geom}.
+   */
   public static Geometry ST_Boundary(Geometry geom) {
     return geom.getBoundary();
   }
 
-  /** Returns the distance between {@code geom1} and {@code geom2}. */
+  /**
+   * Returns the centroid of {@code geom}.
+   */
+  public static Geometry ST_Centroid(Geometry geom) {
+    return geom.getCentroid();
+  }
+
+  /**
+   * Returns the dimension of the coordinates of {@code geom}.
+   */
+  public static int ST_CoordDim(Geometry geom) {
+    Coordinate coordinate = geom.getCoordinate();
+    if (coordinate != null && !Double.isNaN(coordinate.getZ())) {
+      return 3;
+    }
+    return 2;
+  }
+
+  /**
+   * Returns the dimension of {@code geom}.
+   */
+  public static int ST_Dimension(Geometry geom) {
+    return geom.getDimension();
+  }
+
+  /**
+   * Returns the distance between {@code geom1} and {@code geom2}.
+   */
   public static double ST_Distance(Geometry geom1, Geometry geom2) {
     return geom1.distance(geom2);
   }
 
-  /** Returns the type of {@code geom}. */
+  /**
+   * Returns the type of {@code geom}.
+   */
   public static String ST_GeometryType(Geometry geom) {
     return SpatialType.fromGeometry(geom).name();
   }
 
-  /** Returns the OGC SFS type code of {@code geom}. */
+  /**
+   * Returns the OGC SFS type code of {@code geom}.
+   */
   public static int ST_GeometryTypeCode(Geometry geom) {
     return SpatialType.fromGeometry(geom).code();
   }
 
-  /** Returns the minimum bounding box of {@code geom} (which may be a
-   *  GEOMETRYCOLLECTION). */
+  /**
+   * Returns the minimum bounding box of {@code geom} (which may be a 
GEOMETRYCOLLECTION).
+   */
   public static Geometry ST_Envelope(Geometry geom) {
     return geom.getEnvelope();
   }
 
   // Geometry predicates ======================================================
 
-  /** Returns whether {@code geom1} contains {@code geom2}. */
+  /**
+   * Returns whether {@code geom1} contains {@code geom2}.
+   */
   @Hints({"SqlKind:ST_CONTAINS"})
   public static boolean ST_Contains(Geometry geom1, Geometry geom2) {
     return geom1.contains(geom2);
   }
 
-  /** Returns whether {@code geom1} contains {@code geom2} but does not
-   * intersect its boundary. */
+  /**
+   * Returns whether {@code geom1} contains {@code geom2} but does not 
intersect its boundary.
+   */
   public static boolean ST_ContainsProperly(Geometry geom1, Geometry geom2) {
     return geom1.contains(geom2)
         && !geom1.crosses(geom2);
   }
 
-  /** Returns whether no point in {@code geom2} is outside {@code geom1}. */
-  private static boolean ST_Covers(Geometry geom1, Geometry geom2)  {
+  /**
+   * Returns whether no point in {@code geom2} is outside {@code geom1}.
+   */
+  public static boolean ST_Covers(Geometry geom1, Geometry geom2) {
     return geom1.covers(geom2);
   }
 
-  /** Returns whether {@code geom1} crosses {@code geom2}. */
-  public static boolean ST_Crosses(Geometry geom1, Geometry geom2)  {
+  /**
+   * Returns whether {@code geom1} crosses {@code geom2}.
+   */
+  public static boolean ST_Crosses(Geometry geom1, Geometry geom2) {
     return geom1.crosses(geom2);
   }
 
-  /** Returns whether {@code geom1} and {@code geom2} are disjoint. */
-  public static boolean ST_Disjoint(Geometry geom1, Geometry geom2)  {
+  /**
+   * Returns whether {@code geom1} and {@code geom2} are disjoint.
+   */
+  public static boolean ST_Disjoint(Geometry geom1, Geometry geom2) {
     return geom1.disjoint(geom2);
   }
 
-  /** Returns whether the envelope of {@code geom1} intersects the envelope of
-   *  {@code geom2}. */
-  public static boolean ST_EnvelopesIntersect(Geometry geom1, Geometry geom2)  
{
+  /**
+   * Returns whether the envelope of {@code geom1} intersects the envelope of 
{@code geom2}.
+   */
+  public static boolean ST_EnvelopesIntersect(Geometry geom1, Geometry geom2) {
     final Geometry e1 = geom1.getEnvelope();
     final Geometry e2 = geom2.getEnvelope();
     return e1.intersects(e2);
   }
 
-  /** Returns whether {@code geom1} equals {@code geom2}. */
-  public static boolean ST_Equals(Geometry geom1, Geometry geom2)  {
+  /**
+   * Returns whether {@code geom1} equals {@code geom2}.
+   */
+  public static boolean ST_Equals(Geometry geom1, Geometry geom2) {
     return geom1.equals(geom2);
   }
 
-  /** Returns whether {@code geom1} intersects {@code geom2}. */
-  public static boolean ST_Intersects(Geometry geom1, Geometry geom2)  {
+  /**
+   * Returns whether {@code geom1} intersects {@code geom2}.
+   */
+  public static boolean ST_Intersects(Geometry geom1, Geometry geom2) {
     return geom1.intersects(geom2);
   }
 
-  /** Returns whether {@code geom1} equals {@code geom2} and their coordinates
-   * and component Geometries are listed in the same order. */
-  public static boolean ST_OrderingEquals(Geometry geom1, Geometry geom2)  {
+  /**
+   * Returns whether {@code geom1} equals {@code geom2} and their coordinates 
and component
+   * Geometries are listed in the same order.
+   */
+  public static boolean ST_OrderingEquals(Geometry geom1, Geometry geom2) {
     return geom1.equals(geom2);
   }
 
-  /** Returns {@code geom1} overlaps {@code geom2}. */
-  public static boolean ST_Overlaps(Geometry geom1, Geometry geom2)  {
+  /**
+   * Returns {@code geom1} overlaps {@code geom2}.
+   */
+  public static boolean ST_Overlaps(Geometry geom1, Geometry geom2) {
     return geom1.overlaps(geom2);
   }
 
-  /** Returns whether {@code geom1} touches {@code geom2}. */
-  public static boolean ST_Touches(Geometry geom1, Geometry geom2)  {
+  /**
+   * Returns whether {@code geom1} touches {@code geom2}.
+   */
+  public static boolean ST_Touches(Geometry geom1, Geometry geom2) {
     return geom1.touches(geom2);
   }
 
-  /** Returns whether {@code geom1} is within {@code geom2}. */
-  public static boolean ST_Within(Geometry geom1, Geometry geom2)  {
+  /**
+   * Returns whether {@code geom1} is within {@code geom2}.
+   */
+  public static boolean ST_Within(Geometry geom1, Geometry geom2) {
     return geom1.within(geom2);
   }
 
-  /** Returns whether {@code geom1} and {@code geom2} are within
-   * {@code distance} of each other. */
+  /**
+   * Returns whether {@code geom1} and {@code geom2} are within {@code 
distance} of each other.
+   */
   @Hints({"SqlKind:ST_DWITHIN"})
   public static boolean ST_DWithin(Geometry geom1, Geometry geom2, double 
distance) {
     final double distance1 = geom1.distance(geom2);
@@ -406,51 +1096,227 @@ public class SpatialTypeFunctions {
 
   // Geometry operators (2D and 3D) ===========================================
 
-  /** Computes a buffer around {@code geom}. */
+  /**
+   * Computes a buffer around {@code geom}.
+   */
   public static Geometry ST_Buffer(Geometry geom, double distance) {
     return geom.buffer(distance);
   }
 
-  /** Computes a buffer around {@code geom}. */
+  /**
+   * Computes a buffer around {@code geom}.
+   */
   public static Geometry ST_Buffer(Geometry geom, double distance, int 
quadSegs) {
     return geom.buffer(distance, quadSegs);
   }
 
-  /** Computes a buffer around {@code geom}. */
+  /**
+   * Computes a buffer around {@code geom}.
+   */
   public static Geometry ST_Buffer(Geometry geom, double distance, int 
quadSegs, int endCapStyle) {
     return geom.buffer(distance, quadSegs, endCapStyle);
   }
 
-  /** Computes the union of {@code geom1} and {@code geom2}. */
+  /**
+   * Computes the smallest convex POLYGON that contains all the points of geom.
+   */
+  public static Geometry ST_ConvexHull(Geometry geom) {
+    return geom.convexHull();
+  }
+
+  /**
+   * Computes the difference between geom1 and geom2.
+   */
+  public static Geometry ST_Difference(Geometry geom1, Geometry geom2) {
+    return geom1.difference(geom2);
+  }
+
+  /**
+   * Computes the symmetric difference between geom1 and geom2.
+   */
+  public static Geometry ST_SymDifference(Geometry geom1, Geometry geom2) {
+    return geom1.symDifference(geom2);
+  }
+
+  /**
+   * Computes the intersection between geom1 and geom2.
+   */
+  public static Geometry ST_Intersection(Geometry geom1, Geometry geom2) {
+    return geom1.intersection(geom2);
+  }
+
+  /**
+   * Returns the DE-9IM intersection matrix for geom1 and geom2.
+   */
+  public static String ST_Relate(Geometry geom1, Geometry geom2) {
+    return geom1.relate(geom2).toString();
+  }
+
+  /**
+   * Returns true if geom1 and geom2 are related by the intersection matrix 
specified by iMatrix.
+   */
+  public static boolean ST_Relate(Geometry geom1, Geometry geom2, String 
iMatrix) {
+    return geom1.relate(geom2, iMatrix);
+  }
+
+  /**
+   * Computes the union of {@code geom1} and {@code geom2}.
+   */
   public static Geometry ST_Union(Geometry geom1, Geometry geom2) {
     return geom1.union(geom2);
   }
 
-  /** Computes the union of the geometries in {@code geomCollection}. */
+  /**
+   * Computes the union of the geometries in {@code geomCollection}.
+   */
   @SemiStrict public static Geometry ST_Union(Geometry geomCollection) {
     return geomCollection.union();
   }
 
   // Geometry projection functions ============================================
 
-  /** Transforms {@code geom} from one coordinate reference
-   * system (CRS) to the CRS specified by {@code srid}. */
+  /**
+   * Transforms {@code geom} from one coordinate reference system (CRS) to the 
CRS specified by
+   * {@code srid}.
+   */
   public static Geometry ST_Transform(Geometry geom, int srid) {
     ProjectionTransformer projectionTransformer =
         new ProjectionTransformer(geom.getSRID(), srid);
     return projectionTransformer.transform(geom);
   }
 
-  /** Returns a copy of {@code geom} with a new SRID. */
+  /**
+   * Returns a copy of {@code geom} with a new SRID.
+   */
   public static Geometry ST_SetSRID(Geometry geom, int srid) {
     geom.setSRID(srid);
     return geom;
   }
 
+  // Process Geometries
+
+  /**
+   * Merges a collection of linear components to form a line-string of maximal 
length.
+   */
+  public static Geometry ST_LineMerge(Geometry geom) {
+    LineMerger merger = new LineMerger();
+    merger.add(geom);
+    LineString[] geometries = ((Stream<Object>) 
merger.getMergedLineStrings().stream())
+        .map(LineString.class::cast)
+        .toArray(size -> new LineString[size]);
+    return GEOMETRY_FACTORY.createMultiLineString(geometries);
+  }
+
+  /**
+   * Makes a valid geometry of a given invalid geometry.
+   */
+  public static Geometry ST_MakeValid(Geometry geometry) {
+    return new GeometryFixer(geometry).getResult();
+  }
+
+  /**
+   * Creates a multipolygon from the geometry.
+   */
+  public static Geometry ST_Polygonize(Geometry geometry) {
+    Polygonizer polygonizer = new Polygonizer(true);
+    polygonizer.add(geometry);
+    return polygonizer.getGeometry();
+  }
+
+  /**
+   * Reduces the geometry's precision to n decimal places.
+   */
+  public static Geometry ST_PrecisionReducer(Geometry geometry, BigDecimal 
decimal) {
+    double scale = Math.pow(10, decimal.doubleValue());
+    PrecisionModel precisionModel = new PrecisionModel(scale);
+    GeometryPrecisionReducer precisionReducer = new 
GeometryPrecisionReducer(precisionModel);
+    return precisionReducer.reduce(geometry);
+  }
+
+  /**
+   * Simplifies geom a geometry using the Douglas-Peuker algorithm.
+   */
+  public static Geometry ST_Simplify(Geometry geom, BigDecimal distance) {
+    DouglasPeuckerSimplifier simplifier = new DouglasPeuckerSimplifier(geom);
+    simplifier.setDistanceTolerance(distance.doubleValue());
+    return simplifier.getResultGeometry();
+  }
+
+  /**
+   * Simplifies a geometry and preserves its topology.
+   */
+  public static Geometry ST_SimplifyPreserveTopology(Geometry geom, BigDecimal 
distance) {
+    TopologyPreservingSimplifier simplifier = new 
TopologyPreservingSimplifier(geom);
+    simplifier.setDistanceTolerance(distance.doubleValue());
+    return simplifier.getResultGeometry();
+  }
+
+  /**
+   * Snaps geom1 and geom2 together with the given snapTolerance.
+   */
+  public static Geometry ST_Snap(Geometry geom1, Geometry geom2, BigDecimal 
snapTolerance) {
+    GeometrySnapper snapper = new GeometrySnapper(geom1);
+    return snapper.snapTo(geom2, snapTolerance.doubleValue());
+  }
+
+  // Affine transformation functions (3D and 2D)
+
+  /**
+   * Rotates geom counter-clockwise by angle (in radians) about the point 
origin.
+   */
+  public static Geometry ST_Rotate(Geometry geom, BigDecimal angle) {
+    AffineTransformation transformation = new AffineTransformation();
+    transformation.rotate(angle.doubleValue());
+    return transformation.transform(geom);
+  }
+
+  /**
+   * Rotates geom counter-clockwise by angle (in radians) about the point 
origin.
+   */
+  public static Geometry ST_Rotate(Geometry geom, BigDecimal angle, Geometry 
origin) {
+    // Note: check whether we can add support for the Point type.
+    if (!(origin instanceof Point)) {
+      throw new RuntimeException("The origin must be a point");
+    }
+    Point point = (Point) origin;
+    AffineTransformation transformation = new AffineTransformation();
+    transformation.rotate(angle.doubleValue(), point.getX(), point.getY());
+    return transformation.transform(geom);
+  }
+
+  /**
+   * Rotates geom counter-clockwise by angle (in radians) about the point 
origin.
+   */
+  public static Geometry ST_Rotate(Geometry geom, BigDecimal angle, BigDecimal 
x, BigDecimal y) {
+    AffineTransformation transformation = new AffineTransformation();
+    transformation.rotate(angle.doubleValue(), x.doubleValue(), 
y.doubleValue());
+    return transformation.transform(geom);
+  }
+
+  /**
+   * Scales geom Geometry by multiplying the ordinates by the indicated scale 
factors.
+   */
+  public static Geometry ST_Scale(Geometry geom, BigDecimal xFactor, 
BigDecimal yFactor) {
+    AffineTransformation transformation = new AffineTransformation();
+    transformation.scale(xFactor.doubleValue(), yFactor.doubleValue());
+    return transformation.transform(geom);
+  }
+
+  /**
+   * Translates geom by the vector (x, y).
+   */
+  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);
+  }
+
   // Space-filling curves
 
-  /** Returns the position of a point on the Hilbert curve,
-   * or null if it is not a 2-dimensional point. */
+  /**
+   * Returns the position of a point on the Hilbert curve, or null if it is 
not a 2-dimensional
+   * point.
+   */
   @Hints({"SqlKind:HILBERT"})
   public static @Nullable Long hilbert(Geometry geom) {
     if (geom instanceof Point) {
@@ -461,16 +1327,21 @@ public class SpatialTypeFunctions {
     return null;
   }
 
-  /** Returns the position of a point on the Hilbert curve. */
+  /**
+   * Returns the position of a point on the Hilbert curve.
+   */
   @Hints({"SqlKind:HILBERT"})
   public static long hilbert(BigDecimal x, BigDecimal y) {
     return new HilbertCurve2D(8).toIndex(x.doubleValue(), y.doubleValue());
   }
 
+
   // Inner classes ============================================================
 
-  /** Used at run time by the {@link #ST_MakeGrid} and
-   * {@link #ST_MakeGridPoints} functions. */
+
+  /**
+   * Used at run time by the {@link #ST_MakeGrid} and {@link 
#ST_MakeGridPoints} functions.
+   */
   public static class GridEnumerable extends AbstractEnumerable<Object[]> {
     private final Envelope envelope;
     private final boolean point;
@@ -520,7 +1391,7 @@ public class SpatialTypeFunctions {
             final double bottom = minY + y * deltaY;
             final double top = bottom + deltaY;
 
-            Coordinate[] coordinates = new Coordinate[] {
+            Coordinate[] coordinates = new Coordinate[]{
                 new Coordinate(left, bottom),
                 new Coordinate(left, top),
                 new Coordinate(right, top),
@@ -533,7 +1404,7 @@ public class SpatialTypeFunctions {
 
             geom = polygon;
           }
-          return new Object[] {geom, id, x + 1, y + 1, baseX + x, baseY + y};
+          return new Object[]{geom, id, x + 1, y + 1, baseX + x, baseY + y};
         }
 
         @Override public boolean moveNext() {
diff --git 
a/core/src/main/java/org/apache/calcite/runtime/SpatialTypeUtils.java 
b/core/src/main/java/org/apache/calcite/runtime/SpatialTypeUtils.java
index 7828083203..5a5dba34cf 100644
--- a/core/src/main/java/org/apache/calcite/runtime/SpatialTypeUtils.java
+++ b/core/src/main/java/org/apache/calcite/runtime/SpatialTypeUtils.java
@@ -16,6 +16,7 @@
  */
 package org.apache.calcite.runtime;
 
+import org.apache.calcite.avatica.util.ByteString;
 import org.apache.calcite.linq4j.function.Deterministic;
 import org.apache.calcite.linq4j.function.Experimental;
 import org.apache.calcite.linq4j.function.Strict;
@@ -30,9 +31,14 @@ import org.locationtech.jts.io.WKTReader;
 import org.locationtech.jts.io.WKTWriter;
 import org.locationtech.jts.io.geojson.GeoJsonReader;
 import org.locationtech.jts.io.geojson.GeoJsonWriter;
+import org.locationtech.jts.io.gml2.GMLReader;
+import org.locationtech.jts.io.gml2.GMLWriter;
+import org.xml.sax.SAXException;
 
+import java.io.IOException;
 import java.util.Locale;
 import java.util.regex.Pattern;
+import javax.xml.parsers.ParserConfigurationException;
 
 /**
  * Utilities for spatial types.
@@ -119,16 +125,31 @@ public class SpatialTypeUtils {
     }
   }
 
+  /**
+   * Constructs a geometry from a GML representation.
+   *
+   * @param gml a GML
+   * @return a geometry
+   */
+  public static Geometry fromGml(String gml) {
+    try {
+      GMLReader reader = new GMLReader();
+      return reader.read(gml, GEOMETRY_FACTORY);
+    } catch (SAXException | IOException | ParserConfigurationException e) {
+      throw new RuntimeException("Unable to parse GML");
+    }
+  }
+
   /**
    * Constructs a geometry from a Well-Known binary (WKB) representation.
    *
    * @param wkb a WKB
    * @return a geometry
    */
-  public static Geometry fromWkb(byte[] wkb) {
+  public static Geometry fromWkb(ByteString wkb) {
     try {
       WKBReader reader = new WKBReader();
-      return reader.read(wkb);
+      return reader.read(wkb.getBytes());
     } catch (ParseException e) {
       throw new RuntimeException("Unable to parse WKB");
     }
@@ -188,16 +209,31 @@ public class SpatialTypeUtils {
     return geoJsonWriter.write(geometry);
   }
 
+  /**
+   * Returns the GML representation of the geometry.
+   *
+   * @param geometry a geometry
+   * @return a GML
+   */
+  public static String asGml(Geometry geometry) {
+    GMLWriter gmlWriter = new GMLWriter();
+    // remove line breaks and indentation
+    String minified = gmlWriter.write(geometry)
+        .replace("\n", "")
+        .replace("  ", "");
+    return minified;
+  }
+
   /**
    * Returns the Extended Well-Known binary (WKB) representation of the 
geometry.
    *
    * @param geometry a geometry
    * @return an WKB
    */
-  public static byte[] asWkb(Geometry geometry) {
+  public static ByteString asWkb(Geometry geometry) {
     int outputDimension = dimension(geometry);
     WKBWriter wkbWriter = new WKBWriter(outputDimension);
-    return wkbWriter.write(geometry);
+    return new ByteString(wkbWriter.write(geometry));
   }
 
   /**
diff --git a/core/src/test/resources/sql/spatial.iq 
b/core/src/test/resources/sql/spatial.iq
index dfafb93e2c..892980e228 100644
--- a/core/src/test/resources/sql/spatial.iq
+++ b/core/src/test/resources/sql/spatial.iq
@@ -36,11 +36,29 @@ C
 
 #### Geometry conversion functions (2D)
 
-# ST_AsBinary(geom) Geometry to Well Known Binary
-# Not implemented
+# ST_AsBinary(geom) Geometry to WKB
+SELECT ST_AsBinary(ST_GeomFromText('LINESTRING (1 2, 3 4)'));
+EXPR$0
+0000000002000000023ff0000000000000400000000000000040080000000000004010000000000000
+!ok
+
+# ST_AsEWKT(geom) Geometry to EWKT
+SELECT ST_AsEWKT(ST_GeomFromText('LINESTRING (1 2, 3 4)'));
+EXPR$0
+srid:0;LINESTRING (1 2, 3 4)
+!ok
+
+# ST_AsGeoJSON(geom) Geometry to GeoJSON
+SELECT ST_AsGeoJSON(ST_GeomFromText('LINESTRING (1 2, 3 4)'));
+EXPR$0
+{"type":"LineString","coordinates":[[1,2],[3,4]],"crs":{"type":"name","properties":{"name":"EPSG:0"}}}
+!ok
 
 # ST_AsGML(geom) Geometry to GML
-# Not implemented
+SELECT ST_AsGML(ST_GeomFromText('LINESTRING (1 2, 3 4)'));
+EXPR$0
+<gml:LineString><gml:coordinates>1.0,2.0 3.0,4.0 
</gml:coordinates></gml:LineString>
+!ok
 
 # ST_AsText(geom) Alias for `ST_AsWKT`
 SELECT ST_AsText(ST_GeomFromText('POINT(-71.064544 42.28787)'));
@@ -49,12 +67,17 @@ POINT (-71.064544 42.28787)
 !ok
 
 # ST_AsWKT(geom) Converts *geom* → Well-Known Text
-
 SELECT ST_AsWKT(ST_GeomFromText('POINT(-71.064544 42.28787)'));
 EXPR$0
 POINT (-71.064544 42.28787)
 !ok
 
+# ST_AsEWKB(geom) Geometry to WKB
+SELECT ST_AsWKB(ST_GeomFromText('LINESTRING (1 2, 3 4)'));
+EXPR$0
+0000000002000000023ff0000000000000400000000000000040080000000000004010000000000000
+!ok
+
 # PostGIS can implicitly assign from CHAR to GEOMETRY; we can't
 !if (false) {
 # ST_AsWKT(geom) Geometry to Well Known Text
@@ -71,13 +94,47 @@ null
 !ok
 
 # ST_Force2D(geom) 3D Geometry to 2D Geometry
-# Not implemented
 
-# ST_GeomFromGML(gml [, srid ]) GML to Geometry
-# Not implemented
+SELECT ST_AsText(ST_Force2D(ST_GeomFromText('POINT(-10 10)')));
+EXPR$0
+POINT (-10 10)
+!ok
 
-# ST_GeomFromText(wkt [, srid ]) Returns a specified geometry value from 
Well-Known Text representation
+SELECT ST_AsText(ST_Force2D(ST_GeomFromText('POINT(-10 10 6)')));
+EXPR$0
+POINT (-10 10)
+!ok
+
+SELECT ST_AsText(ST_Force2D(ST_GeomFromText('LINESTRING(-10 10 2, 10 10 3)')));
+EXPR$0
+LINESTRING (-10 10, 10 10)
+!ok
 
+# ST_GeomFromEWKT(ewkt) EWKT to Geometry
+SELECT ST_AsEWKT(ST_GeomFromEWKT('srid:4326;LINESTRING (1 2, 3 4)'));
+EXPR$0
+srid:4326;LINESTRING (1 2, 3 4)
+!ok
+
+# ST_GeomFromGeoJSON(geoJSON) GeoJSON to Geometry
+SELECT 
ST_GeomFromGeoJSON('{"type":"LineString","coordinates":[[1,2],[3,4]],"crs":{"type":"name","properties":{"name":"EPSG:0"}}}');
+EXPR$0
+LINESTRING (1 2, 3 4)
+!ok
+
+# ST_GeomFromGML(gml) GML to Geometry
+SELECT ST_GeomFromGML('<gml:LineString><gml:coordinates>1.0,2.0 
3.0,4.0</gml:coordinates></gml:LineString>');
+EXPR$0
+LINESTRING (1 2, 3 4)
+!ok
+
+# ST_GeomFromGML(gml, srid) GML to Geometry
+SELECT ST_AsEWKT(ST_GeomFromGML('<gml:LineString><gml:coordinates>1.0,2.0 
3.0,4.0</gml:coordinates></gml:LineString>', 4326));
+EXPR$0
+srid:4326;LINESTRING (1 2, 3 4)
+!ok
+
+# ST_GeomFromText(wkt [, srid ]) Returns a specified geometry value from 
Well-Known Text representation
 SELECT ST_GeomFromText('LINESTRING(-71.160281 42.258729,-71.160837 
42.259113,-71.161144 42.25932)');
 EXPR$0
 LINESTRING (-71.160281 42.258729, -71.160837 42.259113, -71.161144 42.25932)
@@ -155,8 +212,17 @@ EXPR$0
 GEOMETRYCOLLECTION EMPTY
 !ok
 
-# ST_GeomFromWKB(wkb [, srid ]) Well Known Binary to Geometry
-# Not implemented
+# ST_GeomFromWKB(wkb) WKB to Geometry
+SELECT ST_GeomFromWKB(ST_AsWKB(ST_GeomFromText('LINESTRING (1 2, 3 4)')));
+EXPR$0
+LINESTRING (1 2, 3 4)
+!ok
+
+# ST_GeomFromWKB(wkb, srid) WKB to Geometry
+SELECT ST_AsEWKT(ST_GeomFromWKB(ST_AsWKB(ST_GeomFromText('LINESTRING (1 2, 3 
4)')), 4326));
+EXPR$0
+srid:4326;LINESTRING (1 2, 3 4)
+!ok
 
 # ST_GoogleMapLink(geom [, layerType [, zoom ]]) Geometry to Google map link
 # Not implemented
@@ -165,7 +231,7 @@ GEOMETRYCOLLECTION EMPTY
 SELECT ST_LineFromText('LINESTRING(1 2, 3 4)') AS aline,
   ST_LineFromText('POINT(1 2)') AS null_return;
 ALINE, NULL_RETURN
-LINESTRING (1 2, 3 4), POINT (1 2)
+LINESTRING (1 2, 3 4), null
 !ok
 
 # ST_LineFromWKB(wkb [, srid ]) Well Known Binary to LINESTRING
@@ -237,36 +303,118 @@ POLYGON ((0 0, 0 1, 1 1, 0 0))
 # Not implemented
 
 # ST_ToMultiLine(geom) Converts the coordinates of *geom* (which may be a 
geometry-collection) into a multi-line-string
-# Not implemented
+
+SELECT ST_ToMultiLine(ST_GeomFromText('POLYGON((0 0, 10 0, 10 6, 0 6, 0 0), (1 
1, 2 1, 2 5, 1 5, 1 1))'));
+EXPR$0
+MULTILINESTRING ((0 0, 10 0, 10 6, 0 6, 0 0), (1 1, 2 1, 2 5, 1 5, 1 1))
+!ok
+
+SELECT ST_ToMultiLine(ST_GeomFromText('GEOMETRYCOLLECTION(LINESTRING(1 4 3, 10 
7 9, 12 9 22), POLYGON((1 1 -1, 3 1 0, 3 2 1, 1 2 2, 1 1 -1)))'));
+EXPR$0
+MULTILINESTRING ((1 4, 10 7, 12 9), (1 1, 3 1, 3 2, 1 2, 1 1))
+!ok
 
 # ST_ToMultiPoint(geom)) Converts the coordinates of *geom* (which may be a 
geometry-collection) into a multi-point
-# Not implemented
+
+SELECT ST_ToMultiPoint(ST_GeomFromText('POINT(5 5)'));
+EXPR$0
+MULTIPOINT ((5 5))
+!ok
+
+SELECT ST_ToMultiPoint(ST_GeomFromText('MULTIPOINT(5 5, 1 2, 3 4, 99 3)'));
+EXPR$0
+MULTIPOINT ((5 5), (1 2), (3 4), (99 3))
+!ok
+
+SELECT ST_ToMultiPoint(ST_GeomFromText('LINESTRING(5 5, 1 2, 3 4, 1 5)'));
+EXPR$0
+MULTIPOINT ((5 5), (1 2), (3 4), (1 5))
+!ok
 
 # ST_ToMultiSegments(geom) Converts *geom* (which may be a 
geometry-collection) into a set of distinct segments stored in a 
multi-line-string
-# Not implemented
+
+SELECT ST_ToMultiSegments(ST_GeomFromText('LINESTRING(5 4, 1 1, 3 4, 4 5)'));
+EXPR$0
+MULTILINESTRING ((5 4, 1 1), (1 1, 3 4), (3 4, 4 5))
+!ok
+
+SELECT ST_ToMultiSegments(ST_GeomFromText('MULTILINESTRING((1 4 3, 15 7 9, 16 
17 22), (0 0 0, 1 0 0, 1 2 0, 0 2 1))'));
+EXPR$0
+MULTILINESTRING ((1 4, 15 7), (15 7, 16 17), (0 0, 1 0), (1 0, 1 2), (1 2, 0 
2))
+!ok
+
+SELECT ST_ToMultiSegments(ST_GeomFromText('POLYGON((0 0, 10 0, 10 6, 0 6, 0 
0), (1 1, 2 1, 2 5, 1 5, 1 1))'));
+EXPR$0
+MULTILINESTRING ((0 0, 10 0), (10 0, 10 6), (10 6, 0 6), (0 6, 0 0), (1 1, 2 
1), (2 1, 2 5), (2 5, 1 5), (1 5, 1 1))
+!ok
 
 #### Geometry conversion functions (3D)
 
 # ST_Force3D(geom) 2D Geometry to 3D Geometry
-# Not implemented
+
+SELECT ST_AsText(ST_Force3D(ST_GeomFromText('POINT(-10 10 6)')));
+EXPR$0
+POINT Z(-10 10 6)
+!ok
+
+SELECT ST_AsText(ST_Force3D(ST_GeomFromText('POINT(-10 10)')));
+EXPR$0
+POINT Z(-10 10 0)
+!ok
+
+SELECT ST_AsText(ST_Force3D(ST_GeomFromText('LINESTRING(-10 10, 10 10 3)')));
+EXPR$0
+LINESTRING Z(-10 10 0, 10 10 3)
+!ok
 
 #### Geometry creation functions (2D)
 
 # ST_BoundingCircle(geom) Returns the minimum bounding circle of *geom*
-# Not implemented
+
+SELECT roundGeom(ST_asText(ST_BoundingCircle(ST_GeomFromText('POLYGON((1 1, 1 
4, 4 4, 4 1, 1 1))'))), 2);
+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))
+!ok
+
+SELECT roundGeom(ST_asText(ST_BoundingCircle(ST_GeomFromText('MULTIPOINT((1 
1), (4 2))'))), 2);
+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))
+!ok
+
+SELECT roundGeom(ST_asText(ST_BoundingCircle(ST_GeomFromText('LINESTRING(1 1, 
4 5, 3 2)'))), 2);
+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))
+!ok
 
 # ST_Expand(geom, distance) Expands *geom*'s envelope
-# Not implemented
+
+SELECT ST_Expand(ST_GeomFromText('POINT(1 1)'), 1);
+EXPR$0
+POLYGON ((0 0, 0 2, 2 2, 2 0, 0 0))
+!ok
 
 # ST_Expand(geom, deltaX, deltaY) Expands *geom*'s envelope
-# Not implemented
+
+SELECT ST_Expand(ST_GeomFromText('POINT(4 4)'), 5, 2);
+EXPR$0
+POLYGON ((-1 2, -1 6, 9 6, 9 2, -1 2))
+!ok
+
+SELECT ST_Expand(ST_GeomFromText('LINESTRING(3 2, 7 5, 2 7)'), 2, 1);
+EXPR$0
+POLYGON ((0 1, 0 8, 9 8, 9 1, 0 1))
+!ok
 
 # ST_MakeEllipse(point, width, height) Constructs an ellipse
-# Not implemented
+
+SELECT roundGeom(ST_AsText(ST_MakeEllipse(ST_GeomFromText('POINT(5 5)'), 2, 
5)), 2);
+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 [...]
+!ok
 
 # ST_MakeEnvelope(xMin, yMin, xMax, yMax  [, srid ]) Creates a rectangular 
Polygon
-SELECT ST_AsText(ST_MakeEnvelope(10.0, 10.0, 11.0, 11.0, 4326));
 
+SELECT ST_AsText(ST_MakeEnvelope(10.0, 10.0, 11.0, 11.0, 4326));
 EXPR$0
 POLYGON ((10 10, 10 11, 11 11, 11 10, 10 10))
 !ok
@@ -361,16 +509,51 @@ Greenland, POINT (-42.604303 71.706936)
 !ok
 
 # ST_MakePolygon(lineString [, hole ]*) Creates a polygon from *lineString* 
with the given holes (which are required to be closed line-strings)
-# Not implemented
+
+SELECT ST_MakePolygon(ST_GeomFromText('LINESTRING(100 250, 100 350, 200 350, 
200 250, 100 250)'));
+EXPR$0
+POLYGON ((100 250, 100 350, 200 350, 200 250, 100 250))
+!ok
+
+SELECT ST_MakePolygon(ST_GeomFromText('LINESTRING(0 5, 4 5, 4 0, 0 0, 0 5)'), 
ST_GeomFromText('LINESTRING(1 1, 1 2, 2 2, 2 1, 1 1)'));
+EXPR$0
+POLYGON ((0 5, 4 5, 4 0, 0 0, 0 5), (1 1, 1 2, 2 2, 2 1, 1 1))
+!ok
+
+SELECT ST_MakePolygon(ST_GeomFromText('POINT(1 1)'));
+Only supports LINESTRINGs.
+!error
 
 # ST_MinimumDiameter(geom) Returns the minimum diameter of *geom*
-# Not implemented
+
+SELECT ST_MinimumDiameter(ST_GeomFromText('POINT(395 278)'));
+EXPR$0
+LINESTRING (395 278, 395 278)
+!ok
+
+SELECT ST_MinimumDiameter(ST_GeomFromText('LINESTRING(0 0, 1 1, 3 9, 7 1)'));
+EXPR$0
+LINESTRING (1 3, 7 1)
+!ok
 
 # ST_MinimumRectangle(geom) Returns the minimum rectangle enclosing *geom*
-# Not implemented
 
-# ST_OctogonalEnvelope(geom) Returns the octogonal envelope of *geom*
-# Not implemented
+SELECT ST_MinimumRectangle(ST_GeomFromText('MULTIPOINT((8 3), (4 6))'));
+EXPR$0
+LINESTRING (4 6, 8 3)
+!ok
+
+SELECT ST_MinimumRectangle(ST_GeomFromText('POLYGON((1 2, 3 0, 5 2, 3 2, 2 3, 
1 2))'));
+EXPR$0
+POLYGON ((1.4 3.1999999999999993, 0.6 0.8, 4.2 -0.3999999999999996, 
4.999999999999999 1.9999999999999993, 1.4 3.1999999999999993))
+!ok
+
+# ST_OctagonalEnvelope(geom) Returns the octogonal envelope of *geom*
+
+SELECT ST_OctagonalEnvelope(ST_GeomFromText('POLYGON((2 1, 1 2, 2 2, 2 4, 3 5, 
3 3, 5 5, 7 2, 5 2, 6 1, 2 1))'));
+EXPR$0
+POLYGON ((1 2, 1 3, 3 5, 5 5, 7 3, 7 2, 6 1, 2 1, 1 2))
+!ok
 
 # ST_RingBuffer(geom, bufferSize, bufferCount [, endCapStyle [, 
doDifference]]) Returns a multi-polygon of buffers centered at *geom* and of 
increasing buffer size
 # Not implemented
@@ -412,16 +595,77 @@ MULTIPOINT Z((-1 1 1), (1 1 1))
 !ok
 
 # ST_Centroid(geom) Returns the centroid of *geom* (which may be a 
geometry-collection)
-# Not implemented
+
+SELECT ST_Centroid(ST_GeomFromText('MULTIPOINT((4 4), (1 1), (1 0), (0 3)))'));
+EXPR$0
+POINT (1.5 2)
+!ok
+
+SELECT ST_Centroid(ST_GeomFromText('LINESTRING(2 1, 1 3, 5 2)'));
+EXPR$0
+POINT (2.472556942838389 2.3241856476127962)
+!ok
+
+SELECT ST_Centroid(ST_GeomFromText('MULTILINESTRING((1 5, 6 5), (5 1, 5 4))'));
+EXPR$0
+POINT (4.0625 4.0625)
+!ok
+
+SELECT ST_Centroid(ST_GeomFromText('POLYGON((1 5, 1 2, 6 2, 3 3, 3 4, 5 6, 1 
5))'));
+EXPR$0
+POINT (2.5964912280701755 3.666666666666667)
+!ok
+
+SELECT ST_Centroid(ST_GeomFromText('MULTIPOLYGON(((0 2, 3 2, 3 6, 0 6, 0 2)), 
((5 0, 7 0, 7 1, 5 1, 5 0)))'));
+EXPR$0
+POINT (2.142857142857143 3.5)
+!ok
+
+SELECT ST_Centroid(ST_GeomFromText('GEOMETRYCOLLECTION(
+                      POLYGON((1 2, 4 2, 4 6, 1 6, 1 2)),
+                      LINESTRING(2 6, 6 2),
+                      MULTIPOINT((4 4), (1 1), (1 0), (0 3)))'));
+EXPR$0
+POINT (2.5 4)
+!ok
+
 
 # ST_CompactnessRatio(polygon) Returns the square root of *polygon*'s area 
divided by the area of the circle with circumference equal to its perimeter
 # Not implemented
 
 # ST_CoordDim(geom) Returns the dimension of the coordinates of *geom*
-# Not implemented
+
+SELECT ST_CoordDim(ST_GeomFromText('Point(1 2 3)'));
+EXPR$0
+3
+!ok
+
+SELECT ST_CoordDim(ST_GeomFromText('Point(1 2)'));
+EXPR$0
+2
+!ok
+
+SELECT ST_CoordDim(ST_GeomFromText('Point Empty'));
+EXPR$0
+2
+!ok
 
 # ST_Dimension(geom) Returns the dimension of *geom*
-# Not implemented
+
+SELECT ST_Dimension(ST_GeomFromText('MULTIPOINT((4 4), (1 1), (1 0), (0 
3)))'));
+EXPR$0
+0
+!ok
+
+SELECT ST_Dimension(ST_GeomFromText('LINESTRING(2 1, 1 3, 5 2)'));
+EXPR$0
+1
+!ok
+
+SELECT ST_Dimension(ST_GeomFromText('MULTIPOLYGON(((0 2, 3 2, 3 6, 0 6, 0 2)), 
((5 0, 7 0, 7 1, 5 1, 5 0)))'));
+EXPR$0
+2
+!ok
 
 # ST_Distance(geom1, geom2) Returns the distance between *geom1* and *geom2*
 
@@ -478,8 +722,12 @@ FROM (SELECT
 !ok
 !}
 
-# ST_EndPoint(lineString) Returns the last coordinate of *lineString*
-# Not implemented
+# ST_EndPoint(geom) Returns the last coordinate of *geom*
+
+SELECT ST_EndPoint(ST_GeomFromText('MULTILINESTRING((1 1, 1 6, 2 2, -1 2))'));
+EXPR$0
+POINT (-1 2)
+!ok
 
 # ST_Envelope(geom [, srid ]) Returns the envelope of *geom* (which may be a 
geometry-collection) as a geometry
 
@@ -507,19 +755,50 @@ POLYGON ((0 0, 0 1, 1.0000000001 1, 1.0000000001 0, 0 0))
 # Not implemented
 
 # ST_Extent(geom) Returns the minimum bounding box of *geom* (which may be a 
geometry-collection)
-# Not implemented
+
+SELECT ST_Extent(ST_GeomFromText('MULTIPOINT((5 6), (1 2), (3 4), (10 3))'));
+EXPR$0
+POLYGON ((1 2, 1 6, 10 6, 10 2, 1 2))
+!ok
+
+SELECT ST_Extent(ST_GeomFromText('POINT(5 6)'));
+EXPR$0
+POINT (5 6)
+!ok
+
+SELECT ST_Extent(ST_GeomFromText('GEOMETRYCOLLECTION(
+  POLYGON((0 0, 3 -1, 1.5 2, 0 0)),
+  POLYGON((2 0, 3 3, 4 2, 2 0)),
+  POINT(5 6),
+  LINESTRING(1 1, 1 6))'));
+EXPR$0
+POLYGON ((0 -1, 0 6, 5 6, 5 -1, 0 -1))
+!ok
 
 # ST_ExteriorRing(polygon) Returns the exterior ring of *polygon* as a 
linear-ring
-# Not implemented
+
+SELECT ST_ExteriorRing(ST_GeomFromText('POLYGON((0 -1, 0 2, 3 2, 3 -1, 0 
-1))'));
+EXPR$0
+LINEARRING (0 -1, 0 2, 3 2, 3 -1, 0 -1)
+!ok
+
+SELECT ST_ExteriorRing(ST_GeomFromText('POINT(1 2)'));
+EXPR$0
+null
+!ok
 
 # ST_GeometryN(geomCollection, n) Returns the *n*th geometry of 
*geomCollection*
-# Not implemented
+
+SELECT ST_ExteriorRing(ST_GeomFromText('POINT(1 2)'));
+EXPR$0
+null
+!ok
 
 # ST_GeometryType(geom) Returns the type of *geom*
 
-SELECT ST_GeometryType(ST_Point(0.0, 0.0));
+SELECT ST_GeometryN(ST_GeomFromText('MULTIPOLYGON(((0 0, 3 -1, 1.5 2, 0 0)), 
((1 2, 4 2, 4 6, 1 6, 1 2)))'), 0);
 EXPR$0
-POINT
+POLYGON ((0 0, 3 -1, 1.5 2, 0 0))
 !ok
 
 # ST_GeometryTypeCode(geom) Returns the type code of *geom*
@@ -537,59 +816,308 @@ np, null, null
 p , POINT, 1
 !ok
 
-# ST_InteriorRingN(polygon, n) Returns the *n*th interior ring of *polygon*
-# Not implemented
+# ST_InteriorRingN(polygon, n) Returns the *n*th interior ring of *polygon*
+
+SELECT ST_InteriorRing(ST_GeomFromText('POLYGON((0 0, 10 0, 10 6, 0 6, 0 0), 
(1 1, 2 1, 2 5, 1 5, 1 1), (8 5, 8 4, 9 4, 9 5, 8 5))'), 0);
+EXPR$0
+LINEARRING (1 1, 2 1, 2 5, 1 5, 1 1)
+!ok
+
+SELECT ST_InteriorRing(ST_GeomFromText('POLYGON((0 0, 10 0, 10 6, 0 6, 0 0), 
(1 1, 2 1, 2 5, 1 5, 1 1), (8 5, 8 4, 9 4, 9 5, 8 5))'), 1);
+EXPR$0
+LINEARRING (8 5, 8 4, 9 4, 9 5, 8 5)
+!ok
+
+# ST_IsClosed(geom) Returns whether *geom* is a closed line-string or 
multi-line-string
+
+SELECT ST_IsClosed(ST_GeomFromText('LINESTRING(2 1, 1 3, 5 2)'));
+EXPR$0
+false
+!ok
+
+SELECT ST_IsClosed(ST_GeomFromText('LINESTRING(2 1, 1 3, 5 2, 2 1)'));
+EXPR$0
+true
+!ok
+
+# ST_IsEmpty(geom) Returns whether *geom* is empty
+
+SELECT ST_IsEmpty(ST_GeomFromText('MULTIPOINT((4 4), (1 1), (1 0), (0 3)))'));
+EXPR$0
+false
+!ok
+
+SELECT ST_IsEmpty(ST_GeomFromText('GEOMETRYCOLLECTION(
+  MULTIPOINT((4 4), (1 1), (1 0), (0 3)),
+  LINESTRING(2 6, 6 2),
+  POLYGON((1 2, 4 2, 4 6, 1 6, 1 2)))'));
+EXPR$0
+false
+!ok
+
+SELECT ST_IsEmpty(ST_GeomFromText('POLYGON EMPTY'));
+EXPR$0
+true
+!ok
+
+# ST_IsRectangle(geom) Returns whether *geom* is a rectangle
+
+SELECT ST_IsRectangle(ST_GeomFromText('POLYGON((0 0, 10 0, 10 5, 0 5, 0 0))'));
+EXPR$0
+true
+!ok
+
+SELECT ST_IsRectangle(ST_GeomFromText('POLYGON((0 0, 10 0, 10 7, 0 5, 0 0))'));
+EXPR$0
+false
+!ok
+
+# ST_IsRing(geom) Returns whether *geom* is a closed and simple line-string or 
multi-line-string
+
+SELECT ST_IsRing(ST_GeomFromText('LINESTRING(2 1, 1 3, 6 6, 2 1)'));
+EXPR$0
+true
+!ok
+
+SELECT ST_IsRing(ST_GeomFromText('LINESTRING(2 1, 1 3, 6 6)'));
+EXPR$0
+false
+!ok
+
+SELECT ST_IsRing(ST_GeomFromText('LINESTRING(2 1, 1 3, 6 6, 5 7, 5 2, 2 1)'));
+EXPR$0
+false
+!ok
+
+SELECT ST_IsRing(ST_GeomFromText('LINESTRING(2 1, 1 3, 6 6, 5 7, 5 2)'));
+EXPR$0
+false
+!ok
+
+# ST_IsSimple(geom) Returns whether *geom* is simple
+
+SELECT ST_IsSimple(ST_GeomFromText(
+ 'POLYGON((0 0, 10 0, 10 6, 0 6, 0 0), (1 1, 2 1, 2 5, 1 5, 1 1), (8 5, 8 4, 9 
4, 9 5, 8 5))'));
+EXPR$0
+true
+!ok
+
+SELECT ST_IsSimple(ST_GeomFromText(
+  'MULTILINESTRING((0 2, 3 2, 3 6, 0 6, 0 2), (5 0, 7 0, 7 1, 5 1, 5 0))'));
+EXPR$0
+true
+!ok
+
+SELECT ST_IsSimple(ST_GeomFromText(
+ 'GEOMETRYCOLLECTION(
+   MULTIPOINT((4 4), (1 1), (1 0), (0 3)),
+   LINESTRING(2 6, 6 2),
+   POLYGON((1 2, 4 2, 4 6, 1 6, 1 2)))'));
+EXPR$0
+true
+!ok
+
+SELECT ST_IsSimple(ST_GeomFromText('LINESTRING(2 1, 1 3, 6 6, 5 7, 5 6)'));
+EXPR$0
+true
+!ok
+
+SELECT ST_IsSimple(ST_GeomFromText('LINESTRING(2 1, 1 3, 6 6, 5 7, 5 2)'));
+EXPR$0
+false
+!ok
+
+# ST_IsValid(geom) Returns whether *geom* is valid
+
+SELECT ST_IsValid(ST_GeomFromText('POLYGON((0 0, 10 0, 10 5, 0 5, 0 0))'));
+EXPR$0
+true
+!ok
+
+SELECT ST_IsValid(ST_GeomFromText('POLYGON((0 0, 10 0, 10 5, 6 -2, 0 0))'));
+EXPR$0
+false
+!ok
+
+# ST_IsValidDetail(geom [, selfTouchValid ]) Returns a valid detail as an 
array of objects
+# Not implemented
+
+# ST_IsValidReason(geom [, selfTouchValid ]) Returns text stating whether 
*geom* is valid, and if not valid, a reason why
+# Not implemented
+
+# ST_NPoints(geom) Returns the number of points in *geom*
+# Not implemented
+
+# ST_NumGeometries(geom) Returns the number of geometries in *geom* (1 if it 
is not a geometry-collection)
+
+SELECT ST_NumGeometries(ST_GeomFromText('LINESTRING(2 1, 1 3, 5 2)'));
+EXPR$0
+1
+!ok
+
+SELECT ST_NumGeometries(ST_GeomFromText('MULTILINESTRING(
+  (0 2, 3 2, 3 6, 0 6, 0 1),
+  (5 0, 7 0, 7 1, 5 1, 5 0))'));
+EXPR$0
+2
+!ok
+
+SELECT ST_NumGeometries(ST_GeomFromText('POLYGON(
+  (0 0, 10 0, 10 6, 0 6, 0 0),
+  (1 1, 2 1, 2 5, 1 5, 1 1),
+  (8 5, 8 4, 9 4, 9 5, 8 5))'));
+EXPR$0
+1
+!ok
+
+SELECT ST_NumGeometries(ST_GeomFromText('MULTIPOLYGON(
+  ((0 0, 10 0, 10 6, 0 6, 0 0)),
+  ((1 1, 2 1, 2 5, 1 5, 1 1)),
+  ((8 5, 8 4, 9 4, 9 5, 8 5)))'));
+EXPR$0
+3
+!ok
+
+SELECT ST_NumGeometries(ST_GeomFromText('GEOMETRYCOLLECTION(
+  MULTIPOINT((4 4), (1 1), (1 0), (0 3)),
+  LINESTRING(2 6, 6 2),
+  POLYGON((1 2, 4 2, 4 6, 1 6, 1 2)))'));
+EXPR$0
+3
+!ok
+
+SELECT ST_NumGeometries(ST_GeomFromText('MULTIPOINT(
+  (0 2), (3 2), (3 6), (0 6),
+  (0 1), (5 0), (7 0))'));
+EXPR$0
+7
+!ok
+
+# ST_NumInteriorRings(geom) Returns the number of interior rings of *geom*
+
+SELECT ST_NumInteriorRings(ST_GeomFromText('POLYGON(
+  (0 0, 10 0, 10 6, 0 6, 0 0),
+  (1 1, 2 1, 2 5, 1 5, 1 1),
+  (8 5, 8 4, 9 4, 9 5, 8 5))'));
+EXPR$0
+2
+!ok
+
+SELECT ST_NumInteriorRings(ST_GeomFromText('MULTIPOLYGON(
+  ((0 0, 10 0, 10 6, 0 6, 0 0), (1 1, 2 1, 2 5, 1 5, 1 1)),
+  ((1 1, 2 1, 2 5, 1 5, 1 1)),
+  ((8 5, 8 4, 9 4, 9 5, 8 5)))'));
+EXPR$0
+1
+!ok
+
+SELECT ST_NumInteriorRings(ST_GeomFromText('GEOMETRYCOLLECTION(
+  MULTIPOINT((4 4), (1 1), (1 0), (0 3)),
+  LINESTRING(2 6, 6 2),
+  POLYGON((1 2, 4 2, 4 6, 1 6, 1 2)))'));
+EXPR$0
+0
+!ok
+
+SELECT ST_NumInteriorRings(ST_GeomFromText(
+  'GEOMETRYCOLLECTION(
+    MULTIPOINT((4 4), (1 1), (1 0), (0 3)),
+    LINESTRING(2 6, 6 2),
+    POLYGON((1 2, 4 2, 4 6, 1 6, 1 2), (2 4, 3 4, 3 5, 2 5, 2 4)))'));
+EXPR$0
+1
+!ok
+
+
+# ST_NumPoints(geom) Returns the number of points in *geom*
+
+SELECT ST_NumPoints(ST_GeomFromText('POINT(2 2)'));
+EXPR$0
+1
+!ok
+
+SELECT ST_NumPoints(ST_GeomFromText('MULTIPOINT(2 2, 4 4)'));
+EXPR$0
+2
+!ok
 
-# ST_IsClosed(geom) Returns whether *geom* is a closed line-string or 
multi-line-string
-# Not implemented
+SELECT ST_NumPoints(ST_GeomFromText('MULTIPOINT(2 2, 4 4, 4 4)'));
+EXPR$0
+3
+!ok
 
-# ST_IsEmpty(geom) Returns whether *geom* is empty
-# Not implemented
+SELECT ST_NumPoints(ST_GeomFromText('MULTILINESTRING((2 2, 4 4), (3 1, 6 
3))'));
+EXPR$0
+4
+!ok
 
-# ST_IsRectangle(geom) Returns whether *geom* is a rectangle
-# Not implemented
+SELECT ST_NumPoints(ST_GeomFromText('POLYGON((0 0, 10 0, 10 6, 0 6, 0 0), (1 
1, 2 1, 2 5, 1 5, 1 1), (8 5, 8 4, 9 4, 9 5, 8 5))'));
+EXPR$0
+15
+!ok
 
-# ST_IsRing(geom) Returns whether *geom* is a closed and simple line-string or 
multi-line-string
-# Not implemented
+# ST_PointN(geom, n) Returns the *n*th point of a *geom*
 
-# ST_IsSimple(geom) Returns whether *geom* is simple
-# Not implemented
+SELECT ST_PointN(ST_GeomFromText('LINESTRING(1 1, 1 6, 2 2, -1 2))'), 2);
+EXPR$0
+POINT (2 2)
+!ok
 
-# ST_IsValid(geom) Returns whether *geom* is valid
-# Not implemented
+SELECT ST_PointN(ST_GeomFromText('MULTILINESTRING((1 1, 1 6, 2 2, -1 2))'), 3);
+EXPR$0
+POINT (-1 2)
+!ok
 
-# ST_IsValidDetail(geom [, selfTouchValid ]) Returns a valid detail as an 
array of objects
-# Not implemented
+SELECT ST_PointN(ST_GeomFromText('MULTIPOINT(1 1, 1 6, 2 2, -1 2)'), -1);
+EXPR$0
+POINT (-1 2)
+!ok
 
-# ST_IsValidReason(geom [, selfTouchValid ]) Returns text stating whether 
*geom* is valid, and if not valid, a reason why
-# Not implemented
+SELECT ST_PointN(ST_GeomFromText('MULTILINESTRING((1 1, 1 6, 2 2, -1 2), (0 1, 
2 4))'), 4);
+EXPR$0
+POINT (0 1)
+!ok
 
-# ST_NPoints(geom) Returns the number of points in *geom*
-# Not implemented
+# ST_PointOnSurface(geom) Returns an interior or boundary point of *geom*
 
-# ST_NumGeometries(geom) Returns the number of geometries in *geom* (1 if it 
is not a geometry-collection)
-# Not implemented
+SELECT ST_PointOnSurface(ST_GeomFromText('POINT(1 5)'));
+EXPR$0
+POINT (1 5)
+!ok
 
-# ST_NumInteriorRing(geom) Alias for `ST_NumInteriorRings`
-# Not implemented
+SELECT ST_PointOnSurface(ST_GeomFromText('MULTIPOINT((4 4), (1 1), (1 0), (0 
3)))'));
+EXPR$0
+POINT (1 1)
+!ok
 
-# ST_NumInteriorRings(geom) Returns the number of interior rings of *geom*
-# Not implemented
+SELECT ST_PointOnSurface(ST_GeomFromText('LINESTRING(-1 5, 0 10)'));
+EXPR$0
+POINT (0 10)
+!ok
 
-# ST_NumPoints(lineString) Returns the number of points in *lineString*
-# Not implemented
+SELECT ST_PointOnSurface(ST_GeomFromText('POLYGON((0 0, 0 5, 5 5, 5 0, 0 
0))'));
+EXPR$0
+POINT (2.5 2.5)
+!ok
 
-# ST_PointN(geom, n) Returns the *n*th point of a *lineString*
-# Not implemented
+# ST_SRID(geom) Returns SRID value of *geom* or 0 if it does not have one
 
-# ST_PointOnSurface(geom) Returns an interior or boundary point of *geom*
-# Not implemented
+SELECT ST_SRID(ST_GeomFromText('POINT(15 25)', 2154));
+EXPR$0
+2154
+!ok
 
-# ST_SRID(geom) Returns SRID value of *geom* or 0 if it does not have one
-# Not implemented
+SELECT ST_SRID(ST_GeomFromText('LINESTRING(2 1, 1 3, 5 2, 2 1)', 4326));
+EXPR$0
+4326
+!ok
 
-# ST_StartPoint(lineString) Returns the first coordinate of *lineString*
-# Not implemented
+# ST_StartPoint(geom) Returns the first coordinate of *geom*
+
+SELECT ST_StartPoint(ST_GeomFromText('MULTILINESTRING((1 1, 1 6, 2 2, -1 
2))'));
+EXPR$0
+POINT (1 1)
+!ok
 
 # ST_X(geom) Returns the x-value of the first coordinate of *geom*
 
@@ -604,10 +1132,16 @@ EXPR$0
 !ok
 
 # ST_XMax(geom) Returns the maximum x-value of *geom*
-# Not implemented
+SELECT ST_XMax(ST_GeomFromText('LINESTRING(1 3 4, 5 6 7)'));
+EXPR$0
+5.0
+!ok
 
 # ST_XMin(geom) Returns the minimum x-value of *geom*
-# Not implemented
+SELECT ST_XMin(ST_GeomFromText('LINESTRING(1 3 4, 5 6 7)'));
+EXPR$0
+1.0
+!ok
 
 # ST_Y(geom) Returns the y-value of the first coordinate of *geom*
 
@@ -622,10 +1156,16 @@ EXPR$0
 !ok
 
 # ST_YMax(geom) Returns the maximum y-value of *geom*
-# Not implemented
+SELECT ST_YMax(ST_GeomFromText('LINESTRING(1 3 4, 5 6 7)'));
+EXPR$0
+6.0
+!ok
 
 # ST_YMin(geom) Returns the minimum y-value of *geom*
-# Not implemented
+SELECT ST_YMin(ST_GeomFromText('LINESTRING(1 3 4, 5 6 7)'));
+EXPR$0
+3.0
+!ok
 
 #### Geometry properties (3D)
 
@@ -650,16 +1190,32 @@ EXPR$0
 
 SELECT ST_Z(ST_GeomFromText('POINT (1 2)'));
 EXPR$0
-null
+NaN
 !ok
 
-# Not implemented
-
 # ST_ZMax(geom) Returns the maximum z-value of *geom*
-# Not implemented
+
+SELECT ST_ZMax(ST_GeomFromText('LINESTRING(1 2 3, 4 5 6)'));
+EXPR$0
+6.0
+!ok
+
+SELECT ST_ZMax(ST_GeomFromText('LINESTRING(1 2, 4 5)'));
+EXPR$0
+NaN
+!ok
 
 # ST_ZMin(geom) Returns the minimum z-value of *geom*
-# Not implemented
+
+SELECT ST_ZMin(ST_GeomFromText('LINESTRING(1 2 3, 4 5 6)'));
+EXPR$0
+3.0
+!ok
+
+SELECT ST_ZMin(ST_GeomFromText('LINESTRING(1 2, 4 5)'));
+EXPR$0
+NaN
+!ok
 
 ### Geometry predicates
 
@@ -691,7 +1247,16 @@ POLYGON, true, true, false, false
 !ok
 
 # ST_Covers(geom1, geom2) Returns whether no point in *geom2* is outside 
*geom1*
-# Not implemented
+
+SELECT ST_Covers(ST_Buffer(ST_GeomFromText('POINT(0 0)'), 1), 
ST_GeomFromText('POINT(0 0)'));
+EXPR$0
+true
+!ok
+
+SELECT ST_Covers(ST_Buffer(ST_GeomFromText('POINT(0 0)'), 1), 
ST_GeomFromText('POINT(1 1)'));
+EXPR$0
+false
+!ok
 
 # ST_Crosses(geom1, geom2) Returns whether *geom1* crosses *geom2*
 
@@ -782,10 +1347,41 @@ false
 !ok
 
 # ST_Relate(geom1, geom2) Returns the DE-9IM intersection matrix of *geom1* 
and *geom2*
-# Not implemented
+
+SELECT ST_Relate(ST_GeomFromText('LINESTRING(1 2, 3 4)'),
+                 ST_GeomFromText('LINESTRING(5 6, 7 3)'));
+EXPR$0
+FF1FF0102
+!ok
+
+SELECT ST_Relate(ST_GeomFromText('POLYGON((1 1, 4 1, 4 5, 1 5, 1 1))'),
+                 ST_GeomFromText('POLYGON((3 2, 6 2, 6 6, 3 6, 3 2))'));
+EXPR$0
+212101212
+!ok
 
 # ST_Relate(geom1, geom2, iMatrix) Returns whether *geom1* and *geom2* are 
related by the given intersection matrix *iMatrix*
-# Not implemented
+
+SELECT ST_Relate(ST_GeomFromText('POLYGON((1 1, 4 1, 4 5, 1 5, 1 1))'),
+                 ST_GeomFromText('POLYGON((3 2, 6 2, 6 6, 3 6, 3 2))'),
+                 '212101212');
+EXPR$0
+true
+!ok
+
+SELECT ST_Relate(ST_GeomFromText('POLYGON((1 1, 4 1, 4 5, 1 5, 1 1))'),
+                 ST_GeomFromText('POLYGON((3 2, 6 2, 6 6, 3 6, 3 2))'),
+                 '112101212');
+EXPR$0
+false
+!ok
+
+SELECT ST_Relate(ST_GeomFromText('POINT(1 2)'),
+                 ST_Buffer(ST_GeomFromText('POINT(1 2)'), 2),
+                 '0F*FFF212');
+EXPR$0
+true
+!ok
 
 # ST_Touches(geom1, geom2) Returns whether *geom1* touches *geom2*
 
@@ -855,23 +1451,75 @@ at org.apache.calcite.runtime.Geometries.todo
 !}
 
 # ST_ConvexHull(geom) Computes the smallest convex polygon that contains all 
the points in the Geometry
-# Not implemented
+
+SELECT ST_ConvexHull(ST_GeomFromText('GEOMETRYCOLLECTION(
+                        POINT(1 2),
+                        LINESTRING(1 4, 4 7),
+                        POLYGON((3 1, 7 1, 7 6, 3 1)))'));
+EXPR$0
+POLYGON ((3 1, 1 2, 1 4, 4 7, 7 6, 7 1, 3 1))
+!ok
 
 # ST_Difference(geom1, geom2) Computes the difference between two geometries
-# Not implemented
+
+SELECT ST_Difference(ST_GeomFromText('POLYGON((1 1, 7 1, 7 6, 1 6, 1 1))'),
+                     ST_GeomFromText('POLYGON((3 2, 8 2, 8 8, 3 8, 3 2))'));
+EXPR$0
+POLYGON ((7 2, 7 1, 1 1, 1 6, 3 6, 3 2, 7 2))
+!ok
+
+SELECT ST_Difference(ST_GeomFromText('POLYGON((3 2, 8 2, 8 8, 3 8, 3 2))'),
+                     ST_GeomFromText('POLYGON((1 1, 7 1, 7 6, 1 6, 1 1))'));
+EXPR$0
+POLYGON ((3 6, 3 8, 8 8, 8 2, 7 2, 7 6, 3 6))
+!ok
 
 # ST_Intersection(geom1, geom2) Computes the intersection of two geometries
-# Not implemented
+
+SELECT ST_Intersection(ST_GeomFromText('POLYGON((1 1, 7 1, 7 6, 1 6, 1 1))'),
+                       ST_GeomFromText('POLYGON((3 2, 8 2, 8 8, 3 8, 3 2))'));
+EXPR$0
+POLYGON ((3 6, 7 6, 7 2, 3 2, 3 6))
+!ok
+
+SELECT ST_Intersection(ST_GeomFromText('POLYGON((1 1, 4 1, 4 6, 1 6, 1 1))'),
+                       ST_GeomFromText('POLYGON((4 2, 8 2, 8 8, 4 8, 4 2))'));
+EXPR$0
+LINESTRING (4 2, 4 6)
+!ok
+
+SELECT ST_Intersection(ST_GeomFromText('POLYGON((1 1, 4 1, 4 6, 1 6, 1 1))'),
+                       ST_GeomFromText('POLYGON((4 6, 8 6, 8 8, 4 8, 4 6))'));
+EXPR$0
+POINT (4 6)
+!ok
+
+SELECT ST_Intersection(ST_GeomFromText('LINESTRING(2 2, 6 6)'),
+                       ST_GeomFromText('LINESTRING(2 8, 8 2)'));
+EXPR$0
+POINT (5 5)
+!ok
+
+SELECT ST_Intersection(ST_GeomFromText('POLYGON((1 1, 7 1, 7 6, 1 6, 1 1))'),
+                       ST_GeomFromText('POINT(3 5)'));
+EXPR$0
+POINT (3 5)
+!ok
+
 
 # ST_SymDifference(geom1, geom2) Computes the symmetric difference between two 
geometries
-# Not implemented
+
+SELECT ST_SymDifference(ST_GeomFromText('POLYGON((1 1, 7 1, 7 6, 1 6, 1 1))'),
+                        ST_GeomFromText('POLYGON((3 2, 8 2, 8 8, 3 8, 3 2))'));
+EXPR$0
+MULTIPOLYGON (((7 2, 7 1, 1 1, 1 6, 3 6, 3 2, 7 2)), ((7 2, 7 6, 3 6, 3 8, 8 
8, 8 2, 7 2)))
+!ok
 
 # ST_Union(geom1, geom2) Computes the union of two or more geometries
 
 # NOTE: PostGIS altered the order: it returned MULTIPOINT(-2 3,1 2)
 SELECT ST_AsText(ST_Union(ST_GeomFromText('POINT(1 2)'),
     ST_GeomFromText('POINT(-2 3)')));
-
 EXPR$0
 MULTIPOINT ((-2 3), (1 2))
 !ok
@@ -915,13 +1563,63 @@ MULTILINESTRING((3 4,4 5),(1 2,3 4))
 #### Affine transformation functions (3D and 2D)
 
 # ST_Rotate(geom, angle [, origin | x, y]) Rotates a *geom* counter-clockwise 
by *angle* (in radians) about *origin* (or the point (*x*, *y*))
-# Not implemented
 
-# ST_Scale(geom, xFactor, yFactor [, zFactor ]) Scales *geom* by multiplying 
the ordinates by the indicated scale factors
-# Not implemented
+SELECT ST_Rotate(ST_GeomFromText('LINESTRING(1 3, 1 1, 2 1)'), pi());
+EXPR$0
+LINESTRING (-1.0000000000000004 -3, -1.0000000000000002 -0.9999999999999999, 
-2 -0.9999999999999998)
+!ok
+
+SELECT ST_Rotate(ST_GeomFromText('LINESTRING(1 3, 1 1, 2 1)'), pi() / 3);
+EXPR$0
+LINESTRING (-2.098076211353316 2.3660254037844393, -0.3660254037844385 
1.3660254037844388, 0.1339745962155616 2.232050807568877)
+!ok
+
+SELECT ST_Rotate(ST_GeomFromText('LINESTRING(1 3, 1 1, 2 1)'), -pi() / 2, 
ST_PointFromText('POINT(2 1)'));
+EXPR$0
+LINESTRING (4 2, 1.9999999999999998 2, 2 1)
+!ok
+
+SELECT ST_Rotate(ST_GeomFromText('LINESTRING(1 3, 1 1, 2 1)'), pi() / 2, 1.0, 
1.0);
+EXPR$0
+LINESTRING (-1 1.0000000000000002, 1 0.9999999999999999, 1 2)
+!ok
+
+# ST_Scale(geom, xFactor, yFactor) Scales *geom* by multiplying the ordinates 
by the indicated scale factors
+
+SELECT ST_Scale(ST_GeomFromText('LINESTRING(1 2, 4 5)'), 0.5, 0.75);
+EXPR$0
+LINESTRING (0.5 1.5, 2 3.75)
+!ok
 
 # ST_Translate(geom, x, y, [, z]) Translates *geom*
-# Not implemented
+
+SELECT ST_Translate(ST_GeomFromText('POINT(1 2)'), 10, 20);
+EXPR$0
+POINT (11 22)
+!ok
+
+SELECT ST_Translate(ST_GeomFromText('LINESTRING(0 0, 1 0)'), 1, 2);
+EXPR$0
+LINESTRING (1 2, 2 2)
+!ok
+
+SELECT ST_Translate(ST_GeomFromText('LINESTRING(-71.01 42.37, -71.11 42.38)'), 
1, 0.5);
+EXPR$0
+LINESTRING (-70.01 42.87, -70.11 42.88)
+!ok
+
+SELECT ST_Translate(ST_GeomFromText('MULTIPOINT((0 1), (2 2), (1 3))'), 1, 0);
+EXPR$0
+MULTIPOINT ((1 1), (3 2), (2 3))
+!ok
+
+SELECT ST_Translate(ST_GeomFromText('GEOMETRYCOLLECTION(
+  POLYGON((0 0, 3 5, 6  6, 0 7, 0 0)),
+  MULTIPOINT((0 1), (2 2), (1 3)))'), -1, 1);
+EXPR$0
+GEOMETRYCOLLECTION (POLYGON ((-1 1, 2 6, 5 7, -1 8, -1 1)), MULTIPOINT ((-1 
2), (1 3), (0 4)))
+!ok
+
 
 #### Geometry editing functions (2D)
 
@@ -1030,16 +1728,36 @@ MULTILINESTRING((3 4,4 5),(1 2,3 4))
 # Not implemented
 
 # ST_LineMerge(geom) Merges a collection of linear components to form a 
line-string of maximal length
-# Not implemented
+
+SELECT ST_LineMerge(ST_GeomFromText('LINESTRING (1 1, 1 4)'));
+EXPR$0
+MULTILINESTRING ((1 1, 1 4))
+!ok
+
+SELECT ST_LineMerge(ST_GeomFromText('MULTILINESTRING ((1 1, 1 4), (1 4, 5 4), 
(5 4, 5 1), (3 3, 3 4))'));
+EXPR$0
+MULTILINESTRING ((1 1, 1 4, 5 4, 5 1), (3 3, 3 4))
+!ok
 
 # ST_MakeValid(geom [, preserveGeomDim [, preserveDuplicateCoord [, 
preserveCoordDim]]]) Makes *geom* valid
-# Not implemented
+
+SELECT ST_MakeValid(ST_GeomFromText('LINESTRING(0 0, 0 0)'));
+EXPR$0
+LINESTRING EMPTY
+!ok
 
 # ST_Polygonize(geom) Creates a multi-polygon from edges of *geom*
-# Not implemented
+
+SELECT ST_Polygonize(ST_GeomFromText('LINESTRING(1 2, 2 4, 4 4, 5 2, 1 2)'));
+EXPR$0
+POLYGON ((1 2, 2 4, 4 4, 5 2, 1 2))
+!ok
 
 # ST_PrecisionReducer(geom, n) Reduces *geom*'s precision to *n* decimal places
-# Not implemented
+SELECT ST_PrecisionReducer(ST_GeomFromText('MULTIPOINT((190.1239999997 300), 
(10 11.1233))'), 3);
+EXPR$0
+MULTIPOINT ((190.124 300), (10 11.123))
+!ok
 
 # ST_RingSideBuffer(geom, bufferSize, bufferCount [, endCapStyle [, 
doDifference]]) Computes a ring buffer on one side
 # Not implemented
@@ -1048,13 +1766,85 @@ MULTILINESTRING((3 4,4 5),(1 2,3 4))
 # Not implemented
 
 # ST_Simplify(geom, distance) Simplifies *geom* using the Douglas-Peuker 
algorithm with a *distance* tolerance
-# Not implemented
 
-# ST_SimplifyPreserveTopology(geom) Simplifies *geom*, preserving its topology
-# Not implemented
+SELECT ST_Simplify(ST_GeomFromText('POLYGON((2 1, 1 2, 2 2, 2 3, 3 3, 3 2, 4 
2, 4 1, 3 0, 2 0, 2 1))'), 0.5);
+EXPR$0
+POLYGON ((2 1, 1 2, 3 3, 4 1, 3 0, 2 0, 2 1))
+!ok
+
+SELECT ST_Simplify(ST_GeomFromText('POLYGON((2 1, 1 2, 2 2, 2 3, 3 3, 3 2, 4 
2, 4 1, 3 0, 2 0, 2 1))'), 1);
+EXPR$0
+POLYGON ((2 1, 1 2, 3 3, 4 1, 2 1))
+!ok
+
+SELECT ST_Simplify(ST_GeomFromText('POLYGON((2 1, 1 2, 2 2, 2 3, 3 3, 3 2, 4 
2, 4 1, 3 0, 2 0, 2 1))'), 2);
+EXPR$0
+POLYGON EMPTY
+!ok
+
+SELECT ST_Simplify(ST_GeomFromText('MULTIPOINT((190 300), (10 11))'), 4);
+EXPR$0
+MULTIPOINT ((190 300), (10 11))
+!ok
+
+SELECT ST_Simplify(ST_GeomFromText('LINESTRING(250 250, 280 290, 300 230, 340 
300, 360 260, 440 310, 470 360, 604 286)'), 40);
+EXPR$0
+LINESTRING (250 250, 280 290, 300 230, 470 360, 604 286)
+!ok
+
+# ST_SimplifyPreserveTopology(geom, distance) Simplifies *geom*, preserving 
its topology
+
+SELECT ST_SimplifyPreserveTopology(ST_GeomFromText('POLYGON((8 25, 28 22, 28 
20, 15 11, 33 3, 56 30, 46 33,  46 34, 47 44, 35 36, 45 33, 43 19, 29 21, 29 
22, 35 26, 24 39, 8 25))'), 10);
+EXPR$0
+POLYGON ((8 25, 28 22, 15 11, 33 3, 56 30, 47 44, 35 36, 43 19, 24 39, 8 25))
+!ok
+
+SELECT ST_SimplifyPreserveTopology(ST_GeomFromText('POLYGON((8 25, 28 22, 28 
20, 15 11, 33 3, 56 30, 46 33,  46 34, 47 44, 35 36, 45 33, 43 19, 29 21, 29 
22, 35 26, 24 39, 8 25))'), 20);
+EXPR$0
+POLYGON ((8 25, 33 3, 56 30, 47 44, 43 19, 8 25))
+!ok
+
+SELECT ST_SimplifyPreserveTopology(ST_GeomFromText('POLYGON((8 25, 28 22, 28 
20, 15 11, 33 3, 56 30, 46 33,  46 34, 47 44, 35 36, 45 33, 43 19, 29 21, 29 
22, 35 26, 24 39, 8 25))'), 30);
+EXPR$0
+POLYGON ((8 25, 33 3, 56 30, 47 44, 8 25))
+!ok
 
 # ST_Snap(geom1, geom2, tolerance) Snaps *geom1* and *geom2* together
-# Not implemented
+
+SELECT ST_Snap(
+  ST_GeomFromText('LINESTRING(1 2, 2 4, 4 4, 5 2)'),
+  ST_GeomFromText('LINESTRING(5 2, 2 1, 1 2)'), 1);
+EXPR$0
+LINESTRING (1 2, 2 4, 4 4, 5 2)
+!ok
+
+SELECT ST_Snap(
+  ST_GeomFromText('LINESTRING(1 2, 2 4, 4 4, 5 2)'),
+  ST_GeomFromText('LINESTRING(5 2, 2 1, 1 2)'), 2);
+EXPR$0
+LINESTRING (1 2, 2 1, 2 4, 4 4, 5 2)
+!ok
+
+SELECT ST_Snap(
+  ST_GeomFromText('LINESTRING(1 2, 2 4, 4 4, 5 2)'),
+  ST_GeomFromText('LINESTRING(5 2, 2 1, 1 2)'), 3);
+EXPR$0
+LINESTRING (1 2, 1 2, 2 1, 5 2, 5 2)
+!ok
+
+SELECT ST_Snap(
+  ST_GeomFromText('POLYGON((1 1, 1 7, 7 7, 7 1, 1 1))'),
+  ST_GeomFromText('POLYGON((3 3, 1 2, 0 2, 0 1, -2 1, -1 7, 3 6, 4 8, 7 8, 6 
6, 9 6, 8 1, 8 1, 3 3))'), 2);
+EXPR$0
+POLYGON ((0 1, 1 2, 0 2, -1 7, 1 7, 3 6, 6 6, 8 1, 0 1))
+!ok
+
+SELECT ST_Snap(
+  ST_GeomFromText('POLYGON((3 3, 1 2, 0 2, 0 1, -2 1, -1 7, 3 6, 4 8, 7 8, 6 
6, 9 6, 8 1, 8 1, 3 3))'),
+  ST_GeomFromText('POLYGON((1 1, 1 7, 7 7, 7 1, 1 1))'), 2);
+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
@@ -1263,4 +2053,5 @@ EXPR$0
 33204
 !ok
 
+
 # End spatial.iq
diff --git a/site/_docs/reference.md b/site/_docs/reference.md
index 54befa1199..995a4ff3f7 100644
--- a/site/_docs/reference.md
+++ b/site/_docs/reference.md
@@ -1,3 +1,4 @@
+
 ---
 layout: docs
 title: SQL language
@@ -1181,11 +1182,11 @@ or binary strings encoded as
 Where you would use a literal, apply the `ST_GeomFromText` function,
 for example `ST_GeomFromText('POINT (30 10)')`.
 
-| Data type   | Type code | Examples in WKT
-|:----------- |:--------- |:---------------------
+| Data type          | Type code | Examples in WKT
+|:-------------------|:--------- |:---------------------
 | GEOMETRY           |  0 | generalization of Point, Curve, Surface, 
GEOMETRYCOLLECTION
 | POINT              |  1 | <code>ST_GeomFromText(&#8203;'POINT (30 
10)')</code> is a point in 2D space; <code>ST_GeomFromText(&#8203;'POINT Z(30 
10 2)')</code> is point in 3D space
-| CURVE            | 13 | generalization of LINESTRING
+| CURVE              | 13 | generalization of LINESTRING
 | LINESTRING         |  2 | <code>ST_GeomFromText(&#8203;'LINESTRING (30 10, 
10 30, 40 40)')</code>
 | SURFACE            | 14 | generalization of Polygon, PolyhedralSurface
 | POLYGON            |  3 | <code>ST_GeomFromText(&#8203;'POLYGON ((30 10, 40 
40, 20 40, 10 20, 30 10))')</code> is a pentagon; 
<code>ST_GeomFromText(&#8203;'POLYGON ((35 10, 45 45, 15 40, 10 20, 35 10), (20 
30, 35 35, 30 20, 20 30))')</code> is a pentagon with a quadrilateral hole
@@ -2159,59 +2160,67 @@ implements the OpenGIS Simple Features Implementation 
Specification for SQL,
 
 | C | Operator syntax      | Description
 |:- |:-------------------- |:-----------
+| p | ST_AsBinary(geom) | Synonym for `ST_AsWKB`
+| p | ST_AsEWKB(geom) | Synonym for `ST_AsWKB`
+| p | ST_AsEWKT(geom) | Converts GEOMETRY → EWKT
+| p | ST_AsGeoJSON(geom) | Converts GEOMETRY → GeoJSON
+| p | ST_AsGML(geom) | Converts GEOMETRY → GML
 | p | ST_AsText(geom) | Synonym for `ST_AsWKT`
-| o | ST_AsWKT(geom) | Converts *geom* → WKT
-| o | ST_GeomFromText(wkt [, srid ]) | Returns a specified GEOMETRY value from 
WKT representation
+| o | ST_AsWKB(geom) | Converts GEOMETRY → WKB
+| o | ST_AsWKT(geom) | Converts GEOMETRY → WKT
+| o | ST_Force2D(geom) | 3D GEOMETRY → 2D GEOMETRY
+| o | ST_GeomFromEWKB(wkb [, srid ]) | Synonym for `ST_GeomFromWKB`
+| o | ST_GeomFromEWKT(wkb [, srid ]) | Converts EWKT → GEOMETRY
+| o | ST_GeomFromGeoJSON(json) | Converts GeoJSON → GEOMETRY
+| o | ST_GeomFromGML(wkb [, srid ]) | Converts GML → GEOMETRY
+| o | ST_GeomFromText(wkt [, srid ]) | Synonym for `ST_GeomFromWKT`
+| o | ST_GeomFromWKB(wkb [, srid ]) | Converts WKB → GEOMETRY
+| o | ST_GeomFromWKT(wkb [, srid ]) | Converts WKT → GEOMETRY
 | o | ST_LineFromText(wkt [, srid ]) | Converts WKT → LINESTRING
+| o | ST_LineFromWKB(wkt [, srid ]) | Converts WKT → LINESTRING
 | o | ST_MLineFromText(wkt [, srid ]) | Converts WKT → MULTILINESTRING
 | o | ST_MPointFromText(wkt [, srid ]) | Converts WKT → MULTIPOINT
 | o | ST_MPolyFromText(wkt [, srid ]) Converts WKT → MULTIPOLYGON
 | o | ST_PointFromText(wkt [, srid ]) | Converts WKT → POINT
+| o | ST_PointFromWKB(wkt [, srid ]) | Converts WKB → POINT
 | o | ST_PolyFromText(wkt [, srid ]) | Converts WKT → POLYGON
+| o | ST_PolyFromWKB(wkt [, srid ]) | Converts WKB → POLYGON
+| 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
 
 Not implemented:
 
-* ST_AsBinary(geom) GEOMETRY → WKB
-* ST_AsGML(geom) GEOMETRY → GML
-* ST_Force2D(geom) 3D GEOMETRY → 2D GEOMETRY
-* ST_GeomFromGML(gml [, srid ]) GML → GEOMETRY
-* ST_GeomFromWKB(wkb [, srid ]) WKB → GEOMETRY
 * ST_GoogleMapLink(geom [, layerType [, zoom ]]) GEOMETRY → Google map link
-* ST_LineFromWKB(wkb [, srid ]) WKB → LINESTRING
 * ST_OSMMapLink(geom [, marker ]) GEOMETRY → OSM map link
-* ST_PointFromWKB(wkb [, srid ]) WKB → POINT
-* ST_PolyFromWKB(wkb [, srid ]) WKB → POLYGON
-* ST_ToMultiLine(geom) Converts the coordinates of *geom* (which may be a 
GEOMETRYCOLLECTION) into a MULTILINESTRING
-* ST_ToMultiPoint(geom)) Converts the coordinates of *geom* (which may be a 
GEOMETRYCOLLECTION) into a MULTIPOINT
-* ST_ToMultiSegments(geom) Converts *geom* (which may be a GEOMETRYCOLLECTION) 
into a set of distinct segments stored in a MULTILINESTRING
 
 #### Geometry conversion functions (3D)
 
-Not implemented:
-
-* ST_Force3D(geom) 2D GEOMETRY → 3D GEOMETRY
+| C | Operator syntax      | Description
+|:- |:-------------------- |:-----------
+| o | ST_Force3D(geom) | 2D GEOMETRY → 3D GEOMETRY
 
 #### Geometry creation functions (2D)
 
 | C | Operator syntax      | Description
 |:- |:-------------------- |:-----------
+| h | ST_BoundingCircle(geom) | Returns the minimum bounding circle of *geom*
+| h | ST_Expand(geom, distance) | Expands *geom*'s envelope
+| h | ST_Expand(geom, deltaX, deltaY) | Expands *geom*'s envelope
+| h | ST_MakeEllipse(point, width, height) | Constructs an ellipse
 | p | ST_MakeEnvelope(xMin, yMin, xMax, yMax  [, srid ]) | Creates a 
rectangular POLYGON
 | h | ST_MakeGrid(geom, deltaX, deltaY) | Calculates a regular grid of 
POLYGONs based on *geom*
 | h | ST_MakeGridPoints(geom, deltaX, deltaY) | Calculates a regular grid of 
points based on *geom*
 | o | ST_MakeLine(point1 [, point ]*) | Creates a line-string from the given 
POINTs (or MULTIPOINTs)
 | p | ST_MakePoint(x, y [, z ]) | Synonym for `ST_Point`
+| p | ST_MakePolygon(lineString [, hole ]*)| Creates a POLYGON from 
*lineString* with the given holes (which are required to be closed LINESTRINGs)
+| h | ST_MinimumDiameter(geom) | Returns the minimum diameter of *geom*
+| h | ST_MinimumRectangle(geom) | Returns the minimum rectangle enclosing 
*geom*
+| h | ST_OctogonalEnvelope(geom) | Returns the octogonal envelope of *geom*
 | o | ST_Point(x, y [, z ]) | Constructs a point from two or three coordinates
 
 Not implemented:
 
-* ST_BoundingCircle(geom) Returns the minimum bounding circle of *geom*
-* ST_Expand(geom, distance) Expands *geom*'s envelope
-* ST_Expand(geom, deltaX, deltaY) Expands *geom*'s envelope
-* ST_MakeEllipse(point, width, height) Constructs an ellipse
-* ST_MakePolygon(lineString [, hole ]*) Creates a POLYGON from *lineString* 
with the given holes (which are required to be closed LINESTRINGs)
-* ST_MinimumDiameter(geom) Returns the minimum diameter of *geom*
-* ST_MinimumRectangle(geom) Returns the minimum rectangle enclosing *geom*
-* ST_OctogonalEnvelope(geom) Returns the octogonal envelope of *geom*
 * ST_RingBuffer(geom, distance, bufferCount [, endCapStyle [, doDifference]]) 
Returns a MULTIPOLYGON of buffers centered at *geom* and of increasing buffer 
size
 
 ### Geometry creation functions (3D)
@@ -2227,47 +2236,47 @@ Not implemented:
 | C | Operator syntax      | Description
 |:- |:-------------------- |:-----------
 | o | ST_Boundary(geom [, srid ]) | Returns the boundary of *geom*
+| o | ST_Centroid(geom) | Returns the centroid of *geom*
+| o | ST_CoordDim(geom) | Returns the dimension of the coordinates of *geom*
+| o | ST_Dimension(geom) | Returns the dimension of *geom*
 | o | ST_Distance(geom1, geom2) | Returns the distance between *geom1* and 
*geom2*
+| h | ST_ExteriorRing(geom) | Returns the exterior ring of *geom*, or null if 
*geom* is not a polygon
 | o | ST_GeometryType(geom) | Returns the type of *geom*
 | o | ST_GeometryTypeCode(geom) | Returns the OGC SFS type code of *geom*
+| p | ST_EndPoint(lineString) | Returns the last coordinate of *geom*
 | o | ST_Envelope(geom [, srid ]) | Returns the envelope of *geom* (which may 
be a GEOMETRYCOLLECTION) as a GEOMETRY
+| o | ST_Extent(geom) | Returns the minimum bounding box of *geom* (which may 
be a GEOMETRYCOLLECTION)
+| h | ST_GeometryN(geomCollection, n) | Returns the *n*th GEOMETRY of 
*geomCollection*
+| h | ST_InteriorRingN(geom) | Returns the nth interior ring of *geom*, or 
null if *geom* is not a polygon
+| h | ST_IsClosed(geom) | Returns whether *geom* is a closed LINESTRING or 
MULTILINESTRING
+| o | ST_IsEmpty(geom) | Returns whether *geom* is empty
+| o | ST_IsRectangle(geom) | Returns whether *geom* is a rectangle
+| h | ST_IsRing(geom) | Returns whether *geom* is a closed and simple 
line-string or MULTILINESTRING
+| o | ST_IsSimple(geom) | Returns whether *geom* is simple
+| o | ST_IsValid(geom) | Returns whether *geom* is valid
+| h | ST_NPoints(geom)  | Returns the number of points in *geom*
+| h | ST_NumGeometries(geom) | Returns the number of geometries in *geom* (1 
if it is not a GEOMETRYCOLLECTION)
+| h | ST_NumInteriorRing(geom) | Synonym for `ST_NumInteriorRings`
+| h | ST_NumInteriorRings(geom) | Returns the number of interior rings of 
*geom*
+| h | ST_NumPoints(geom) | Returns the number of points in *geom*
+| p | ST_PointN(geom, n) | Returns the *n*th point of a *geom*
+| p | ST_PointOnSurface(geom) | Returns an interior or boundary point of *geom*
+| o | ST_SRID(geom) | Returns SRID value of *geom* or 0 if it does not have one
+| p | ST_StartPoint(geom) | Returns the first point of *geom*
 | o | ST_X(geom) | Returns the x-value of the first coordinate of *geom*
+| o | ST_XMax(geom) | Returns the maximum x-value of *geom*
+| o | ST_XMin(geom) | Returns the minimum x-value of *geom*
 | o | ST_Y(geom) | Returns the y-value of the first coordinate of *geom*
+| o | ST_YMax(geom) | Returns the maximum y-value of *geom*
+| o | ST_YMin(geom) | Returns the minimum y-value of *geom*
 
 Not implemented:
 
-* ST_Centroid(geom) Returns the centroid of *geom* (which may be a 
GEOMETRYCOLLECTION)
 * ST_CompactnessRatio(polygon) Returns the square root of *polygon*'s area 
divided by the area of the circle with circumference equal to its perimeter
-* ST_CoordDim(geom) Returns the dimension of the coordinates of *geom*
-* ST_Dimension(geom) Returns the dimension of *geom*
-* ST_EndPoint(lineString) Returns the last coordinate of *lineString*
-* ST_Envelope(geom [, srid ]) Returns the envelope of *geom* (which may be a 
GEOMETRYCOLLECTION) as a GEOMETRY
 * ST_Explode(query [, fieldName]) Explodes the GEOMETRYCOLLECTIONs in the 
*fieldName* column of a query into multiple geometries
-* ST_Extent(geom) Returns the minimum bounding box of *geom* (which may be a 
GEOMETRYCOLLECTION)
-* ST_ExteriorRing(polygon) Returns the exterior ring of *polygon* as a 
linear-ring
-* ST_GeometryN(geomCollection, n) Returns the *n*th GEOMETRY of 
*geomCollection*
-* ST_InteriorRingN(polygon, n) Returns the *n*th interior ring of *polygon*
-* ST_IsClosed(geom) Returns whether *geom* is a closed LINESTRING or 
MULTILINESTRING
-* ST_IsEmpty(geom) Returns whether *geom* is empty
-* ST_IsRectangle(geom) Returns whether *geom* is a rectangle
-* ST_IsRing(geom) Returns whether *geom* is a closed and simple line-string or 
MULTILINESTRING
-* ST_IsSimple(geom) Returns whether *geom* is simple
-* ST_IsValid(geom) Returns whether *geom* is valid
 * ST_IsValidDetail(geom [, selfTouchValid ]) Returns a valid detail as an 
array of objects
 * ST_IsValidReason(geom [, selfTouchValid ]) Returns text stating whether 
*geom* is valid, and if not valid, a reason why
-* ST_NPoints(geom) Returns the number of points in *geom*
-* ST_NumGeometries(geom) Returns the number of geometries in *geom* (1 if it 
is not a GEOMETRYCOLLECTION)
-* ST_NumInteriorRing(geom) Synonym for `ST_NumInteriorRings`
-* ST_NumInteriorRings(geom) Returns the number of interior rings of *geom*
-* ST_NumPoints(lineString) Returns the number of points in *lineString*
-* ST_PointN(geom, n) Returns the *n*th point of a *lineString*
-* ST_PointOnSurface(geom) Returns an interior or boundary point of *geom*
-* ST_SRID(geom) Returns SRID value of *geom* or 0 if it does not have one
-* ST_StartPoint(lineString) Returns the first coordinate of *lineString*
-* ST_XMax(geom) Returns the maximum x-value of *geom*
-* ST_XMin(geom) Returns the minimum x-value of *geom*
-* ST_YMax(geom) Returns the maximum y-value of *geom*
-* ST_YMin(geom) Returns the minimum y-value of *geom*
+
 
 #### Geometry properties (3D)
 
@@ -2275,11 +2284,8 @@ Not implemented:
 |:- |:-------------------- |:-----------
 | p | ST_Is3D(s) | Returns whether *geom* has at least one z-coordinate
 | o | ST_Z(geom) | Returns the z-value of the first coordinate of *geom*
-
-Not implemented:
-
-* ST_ZMax(geom) Returns the maximum z-value of *geom*
-* ST_ZMin(geom) Returns the minimum z-value of *geom*
+| o | ST_ZMax(geom) | Returns the maximum z-value of *geom*
+| o | ST_ZMin(geom) | Returns the minimum z-value of *geom*
 
 ### Geometry predicates
 
@@ -2287,6 +2293,7 @@ Not implemented:
 |:- |:-------------------- |:-----------
 | o | ST_Contains(geom1, geom2) | Returns whether *geom1* contains *geom2*
 | p | ST_ContainsProperly(geom1, geom2) | Returns whether *geom1* contains 
*geom2* but does not intersect its boundary
+| p | ST_Covers(geom1, geom2) | Returns whether no point in *geom2* is outside 
*geom1*
 | o | ST_Crosses(geom1, geom2) | Returns whether *geom1* crosses *geom2*
 | o | ST_Disjoint(geom1, geom2) | Returns whether *geom1* and *geom2* are 
disjoint
 | p | ST_DWithin(geom1, geom2, distance) | Returns whether *geom1* and *geom* 
are within *distance* of one another
@@ -2294,15 +2301,14 @@ Not implemented:
 | o | ST_Equals(geom1, geom2) | Returns whether *geom1* equals *geom2*
 | o | ST_Intersects(geom1, geom2) | Returns whether *geom1* intersects *geom2*
 | o | ST_Overlaps(geom1, geom2) | Returns whether *geom1* overlaps *geom2*
+| o | ST_Relate(geom1, geom2) | Returns the DE-9IM intersection matrix of 
*geom1* and *geom2*
+| o | ST_Relate(geom1, geom2, iMatrix) | Returns whether *geom1* and *geom2* 
are related by the given intersection matrix *iMatrix*
 | o | ST_Touches(geom1, geom2) | Returns whether *geom1* touches *geom2*
 | o | ST_Within(geom1, geom2) | Returns whether *geom1* is within *geom2*
 
 Not implemented:
 
-* ST_Covers(geom1, geom2) Returns whether no point in *geom2* is outside 
*geom1*
 * ST_OrderingEquals(geom1, geom2) Returns whether *geom1* equals *geom2* and 
their coordinates and component Geometries are listed in the same order
-* ST_Relate(geom1, geom2) Returns the DE-9IM intersection matrix of *geom1* 
and *geom2*
-* ST_Relate(geom1, geom2, iMatrix) Returns whether *geom1* and *geom2* are 
related by the given intersection matrix *iMatrix*
 
 #### Geometry operators (2D)
 
@@ -2311,23 +2317,27 @@ The following functions combine 2D geometries.
 | C | Operator syntax      | Description
 |:- |:-------------------- |:-----------
 | o | ST_Buffer(geom, distance [, quadSegs \| style ]) | 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*
 | o | ST_Union(geom1, geom2) | Computes the union of *geom1* and *geom2*
 | o | ST_Union(geomCollection) | Computes the union of the geometries in 
*geomCollection*
 
 See also: the `ST_Union` aggregate function.
 
-Not implemented:
+#### Affine transformation functions (3D and 2D)
 
-* ST_ConvexHull(geom) Computes the smallest convex polygon that contains all 
the points in *geom*
-* ST_Difference(geom1, geom2) Computes the difference between two geometries
-* ST_Intersection(geom1, geom2) Computes the intersection of two geometries
-* ST_SymDifference(geom1, geom2) Computes the symmetric difference between two 
geometries
+The following functions transform 2D geometries.
 
-#### Affine transformation functions (3D and 2D)
+| C | Operator syntax      | Description
+|:- |:-------------------- |:-----------
+| o | ST_Rotate(geom, angle [, origin \| x, y]) | Rotates a *geom* 
counter-clockwise by *angle* (in radians) about *origin* (or the point (*x*, 
*y*))
+| o | ST_Scale(geom, xFactor, yFactor) | Scales *geom* by multiplying the 
ordinates by the indicated scale factors
+| o | ST_Translate(geom, x, y) | Translates *geom* by the vector (x, y)
 
 Not implemented:
 
-* ST_Rotate(geom, angle [, origin \| x, y]) Rotates a *geom* counter-clockwise 
by *angle* (in radians) about *origin* (or the point (*x*, *y*))
 * ST_Scale(geom, xFactor, yFactor [, zFactor ]) Scales *geom* by multiplying 
the ordinates by the indicated scale factors
 * ST_Translate(geom, x, y, [, z]) Translates *geom*
 
@@ -2390,18 +2400,23 @@ Not implemented:
 
 The following functions process geometries.
 
+| C | Operator syntax      | Description
+|:- |:-------------------- |:-----------
+| o | ST_LineMerge(geom)  | Merges a collection of linear components to form a 
line-string of maximal length
+| o | ST_MakeValid(geom)  | Makes a valid geometry of a given invalid geometry
+| o | ST_Polygonize(geom)  | Creates a MULTIPOLYGON from edges of *geom*
+| o | ST_PrecisionReducer(geom, n) | Reduces *geom*'s precision to *n* decimal 
places
+| 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
+
 Not implemented:
 
 * ST_LineIntersector(geom1, geom2) Splits *geom1* (a line-string) with *geom2*
 * ST_LineMerge(geom) Merges a collection of linear components to form a 
line-string of maximal length
 * ST_MakeValid(geom [, preserveGeomDim [, preserveDuplicateCoord [, 
preserveCoordDim]]]) Makes *geom* valid
-* ST_Polygonize(geom) Creates a MULTIPOLYGON from edges of *geom*
-* ST_PrecisionReducer(geom, n) Reduces *geom*'s precision to *n* decimal places
 * 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_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
-* ST_SimplifyPreserveTopology(geom) Simplifies *geom*, preserving its topology
-* ST_Snap(geom1, geom2, tolerance) Snaps *geom1* and *geom2* together
 * 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
diff --git a/site/_docs/spatial.md b/site/_docs/spatial.md
index a74e4ce758..d0b2ec85d4 100644
--- a/site/_docs/spatial.md
+++ b/site/_docs/spatial.md
@@ -151,7 +151,7 @@ But for safety, Calcite applies the original predicate, to 
remove false positive
 ## Acknowledgements
 
 Calcite's OpenGIS implementation uses the
-[Esri geometry API](https://github.com/Esri/geometry-api-java). Thanks for the
+[JTS Topology Suite](https://github.com/locationtech/jts). Thanks for the
 help we received from their community.
 
 While developing this feature, we made extensive use of the

Reply via email to