You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sis.apache.org by js...@apache.org on 2026/09/11 09:18:09 UTC

(sis) branch geoapi-4.0 updated (8d99394ab2 -> 57b466cbb6)

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

asf-gitbox-commits pushed a change to branch geoapi-4.0
in repository https://gitbox.apache.org/repos/asf/sis.git


    from 8d99394ab2 doc(Geometry): add javadoc on remaining geometry subtypes interfaces
     new cb2de8f344 refactor(Geometry): remove unecessary public keyword on interfaces
     new 5e5f583668 feat(Geometry): define all describe parameters on geometry interfaces
     new 57b466cbb6 feat(Geometry): add WKT encoding

The 3 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .../org.apache.sis.geometry/main/module-info.java  |   1 +
 .../org/apache/sis/geometries/AttributesType.java  |  30 +-
 .../main/org/apache/sis/geometries/Curve.java      |  24 +-
 .../main/org/apache/sis/geometries/Empty.java      | 241 ++++++-
 .../main/org/apache/sis/geometries/Geometry.java   |  67 +-
 .../apache/sis/geometries/GeometryCollection.java  |  23 +-
 .../org/apache/sis/geometries/GeometryFactory.java |   2 +-
 .../main/org/apache/sis/geometries/Point.java      |  95 ++-
 .../main/org/apache/sis/geometries/Primitive.java  |  12 +
 .../main/org/apache/sis/geometries/Prism.java      |   6 +-
 .../main/org/apache/sis/geometries/Solid.java      |  14 +-
 .../main/org/apache/sis/geometries/Surface.java    |  10 +
 .../WellKnownBinary.java}                          |   6 +-
 .../sis/geometries/adapter/WellKnownText.java      | 512 ++++++++++++++
 .../geometries/adapter/WellKnownTextParser.java    | 786 +++++++++++++++++++++
 .../main/org/apache/sis/geometries/curve/Arc.java  |  12 +
 .../apache/sis/geometries/curve/ArcByBulge.java    |   6 +-
 .../sis/geometries/curve/ArcByCenterPoint.java     |   8 +-
 .../apache/sis/geometries/curve/BSplineCurve.java  |  11 +
 .../org/apache/sis/geometries/curve/Bezier.java    |  13 +
 .../org/apache/sis/geometries/curve/Circle.java    |  10 +
 .../sis/geometries/curve/CircularString.java       |   7 +-
 .../org/apache/sis/geometries/curve/Clothoid.java  |  22 +
 .../apache/sis/geometries/curve/CompoundCurve.java |   4 +-
 .../org/apache/sis/geometries/curve/Conic.java     |  12 +
 .../apache/sis/geometries/curve/CubicSpline.java   |  10 +
 .../apache/sis/geometries/curve/EllipticArc.java   |  11 +
 .../org/apache/sis/geometries/curve/Geodesic.java  |  20 +-
 .../apache/sis/geometries/curve/LineString.java    |  21 +-
 .../apache/sis/geometries/curve/LinearRing.java    |  45 +-
 .../apache/sis/geometries/curve/MultiCurve.java    |  16 +-
 .../sis/geometries/curve/MultiLineString.java      |  33 +-
 .../org/apache/sis/geometries/curve/NurbCurve.java |  12 +-
 .../sis/geometries/curve/PolynomialSpline.java     |  24 +
 .../apache/sis/geometries/curve/ProductCurve.java  |  37 +
 .../org/apache/sis/geometries/curve/Rhumb.java     |  20 +-
 .../org/apache/sis/geometries/curve/Spiral.java    |  12 +
 .../internal/shared/AbstractGeometry.java          |  36 -
 .../internal/shared/DefaultArcByBulge.java         |   9 -
 .../internal/shared/DefaultArcByCenterPoint.java   |  11 -
 .../internal/shared/DefaultCircularString.java     |   7 -
 .../internal/shared/DefaultCompoundCurve.java      |  10 -
 .../internal/shared/DefaultCurvePolygon.java       |   8 -
 .../internal/shared/DefaultPolyhedralSurface.java  |   9 -
 .../internal/shared/DefaultPolyhedron.java         |  20 -
 .../internal/shared/DefaultReversedCurve.java      |   5 -
 .../internal/shared/DefaultReversedSurface.java    |   4 -
 .../internal/shared/DefaultTriangle.java           |   5 -
 .../shared/DefaultTriangulatedSurface.java         |   8 -
 .../operation/triangulate/delaunay/Delaunay.java   |   1 -
 .../apache/sis/geometries/point/MultiPoint.java    |  30 +-
 .../apache/sis/geometries/solid/BSolidSpline.java  |  12 +
 .../sis/geometries/solid/MultiPolyhedron.java      |   4 +-
 .../sis/geometries/solid/ParametricCurveSolid.java |  13 +
 .../apache/sis/geometries/solid/Polyhedron.java    |  18 +-
 .../org/apache/sis/geometries/solid/Sphere.java    |  12 +
 .../sis/geometries/surface/BSplineSurface.java     |  14 +
 .../sis/geometries/surface/BilinearGrid.java       |  13 +
 .../sis/geometries/surface/CurvePolygon.java       |  22 +-
 .../sis/geometries/surface/MultiPolygon.java       |  33 +-
 .../sis/geometries/surface/MultiSurface.java       |  16 +-
 .../apache/sis/geometries/surface/NurbSurface.java |  25 +-
 .../geometries/surface/ParametricCurveSurface.java |  13 +
 .../org/apache/sis/geometries/surface/Polygon.java |  34 +-
 .../sis/geometries/surface/PolyhedralSurface.java  |  19 +-
 .../apache/sis/geometries/surface/PreparedTIN.java |   4 +-
 .../org/apache/sis/geometries/surface/TIN.java     |  53 +-
 .../apache/sis/geometries/surface/Triangle.java    |  30 +-
 .../geometries/surface/TriangulatedSurface.java    |  12 +
 .../org/apache/sis/geometries/PreparedTINTest.java |   8 +-
 .../sis/geometries/adapter/WellKnownTextTest.java  | 299 ++++++++
 .../sis/geometries/mesh/PrimitiveVisitorTest.java  | 140 ++--
 .../org/apache/sis/referencing/dggs/s2/S2Test.java |   2 +-
 73 files changed, 2608 insertions(+), 546 deletions(-)
 copy incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/{cs/Projection.java => adapter/WellKnownBinary.java} (90%)
 create mode 100644 incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/adapter/WellKnownText.java
 create mode 100644 incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/adapter/WellKnownTextParser.java
 create mode 100644 incubator/src/org.apache.sis.geometry/test/org/apache/sis/geometries/adapter/WellKnownTextTest.java


(sis) 02/03: feat(Geometry): define all describe parameters on geometry interfaces

Posted by js...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

asf-gitbox-commits pushed a commit to branch geoapi-4.0
in repository https://gitbox.apache.org/repos/asf/sis.git

commit 5e5f58366880601feb49d48018112d897a2be5f2
Author: jsorel <jo...@geomatys.com>
AuthorDate: Thu Sep 10 15:30:15 2026 +0200

    feat(Geometry): define all describe parameters on geometry interfaces
---
 .../main/org/apache/sis/geometries/Curve.java      |  24 ++-
 .../main/org/apache/sis/geometries/Empty.java      | 239 +++++++++++++++++++++
 .../main/org/apache/sis/geometries/Geometry.java   |  13 +-
 .../apache/sis/geometries/GeometryCollection.java  |  15 ++
 .../main/org/apache/sis/geometries/Point.java      |  65 ++++++
 .../main/org/apache/sis/geometries/Primitive.java  |  12 ++
 .../main/org/apache/sis/geometries/Solid.java      |  12 ++
 .../main/org/apache/sis/geometries/Surface.java    |  10 +
 .../main/org/apache/sis/geometries/curve/Arc.java  |  12 ++
 .../apache/sis/geometries/curve/BSplineCurve.java  |  11 +
 .../org/apache/sis/geometries/curve/Bezier.java    |  13 ++
 .../org/apache/sis/geometries/curve/Circle.java    |  10 +
 .../org/apache/sis/geometries/curve/Clothoid.java  |  22 ++
 .../org/apache/sis/geometries/curve/Conic.java     |  12 ++
 .../apache/sis/geometries/curve/CubicSpline.java   |  10 +
 .../apache/sis/geometries/curve/EllipticArc.java   |  11 +
 .../apache/sis/geometries/curve/LinearRing.java    |  30 +++
 .../apache/sis/geometries/curve/MultiCurve.java    |  12 ++
 .../sis/geometries/curve/MultiLineString.java      |  12 ++
 .../sis/geometries/curve/PolynomialSpline.java     |  24 +++
 .../apache/sis/geometries/curve/ProductCurve.java  |  37 ++++
 .../org/apache/sis/geometries/curve/Spiral.java    |  12 ++
 .../internal/shared/DefaultPolyhedron.java         |   6 -
 .../apache/sis/geometries/point/MultiPoint.java    |  12 ++
 .../apache/sis/geometries/solid/BSolidSpline.java  |  12 ++
 .../sis/geometries/solid/ParametricCurveSolid.java |  13 ++
 .../apache/sis/geometries/solid/Polyhedron.java    |  12 ++
 .../org/apache/sis/geometries/solid/Sphere.java    |  12 ++
 .../sis/geometries/surface/BSplineSurface.java     |  14 ++
 .../sis/geometries/surface/BilinearGrid.java       |  13 ++
 .../sis/geometries/surface/MultiPolygon.java       |  12 ++
 .../sis/geometries/surface/MultiSurface.java       |  12 ++
 .../apache/sis/geometries/surface/NurbSurface.java |  23 ++
 .../geometries/surface/ParametricCurveSurface.java |  13 ++
 .../org/apache/sis/geometries/surface/Polygon.java |  12 ++
 .../sis/geometries/surface/PolyhedralSurface.java  |  13 ++
 .../org/apache/sis/geometries/surface/TIN.java     |  13 ++
 .../geometries/surface/TriangulatedSurface.java    |  12 ++
 38 files changed, 804 insertions(+), 18 deletions(-)

diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/Curve.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/Curve.java
index 19d84ffc8d..c120566a77 100644
--- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/Curve.java
+++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/Curve.java
@@ -80,6 +80,16 @@ public sealed interface Curve extends Orientable
                 DefaultReversedCurve
 {
 
+    /**
+     * Returns 1: a curve bounds a length.
+     *
+     * @see ISO 19107:2019 - 6.4.4.22
+     */
+    @Override
+    default int getTopologicDimension() {
+        return 1;
+    }
+
     /**
      * Points lying on this curve, the first one being the start point and the last one the end point.
      *
@@ -159,6 +169,18 @@ public sealed interface Curve extends Orientable
         return points.getPosition(0).equals(points.getPosition(size - 1), 0);
     }
 
+    /**
+     * Returns whether this curve closes on itself,
+     * which for a curve means that its start point is its end point.
+     *
+     * @see ISO 19107:2019 - 6.4.4.14
+     */
+    @UML(identifier="isCycle", specification=ISO_19107)
+    @Override
+    default boolean isCycle() {
+        return isClosed();
+    }
+
     /**
      * Returns whether this curve is both closed and simple,
      * in which case it is a valid boundary component of a surface.
@@ -176,7 +198,7 @@ public sealed interface Curve extends Orientable
      */
     @UML(identifier="isRing", specification=ISO_19107)
     default boolean isRing() {
-        throw new UnsupportedOperationException();
+        return isCycle() && isSimple();
     }
 
     /**
diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/Empty.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/Empty.java
index eba8423068..8d6631a3a2 100644
--- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/Empty.java
+++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/Empty.java
@@ -16,9 +16,13 @@
  */
 package org.apache.sis.geometries;
 
+import javax.measure.quantity.Length;
 import org.apache.sis.geometries.internal.shared.DefaultEmpty;
+import org.apache.sis.measure.Quantities;
+import org.apache.sis.measure.Units;
 import static org.opengis.annotation.Specification.ISO_19107;
 import org.opengis.annotation.UML;
+import org.opengis.geometry.DirectPosition;
 
 
 /**
@@ -77,9 +81,244 @@ public sealed interface Empty extends Geometry
         return TYPE;
     }
 
+    /**
+     * Returns −1: the empty set has no dimension.
+     *
+     * @see ISO 19107:2019 - 6.4.4.22
+     */
     @Override
     default int getTopologicDimension() {
         return -1;
     }
 
+    /**
+     * Returns {@code true}: the boundary of the empty set is empty.
+     *
+     * @see ISO 19107:2019 - 6.4.4.14
+     */
+    @UML(identifier="isCycle", specification=ISO_19107)
+    @Override
+    default boolean isCycle() {
+        return true;
+    }
+
+    /**
+     * Returns {@code true}: the empty set has no position, therefore no anomalous position.
+     *
+     * @see ISO 19107:2019 - 6.4.4.15
+     */
+    @UML(identifier="isSimple", specification=ISO_19107)
+    @Override
+    default boolean isSimple() {
+        return true;
+    }
+
+    /**
+     * Returns {@code true}: the empty set is always a valid geometry.
+     *
+     * @see ISO 19107:2019 - 6.4.4.16
+     */
+    @UML(identifier="isValid", specification=ISO_19107)
+    @Override
+    default boolean isValid() {
+        return true;
+    }
+
+    /**
+     * Returns {@code this}: the boundary of the empty set is empty.
+     *
+     * @see ISO 19107:2019 - 6.4.4.7
+     */
+    @UML(identifier="boundary", specification=ISO_19107)
+    @Override
+    default Geometry boundary() {
+        return this;
+    }
+
+    /**
+     * Returns {@code this}: the empty set is topologically closed.
+     *
+     * @see ISO 19107:2019 - 6.4.4.9
+     */
+    @UML(identifier="closure", specification=ISO_19107)
+    @Override
+    default Geometry getClosure() {
+        return this;
+    }
+
+    /**
+     * Returns {@code this}: the convex hull of the empty set is empty.
+     *
+     * @see ISO 19107:2019 - 6.4.4.10
+     */
+    @UML(identifier="convexHull", specification=ISO_19107)
+    @Override
+    default Geometry convexHull() {
+        return this;
+    }
+
+    /**
+     * Returns {@code this}: there is no position to grow a buffer around.
+     *
+     * @see ISO 19107:2019 - 6.4.4.24
+     */
+    @Override
+    default Geometry buffer(double distance) {
+        return this;
+    }
+
+    /**
+     * Returns {@code this}: there is no position to grow a buffer around.
+     *
+     * @see ISO 19107:2019 - 6.4.4.24
+     */
+    @UML(identifier="buffer", specification=ISO_19107)
+    @Override
+    default Geometry buffer(Length radius) {
+        return this;
+    }
+
+    /**
+     * Returns {@code null}: the centroid of the empty set is undefined.
+     *
+     * @see ISO 19107:2019 - 6.4.4.8
+     */
+    @UML(identifier="centroid", specification=ISO_19107)
+    @Override
+    default Point getCentroid() {
+        return null;
+    }
+
+    /**
+     * Returns {@code null}: the empty set has no interior position.
+     *
+     * @see ISO 19107:2019 - 6.4.4.19
+     */
+    @UML(identifier="representativePoint", specification=ISO_19107)
+    @Override
+    default Point getRepresentativePoint() {
+        return null;
+    }
+
+    /**
+     * Returns an infinite distance: no position of this geometry can come close to another geometry.
+     *
+     * @see ISO 19107:2019 - 6.4.4.26
+     */
+    @UML(identifier="distance", specification=ISO_19107)
+    @Override
+    default Length distance(Geometry other) {
+        return Quantities.create(Double.POSITIVE_INFINITY, Units.METRE);
+    }
+
+    /**
+     * Returns {@code this}: {@code ∅ ∩ A = ∅}.
+     *
+     * @see ISO 19107:2019 - 6.4.8.4
+     */
+    @UML(identifier="intersection", specification=ISO_19107)
+    @Override
+    default Geometry intersection(Geometry other) {
+        return this;
+    }
+
+    /**
+     * Returns the given geometry: {@code ∅ ∪ A = A}.
+     *
+     * @see ISO 19107:2019 - 6.4.8.7
+     */
+    @UML(identifier="union", specification=ISO_19107)
+    @Override
+    default Geometry union(Geometry other) {
+        return other;
+    }
+
+    /**
+     * Returns {@code this}: {@code ∅ − A = ∅}.
+     *
+     * @see ISO 19107:2019 - 6.4.8.5
+     */
+    @UML(identifier="difference", specification=ISO_19107)
+    @Override
+    default Geometry difference(Geometry other) {
+        return this;
+    }
+
+    /**
+     * Returns the given geometry: {@code (∅ − A) ∪ (A − ∅) = A}.
+     *
+     * @see ISO 19107:2019 - 6.4.8.6
+     */
+    @UML(identifier="symDifference", specification=ISO_19107)
+    @Override
+    default Geometry symDifference(Geometry other) {
+        return other;
+    }
+
+    /**
+     * Returns {@code false}: the empty set contains no position at all.
+     *
+     * @see ISO 19107:2019 - 6.4.2
+     */
+    @UML(identifier="contains", specification=ISO_19107)
+    @Override
+    default boolean contains(DirectPosition element) {
+        return false;
+    }
+
+    /**
+     * Returns whether the given geometry is empty: the empty set is a superset of itself only.
+     *
+     * @see ISO 19107:2019 - 10.8.6.3.2
+     */
+    @UML(identifier="contains", specification=ISO_19107)
+    @Override
+    default boolean contains(Geometry other) {
+        return other.isEmpty();
+    }
+
+    /**
+     * Returns {@code true}: the empty set is a subset of every geometry.
+     *
+     * @see ISO 19107:2019 - 10.8.6.3.7
+     */
+    @UML(identifier="within", specification=ISO_19107)
+    @Override
+    default boolean within(Geometry other) {
+        return true;
+    }
+
+    /**
+     * Returns whether the given geometry is empty: any two empty geometries are equal.
+     *
+     * @see ISO 19107:2019 - 6.4.4.27, 10.8.6.3.1
+     */
+    @UML(identifier="equals", specification=ISO_19107)
+    @Override
+    default boolean equal(Geometry other) {
+        return other.isEmpty();
+    }
+
+    /**
+     * Returns {@code true}: the empty set has no position in common with any geometry.
+     *
+     * @see ISO 19107:2019 - 10.8.6.3.3
+     */
+    @UML(identifier="disjoint", specification=ISO_19107)
+    @Override
+    default boolean disjoint(Geometry other) {
+        return true;
+    }
+
+    /**
+     * Returns {@code false}: the empty set has no position in common with any geometry.
+     *
+     * @see ISO 19107:2019 - 10.8.6.3.4
+     */
+    @UML(identifier="intersects", specification=ISO_19107)
+    @Override
+    default boolean intersects(Geometry other) {
+        return false;
+    }
+
 }
diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/Geometry.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/Geometry.java
index 98ecb67f16..f5f19cf5a6 100644
--- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/Geometry.java
+++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/Geometry.java
@@ -222,17 +222,8 @@ public sealed interface Geometry
      */
     @UML(identifier="topologicalDimension", specification=ISO_19107)
     default int getTopologicDimension() {
-        if (this instanceof Empty) {
-            return -1;
-        } else if (this instanceof Point) {
-            return 0;
-        } else if (this instanceof Curve) {
-            return 1;
-        } else if (this instanceof Surface) {
-            return 2;
-        } else if (this instanceof Solid) {
-            return 3;
-        }
+        //TODO remove this method default when all classes implement it.
+        //Empty, Point, Curve, Surface, Solid and GeometryCollection already do.
         throw new UnsupportedOperationException();
     }
 
diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/GeometryCollection.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/GeometryCollection.java
index 9876ede51d..1aacaed168 100644
--- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/GeometryCollection.java
+++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/GeometryCollection.java
@@ -114,6 +114,21 @@ public sealed interface GeometryCollection<T extends Geometry> extends Geometry
         return getNumGeometries() == 0;
     }
 
+    /**
+     * Returns the largest topological dimension among the elements of this collection,
+     * or −1 if this collection is empty.
+     *
+     * @see ISO 19107:2019 - 6.4.4.22
+     */
+    @Override
+    default int getTopologicDimension() {
+        int dimension = -1;
+        for (int i = 0, n = getNumGeometries(); i < n; i++) {
+            dimension = Math.max(dimension, getGeometryN(i).getTopologicDimension());
+        }
+        return dimension;
+    }
+
     /**
      * Returns the number of geometries in this GeometryCollection.
      * It can only change by adding or removing elements.
diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/Point.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/Point.java
index 81f077e7b2..4026b590c5 100644
--- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/Point.java
+++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/Point.java
@@ -16,6 +16,7 @@
  */
 package org.apache.sis.geometries;
 
+import java.util.List;
 import org.apache.sis.geometries.internal.shared.AbstractGeometry;
 import org.apache.sis.geometries.internal.shared.ArrayDataPoints;
 import org.apache.sis.geometries.internal.shared.DefaultPoint;
@@ -160,6 +161,70 @@ public sealed interface Point extends Primitive
         return TYPE;
     }
 
+    /**
+     * Returns 0: a point is a single location.
+     *
+     * @see ISO 19107:2019 - 6.4.4.22
+     */
+    @Override
+    default int getTopologicDimension() {
+        return 0;
+    }
+
+    /**
+     * Returns an empty list: a point cannot be decomposed.
+     *
+     * @see ISO 19107:2019 - 6.4.11.2
+     */
+    @Override
+    default List<Primitive> getSegments() {
+        return List.of();
+    }
+
+    /**
+     * Returns {@code true}: the boundary of a point is empty, therefore a point closes on itself.
+     *
+     * @see ISO 19107:2019 - 6.4.4.14
+     */
+    @UML(identifier="isCycle", specification=ISO_19107)
+    @Override
+    default boolean isCycle() {
+        return true;
+    }
+
+    /**
+     * Returns {@code true}: a single location can neither self-intersect nor self-tangent.
+     *
+     * @see ISO 19107:2019 - 6.4.4.15
+     */
+    @UML(identifier="isSimple", specification=ISO_19107)
+    @Override
+    default boolean isSimple() {
+        return true;
+    }
+
+    /**
+     * Returns {@code this}: a point is its own centroid.
+     *
+     * @see ISO 19107:2019 - 6.4.4.8
+     */
+    @UML(identifier="centroid", specification=ISO_19107)
+    @Override
+    default Point getCentroid() {
+        return this;
+    }
+
+    /**
+     * Returns {@code this}: a point is interior to itself.
+     *
+     * @see ISO 19107:2019 - 6.4.4.19
+     */
+    @UML(identifier="representativePoint", specification=ISO_19107)
+    @Override
+    default Point getRepresentativePoint() {
+        return this;
+    }
+
     @Override
     default Envelope getEnvelope() {
         final Tuple<?> first = getPosition();
diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/Primitive.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/Primitive.java
index f1a202e857..750cbde8d5 100644
--- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/Primitive.java
+++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/Primitive.java
@@ -53,6 +53,18 @@ public sealed interface Primitive extends Geometry
                 Solid
 {
 
+    /**
+     * Returns {@link BoundaryType#METRIC}: a primitive is not an aggregate,
+     * therefore its boundary is unambiguous and is the metric one.
+     *
+     * @see ISO 19107:2019 - 6.4.3, 10.8.3
+     */
+    @UML(identifier="boundaryType", specification=ISO_19107)
+    @Override
+    default BoundaryType getBoundaryType() {
+        return BoundaryType.METRIC;
+    }
+
     /**
      * Smaller primitives of the same dimension contained in this primitive,
      * each of them defining a portion of it.
diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/Solid.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/Solid.java
index 8b200d8fbd..16c87596e9 100644
--- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/Solid.java
+++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/Solid.java
@@ -71,6 +71,18 @@ public sealed interface Solid extends Primitive
         return 3;
     }
 
+    /**
+     * Returns {@code false}: a solid of finite size in a 3-dimensional coordinate space
+     * always has a boundary, therefore it never closes on itself.
+     *
+     * @see ISO 19107:2019 - 6.4.4.14
+     */
+    @UML(identifier="isCycle", specification=ISO_19107)
+    @Override
+    default boolean isCycle() {
+        return false;
+    }
+
     /**
      * Shells bounding this solid, each of them a closed surface without boundary.
      *
diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/Surface.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/Surface.java
index 110175af39..d725c2ca94 100644
--- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/Surface.java
+++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/Surface.java
@@ -74,6 +74,16 @@ public sealed interface Surface extends Orientable
                 DefaultReversedSurface
 {
 
+    /**
+     * Returns 2: a surface bounds an area.
+     *
+     * @see ISO 19107:2019 - 6.4.4.22
+     */
+    @Override
+    default int getTopologicDimension() {
+        return 2;
+    }
+
     /**
      * The area of this Surface, as measured in the spatial reference system of this Surface.
      *
diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/Arc.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/Arc.java
index f5f56ba827..c87fe99bb4 100644
--- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/Arc.java
+++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/Arc.java
@@ -17,6 +17,7 @@
 package org.apache.sis.geometries.curve;
 
 import java.util.List;
+import org.apache.sis.geometries.CurveInterpolation;
 import org.apache.sis.geometries.DataPoints;
 import org.apache.sis.maths.Array;
 import org.apache.sis.maths.Vector;
@@ -37,6 +38,17 @@ public sealed interface Arc extends Conic
         permits Circle
 {
 
+    /**
+     * Returns {@link CurveInterpolation#CIRCULAR}.
+     *
+     * @see ISO 19107:2019 - 7.9.2
+     */
+    @UML(identifier="interpolation", specification=ISO_19107)
+    @Override
+    default CurveInterpolation getInterpolation() {
+        return CurveInterpolation.CIRCULAR;
+    }
+
     /**
      * Number of circular arcs in this chain.
      *
diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/BSplineCurve.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/BSplineCurve.java
index 85e5026a35..00e3eb48b3 100644
--- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/BSplineCurve.java
+++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/BSplineCurve.java
@@ -16,6 +16,7 @@
  */
 package org.apache.sis.geometries.curve;
 
+import org.apache.sis.geometries.CurveInterpolation;
 import static org.opengis.annotation.Specification.ISO_19107;
 import org.opengis.annotation.UML;
 
@@ -48,4 +49,14 @@ public sealed interface BSplineCurve extends SplineCurve
                 NurbCurve
 {
 
+    /**
+     * Returns {@link CurveInterpolation#BSPLINE}.
+     *
+     * @see ISO 19107:2019 - 7.13.8
+     */
+    @UML(identifier="interpolation", specification=ISO_19107)
+    @Override
+    default CurveInterpolation getInterpolation() {
+        return CurveInterpolation.BSPLINE;
+    }
 }
diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/Bezier.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/Bezier.java
index 56d564b062..3b1f487336 100644
--- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/Bezier.java
+++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/Bezier.java
@@ -16,6 +16,7 @@
  */
 package org.apache.sis.geometries.curve;
 
+import org.apache.sis.geometries.CurveInterpolation;
 import static org.opengis.annotation.Specification.ISO_19107;
 import org.opengis.annotation.UML;
 
@@ -48,4 +49,16 @@ import org.opengis.annotation.UML;
 @UML(identifier="Bezier", specification=ISO_19107)
 public non-sealed interface Bezier extends PolynomialSpline, BSplineCurve {
 
+    /**
+     * Returns {@link CurveInterpolation#BEZIER_SPLINE}.
+     * This value takes precedence over the interpolations declared by the two parent interfaces,
+     * a Bézier curve being at the same time a polynomial spline and a b-spline.
+     *
+     * @see ISO 19107:2019 - 7.13.7
+     */
+    @UML(identifier="interpolation", specification=ISO_19107)
+    @Override
+    default CurveInterpolation getInterpolation() {
+        return CurveInterpolation.BEZIER_SPLINE;
+    }
 }
diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/Circle.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/Circle.java
index 296d73e2b4..2b35034f6d 100644
--- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/Circle.java
+++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/Circle.java
@@ -43,4 +43,14 @@ import org.opengis.annotation.UML;
 @UML(identifier="Circle", specification=ISO_19107)
 public non-sealed interface Circle extends Arc {
 
+    /**
+     * Returns {@code true}: a circle is a complete curve closing on itself.
+     *
+     * @see ISO 19107:2019 - 7.9.4
+     */
+    @UML(identifier="isCycle", specification=ISO_19107)
+    @Override
+    default boolean isCycle() {
+        return true;
+    }
 }
diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/Clothoid.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/Clothoid.java
index 4e9a13bdee..12be0b05e7 100644
--- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/Clothoid.java
+++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/Clothoid.java
@@ -16,6 +16,7 @@
  */
 package org.apache.sis.geometries.curve;
 
+import org.apache.sis.geometries.CurveInterpolation;
 import static org.opengis.annotation.Specification.ISO_19107;
 import org.opengis.annotation.UML;
 
@@ -47,4 +48,25 @@ import org.opengis.annotation.UML;
 @UML(identifier="Clothoid", specification=ISO_19107)
 public non-sealed interface Clothoid extends Spiral {
 
+    /**
+     * Returns {@link CurveInterpolation#CLOTHOID}.
+     *
+     * @see ISO 19107:2019 - 7.11.3
+     */
+    @UML(identifier="interpolation", specification=ISO_19107)
+    @Override
+    default CurveInterpolation getInterpolation() {
+        return CurveInterpolation.CLOTHOID;
+    }
+
+    /**
+     * Returns {@code null}: a clothoid is a planar spiral.
+     *
+     * @see ISO 19107:2019 - 7.11.3
+     */
+    @UML(identifier="torsion", specification=ISO_19107)
+    @Override
+    default RealFunction getTorsion() {
+        return null;
+    }
 }
diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/Conic.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/Conic.java
index d1f3200cdb..e8d7ec1461 100644
--- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/Conic.java
+++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/Conic.java
@@ -17,6 +17,7 @@
 package org.apache.sis.geometries.curve;
 
 import org.apache.sis.geometries.Curve;
+import org.apache.sis.geometries.CurveInterpolation;
 import org.apache.sis.geometries.DataPoints;
 import org.apache.sis.maths.Array;
 import static org.opengis.annotation.Specification.ISO_19107;
@@ -51,6 +52,17 @@ public sealed interface Conic extends Curve
                 EllipticArc
 {
 
+    /**
+     * Returns {@link CurveInterpolation#CONIC}.
+     *
+     * @see ISO 19107:2019 - 7.9.5
+     */
+    @UML(identifier="interpolation", specification=ISO_19107)
+    @Override
+    default CurveInterpolation getInterpolation() {
+        return CurveInterpolation.CONIC;
+    }
+
     /**
      * Points lying on this conic, five of them being needed to determine each arc.
      *
diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/CubicSpline.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/CubicSpline.java
index 3c33255a53..e68a005f5d 100644
--- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/CubicSpline.java
+++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/CubicSpline.java
@@ -42,4 +42,14 @@ import org.opengis.annotation.UML;
 @UML(identifier="CubicSpline", specification=ISO_19107)
 public non-sealed interface CubicSpline extends PolynomialSpline {
 
+    /**
+     * Returns 3: a cubic spline is defined by polynomials of degree 3.
+     *
+     * @see ISO 19107:2019 - 7.13.6
+     */
+    @UML(identifier="degree", specification=ISO_19107)
+    @Override
+    default int getDegree() {
+        return 3;
+    }
 }
diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/EllipticArc.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/EllipticArc.java
index 45d58f8910..5e5ad6116c 100644
--- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/EllipticArc.java
+++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/EllipticArc.java
@@ -16,6 +16,7 @@
  */
 package org.apache.sis.geometries.curve;
 
+import org.apache.sis.geometries.CurveInterpolation;
 import static org.opengis.annotation.Specification.ISO_19107;
 import org.opengis.annotation.UML;
 
@@ -38,4 +39,14 @@ import org.opengis.annotation.UML;
 @UML(identifier="EllipticArc", specification=ISO_19107)
 public non-sealed interface EllipticArc extends Conic {
 
+    /**
+     * Returns {@link CurveInterpolation#ELLIPTICAL}.
+     *
+     * @see ISO 19107:2019 - 7.9.6
+     */
+    @UML(identifier="interpolation", specification=ISO_19107)
+    @Override
+    default CurveInterpolation getInterpolation() {
+        return CurveInterpolation.ELLIPTICAL;
+    }
 }
diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/LinearRing.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/LinearRing.java
index 7677dd0446..a49ed90fc0 100644
--- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/LinearRing.java
+++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/LinearRing.java
@@ -38,6 +38,36 @@ public sealed interface LinearRing extends LineString
         return TYPE;
     }
 
+    /**
+     * Returns {@code true}: a linear ring is closed by definition.
+     *
+     * @see OGC Simple Feature Access 1.2.1 - 6.1.6.2
+     */
+    @Override
+    default boolean isClosed() {
+        return true;
+    }
+
+    /**
+     * Returns {@code true}: a linear ring is simple by definition.
+     *
+     * @see ISO 19107:2019 - 6.4.4.15
+     */
+    @Override
+    default boolean isSimple() {
+        return true;
+    }
+
+    /**
+     * Returns {@code true}: a linear ring is both closed and simple, therefore a ring.
+     *
+     * @see ISO 19107:2019 - 6.4.18.8
+     */
+    @Override
+    default boolean isRing() {
+        return true;
+    }
+
     @Override
     default String asText() {
         final StringBuilder sb = new StringBuilder("LINEARRING (");
diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/MultiCurve.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/MultiCurve.java
index 39883fc653..98edf739d1 100644
--- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/MultiCurve.java
+++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/MultiCurve.java
@@ -16,9 +16,11 @@
  */
 package org.apache.sis.geometries.curve;
 
+import java.util.Set;
 import javax.measure.quantity.Length;
 import org.apache.sis.geometries.Curve;
 import org.apache.sis.geometries.GeometryCollection;
+import org.apache.sis.geometries.GeometryType;
 import org.apache.sis.geometries.internal.shared.DefaultMultiCurve;
 import org.apache.sis.measure.Quantities;
 import org.apache.sis.measure.Units;
@@ -57,6 +59,16 @@ public sealed interface MultiCurve<T extends Curve> extends GeometryCollection<T
         return TYPE;
     }
 
+    /**
+     * Returns {@link GeometryType#CURVE}: all the elements of this collection are curves.
+     *
+     * @see ISO 19107:2019 - 6.4.31.2
+     */
+    @Override
+    default Set<GeometryType> getElementType() {
+        return Set.of(GeometryType.CURVE);
+    }
+
     /**
      * Returns TRUE if this MultiCurve is closed [StartPoint () = EndPoint () for each Curve in this MultiCurve].
      *
diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/MultiLineString.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/MultiLineString.java
index de80212aa2..ba7990bbfe 100644
--- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/MultiLineString.java
+++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/MultiLineString.java
@@ -16,7 +16,9 @@
  */
 package org.apache.sis.geometries.curve;
 
+import java.util.Set;
 import org.apache.sis.geometries.DataPoints;
+import org.apache.sis.geometries.GeometryType;
 import org.apache.sis.geometries.internal.shared.AbstractGeometry;
 import org.apache.sis.geometries.internal.shared.DefaultMultiLineString;
 import org.apache.sis.geometries.mesh.MeshPrimitive;
@@ -40,6 +42,16 @@ public sealed interface MultiLineString extends MultiCurve<LineString>
         return TYPE;
     }
 
+    /**
+     * Returns {@link GeometryType#LINE}: all the elements of this collection are line strings.
+     *
+     * @see ISO 19107:2019 - 6.4.31.2
+     */
+    @Override
+    default Set<GeometryType> getElementType() {
+        return Set.of(GeometryType.LINE);
+    }
+
     @Override
     default String asText() {
         final StringBuilder sb = new StringBuilder("MULTILINESTRING (");
diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/PolynomialSpline.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/PolynomialSpline.java
index b7dfbe0134..95d5cd090a 100644
--- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/PolynomialSpline.java
+++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/PolynomialSpline.java
@@ -16,6 +16,7 @@
  */
 package org.apache.sis.geometries.curve;
 
+import org.apache.sis.geometries.CurveInterpolation;
 import org.apache.sis.maths.Vector;
 import static org.opengis.annotation.Specification.ISO_19107;
 import org.opengis.annotation.UML;
@@ -48,6 +49,29 @@ public sealed interface PolynomialSpline extends SplineCurve
                 Bezier
 {
 
+    /**
+     * Returns {@link CurveInterpolation#POLYNOMIAL_SPLINE}.
+     *
+     * @see ISO 19107:2019 - 7.13.5
+     */
+    @UML(identifier="interpolation", specification=ISO_19107)
+    @Override
+    default CurveInterpolation getInterpolation() {
+        return CurveInterpolation.POLYNOMIAL_SPLINE;
+    }
+
+    /**
+     * Returns {@code false}: coordinate offsets being fitted separately,
+     * an interpolant spline never uses homogeneous coordinates.
+     *
+     * @see ISO 19107:2019 - 7.13.5
+     */
+    @UML(identifier="isRational", specification=ISO_19107)
+    @Override
+    default boolean isRational() {
+        return false;
+    }
+
     /**
      * Derivative imposed at the start point of this spline.
      *
diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/ProductCurve.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/ProductCurve.java
index f8c61c16a7..3ad62d5fe8 100644
--- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/ProductCurve.java
+++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/ProductCurve.java
@@ -17,8 +17,11 @@
 package org.apache.sis.geometries.curve;
 
 import java.util.List;
+import java.util.Set;
 import org.apache.sis.geometries.Curve;
+import org.apache.sis.geometries.CurveInterpolation;
 import org.apache.sis.geometries.GeometryCollection;
+import org.apache.sis.geometries.GeometryType;
 import org.apache.sis.geometries.cs.Projection;
 import org.apache.sis.measure.Range;
 import static org.opengis.annotation.Specification.ISO_19107;
@@ -51,6 +54,40 @@ import org.opengis.annotation.UML;
 @UML(identifier="ProductCurve", specification=ISO_19107)
 public non-sealed interface ProductCurve extends Curve, GeometryCollection<Curve> {
 
+    /**
+     * Returns {@link CurveInterpolation#PRODUCT_CURVE}: the interpolation of a product curve is
+     * not a single one, it is whatever each projection declares.
+     *
+     * @see ISO 19107:2019 - 6.4.22
+     */
+    @UML(identifier="interpolation", specification=ISO_19107)
+    @Override
+    default CurveInterpolation getInterpolation() {
+        return CurveInterpolation.PRODUCT_CURVE;
+    }
+
+    /**
+     * Returns 1: a product curve is a curve, not an aggregate of geometries of various dimensions.
+     * This value resolves the ambiguity between {@link Curve} and {@link GeometryCollection}.
+     *
+     * @see ISO 19107:2019 - 6.4.4.22
+     */
+    @Override
+    default int getTopologicDimension() {
+        return 1;
+    }
+
+    /**
+     * Returns {@link GeometryType#CURVE}: all the elements of a product curve are curves.
+     *
+     * @see ISO 19107:2019 - 6.4.31.2
+     */
+    @UML(identifier="elementType", specification=ISO_19107)
+    @Override
+    default Set<GeometryType> getElementType() {
+        return Set.of(GeometryType.CURVE);
+    }
+
     /**
      * Interval of the construction parameter shared by this curve and its projections,
      * i.e. the interval between the first and the last {@linkplain #getKnots() knot}.
diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/Spiral.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/Spiral.java
index d77ec0fc45..9981805242 100644
--- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/Spiral.java
+++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/Spiral.java
@@ -18,6 +18,7 @@ package org.apache.sis.geometries.curve;
 
 import java.util.List;
 import org.apache.sis.geometries.Curve;
+import org.apache.sis.geometries.CurveInterpolation;
 import org.apache.sis.maths.Vector;
 import static org.opengis.annotation.Specification.ISO_19107;
 import org.opengis.annotation.UML;
@@ -49,6 +50,17 @@ public sealed interface Spiral extends Curve
         permits Clothoid
 {
 
+    /**
+     * Returns {@link CurveInterpolation#SPIRAL}.
+     *
+     * @see ISO 19107:2019 - 7.11.2
+     */
+    @UML(identifier="interpolation", specification=ISO_19107)
+    @Override
+    default CurveInterpolation getInterpolation() {
+        return CurveInterpolation.SPIRAL;
+    }
+
     /**
      * Curvature of this spiral as a function of arc length.
      *
diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultPolyhedron.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultPolyhedron.java
index 90bb2bd4b8..14cc4bb18d 100644
--- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultPolyhedron.java
+++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultPolyhedron.java
@@ -23,7 +23,6 @@ import javax.measure.quantity.Area;
 import javax.measure.quantity.Volume;
 import org.apache.sis.geometries.DataPoints;
 import org.apache.sis.geometries.Geometry;
-import org.apache.sis.geometries.SolidInterpolation;
 import org.apache.sis.geometries.solid.Polyhedron;
 import org.apache.sis.geometries.surface.MultiPolygon;
 import org.opengis.geometry.DirectPosition;
@@ -125,11 +124,6 @@ public non-sealed class DefaultPolyhedron extends AbstractGeometry implements Po
         throw new UnsupportedOperationException("Not supported yet.");
     }
 
-    @Override
-    public SolidInterpolation getInterpolation() {
-        throw new UnsupportedOperationException("Not supported yet.");
-    }
-
     @Override
     public List<double[]> getKnots() {
         throw new UnsupportedOperationException("Not supported yet.");
diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/point/MultiPoint.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/point/MultiPoint.java
index 3318bc56a9..764c6095b4 100644
--- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/point/MultiPoint.java
+++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/point/MultiPoint.java
@@ -18,7 +18,9 @@ package org.apache.sis.geometries.point;
 
 import org.apache.sis.geometries.AttributesType;
 import org.apache.sis.geometries.DataPoints;
+import java.util.Set;
 import org.apache.sis.geometries.GeometryCollection;
+import org.apache.sis.geometries.GeometryType;
 import org.apache.sis.geometries.Point;
 import org.apache.sis.geometries.internal.shared.AbstractGeometry;
 import org.apache.sis.geometries.internal.shared.DefaultMultiPoint;
@@ -51,6 +53,16 @@ public sealed interface MultiPoint<T extends Point> extends GeometryCollection<T
         return TYPE;
     }
 
+    /**
+     * Returns {@link GeometryType#POINT}: all the elements of this collection are points.
+     *
+     * @see ISO 19107:2019 - 6.4.31.2
+     */
+    @Override
+    default Set<GeometryType> getElementType() {
+        return Set.of(GeometryType.POINT);
+    }
+
     /**
      * View this multipoint as a point sequence
      */
diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/solid/BSolidSpline.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/solid/BSolidSpline.java
index 761562034a..bf3a72c6af 100644
--- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/solid/BSolidSpline.java
+++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/solid/BSolidSpline.java
@@ -16,6 +16,7 @@
  */
 package org.apache.sis.geometries.solid;
 
+import org.apache.sis.geometries.SolidInterpolation;
 import static org.opengis.annotation.Specification.ISO_19107;
 import org.opengis.annotation.UML;
 
@@ -39,4 +40,15 @@ import org.opengis.annotation.UML;
 @UML(identifier="BSolidSpline", specification=ISO_19107)
 public non-sealed interface BSolidSpline extends ParametricCurveSolid {
 
+    /**
+     * Returns {@link SolidInterpolation#BSPLINE}: the three families of curves defining the
+     * interior of this solid are b-splines.
+     *
+     * @see ISO 19107:2019 - 9.3.2
+     */
+    @UML(identifier="interpolation", specification=ISO_19107)
+    @Override
+    default SolidInterpolation getInterpolation() {
+        return SolidInterpolation.BSPLINE;
+    }
 }
diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/solid/ParametricCurveSolid.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/solid/ParametricCurveSolid.java
index 8c872c44eb..2feff2cc92 100644
--- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/solid/ParametricCurveSolid.java
+++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/solid/ParametricCurveSolid.java
@@ -21,6 +21,7 @@ import org.apache.sis.geometries.Curve;
 import org.apache.sis.geometries.DataPoints;
 import org.apache.sis.geometries.GeometryType;
 import org.apache.sis.geometries.Solid;
+import org.apache.sis.geometries.SolidInterpolation;
 import static org.opengis.annotation.Specification.ISO_19107;
 import org.opengis.annotation.UML;
 import org.opengis.geometry.DirectPosition;
@@ -48,6 +49,18 @@ public sealed interface ParametricCurveSolid extends Solid
         permits BSolidSpline
 {
 
+    /**
+     * Returns {@link SolidInterpolation#PARAMETRIC_CURVE}: this solid is defined by families of
+     * curves over a 3-dimensional parameter space.
+     *
+     * @see ISO 19107:2019 - 9.3.1
+     */
+    @UML(identifier="interpolation", specification=ISO_19107)
+    @Override
+    default SolidInterpolation getInterpolation() {
+        return SolidInterpolation.PARAMETRIC_CURVE;
+    }
+
     /**
      * Type of the curves used to traverse this solid horizontally.
      *
diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/solid/Polyhedron.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/solid/Polyhedron.java
index d1d5711e69..7f46aec9c4 100644
--- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/solid/Polyhedron.java
+++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/solid/Polyhedron.java
@@ -18,6 +18,7 @@ package org.apache.sis.geometries.solid;
 
 import java.util.List;
 import org.apache.sis.geometries.AttributesType;
+import org.apache.sis.geometries.SolidInterpolation;
 import org.apache.sis.geometries.Solid;
 import org.apache.sis.geometries.internal.shared.DefaultPolyhedron;
 import org.apache.sis.geometries.solid.polyhedron.AbstractPolyhedron;
@@ -55,6 +56,17 @@ public sealed interface Polyhedron extends Solid
         return getExteriorShell().getAttributesType();
     }
 
+    /**
+     * Returns {@link SolidInterpolation#NONE}: a polyhedron is defined by its boundary shells,
+     * which leaves its interior unspecified.
+     *
+     * @see ISO 19107:2019 - 6.4.30
+     */
+    @Override
+    default SolidInterpolation getInterpolation() {
+        return SolidInterpolation.NONE;
+    }
+
     /**
      * Returns the exterior shell of this polyhedron.
      *
diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/solid/Sphere.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/solid/Sphere.java
index 586f283da7..5cb802e25a 100644
--- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/solid/Sphere.java
+++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/solid/Sphere.java
@@ -24,6 +24,7 @@ import org.apache.sis.geometries.Curve;
 import org.apache.sis.geometries.DataPoints;
 import org.apache.sis.geometries.Geometries;
 import org.apache.sis.geometries.GeometryType;
+import org.apache.sis.geometries.SurfaceInterpolation;
 import org.apache.sis.geometries.internal.shared.AbstractGeometry;
 import org.apache.sis.geometries.surface.ParametricCurveSurface;
 import org.apache.sis.maths.Tuple;
@@ -136,6 +137,17 @@ public final class Sphere extends AbstractGeometry implements ParametricCurveSur
         return AttributesType.EMPTY;
     }
 
+    /**
+     * Returns {@link SurfaceInterpolation#SPHERICAL}: this surface is a section of a sphere.
+     *
+     * @see ISO 19107:2019 - 8.5.2
+     */
+    @UML(identifier="interpolation", specification=ISO_19107)
+    @Override
+    public List<SurfaceInterpolation> getInterpolation() {
+        return List.of(SurfaceInterpolation.SPHERICAL);
+    }
+
     /**
      * @return radius of the sphere.
      */
diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/surface/BSplineSurface.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/surface/BSplineSurface.java
index 2fbe1abd82..8dbbe73c00 100644
--- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/surface/BSplineSurface.java
+++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/surface/BSplineSurface.java
@@ -19,6 +19,7 @@ package org.apache.sis.geometries.surface;
 import java.util.List;
 import org.apache.sis.geometries.AttributesType;
 import org.apache.sis.geometries.GeometryType;
+import org.apache.sis.geometries.SurfaceInterpolation;
 import org.apache.sis.geometries.curve.KnotType;
 import static org.opengis.annotation.Specification.ISO_19107;
 import org.opengis.annotation.UML;
@@ -156,4 +157,17 @@ public sealed interface BSplineSurface extends ParametricCurveSurface
     default GeometryType getVerticalCurveType() {
         return GeometryType.SPLINECURVE;
     }
+
+    /**
+     * Returns {@link SurfaceInterpolation#POLYNOMIAL_SPLINE} if this surface
+     * {@linkplain #isPolynomial() is polynomial}, {@link SurfaceInterpolation#NURBS} otherwise.
+     *
+     * @see ISO 19107:2019 - 8.7.2
+     */
+    @UML(identifier="interpolation", specification=ISO_19107)
+    @Override
+    default List<SurfaceInterpolation> getInterpolation() {
+        return List.of(isPolynomial() ? SurfaceInterpolation.POLYNOMIAL_SPLINE
+                                      : SurfaceInterpolation.NURBS);
+    }
 }
diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/surface/BilinearGrid.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/surface/BilinearGrid.java
index 883efca6ec..fda24ad98b 100644
--- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/surface/BilinearGrid.java
+++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/surface/BilinearGrid.java
@@ -16,6 +16,8 @@
  */
 package org.apache.sis.geometries.surface;
 
+import java.util.List;
+import org.apache.sis.geometries.SurfaceInterpolation;
 import static org.opengis.annotation.Specification.ISO_19107;
 import org.opengis.annotation.UML;
 
@@ -49,4 +51,15 @@ import org.opengis.annotation.UML;
 @UML(identifier="BilinearGrid", specification=ISO_19107)
 public non-sealed interface BilinearGrid extends ParametricCurveSurface {
 
+    /**
+     * Returns {@link SurfaceInterpolation#LINEAR}: each cell of the grid is interpolated
+     * bilinearly over the unit square of the parameter space.
+     *
+     * @see ISO 19107:2019 - 8.3.4
+     */
+    @UML(identifier="interpolation", specification=ISO_19107)
+    @Override
+    default List<SurfaceInterpolation> getInterpolation() {
+        return List.of(SurfaceInterpolation.LINEAR);
+    }
 }
diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/surface/MultiPolygon.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/surface/MultiPolygon.java
index a295a895d5..7008210971 100644
--- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/surface/MultiPolygon.java
+++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/surface/MultiPolygon.java
@@ -16,6 +16,8 @@
  */
 package org.apache.sis.geometries.surface;
 
+import java.util.Set;
+import org.apache.sis.geometries.GeometryType;
 import org.apache.sis.geometries.internal.shared.AbstractGeometry;
 import org.apache.sis.geometries.internal.shared.DefaultMultiPolygon;
 
@@ -46,6 +48,16 @@ public sealed interface MultiPolygon extends MultiSurface<Polygon>
         return TYPE;
     }
 
+    /**
+     * Returns {@link GeometryType#POLYGON}: all the elements of this collection are polygons.
+     *
+     * @see ISO 19107:2019 - 6.4.31.2
+     */
+    @Override
+    default Set<GeometryType> getElementType() {
+        return Set.of(GeometryType.POLYGON);
+    }
+
     @Override
     default String asText() {
         final StringBuilder sb = new StringBuilder("MULTIPOLYGON (");
diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/surface/MultiSurface.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/surface/MultiSurface.java
index 4563f9bb21..10dfba4767 100644
--- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/surface/MultiSurface.java
+++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/surface/MultiSurface.java
@@ -16,8 +16,10 @@
  */
 package org.apache.sis.geometries.surface;
 
+import java.util.Set;
 import javax.measure.quantity.Area;
 import org.apache.sis.geometries.GeometryCollection;
+import org.apache.sis.geometries.GeometryType;
 import org.apache.sis.geometries.Point;
 import org.apache.sis.geometries.Surface;
 import org.apache.sis.geometries.internal.shared.DefaultMultiSurface;
@@ -54,6 +56,16 @@ public sealed interface MultiSurface<T extends Surface> extends GeometryCollecti
         return TYPE;
     }
 
+    /**
+     * Returns {@link GeometryType#SURFACE}: all the elements of this collection are surfaces.
+     *
+     * @see ISO 19107:2019 - 6.4.31.2
+     */
+    @Override
+    default Set<GeometryType> getElementType() {
+        return Set.of(GeometryType.SURFACE);
+    }
+
     /**
      * The area of this MultiSurface, as measured in the spatial reference system of this MultiSurface.
      *
diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/surface/NurbSurface.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/surface/NurbSurface.java
index fad2377b05..af916aca67 100644
--- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/surface/NurbSurface.java
+++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/surface/NurbSurface.java
@@ -16,6 +16,8 @@
  */
 package org.apache.sis.geometries.surface;
 
+import java.util.List;
+import org.apache.sis.geometries.SurfaceInterpolation;
 import org.apache.sis.geometries.internal.shared.DefaultNurbSurface;
 
 /**
@@ -35,4 +37,25 @@ public sealed interface NurbSurface extends BSplineSurface
         return TYPE;
     }
 
+    /**
+     * Returns {@code false}: a NURBS is expressed in homogeneous coordinates,
+     * therefore it is rational and not polynomial.
+     *
+     * @see ISO 19107:2019 - 8.7.2
+     */
+    @Override
+    default boolean isPolynomial() {
+        return false;
+    }
+
+    /**
+     * Returns {@link SurfaceInterpolation#NURBS}.
+     *
+     * @see ISO 19107:2019 - 6.4.27
+     */
+    @Override
+    default List<SurfaceInterpolation> getInterpolation() {
+        return List.of(SurfaceInterpolation.NURBS);
+    }
+
 }
diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/surface/ParametricCurveSurface.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/surface/ParametricCurveSurface.java
index fc4ed3e4ff..405aaddb44 100644
--- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/surface/ParametricCurveSurface.java
+++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/surface/ParametricCurveSurface.java
@@ -21,6 +21,7 @@ import org.apache.sis.geometries.Curve;
 import org.apache.sis.geometries.DataPoints;
 import org.apache.sis.geometries.GeometryType;
 import org.apache.sis.geometries.Surface;
+import org.apache.sis.geometries.SurfaceInterpolation;
 import org.apache.sis.geometries.solid.Sphere;
 import static org.opengis.annotation.Specification.ISO_19107;
 import org.opengis.annotation.UML;
@@ -66,6 +67,18 @@ public sealed interface ParametricCurveSurface extends Surface, ReferenceSystem
                 Sphere
 {
 
+    /**
+     * Returns {@link SurfaceInterpolation#PARAMETRIC_CURVE}: this surface is defined by families
+     * of curves over a 2-dimensional parameter space.
+     *
+     * @see ISO 19107:2019 - 8.3.1
+     */
+    @UML(identifier="interpolation", specification=ISO_19107)
+    @Override
+    default List<SurfaceInterpolation> getInterpolation() {
+        return List.of(SurfaceInterpolation.PARAMETRIC_CURVE);
+    }
+
     /**
      * Number of rows in the parameter grid, therefore the number of
      * {@linkplain #getHorizontalCurve(double) horizontal} section curves.
diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/surface/Polygon.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/surface/Polygon.java
index 3c5ec90384..af99ba0fb9 100644
--- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/surface/Polygon.java
+++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/surface/Polygon.java
@@ -19,6 +19,7 @@ package org.apache.sis.geometries.surface;
 import java.util.List;
 import org.apache.sis.geometries.AttributesType;
 import org.apache.sis.geometries.Surface;
+import org.apache.sis.geometries.SurfaceInterpolation;
 import org.apache.sis.geometries.curve.LinearRing;
 import org.apache.sis.geometries.internal.shared.AbstractGeometry;
 import org.apache.sis.geometries.internal.shared.DefaultPolygon;
@@ -87,6 +88,17 @@ public sealed interface Polygon extends Surface
         return getExteriorRing().getAttributesType();
     }
 
+    /**
+     * Returns {@link SurfaceInterpolation#PLANAR}: a polygon and its boundary lie in a single plane.
+     *
+     * @see ISO 19107:2019 - 8.1.2
+     */
+    @UML(identifier="interpolation", specification=ISO_19107)
+    @Override
+    default List<SurfaceInterpolation> getInterpolation() {
+        return List.of(SurfaceInterpolation.PLANAR);
+    }
+
     /**
      * Rings bounding the holes of this polygon, each of them oriented clockwise
      * when viewed from the <cite>top</cite> of this polygon.
diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/surface/PolyhedralSurface.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/surface/PolyhedralSurface.java
index 6c1110c07d..cdb4d7d99e 100644
--- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/surface/PolyhedralSurface.java
+++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/surface/PolyhedralSurface.java
@@ -16,8 +16,10 @@
  */
 package org.apache.sis.geometries.surface;
 
+import java.util.List;
 import javax.measure.quantity.Area;
 import org.apache.sis.geometries.Surface;
+import org.apache.sis.geometries.SurfaceInterpolation;
 import org.apache.sis.geometries.internal.shared.DefaultPolyhedralSurface;
 import org.apache.sis.measure.Quantities;
 import org.apache.sis.measure.Units;
@@ -83,6 +85,17 @@ public sealed interface PolyhedralSurface<T extends Polygon> extends /*GeometryC
 //    @Override
 //    public List<Primitive> getSegments();
 
+    /**
+     * Returns {@link SurfaceInterpolation#PLANAR}: every patch of this surface is a planar polygon.
+     *
+     * @see ISO 19107:2019 - 8.1.4
+     */
+    @UML(identifier="interpolation", specification=ISO_19107)
+    @Override
+    default List<SurfaceInterpolation> getInterpolation() {
+        return List.of(SurfaceInterpolation.PLANAR);
+    }
+
     /**
      * Returns the number of including polygons
      *
diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/surface/TIN.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/surface/TIN.java
index 3960f8c540..837b8c47bc 100644
--- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/surface/TIN.java
+++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/surface/TIN.java
@@ -16,7 +16,9 @@
  */
 package org.apache.sis.geometries.surface;
 
+import java.util.List;
 import org.apache.sis.geometries.DataPoints;
+import org.apache.sis.geometries.SurfaceInterpolation;
 import org.apache.sis.geometries.internal.shared.AbstractGeometry;
 import org.apache.sis.geometries.internal.shared.DefaultTriangulatedSurface;
 import org.apache.sis.geometries.mesh.MeshPrimitive;
@@ -43,6 +45,17 @@ public sealed interface TIN extends TriangulatedSurface<Triangle>
         return TYPE;
     }
 
+    /**
+     * Returns {@link SurfaceInterpolation#TIN}: the triangulation of this surface is derived
+     * from its own data points.
+     *
+     * @see ISO 19107:2019 - 6.4.27
+     */
+    @Override
+    default List<SurfaceInterpolation> getInterpolation() {
+        return List.of(SurfaceInterpolation.TIN);
+    }
+
     /**
      * Produce a Well Known Text representation of this TIN.
      *
diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/surface/TriangulatedSurface.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/surface/TriangulatedSurface.java
index 7105d449e1..e87b606148 100644
--- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/surface/TriangulatedSurface.java
+++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/surface/TriangulatedSurface.java
@@ -16,6 +16,8 @@
  */
 package org.apache.sis.geometries.surface;
 
+import java.util.List;
+import org.apache.sis.geometries.SurfaceInterpolation;
 import static org.opengis.annotation.Specification.ISO_19107;
 import org.opengis.annotation.UML;
 
@@ -43,4 +45,14 @@ public sealed interface TriangulatedSurface<T extends Polygon> extends Polyhedra
         permits TIN
 {
 
+    /**
+     * Returns {@link SurfaceInterpolation#TRIANGULAR}: the patches of this surface are triangles.
+     *
+     * @see ISO 19107:2019 - 8.1.8
+     */
+    @UML(identifier="interpolation", specification=ISO_19107)
+    @Override
+    default List<SurfaceInterpolation> getInterpolation() {
+        return List.of(SurfaceInterpolation.TRIANGULAR);
+    }
 }


(sis) 03/03: feat(Geometry): add WKT encoding

Posted by js...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

asf-gitbox-commits pushed a commit to branch geoapi-4.0
in repository https://gitbox.apache.org/repos/asf/sis.git

commit 57b466cbb6c6dc5cb4c4be2f2996fa9673a0dc07
Author: jsorel <jo...@geomatys.com>
AuthorDate: Fri Sep 11 11:11:42 2026 +0200

    feat(Geometry): add WKT encoding
---
 .../org.apache.sis.geometry/main/module-info.java  |   1 +
 .../org/apache/sis/geometries/AttributesType.java  |  10 +
 .../main/org/apache/sis/geometries/Geometry.java   |   8 +-
 .../org/apache/sis/geometries/GeometryFactory.java |   2 +-
 .../main/org/apache/sis/geometries/Point.java      |  10 -
 .../sis/geometries/adapter/WellKnownBinary.java    |  26 +
 .../sis/geometries/adapter/WellKnownText.java      | 512 ++++++++++++++
 .../geometries/adapter/WellKnownTextParser.java    | 786 +++++++++++++++++++++
 .../org/apache/sis/geometries/curve/Geodesic.java  |  10 -
 .../apache/sis/geometries/curve/LineString.java    |   9 -
 .../apache/sis/geometries/curve/LinearRing.java    |  15 -
 .../sis/geometries/curve/MultiLineString.java      |  21 -
 .../org/apache/sis/geometries/curve/NurbCurve.java |  10 -
 .../org/apache/sis/geometries/curve/Rhumb.java     |  10 -
 .../internal/shared/AbstractGeometry.java          |  36 -
 .../internal/shared/DefaultArcByBulge.java         |   9 -
 .../internal/shared/DefaultArcByCenterPoint.java   |  11 -
 .../internal/shared/DefaultCircularString.java     |   7 -
 .../internal/shared/DefaultCompoundCurve.java      |  10 -
 .../internal/shared/DefaultCurvePolygon.java       |   8 -
 .../internal/shared/DefaultPolyhedralSurface.java  |   9 -
 .../internal/shared/DefaultPolyhedron.java         |  14 -
 .../internal/shared/DefaultReversedCurve.java      |   5 -
 .../internal/shared/DefaultReversedSurface.java    |   4 -
 .../internal/shared/DefaultTriangle.java           |   5 -
 .../shared/DefaultTriangulatedSurface.java         |   8 -
 .../operation/triangulate/delaunay/Delaunay.java   |   1 -
 .../apache/sis/geometries/point/MultiPoint.java    |  14 -
 .../sis/geometries/surface/CurvePolygon.java       |  16 -
 .../sis/geometries/surface/MultiPolygon.java       |  21 -
 .../org/apache/sis/geometries/surface/Polygon.java |  16 -
 .../org/apache/sis/geometries/surface/TIN.java     |  44 +-
 .../apache/sis/geometries/surface/Triangle.java    |  26 +-
 .../org/apache/sis/geometries/PreparedTINTest.java |   8 +-
 .../sis/geometries/adapter/WellKnownTextTest.java  | 299 ++++++++
 .../sis/geometries/mesh/PrimitiveVisitorTest.java  | 140 ++--
 .../org/apache/sis/referencing/dggs/s2/S2Test.java |   2 +-
 37 files changed, 1723 insertions(+), 420 deletions(-)

diff --git a/incubator/src/org.apache.sis.geometry/main/module-info.java b/incubator/src/org.apache.sis.geometry/main/module-info.java
index db680f0f4c..aea12d70e0 100644
--- a/incubator/src/org.apache.sis.geometry/main/module-info.java
+++ b/incubator/src/org.apache.sis.geometry/main/module-info.java
@@ -31,6 +31,7 @@ module org.apache.sis.geometry {
 
     exports org.apache.sis.images;
     exports org.apache.sis.geometries;
+    exports org.apache.sis.geometries.adapter;
     exports org.apache.sis.geometries.curve;
     exports org.apache.sis.geometries.operation;
     exports org.apache.sis.geometries.point;
diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/AttributesType.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/AttributesType.java
index c5939bc4b9..29361b6272 100644
--- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/AttributesType.java
+++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/AttributesType.java
@@ -38,6 +38,16 @@ import org.apache.sis.maths.SampleSystem;
 public interface AttributesType {
 
     static final String ATT_POSITION = "POSITION";
+
+    /**
+     * The single ordinate that common GIS formats call <cite>M</cite>, the measure.
+     * It is a one dimensional attribute, kept apart from {@link #ATT_POSITION} because
+     * it is not a spatial ordinate: it takes part in no distance, area or transform.
+     * This is the attribute that the {@code M} and {@code ZM} flavors of Well-Known Text
+     * are read into and written from.
+     */
+    static final String ATT_M = "M";
+
     static final String ATT_NORMAL = "NORMAL";
     static final String ATT_TANGENT = "TANGENT";
     static final String ATT_TEXCOORD_0 = "TEXCOORD_0";
diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/Geometry.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/Geometry.java
index f5f19cf5a6..bbcf9ae742 100644
--- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/Geometry.java
+++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/Geometry.java
@@ -19,6 +19,7 @@ package org.apache.sis.geometries;
 import java.util.List;
 import java.util.Map;
 import javax.measure.quantity.Length;
+import org.apache.sis.geometries.adapter.WellKnownText;
 import org.apache.sis.geometries.internal.shared.AbstractGeometry;
 import org.apache.sis.geometries.mesh.MeshPrimitive;
 import org.apache.sis.geometries.operation.GeometryProcessor;
@@ -385,15 +386,18 @@ public sealed interface Geometry
      * - this method is located on Encoding sub interface in the standard, it is placed
      *   on Geometry to match OGC SFA.</p>
      *
+     * <p>The encoding is delegated to {@link WellKnownText}, which documents the supported
+     * geometry types and the deviations from the format.</p>
+     *
      * @return this geometry in Well-known Text
+     * @throws IllegalArgumentException if this geometry has no Well-known Text representation.
      *
      * @see OGC Simple Feature Access 1.2.1 - 6.1.2.2
      * @see ISO 19107:2019 - 6.4.4.5, 6.4.7
      */
     @UML(identifier="asText", specification=ISO_19107)
     default String asText() {
-        //TODO remove this method default when all classes implement it.
-        return this.getClass().getSimpleName();
+        return new WellKnownText().encode(this);
     }
 
     /**
diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/GeometryFactory.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/GeometryFactory.java
index 3423f52207..11b8342531 100644
--- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/GeometryFactory.java
+++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/GeometryFactory.java
@@ -414,7 +414,7 @@ public final class GeometryFactory extends org.apache.sis.geometry.wrapper.Geome
             } else {
                 marray = NDArrays.of(SampleSystem.ofSize(1), coordinates.get(dimensions.hasZ ? 3 : 2));
             }
-            points.setAttribute("m", marray);
+            points.setAttribute(AttributesType.ATT_M, marray);
         }
 
         return new DefaultPoint(points);
diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/Point.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/Point.java
index 4026b590c5..444fb9c3af 100644
--- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/Point.java
+++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/Point.java
@@ -17,7 +17,6 @@
 package org.apache.sis.geometries;
 
 import java.util.List;
-import org.apache.sis.geometries.internal.shared.AbstractGeometry;
 import org.apache.sis.geometries.internal.shared.ArrayDataPoints;
 import org.apache.sis.geometries.internal.shared.DefaultPoint;
 import org.apache.sis.geometries.internal.shared.IndexedPoint;
@@ -233,15 +232,6 @@ public sealed interface Point extends Primitive
         return env;
     }
 
-    @Override
-    default String asText() {
-        final Tuple crd = getPosition();
-        final StringBuilder sb = new StringBuilder("POINT (");
-        AbstractGeometry.toText(sb, crd);
-        sb.append(')');
-        return sb.toString();
-    }
-
     /**
      * Returns the vector, in the tangent space at this point, whose direction determines the
      * geodesic curve reaching the given position and whose length is the distance to it.
diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/adapter/WellKnownBinary.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/adapter/WellKnownBinary.java
new file mode 100644
index 0000000000..50df291e6f
--- /dev/null
+++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/adapter/WellKnownBinary.java
@@ -0,0 +1,26 @@
+/*
+ * 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.sis.geometries.adapter;
+
+/**
+ * TODO
+ *
+ * @author Johann Sorel (Geomatys)
+ */
+public final class WellKnownBinary {
+
+}
diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/adapter/WellKnownText.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/adapter/WellKnownText.java
new file mode 100644
index 0000000000..0fe8fde7ca
--- /dev/null
+++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/adapter/WellKnownText.java
@@ -0,0 +1,512 @@
+/*
+ * 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.sis.geometries.adapter;
+
+import java.math.RoundingMode;
+import java.text.NumberFormat;
+import java.util.Locale;
+import java.util.function.IntFunction;
+import org.apache.sis.geometries.AttributesType;
+import org.apache.sis.geometries.Curve;
+import org.apache.sis.geometries.DataPoints;
+import org.apache.sis.geometries.Empty;
+import org.apache.sis.geometries.Geometry;
+import org.apache.sis.geometries.GeometryCollection;
+import org.apache.sis.geometries.Orientable;
+import org.apache.sis.geometries.Point;
+import org.apache.sis.geometries.curve.CircularString;
+import org.apache.sis.geometries.curve.CompoundCurve;
+import org.apache.sis.geometries.curve.LineString;
+import org.apache.sis.geometries.curve.MultiCurve;
+import org.apache.sis.geometries.curve.MultiLineString;
+import org.apache.sis.geometries.point.MultiPoint;
+import org.apache.sis.geometries.surface.CurvePolygon;
+import org.apache.sis.geometries.surface.MultiPolygon;
+import org.apache.sis.geometries.surface.MultiSurface;
+import org.apache.sis.geometries.surface.Polygon;
+import org.apache.sis.geometries.surface.PolyhedralSurface;
+import org.apache.sis.geometries.surface.TIN;
+import org.apache.sis.geometries.surface.Triangle;
+import org.apache.sis.maths.Tuple;
+import org.apache.sis.util.ArgumentChecks;
+import org.apache.sis.util.StringBuilders;
+import org.opengis.referencing.crs.CoordinateReferenceSystem;
+
+
+/**
+ * Encoder and decoder for the Well-Known Text representation of geometries.
+ *
+ * <p>The grammar is the one of <cite>OGC Simple Feature Access 1.2.1</cite> extended with the
+ * curved and surface-patch types of <cite>ISO 13249-3</cite> (SQL/MM Part 3):</p>
+ *
+ * <blockquote><pre>
+ * &lt;keyword&gt; [Z | M | ZM] ( … )
+ * &lt;keyword&gt; [Z | M | ZM] EMPTY
+ * </pre></blockquote>
+ *
+ * <p>The supported keywords, and the geometry type each one maps to, are:</p>
+ *
+ * <table class="sis">
+ *   <caption>Supported Well-Known Text keywords</caption>
+ *   <tr><th>Keyword</th>                <th>Geometry type</th></tr>
+ *   <tr><td>{@code POINT}</td>              <td>{@link Point}</td></tr>
+ *   <tr><td>{@code LINESTRING}</td>         <td>{@link LineString}</td></tr>
+ *   <tr><td>{@code CIRCULARSTRING}</td>     <td>{@link CircularString}</td></tr>
+ *   <tr><td>{@code COMPOUNDCURVE}</td>      <td>{@link CompoundCurve}</td></tr>
+ *   <tr><td>{@code POLYGON}</td>            <td>{@link Polygon}</td></tr>
+ *   <tr><td>{@code TRIANGLE}</td>           <td>{@link Triangle}</td></tr>
+ *   <tr><td>{@code CURVEPOLYGON}</td>       <td>{@link CurvePolygon}</td></tr>
+ *   <tr><td>{@code POLYHEDRALSURFACE}</td>  <td>{@link PolyhedralSurface}</td></tr>
+ *   <tr><td>{@code TIN}</td>                <td>{@link TIN}</td></tr>
+ *   <tr><td>{@code MULTIPOINT}</td>         <td>{@link MultiPoint}</td></tr>
+ *   <tr><td>{@code MULTILINESTRING}</td>    <td>{@link MultiLineString}</td></tr>
+ *   <tr><td>{@code MULTICURVE}</td>         <td>{@link MultiCurve}</td></tr>
+ *   <tr><td>{@code MULTIPOLYGON}</td>       <td>{@link MultiPolygon}</td></tr>
+ *   <tr><td>{@code MULTISURFACE}</td>       <td>{@link MultiSurface}</td></tr>
+ *   <tr><td>{@code GEOMETRYCOLLECTION}</td> <td>{@link GeometryCollection}</td></tr>
+ * </table>
+ *
+ * <p>Any other geometry of the Apache SIS hierarchy
+ * is rejected by {@link #encode encode(…)} with an {@link IllegalArgumentException}.</p>
+ *
+ * <h2>Dimensions and measures</h2>
+ * The {@code Z} flag is the third ordinate of the {@linkplain AttributesType#ATT_POSITION position}
+ * attribute, and the {@code M} flag is the separate {@linkplain AttributesType#ATT_M measure}
+ * attribute. A geometry whose position has neither 2 nor 3 dimensions cannot be written.
+ * When the text carries no flag, the number of ordinates of the first coordinate tuple decides:
+ * two of them are read as <var>x y</var> and three as <var>x y z</var>. Four ordinates without a
+ * flag are ambiguous — {@code ZM} and a 4-dimensional position would be written the same way —
+ * and are therefore rejected.
+ *
+ * <h2>Deviations</h2>
+ * <ul>
+ *   <li>A {@link org.apache.sis.geometries.curve.LinearRing} is written as a {@code LINESTRING}:
+ *       it is a {@code LineString} and Well-Known Text has no standalone ring type. Consequently
+ *       {@code decode(…)} never returns a {@code LinearRing} at the top level.</li>
+ *   <li>An {@linkplain Orientable#getOrientationSign() orientation} of
+ *       {@link Orientable.Sign#NEGATIVE} is written as the underlying
+ *       {@linkplain Orientable#getReverse() reverse} primitive. Well-Known Text has no notion
+ *       of orientation, so that information is lost.</li>
+ *   <li>An {@link Empty} geometry is written as {@code GEOMETRYCOLLECTION EMPTY}, the only
+ *       type-less empty form the grammar offers.</li>
+ *   <li>{@code POINT EMPTY} is read as an {@link Empty} geometry rather than as a {@code Point},
+ *       because the model has no empty point: a point holds exactly one position. It is therefore
+ *       written back as {@code GEOMETRYCOLLECTION EMPTY}, and an empty point may not appear as a
+ *       member of a {@code MULTIPOINT}. Every other type has a genuine empty form which
+ *       round-trips unchanged.</li>
+ *   <li>The coordinate reference system is neither written nor read: the {@code SRID=…;} prefix
+ *       of the extended Well-Known Text of some databases is not part of the standard. Unless a
+ *       system is given to {@link #decode(String, CoordinateReferenceSystem)}, decoded geometries
+ *       use {@link org.apache.sis.geometries.Geometries#getUndefinedCRS(int)}.</li>
+ * </ul>
+ *
+ * <h2>Thread safety</h2>
+ * Instances are cheap to create but are <strong>not</strong> thread-safe, because the number
+ * format they may hold is not. Use one instance per thread.
+ *
+ * @author Johann Sorel (Geomatys)
+ */
+public final class WellKnownText {
+    /**
+     * Format of the ordinates, or {@code null} for writing the shortest text which parses back
+     * to the same {@code double}. Not thread-safe, hence the warning in the class javadoc.
+     */
+    private final NumberFormat format;
+
+    /**
+     * Creates an encoder writing every ordinate as the shortest decimal text which parses back to
+     * the same {@code double} value. A whole number is written without its fractional part:
+     * {@code 2}, not {@code 2.0}.
+     */
+    public WellKnownText() {
+        format = null;
+    }
+
+    /**
+     * Creates an encoder rounding every ordinate to at most the given number of fraction digits,
+     * half away from zero. Trailing zeros are not written, so a precision of 3 writes {@code 1.5}
+     * rather than {@code 1.500}. The precision has no effect on {@link #decode decoding}.
+     *
+     * @param  decimalPrecision  maximal number of digits after the decimal separator, 0 or more.
+     */
+    public WellKnownText(final int decimalPrecision) {
+        ArgumentChecks.ensurePositive("decimalPrecision", decimalPrecision);
+        format = NumberFormat.getNumberInstance(Locale.ROOT);
+        format.setGroupingUsed(false);
+        format.setMaximumFractionDigits(decimalPrecision);
+        format.setRoundingMode(RoundingMode.HALF_UP);       // Not the HALF_EVEN default, which surprises.
+    }
+
+    /**
+     * Returns the Well-Known Text of the given geometry.
+     *
+     * @param  geom  the geometry to encode, not null.
+     * @return the geometry in Well-Known Text.
+     * @throws IllegalArgumentException if the geometry, or one of the geometries it contains,
+     *         has no Well-Known Text representation, or if its positions are neither 2
+     *         nor 3 dimensional.
+     */
+    public String encode(final Geometry geom) {
+        ArgumentChecks.ensureNonNull("geom", geom);
+        final StringBuilder sb = new StringBuilder();
+        format(sb, geom);
+        return sb.toString();
+    }
+
+    /**
+     * Returns the geometry described by the given Well-Known Text.
+     *
+     * @param  geom  the Well-Known Text to decode, not null.
+     * @return the decoded geometry.
+     * @throws IllegalArgumentException if the text is malformed, or names a geometry type which
+     *         is not in the table of this class javadoc.
+     */
+    public Geometry decode(final String geom) {
+        return decode(geom, null);
+    }
+
+    /**
+     * Returns the geometry described by the given Well-Known Text, in the given coordinate
+     * reference system. Well-Known Text carries no system of its own.
+     *
+     * @param  geom  the Well-Known Text to decode, not null.
+     * @param  crs   the coordinate reference system of the coordinates in the text, or
+     *               {@code null}.
+     * @return the decoded geometry.
+     * @throws IllegalArgumentException if the text is malformed, names a geometry type which is
+     *         not in the table of this class javadoc, or has a number of ordinates which
+     *         contradicts the dimension of {@code crs}.
+     */
+    public Geometry decode(final String geom, final CoordinateReferenceSystem crs) {
+        ArgumentChecks.ensureNonNull("geom", geom);
+        return new WellKnownTextParser(geom, crs).parse();
+    }
+
+    // ////////////////////////////////////////////////////////////////////////
+    // Encoding ///////////////////////////////////////////////////////////////
+    // ////////////////////////////////////////////////////////////////////////
+
+    /**
+     * Appends the Well-Known Text of the given geometry, keyword included.
+     *
+     * <p>The order of the tests below is significant: the geometry interfaces form a hierarchy,
+     * so every type must be tested before its supertypes. It mirrors the dispatch of
+     * {@code GML3Writer.writeGeometry(…)}.</p>
+     */
+    private void format(final StringBuilder sb, final Geometry geometry) {
+        if (geometry instanceof Orientable o && o.getOrientationSign() == Orientable.Sign.NEGATIVE) {
+            format(sb, o.getReverse());                         // Orientation is not representable.
+        } else if (geometry instanceof Empty) {
+            sb.append(GeometryCollection.TYPE).append(" EMPTY");
+        } else if (geometry instanceof Point g) {
+            formatPoint(sb, g);
+        } else if (geometry instanceof CircularString g) {      // Before Curve.
+            formatPointList(sb, CircularString.TYPE, g, g.getDataPoints());
+        } else if (geometry instanceof CompoundCurve g) {       // Before Curve.
+            formatCompoundCurve(sb, g);
+        } else if (geometry instanceof LineString g) {          // Also matches LinearRing.
+            formatPointList(sb, LineString.TYPE, g, g.getDataPoints());
+        } else if (geometry instanceof Triangle g) {            // Before Polygon.
+            formatPolygon(sb, Triangle.TYPE, g);
+        } else if (geometry instanceof Polygon g) {             // Before Surface.
+            formatPolygon(sb, Polygon.TYPE, g);
+        } else if (geometry instanceof CurvePolygon g) {        // Before Surface.
+            formatCurvePolygon(sb, g);
+        } else if (geometry instanceof TIN g) {                 // Before PolyhedralSurface.
+            formatPatches(sb, TIN.TYPE, g.getNumPatches(), g::getPatchN, g);
+        } else if (geometry instanceof PolyhedralSurface<?> g) {
+            formatPatches(sb, PolyhedralSurface.TYPE, g.getNumPatches(), g::getPatchN, g);
+        } else if (geometry instanceof MultiPoint<?> g) {       // Before GeometryCollection.
+            formatMultiPoint(sb, g);
+        } else if (geometry instanceof MultiLineString g) {     // Before MultiCurve.
+            formatMultiLineString(sb, g);
+        } else if (geometry instanceof MultiPolygon g) {        // Before MultiSurface.
+            formatMultiPolygon(sb, g);
+        } else if (geometry instanceof MultiCurve<?> g) {       // Before GeometryCollection.
+            formatMembers(sb, MultiCurve.TYPE, g);
+        } else if (geometry instanceof MultiSurface<?> g) {     // Before GeometryCollection.
+            formatMembers(sb, MultiSurface.TYPE, g);
+        } else if (geometry instanceof GeometryCollection<?> g) {
+            formatMembers(sb, GeometryCollection.TYPE, g);
+        } else {
+            throw unsupported(geometry);
+        }
+    }
+
+    /**
+     * Appends {@code "POINT Z (1 2 3)"} or {@code "POINT Z EMPTY"}.
+     */
+    private void formatPoint(final StringBuilder sb, final Point geometry) {
+        if (appendHeader(sb, Point.TYPE, geometry)) return;
+        sb.append('(');
+        appendPosition(sb, geometry.getPosition(), hasMeasure(geometry) ? geometry.getAttribute(AttributesType.ATT_M) : null);
+        sb.append(')');
+    }
+
+    /**
+     * Appends {@code "LINESTRING (1 2, 3 4)"}: a keyword followed by a single parenthesized
+     * list of coordinate tuples.
+     */
+    private void formatPointList(final StringBuilder sb, final String keyword,
+                                 final Geometry geometry, final DataPoints points)
+    {
+        if (appendHeader(sb, keyword, geometry)) return;
+        appendPointList(sb, points, hasMeasure(geometry));
+    }
+
+    /**
+     * Appends {@code "COMPOUNDCURVE ((1 2, 3 4), CIRCULARSTRING (3 4, 5 6, 7 8))"}.
+     * A {@code LINESTRING} component may drop its keyword, and does so here; any other
+     * component keeps it.
+     */
+    private void formatCompoundCurve(final StringBuilder sb, final CompoundCurve geometry) {
+        if (appendHeader(sb, CompoundCurve.TYPE, geometry)) return;
+        final boolean hasM = hasMeasure(geometry);
+        sb.append('(');
+        for (int i = 0, n = geometry.getNumCurves(); i < n; i++) {
+            if (i != 0) sb.append(", ");
+            appendCurveComponent(sb, geometry.getCurveN(i), hasM);
+        }
+        sb.append(')');
+    }
+
+    /**
+     * Appends {@code "POLYGON ((0 0, 1 0, 1 1, 0 0), (…))"}, or the same with the
+     * {@code TRIANGLE} keyword, in which case {@link Triangle#getInteriorRings()} is empty
+     * and only the exterior ring is written.
+     */
+    private void formatPolygon(final StringBuilder sb, final String keyword, final Polygon geometry) {
+        if (appendHeader(sb, keyword, geometry)) return;
+        appendRings(sb, geometry, hasMeasure(geometry));
+    }
+
+    /**
+     * Appends {@code "CURVEPOLYGON (CIRCULARSTRING (…), (…))"}. As in a compound curve, a ring
+     * which is a {@code LINESTRING} drops its keyword.
+     */
+    private void formatCurvePolygon(final StringBuilder sb, final CurvePolygon geometry) {
+        if (appendHeader(sb, CurvePolygon.TYPE, geometry)) return;
+        final boolean hasM = hasMeasure(geometry);
+        sb.append('(');
+        appendCurveComponent(sb, geometry.getExteriorRing(), hasM);
+        for (int i = 0, n = geometry.getNumInteriorRing(); i < n; i++) {
+            sb.append(", ");
+            appendCurveComponent(sb, geometry.getInteriorRingN(i), hasM);
+        }
+        sb.append(')');
+    }
+
+    /**
+     * Appends {@code "POLYHEDRALSURFACE (((…)), ((…)))"} or the same with the {@code TIN}
+     * keyword: a list of polygon patches, each written without its keyword.
+     */
+    private void formatPatches(final StringBuilder sb, final String keyword, final int count,
+                               final IntFunction<? extends Polygon> patches,
+                               final Geometry geometry)
+    {
+        if (appendHeader(sb, keyword, geometry)) return;
+        final boolean hasM = hasMeasure(geometry);
+        sb.append('(');
+        for (int i = 0; i < count; i++) {
+            if (i != 0) sb.append(", ");
+            appendRings(sb, patches.apply(i), hasM);
+        }
+        sb.append(')');
+    }
+
+    /**
+     * Appends {@code "MULTIPOINT ((1 2), (3 4))"}. The grammar also allows the parentheses around
+     * each point to be omitted; they are written here because that form is unambiguous.
+     */
+    private void formatMultiPoint(final StringBuilder sb, final MultiPoint<?> geometry) {
+        if (appendHeader(sb, MultiPoint.TYPE, geometry)) return;
+        final boolean hasM = hasMeasure(geometry);
+        sb.append('(');
+        for (int i = 0, n = geometry.getNumGeometries(); i < n; i++) {
+            if (i != 0) sb.append(", ");
+            final Point point = geometry.getGeometryN(i);
+            if (point.isEmpty()) {
+                sb.append("EMPTY");
+            } else {
+                sb.append('(');
+                appendPosition(sb, point.getPosition(), hasM ? point.getAttribute(AttributesType.ATT_M) : null);
+                sb.append(')');
+            }
+        }
+        sb.append(')');
+    }
+
+    /**
+     * Appends {@code "MULTILINESTRING ((1 2, 3 4), (5 6, 7 8))"}: components keep no keyword.
+     */
+    private void formatMultiLineString(final StringBuilder sb, final MultiLineString geometry) {
+        if (appendHeader(sb, MultiLineString.TYPE, geometry)) return;
+        final boolean hasM = hasMeasure(geometry);
+        sb.append('(');
+        for (int i = 0, n = geometry.getNumGeometries(); i < n; i++) {
+            if (i != 0) sb.append(", ");
+            appendPointList(sb, geometry.getGeometryN(i).getDataPoints(), hasM);
+        }
+        sb.append(')');
+    }
+
+    /**
+     * Appends {@code "MULTIPOLYGON (((…), (…)), ((…)))"}: components keep no keyword.
+     */
+    private void formatMultiPolygon(final StringBuilder sb, final MultiPolygon geometry) {
+        if (appendHeader(sb, MultiPolygon.TYPE, geometry)) return;
+        final boolean hasM = hasMeasure(geometry);
+        sb.append('(');
+        for (int i = 0, n = geometry.getNumGeometries(); i < n; i++) {
+            if (i != 0) sb.append(", ");
+            appendRings(sb, geometry.getGeometryN(i), hasM);
+        }
+        sb.append(')');
+    }
+
+    /**
+     * Appends a collection whose members are written in full, with their own keyword: this is
+     * mandatory for {@code GEOMETRYCOLLECTION}, and is what makes {@code MULTICURVE} and
+     * {@code MULTISURFACE} able to hold the curved types.
+     */
+    private void formatMembers(final StringBuilder sb, final String keyword, final GeometryCollection<?> geometry) {
+        if (appendHeader(sb, keyword, geometry)) return;
+        sb.append('(');
+        for (int i = 0, n = geometry.getNumGeometries(); i < n; i++) {
+            if (i != 0) sb.append(", ");
+            format(sb, geometry.getGeometryN(i));
+        }
+        sb.append(')');
+    }
+
+    /**
+     * Appends a curve used as a component of a compound curve or as a ring of a curve polygon.
+     * A {@code LineString} is written as a bare parenthesized list of coordinates, which the
+     * grammar defines as meaning a line string; anything else is written in full.
+     */
+    private void appendCurveComponent(final StringBuilder sb, final Curve curve, final boolean hasM) {
+        if (curve instanceof LineString g) {
+            appendPointList(sb, g.getDataPoints(), hasM);
+        } else {
+            format(sb, curve);
+        }
+    }
+
+    /**
+     * Appends the rings of a polygon as {@code "((0 0, 1 0, 1 1, 0 0), (…))"}, without keyword.
+     * A triangle is written the same way: its exterior ring already closes on its first corner.
+     */
+    private void appendRings(final StringBuilder sb, final Polygon polygon, final boolean hasM) {
+        if (polygon.isEmpty()) {
+            sb.append("EMPTY");
+            return;
+        }
+        sb.append('(');
+        appendPointList(sb, polygon.getExteriorRing().getDataPoints(), hasM);
+        for (int i = 0, n = polygon.getNumInteriorRing(); i < n; i++) {
+            sb.append(", ");
+            appendPointList(sb, polygon.getInteriorRingN(i).getDataPoints(), hasM);
+        }
+        sb.append(')');
+    }
+
+    /**
+     * Appends {@code "(1 2, 3 4)"}: a parenthesized list of coordinate tuples.
+     */
+    private void appendPointList(final StringBuilder sb, final DataPoints points, final boolean hasM) {
+        sb.append('(');
+        for (int i = 0, n = points.size(); i < n; i++) {
+            if (i != 0) sb.append(", ");
+            appendPosition(sb, points.getPosition(i), hasM ? points.getAttribute(i, AttributesType.ATT_M) : null);
+        }
+        sb.append(')');
+    }
+
+    /**
+     * Appends {@code "1 2 3 4"}: the ordinates of one position, followed by its measure if any.
+     */
+    private void appendPosition(final StringBuilder sb, final Tuple<?> position, final Tuple<?> measure) {
+        for (int i = 0, n = position.getDimension(); i < n; i++) {
+            if (i != 0) sb.append(' ');
+            appendNumber(sb, position.get(i));
+        }
+        if (measure != null) {
+            sb.append(' ');
+            appendNumber(sb, measure.get(0));
+        }
+    }
+
+    /**
+     * Appends one ordinate, rounded to the precision given to the constructor if there was one.
+     * Values which are not finite have no decimal representation to round, and are written
+     * as {@code NaN} or {@code Infinity} — neither of which the grammar allows, but refusing
+     * to write them would make the geometry impossible to inspect at all.
+     */
+    private void appendNumber(final StringBuilder sb, final double value) {
+        if (format == null || !Double.isFinite(value)) {
+            sb.append(value);
+            StringBuilders.trimFractionalPart(sb);
+        } else {
+            sb.append(format.format(value));
+        }
+    }
+
+    /**
+     * Appends the keyword and the dimension flag of a geometry, then {@code "EMPTY"} if the
+     * geometry is empty. Returns {@code true} if the caller has nothing more to write.
+     */
+    private static boolean appendHeader(final StringBuilder sb, final String keyword, final Geometry geometry) {
+        sb.append(keyword);
+        final CoordinateReferenceSystem crs = geometry.getCoordinateReferenceSystem();
+        if (crs == null) {
+            throw new IllegalArgumentException("Cannot write a " + keyword + " in Well-Known Text:"
+                    + " it has no coordinate reference system, therefore no known number of dimensions."
+                    + " An empty collection takes one from the factory method which creates it.");
+        }
+        final int dimension = crs.getCoordinateSystem().getDimension();
+        final boolean hasM = hasMeasure(geometry);
+        switch (dimension) {
+            case 2:  sb.append(hasM ?  " M " : " ");   break;
+            case 3:  sb.append(hasM ? " ZM " : " Z "); break;
+            default: throw new IllegalArgumentException("Cannot write a " + keyword + " in Well-Known Text:"
+                        + " its positions have " + dimension + " dimensions,"
+                        + " but the format defines only 2 and 3.");
+        }
+        if (geometry.isEmpty()) {
+            sb.append("EMPTY");
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Returns whether the given geometry carries the {@linkplain AttributesType#ATT_M measure}
+     * attribute, which is what the {@code M} and {@code ZM} flags stand for.
+     */
+    private static boolean hasMeasure(final Geometry geometry) {
+        final AttributesType type = geometry.getAttributesType();
+        return (type != null) && type.getAttributeNames().contains(AttributesType.ATT_M);
+    }
+
+    /**
+     * Returns the exception to throw for a geometry which the format cannot represent.
+     */
+    private static IllegalArgumentException unsupported(final Geometry geometry) {
+        return new IllegalArgumentException("Well-Known Text defines no representation for "
+                + geometry.getClass().getSimpleName() + '.');
+    }
+}
diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/adapter/WellKnownTextParser.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/adapter/WellKnownTextParser.java
new file mode 100644
index 0000000000..46c933054b
--- /dev/null
+++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/adapter/WellKnownTextParser.java
@@ -0,0 +1,786 @@
+/*
+ * 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.sis.geometries.adapter;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import org.apache.sis.geometries.AttributesType;
+import org.apache.sis.geometries.Curve;
+import org.apache.sis.geometries.DataPoints;
+import org.apache.sis.geometries.Empty;
+import org.apache.sis.geometries.Geometries;
+import org.apache.sis.geometries.Geometry;
+import org.apache.sis.geometries.GeometryCollection;
+import org.apache.sis.geometries.GeometryFactory;
+import org.apache.sis.geometries.Point;
+import org.apache.sis.geometries.Surface;
+import org.apache.sis.geometries.curve.CircularString;
+import org.apache.sis.geometries.curve.CompoundCurve;
+import org.apache.sis.geometries.curve.LinearRing;
+import org.apache.sis.geometries.curve.LineString;
+import org.apache.sis.geometries.curve.MultiCurve;
+import org.apache.sis.geometries.curve.MultiLineString;
+import org.apache.sis.geometries.point.MultiPoint;
+import org.apache.sis.geometries.surface.CurvePolygon;
+import org.apache.sis.geometries.surface.MultiPolygon;
+import org.apache.sis.geometries.surface.MultiSurface;
+import org.apache.sis.geometries.surface.Polygon;
+import org.apache.sis.geometries.surface.PolyhedralSurface;
+import org.apache.sis.geometries.surface.TIN;
+import org.apache.sis.geometries.surface.Triangle;
+import org.apache.sis.maths.Array;
+import org.apache.sis.maths.DataType;
+import org.apache.sis.maths.NDArrays;
+import org.apache.sis.maths.SampleSystem;
+import org.opengis.referencing.crs.CoordinateReferenceSystem;
+
+
+/**
+ * Recursive descent parser of the Well-Known Text representation of geometries.
+ * One instance parses one text and is then discarded.
+ *
+ * <p>The grammar, the set of accepted keywords and the handling of the {@code Z}, {@code M} and
+ * {@code ZM} flags are documented on {@link WellKnownText}, which is the public face of this
+ * class. Everything below is that grammar written as one method per production.</p>
+ *
+ * <h2>Dimensions</h2>
+ * A Well-Known Text is homogeneous: a single geometry cannot mix 2- and 3-dimensional positions,
+ * because an Apache SIS {@link DataPoints} reports one dimension only. The flags are therefore
+ * parser state rather than per-element state: whichever element states them first fixes them, a
+ * later element contradicting them is an error, and if no element states them at all they are
+ * inferred from the width of the first coordinate tuple.
+ *
+ * @author  Johann Sorel (Geomatys)
+ */
+final class WellKnownTextParser {
+    /**
+     * Value returned by {@link #readFlags()} when the text carries no dimension flag.
+     */
+    private static final int UNSPECIFIED = -1;
+
+    /**
+     * Bits of the value returned by {@link #readFlags()}.
+     */
+    private static final int FLAG_Z = 1, FLAG_M = 2;
+
+    /**
+     * Sample system of the {@linkplain AttributesType#ATT_M measure} attribute, which is a single
+     * value with no coordinate reference system of its own.
+     */
+    private static final SampleSystem MEASURE_SYSTEM = SampleSystem.ofSize(1);
+
+    /**
+     * The text being parsed.
+     */
+    private final String text;
+
+    /**
+     * The coordinate reference system given by the caller, or {@code null} for deriving an
+     * {@linkplain Geometries#getUndefinedCRS(int) undefined} one from the number of ordinates.
+     */
+    private final CoordinateReferenceSystem userCRS;
+
+    /**
+     * Index in {@link #text} of the next character to read.
+     */
+    private int pos;
+
+    /**
+     * Whether {@link #hasZ} and {@link #hasM} have been established, either by a dimension flag
+     * or by the width of a coordinate tuple.
+     */
+    private boolean flagsKnown;
+
+    /**
+     * Whether positions have a third ordinate, and whether they carry a measure.
+     */
+    private boolean hasZ, hasM;
+
+    /**
+     * Creates a parser for the given text.
+     *
+     * @param  text  the Well-Known Text to parse.
+     * @param  crs   the coordinate reference system to give to the geometries, or {@code null}.
+     */
+    WellKnownTextParser(final String text, final CoordinateReferenceSystem crs) {
+        this.text = text;
+        this.userCRS = crs;
+    }
+
+    /**
+     * Parses the whole text as a single geometry.
+     *
+     * @throws IllegalArgumentException if the text is malformed or names an unsupported type.
+     */
+    Geometry parse() {
+        final Geometry geometry = parseGeometry();
+        skipSpaces();
+        if (pos < text.length()) {
+            throw error("Unexpected text after the end of the geometry");
+        }
+        return geometry;
+    }
+
+    // ////////////////////////////////////////////////////////////////////////
+    // Productions ////////////////////////////////////////////////////////////
+    // ////////////////////////////////////////////////////////////////////////
+
+    /**
+     * Parses {@code <keyword> [Z|M|ZM] <body>}.
+     */
+    private Geometry parseGeometry() {
+        final String keyword = readWord();
+        if (keyword.isEmpty()) {
+            throw error("Expected a geometry type keyword");
+        }
+        applyFlags(readFlags());
+        switch (keyword) {
+            case Point.TYPE:              return parsePoint();
+            case LineString.TYPE:         return GeometryFactory.createLineString(readPointList());
+            case CircularString.TYPE:     return GeometryFactory.createCircularString(readPointList());
+            case CompoundCurve.TYPE:      return parseCompoundCurve();
+            case Polygon.TYPE:            return readPolygonBody();
+            case Triangle.TYPE:           return readTriangleBody();
+            case CurvePolygon.TYPE:       return parseCurvePolygon();
+            case PolyhedralSurface.TYPE:  return parsePolyhedralSurface();
+            case TIN.TYPE:                return parseTIN();
+            case MultiPoint.TYPE:         return parseMultiPoint();
+            case MultiLineString.TYPE:    return parseMultiLineString();
+            case MultiCurve.TYPE:         return parseMultiCurve();
+            case MultiPolygon.TYPE:       return parseMultiPolygon();
+            case MultiSurface.TYPE:       return parseMultiSurface();
+            case GeometryCollection.TYPE:
+            case "GEOMCOLLECTION":        return parseGeometryCollection();      // Alias in use in some databases.
+            default: throw error("Well-Known Text defines no geometry type named \"" + keyword + '"');
+        }
+    }
+
+    /**
+     * Parses {@code (1 2)} or {@code EMPTY}.
+     *
+     * <p>{@code POINT EMPTY} does not map to a {@link Point}: the Apache SIS model has no empty
+     * point, since a point sequence of length 0 is not a valid position of a point and
+     * {@link GeometryFactory#createPoint(CoordinateReferenceSystem)} builds a point at the origin
+     * rather than an absent one. It maps to {@link Empty} instead, the geometry which stands for
+     * the empty point set whatever its type, and which is written back as
+     * {@code GEOMETRYCOLLECTION EMPTY}.</p>
+     */
+    private Geometry parsePoint() {
+        if (readEmpty()) {
+            return GeometryFactory.createEmpty(crs());
+        }
+        return GeometryFactory.createPoint(readSinglePosition());
+    }
+
+    /**
+     * Parses {@code ((1 2, 3 4), CIRCULARSTRING (3 4, 5 6, 7 8))} or {@code EMPTY}.
+     */
+    private CompoundCurve parseCompoundCurve() {
+        if (readEmpty()) {
+            return GeometryFactory.createCompoundCurve(crs());
+        }
+        final List<Curve> curves = new ArrayList<>();
+        expect('(');
+        do {
+            curves.add(readCurve(false));
+        } while (accept(','));
+        expect(')');
+        return GeometryFactory.createCompoundCurve(curves.toArray(Curve[]::new));
+    }
+
+    /**
+     * Parses {@code (CIRCULARSTRING (…), (…))} or {@code EMPTY}.
+     */
+    private CurvePolygon parseCurvePolygon() {
+        if (readEmpty()) {
+            return GeometryFactory.createCurvePolygon(emptyRing(), List.of());
+        }
+        final List<Curve> rings = new ArrayList<>();
+        expect('(');
+        do {
+            rings.add(readCurve(true));
+        } while (accept(','));
+        expect(')');
+        return GeometryFactory.createCurvePolygon(rings.get(0), new ArrayList<>(rings.subList(1, rings.size())));
+    }
+
+    /**
+     * Parses {@code (((…)), ((…)))} or {@code EMPTY}: a list of polygon patches written
+     * without their keyword.
+     */
+    private PolyhedralSurface<Polygon> parsePolyhedralSurface() {
+        if (readEmpty()) {
+            return GeometryFactory.createPolyhedralSurface(crs(), new Polygon[0]);
+        }
+        final List<Polygon> patches = new ArrayList<>();
+        expect('(');
+        do {
+            patches.add(readPolygonBody());
+        } while (accept(','));
+        expect(')');
+        return GeometryFactory.createPolyhedralSurface(patches.toArray(Polygon[]::new));
+    }
+
+    /**
+     * Parses {@code (((…)), ((…)))} or {@code EMPTY}, where every patch is a triangle.
+     */
+    private TIN parseTIN() {
+        if (readEmpty()) {
+            return GeometryFactory.createTIN(crs(), new Triangle[0]);
+        }
+        final List<Triangle> patches = new ArrayList<>();
+        expect('(');
+        do {
+            patches.add(readTriangleBody());
+        } while (accept(','));
+        expect(')');
+        return GeometryFactory.createTIN(patches.toArray(Triangle[]::new));
+    }
+
+    /**
+     * Parses {@code ((1 2), (3 4))} or {@code (1 2, 3 4)} or {@code EMPTY}. The grammar allows
+     * the parentheses around each point to be omitted, and both forms are accepted here.
+     */
+    private MultiPoint<?> parseMultiPoint() {
+        if (readEmpty()) {
+            return GeometryFactory.createMultiPoint(crs());
+        }
+        final List<Point> points = new ArrayList<>();
+        expect('(');
+        do {
+            if (readEmpty()) {
+                // See parsePoint(): there is no empty point to put in the collection.
+                throw error("A " + MultiPoint.TYPE + " cannot hold an empty point");
+            } else if (peek() == '(') {
+                points.add(GeometryFactory.createPoint(readSinglePosition()));
+            } else {
+                final Coordinates c = new Coordinates();
+                c.add(readTuple());
+                points.add(GeometryFactory.createPoint(c.build()));
+            }
+        } while (accept(','));
+        expect(')');
+        return GeometryFactory.createMultiPoint(points.toArray(Point[]::new));
+    }
+
+    /**
+     * Parses {@code ((1 2, 3 4), (5 6, 7 8))} or {@code EMPTY}.
+     */
+    private MultiLineString parseMultiLineString() {
+        if (readEmpty()) {
+            return GeometryFactory.createMultiLineString(crs());
+        }
+        final List<LineString> members = new ArrayList<>();
+        expect('(');
+        do {
+            members.add(GeometryFactory.createLineString(readPointList()));
+        } while (accept(','));
+        expect(')');
+        return GeometryFactory.createMultiLineString(members.toArray(LineString[]::new));
+    }
+
+    /**
+     * Parses {@code (((…), (…)), ((…)))} or {@code EMPTY}.
+     */
+    private MultiPolygon parseMultiPolygon() {
+        if (readEmpty()) {
+            return GeometryFactory.createMultiPolygon(crs());
+        }
+        final List<Polygon> members = new ArrayList<>();
+        expect('(');
+        do {
+            members.add(readPolygonBody());
+        } while (accept(','));
+        expect(')');
+        return GeometryFactory.createMultiPolygon(members.toArray(Polygon[]::new));
+    }
+
+    /**
+     * Parses {@code (CIRCULARSTRING (…), (…))} or {@code EMPTY}: unlike a
+     * {@code MULTILINESTRING}, the members may be of any curve type and therefore keep their
+     * keyword, except a line string which may be written as a bare coordinate list.
+     */
+    private MultiCurve<Curve> parseMultiCurve() {
+        if (readEmpty()) {
+            return GeometryFactory.<Curve>createMultiCurve(crs());
+        }
+        final List<Curve> members = new ArrayList<>();
+        expect('(');
+        do {
+            members.add(readCurve(false));
+        } while (accept(','));
+        expect(')');
+        return GeometryFactory.createMultiCurve(members.toArray(Curve[]::new));
+    }
+
+    /**
+     * Parses {@code (CURVEPOLYGON (…), ((…)))} or {@code EMPTY}: as in a {@code MULTICURVE},
+     * the members keep their keyword except a polygon, which may be written as a bare ring list.
+     */
+    private MultiSurface<Surface> parseMultiSurface() {
+        if (readEmpty()) {
+            return GeometryFactory.<Surface>createMultiSurface(crs());
+        }
+        final List<Surface> members = new ArrayList<>();
+        expect('(');
+        do {
+            if (peek() == '(') {
+                members.add(readPolygonBody());
+            } else {
+                members.add(expectType(parseGeometry(), Surface.class, MultiSurface.TYPE));
+            }
+        } while (accept(','));
+        expect(')');
+        return GeometryFactory.createMultiSurface(members.toArray(Surface[]::new));
+    }
+
+    /**
+     * Parses {@code (POINT (1 2), LINESTRING (3 4, 5 6))} or {@code EMPTY}. Every member keeps
+     * its keyword, since a collection puts no constraint on the type of its members.
+     */
+    private GeometryCollection<Geometry> parseGeometryCollection() {
+        if (readEmpty()) {
+            return GeometryFactory.<Geometry>createGeometryCollection(crs());
+        }
+        final List<Geometry> members = new ArrayList<>();
+        expect('(');
+        do {
+            members.add(parseGeometry());
+        } while (accept(','));
+        expect(')');
+        return GeometryFactory.createGeometryCollection(members.toArray(Geometry[]::new));
+    }
+
+    // ////////////////////////////////////////////////////////////////////////
+    // Shared productions /////////////////////////////////////////////////////
+    // ////////////////////////////////////////////////////////////////////////
+
+    /**
+     * Parses a curve written either as a bare coordinate list or with its own keyword.
+     * A bare list is a line string, and is closed into a {@link LinearRing} when it is used as
+     * the boundary of a {@link CurvePolygon}.
+     *
+     * @param  asRing  whether a bare coordinate list is a ring rather than a line string.
+     */
+    private Curve readCurve(final boolean asRing) {
+        if (peek() == '(') {
+            final DataPoints points = readPointList();
+            return asRing ? GeometryFactory.createLinearRing(points)
+                          : GeometryFactory.createLineString(points);
+        }
+        return expectType(parseGeometry(), Curve.class, asRing ? CurvePolygon.TYPE : CompoundCurve.TYPE);
+    }
+
+    /**
+     * Parses {@code ((…), (…))} or {@code EMPTY}: the rings of a polygon, without keyword.
+     */
+    private Polygon readPolygonBody() {
+        if (readEmpty()) {
+            return GeometryFactory.createPolygon(emptyRing(), List.of());
+        }
+        final List<LinearRing> rings = readRings();
+        return GeometryFactory.createPolygon(rings.get(0), new ArrayList<>(rings.subList(1, rings.size())));
+    }
+
+    /**
+     * Parses {@code ((…))} or {@code EMPTY}: the single ring of a triangle, without keyword.
+     */
+    private Triangle readTriangleBody() {
+        if (readEmpty()) {
+            return GeometryFactory.createTriangle(emptyRing());
+        }
+        final List<LinearRing> rings = readRings();
+        if (rings.size() != 1) {
+            throw error("A " + Triangle.TYPE + " patch has no interior ring, but " + (rings.size() - 1) + " were given");
+        }
+        return GeometryFactory.createTriangle(rings.get(0));
+    }
+
+    /**
+     * Parses {@code ((0 0, 1 0, 0 0), (…))}: one or more parenthesized coordinate lists.
+     */
+    private List<LinearRing> readRings() {
+        final List<LinearRing> rings = new ArrayList<>();
+        expect('(');
+        do {
+            rings.add(GeometryFactory.createLinearRing(readPointList()));
+        } while (accept(','));
+        expect(')');
+        return rings;
+    }
+
+    /**
+     * Parses {@code (1 2, 3 4)} or {@code EMPTY}: one parenthesized list of coordinate tuples.
+     */
+    private DataPoints readPointList() {
+        if (readEmpty()) {
+            return new Coordinates().build();
+        }
+        final Coordinates coordinates = new Coordinates();
+        expect('(');
+        do {
+            coordinates.add(readTuple());
+        } while (accept(','));
+        expect(')');
+        return coordinates.build();
+    }
+
+    /**
+     * Parses {@code (1 2)}: exactly one parenthesized coordinate tuple, as in a point.
+     */
+    private DataPoints readSinglePosition() {
+        final Coordinates coordinates = new Coordinates();
+        expect('(');
+        coordinates.add(readTuple());
+        expect(')');
+        return coordinates.build();
+    }
+
+    /**
+     * Returns an empty ring, for the {@code EMPTY} form of the types which are made of rings.
+     */
+    private LinearRing emptyRing() {
+        return GeometryFactory.createLinearRing(new Coordinates().build());
+    }
+
+    /**
+     * Casts a parsed member to the type its container requires.
+     */
+    private <T> T expectType(final Geometry geometry, final Class<T> type, final String container) {
+        if (type.isInstance(geometry)) {
+            return type.cast(geometry);
+        }
+        throw error("A " + container + " cannot contain a " + geometry.getGeometryType());
+    }
+
+    // ////////////////////////////////////////////////////////////////////////
+    // Dimensions /////////////////////////////////////////////////////////////
+    // ////////////////////////////////////////////////////////////////////////
+
+    /**
+     * Returns the number of ordinates of a position, which is 2 unless the text has established
+     * that positions carry a <var>z</var> ordinate. A text made only of {@code EMPTY} establishes
+     * nothing, in which case the 2-dimensional default applies.
+     */
+    private int positionDimension() {
+        return (flagsKnown && hasZ) ? 3 : 2;
+    }
+
+    /**
+     * Returns the coordinate reference system to give to the geometries, which is the one the
+     * caller supplied if any, and an {@linkplain Geometries#getUndefinedCRS(int) undefined} one
+     * of the right dimension otherwise.
+     *
+     * @throws IllegalArgumentException if the caller supplied a system whose dimension
+     *         contradicts the number of ordinates found in the text.
+     */
+    private CoordinateReferenceSystem crs() {
+        final int dimension = positionDimension();
+        if (userCRS == null) {
+            return Geometries.getUndefinedCRS(dimension);
+        }
+        final int actual = userCRS.getCoordinateSystem().getDimension();
+        if (actual != dimension) {
+            throw error("The given coordinate reference system has " + actual + " dimensions,"
+                    + " but the text has " + dimension + " ordinates per position");
+        }
+        return userCRS;
+    }
+
+    /**
+     * Records the dimension flag of an element. The first element to state the flags fixes them
+     * for the whole text; a later element may repeat them but not contradict them.
+     */
+    private void applyFlags(final int flags) {
+        if (flags == UNSPECIFIED) {
+            return;
+        }
+        final boolean z = (flags & FLAG_Z) != 0;
+        final boolean m = (flags & FLAG_M) != 0;
+        if (flagsKnown) {
+            if (z != hasZ || m != hasM) {
+                throw error("Dimension flag " + flagName(z, m) + " contradicts the "
+                        + flagName(hasZ, hasM) + " established earlier in the text");
+            }
+        } else {
+            hasZ = z;
+            hasM = m;
+            flagsKnown = true;
+        }
+    }
+
+    /**
+     * Establishes or verifies the dimension flags against the width of a coordinate tuple.
+     * Called by {@link Coordinates} when it receives its first tuple.
+     */
+    private void inferFlags(final int width) {
+        if (flagsKnown) {
+            final int expected = positionDimension() + (hasM ? 1 : 0);
+            if (width != expected) {
+                throw error("A coordinate tuple of " + width + " ordinates contradicts the "
+                        + flagName(hasZ, hasM) + " dimension flag, which requires " + expected);
+            }
+            return;
+        }
+        switch (width) {
+            case 2: hasZ = false; break;
+            case 3: hasZ = true;  break;
+            default: throw error("A coordinate tuple of " + width + " ordinates is ambiguous"
+                        + " without a Z, M or ZM flag");
+        }
+        hasM = false;
+        flagsKnown = true;
+    }
+
+    /**
+     * Returns {@code "Z"}, {@code "M"}, {@code "ZM"} or {@code "XY"} for an error message.
+     */
+    private static String flagName(final boolean z, final boolean m) {
+        if (z) return m ? "ZM" : "Z";
+        return m ? "M" : "XY";
+    }
+
+    /**
+     * Accumulator of the coordinate tuples of one element, and factory of the
+     * {@link DataPoints} the Apache SIS geometry model is built upon.
+     *
+     * <p>Ordinates are accumulated in a single flat array in row-major order, which is the layout
+     * {@link NDArrays#of(SampleSystem, double...)} expects. The measure, when there is one, is the
+     * last ordinate of each tuple in the text but a separate attribute in the model, so
+     * {@link #build()} splits the two apart.</p>
+     */
+    private final class Coordinates {
+        /**
+         * The accumulated ordinates. Only the first {@link #count} elements are meaningful.
+         */
+        private double[] values = new double[12];
+
+        /**
+         * Number of meaningful ordinates in {@link #values}, always a multiple of {@link #width}.
+         */
+        private int count;
+
+        /**
+         * Number of ordinates per tuple, or 0 if no tuple has been added yet.
+         */
+        private int width;
+
+        /**
+         * Appends one coordinate tuple. The first one fixes the width for all the others,
+         * and settles the dimension flags if the text did not state them.
+         */
+        void add(final double[] tuple) {
+            if (width == 0) {
+                width = tuple.length;
+                inferFlags(width);
+            } else if (tuple.length != width) {
+                throw error("A coordinate tuple of " + tuple.length + " ordinates follows tuples of " + width);
+            }
+            if (count + width > values.length) {
+                values = Arrays.copyOf(values, Math.max(count + width, values.length * 2));
+            }
+            System.arraycopy(tuple, 0, values, count, width);
+            count += width;
+        }
+
+        /**
+         * Builds the point sequence of the accumulated tuples, possibly empty.
+         */
+        DataPoints build() {
+            final int posWidth = positionDimension();
+            final SampleSystem posSystem = SampleSystem.of(crs());
+            final int size = (width != 0) ? count / width : 0;
+            if (size == 0) {
+                final Array empty = NDArrays.of(posSystem, DataType.DOUBLE, 0);
+                if (!hasM) {
+                    return GeometryFactory.createSequence(empty);
+                }
+                return createSequence(empty, NDArrays.of(MEASURE_SYSTEM, DataType.DOUBLE, 0));
+            }
+            if (!hasM) {
+                return GeometryFactory.createSequence(NDArrays.of(posSystem, Arrays.copyOf(values, count)));
+            }
+            /*
+             * The measure is the ordinate following the position ones in each tuple of the text,
+             * but a one dimensional attribute of its own in the model.
+             */
+            final double[] p = new double[size * posWidth];
+            final double[] m = new double[size];
+            for (int i = 0; i < size; i++) {
+                System.arraycopy(values, i * width, p, i * posWidth, posWidth);
+                m[i] = values[i * width + posWidth];
+            }
+            return createSequence(NDArrays.of(posSystem, p), NDArrays.of(MEASURE_SYSTEM, m));
+        }
+
+        /**
+         * Returns a sequence holding both the positions and the measures.
+         */
+        private DataPoints createSequence(final Array positions, final Array measures) {
+            final Map<String,Array> attributes = new LinkedHashMap<>(4);
+            attributes.put(AttributesType.ATT_POSITION, positions);
+            attributes.put(AttributesType.ATT_M, measures);
+            return GeometryFactory.createSequence(attributes);
+        }
+    }
+
+    // ////////////////////////////////////////////////////////////////////////
+    // Tokens /////////////////////////////////////////////////////////////////
+    // ////////////////////////////////////////////////////////////////////////
+
+    /**
+     * Reads the numbers of one coordinate tuple, which are separated by spaces only.
+     */
+    private double[] readTuple() {
+        double[] values = new double[4];
+        int n = 0;
+        for (;;) {
+            skipSpaces();
+            if (pos >= text.length() || !isNumberStart(text.charAt(pos))) {
+                break;
+            }
+            if (n == values.length) {
+                values = Arrays.copyOf(values, n * 2);
+            }
+            values[n++] = readNumber();
+        }
+        if (n == 0) {
+            throw error("Expected a coordinate tuple");
+        }
+        return Arrays.copyOf(values, n);
+    }
+
+    /**
+     * Reads one number. The lexical form is the one of {@link Double#parseDouble(String)}
+     * restricted to decimal notation, and is therefore independent of the default locale.
+     */
+    private double readNumber() {
+        final int start = pos;
+        final int length = text.length();
+        if (pos < length && isSign(text.charAt(pos))) pos++;
+        while (pos < length && (isDigit(text.charAt(pos)) || text.charAt(pos) == '.')) pos++;
+        if (pos < length && (text.charAt(pos) == 'e' || text.charAt(pos) == 'E')) {
+            pos++;
+            if (pos < length && isSign(text.charAt(pos))) pos++;
+            while (pos < length && isDigit(text.charAt(pos))) pos++;
+        }
+        final String token = text.substring(start, pos);
+        try {
+            return Double.parseDouble(token);
+        } catch (NumberFormatException e) {
+            pos = start;
+            throw error("\"" + token + "\" is not a number");
+        }
+    }
+
+    /**
+     * Reads the next word, in upper case, or an empty string if the next character is not a
+     * letter. Words are the keywords, the dimension flags and {@code EMPTY}.
+     */
+    private String readWord() {
+        skipSpaces();
+        final int start = pos;
+        while (pos < text.length() && Character.isLetter(text.charAt(pos))) pos++;
+        return text.substring(start, pos).toUpperCase(Locale.ROOT);
+    }
+
+    /**
+     * Reads the optional dimension flag which may follow a keyword.
+     *
+     * @return {@link #FLAG_Z} and/or {@link #FLAG_M}, or {@link #UNSPECIFIED} if there is none.
+     */
+    private int readFlags() {
+        final int mark = pos;
+        switch (readWord()) {
+            case "Z":  return FLAG_Z;
+            case "M":  return FLAG_M;
+            case "ZM": return FLAG_Z | FLAG_M;
+            default: pos = mark; return UNSPECIFIED;
+        }
+    }
+
+    /**
+     * Consumes the {@code EMPTY} keyword if it is the next word, and returns whether it was.
+     */
+    private boolean readEmpty() {
+        final int mark = pos;
+        if ("EMPTY".equals(readWord())) {
+            return true;
+        }
+        pos = mark;
+        return false;
+    }
+
+    /**
+     * Consumes the given character, which must be the next one.
+     */
+    private void expect(final char c) {
+        if (!accept(c)) {
+            throw error("Expected '" + c + '\'');
+        }
+    }
+
+    /**
+     * Consumes the given character if it is the next one, and returns whether it was.
+     */
+    private boolean accept(final char c) {
+        skipSpaces();
+        if (pos < text.length() && text.charAt(pos) == c) {
+            pos++;
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Returns the next character without consuming it, or {@code 0} at the end of the text.
+     */
+    private char peek() {
+        skipSpaces();
+        return (pos < text.length()) ? text.charAt(pos) : 0;
+    }
+
+    /**
+     * Advances past any whitespace.
+     */
+    private void skipSpaces() {
+        while (pos < text.length() && Character.isWhitespace(text.charAt(pos))) pos++;
+    }
+
+    private static boolean isDigit(final char c) {
+        return c >= '0' && c <= '9';
+    }
+
+    private static boolean isSign(final char c) {
+        return c == '+' || c == '-';
+    }
+
+    private static boolean isNumberStart(final char c) {
+        return isDigit(c) || isSign(c) || c == '.';
+    }
+
+    /**
+     * Returns the exception to throw for a malformed text, pointing at the current position.
+     */
+    private IllegalArgumentException error(final String message) {
+        return new IllegalArgumentException(message + ", at offset " + pos + " of \"" + text + '"');
+    }
+}
diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/Geodesic.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/Geodesic.java
index 42575abe8c..f3035a7a62 100644
--- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/Geodesic.java
+++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/Geodesic.java
@@ -19,9 +19,7 @@ package org.apache.sis.geometries.curve;
 import org.apache.sis.geometries.AttributesType;
 import org.apache.sis.geometries.Curve;
 import org.apache.sis.geometries.CurveInterpolation;
-import org.apache.sis.geometries.DataPoints;
 import org.apache.sis.geometries.Point;
-import org.apache.sis.geometries.internal.shared.AbstractGeometry;
 import org.apache.sis.geometries.internal.shared.DefaultGeodesic;
 import org.apache.sis.maths.Array;
 import static org.opengis.annotation.Specification.ISO_19107;
@@ -129,12 +127,4 @@ public sealed interface Geodesic extends Curve
         return getDataPoints().getAttributesType();
     }
 
-    @Override
-    default String asText() {
-        final StringBuilder sb = new StringBuilder("GEODESIC (");
-        final DataPoints points = getDataPoints();
-        AbstractGeometry.toText(sb, points);
-        sb.append(')');
-        return sb.toString();
-    }
 }
diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/LineString.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/LineString.java
index 02388c94bd..fef79f59f4 100644
--- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/LineString.java
+++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/LineString.java
@@ -22,7 +22,6 @@ import org.apache.sis.geometries.Curve;
 import org.apache.sis.geometries.CurveInterpolation;
 import org.apache.sis.geometries.DataPoints;
 import org.apache.sis.geometries.Point;
-import org.apache.sis.geometries.internal.shared.AbstractGeometry;
 import org.apache.sis.geometries.internal.shared.DefaultLineString;
 import org.apache.sis.geometries.mesh.MeshPrimitive;
 import org.apache.sis.geometries.operation.triangulate.delaunay.OrientedEdge;
@@ -162,12 +161,4 @@ public sealed interface LineString extends Curve
         return points.getEnvelope();
     }
 
-    @Override
-    default String asText() {
-        final StringBuilder sb = new StringBuilder("LINESTRING (");
-        final DataPoints points = getDataPoints();
-        AbstractGeometry.toText(sb, points);
-        sb.append(')');
-        return sb.toString();
-    }
 }
diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/LinearRing.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/LinearRing.java
index a49ed90fc0..91ac52b456 100644
--- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/LinearRing.java
+++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/LinearRing.java
@@ -16,10 +16,7 @@
  */
 package org.apache.sis.geometries.curve;
 
-import org.apache.sis.geometries.DataPoints;
-import org.apache.sis.geometries.internal.shared.AbstractGeometry;
 import org.apache.sis.geometries.internal.shared.DefaultLinearRing;
-import org.apache.sis.maths.Tuple;
 
 
 /**
@@ -68,16 +65,4 @@ public sealed interface LinearRing extends LineString
         return true;
     }
 
-    @Override
-    default String asText() {
-        final StringBuilder sb = new StringBuilder("LINEARRING (");
-        final DataPoints points = getDataPoints();
-        for (int i = 0, n = points.size() ; i < n; i++) {
-            final Tuple pt = points.getPosition(i);
-            if (i > 0) sb.append(',');
-            AbstractGeometry.toText(sb, pt);
-        }
-        sb.append(')');
-        return sb.toString();
-    }
 }
diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/MultiLineString.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/MultiLineString.java
index ba7990bbfe..b0b6fe9725 100644
--- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/MultiLineString.java
+++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/MultiLineString.java
@@ -17,12 +17,9 @@
 package org.apache.sis.geometries.curve;
 
 import java.util.Set;
-import org.apache.sis.geometries.DataPoints;
 import org.apache.sis.geometries.GeometryType;
-import org.apache.sis.geometries.internal.shared.AbstractGeometry;
 import org.apache.sis.geometries.internal.shared.DefaultMultiLineString;
 import org.apache.sis.geometries.mesh.MeshPrimitive;
-import org.apache.sis.maths.Tuple;
 
 
 /**
@@ -52,22 +49,4 @@ public sealed interface MultiLineString extends MultiCurve<LineString>
         return Set.of(GeometryType.LINE);
     }
 
-    @Override
-    default String asText() {
-        final StringBuilder sb = new StringBuilder("MULTILINESTRING (");
-        for (int k = 0, kn = getNumGeometries(); k < kn; k++){
-            if (k > 0) sb.append(',');
-            sb.append('(');
-            final LineString line = getGeometryN(k);
-            final DataPoints points = line.getDataPoints();
-            for (int i = 0, n = points.size() ; i < n; i++) {
-                final Tuple pos = points.getPosition(i);
-                if (i > 0) sb.append(',');
-                AbstractGeometry.toText(sb, pos);
-            }
-            sb.append(')');
-        }
-        sb.append(')');
-        return sb.toString();
-    }
 }
diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/NurbCurve.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/NurbCurve.java
index a0519d288f..02c4ef07aa 100644
--- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/NurbCurve.java
+++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/NurbCurve.java
@@ -18,8 +18,6 @@ package org.apache.sis.geometries.curve;
 
 import org.apache.sis.geometries.AttributesType;
 import org.apache.sis.geometries.CurveInterpolation;
-import org.apache.sis.geometries.DataPoints;
-import org.apache.sis.geometries.internal.shared.AbstractGeometry;
 import org.apache.sis.geometries.internal.shared.DefaultNurbCurve;
 import static org.opengis.annotation.Specification.ISO_19107;
 import org.opengis.annotation.UML;
@@ -137,12 +135,4 @@ public sealed interface NurbCurve extends BSplineCurve
         throw new UnsupportedOperationException("Not supported yet.");
     }
 
-    @Override
-    default String asText() {
-        final StringBuilder sb = new StringBuilder("NURBS (");
-        final DataPoints points = getDataPoints();
-        AbstractGeometry.toText(sb, points);
-        sb.append(')');
-        return sb.toString();
-    }
 }
diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/Rhumb.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/Rhumb.java
index 958b55d612..6b2b13a5da 100644
--- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/Rhumb.java
+++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/Rhumb.java
@@ -20,9 +20,7 @@ import org.apache.sis.geometries.AttributesType;
 import org.apache.sis.geometries.Bearing;
 import org.apache.sis.geometries.Curve;
 import org.apache.sis.geometries.CurveInterpolation;
-import org.apache.sis.geometries.DataPoints;
 import org.apache.sis.geometries.Point;
-import org.apache.sis.geometries.internal.shared.AbstractGeometry;
 import org.apache.sis.geometries.internal.shared.DefaultRhumb;
 import org.apache.sis.maths.Array;
 import static org.opengis.annotation.Specification.ISO_19107;
@@ -139,12 +137,4 @@ public sealed interface Rhumb extends Curve
         return getDataPoints().getAttributesType();
     }
 
-    @Override
-    default String asText() {
-        final StringBuilder sb = new StringBuilder("RHUMB (");
-        final DataPoints points = getDataPoints();
-        AbstractGeometry.toText(sb, points);
-        sb.append(')');
-        return sb.toString();
-    }
 }
diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/AbstractGeometry.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/AbstractGeometry.java
index c2be631961..d60d869e56 100644
--- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/AbstractGeometry.java
+++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/AbstractGeometry.java
@@ -18,12 +18,8 @@ package org.apache.sis.geometries.internal.shared;
 
 import java.util.HashMap;
 import java.util.Map;
-import org.apache.sis.geometries.DataPoints;
 import org.apache.sis.geometries.Geometry;
 import org.apache.sis.geometry.GeneralEnvelope;
-import org.apache.sis.maths.Array;
-import org.apache.sis.maths.Cursor;
-import org.apache.sis.maths.Tuple;
 import org.opengis.geometry.Envelope;
 
 
@@ -49,38 +45,6 @@ public abstract non-sealed class AbstractGeometry implements Geometry {
         return asText();
     }
 
-    public static void toText(StringBuilder sb, Tuple tuple) {
-        sb.append(tuple.get(0));
-        for (int i = 1, n = tuple.getDimension(); i < n; i++) {
-            sb.append(' ');
-            sb.append(tuple.get(i));
-        }
-    }
-
-    public static void toText(StringBuilder sb, Array array) {
-        final Cursor cursor = array.cursor();
-        boolean first = true;
-        while (cursor.next()) {
-            if (!first) {
-                sb.append(", ");
-            }
-            toText(sb, cursor.samples());
-            first = false;
-        }
-    }
-
-    public static void toText(StringBuilder sb, DataPoints array) {
-        final int size = array.size();
-        if (size == 0) return;
-
-        for (int i = 0; i < size; i++) {
-            if (i != 0) {
-                sb.append(", ");
-            }
-            toText(sb, array.getPosition(i));
-        }
-    }
-
     /**
      * Returns the union of the envelopes of the given geometries,
      * or {@code null} if there is nothing to compute a union of.
diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultArcByBulge.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultArcByBulge.java
index 543edfa4bb..645043be66 100644
--- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultArcByBulge.java
+++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultArcByBulge.java
@@ -78,13 +78,4 @@ public non-sealed class DefaultArcByBulge extends AbstractGeometry implements Ar
         throw new UnsupportedOperationException("Not supported yet.");
     }
 
-    @Override
-    public String asText() {
-        final StringBuilder sb = new StringBuilder(TYPE).append(" (");
-        toText(sb, points);
-        sb.append(", BULGE ").append(bulge);
-        sb.append(", NORMAL ");
-        toText(sb, normal);
-        return sb.append(')').toString();
-    }
 }
diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultArcByCenterPoint.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultArcByCenterPoint.java
index a4f7d61106..6dbf90ff41 100644
--- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultArcByCenterPoint.java
+++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultArcByCenterPoint.java
@@ -97,15 +97,4 @@ public non-sealed class DefaultArcByCenterPoint extends AbstractGeometry impleme
         throw new UnsupportedOperationException("Not supported yet.");
     }
 
-    @Override
-    public String asText() {
-        final StringBuilder sb = new StringBuilder(TYPE).append(" (");
-        toText(sb, center.getPosition());
-        sb.append(", RADIUS ").append(radius);
-        if (radiusUnit != null) {
-            sb.append(' ').append(radiusUnit);
-        }
-        sb.append(", ANGLES ").append(startAngle).append(' ').append(endAngle);
-        return sb.append(')').toString();
-    }
 }
diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultCircularString.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultCircularString.java
index 298507c309..30ab8e0273 100644
--- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultCircularString.java
+++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultCircularString.java
@@ -55,11 +55,4 @@ public non-sealed class DefaultCircularString extends AbstractGeometry implement
         throw new UnsupportedOperationException("Not supported yet.");
     }
 
-    @Override
-    public String asText() {
-        final StringBuilder sb = new StringBuilder(TYPE).append(" (");
-        toText(sb, points);
-        return sb.append(')').toString();
-    }
-
 }
diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultCompoundCurve.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultCompoundCurve.java
index ea319daec8..37e10aadaf 100644
--- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultCompoundCurve.java
+++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultCompoundCurve.java
@@ -96,16 +96,6 @@ public non-sealed class DefaultCompoundCurve extends AbstractGeometry implements
         return envUnion(curves);
     }
 
-    @Override
-    public String asText() {
-        final StringBuilder sb = new StringBuilder(TYPE).append(" (");
-        for (int i = 0; i < curves.length; i++) {
-            if (i != 0) sb.append(", ");
-            sb.append(curves[i].asText());
-        }
-        return sb.append(')').toString();
-    }
-
     @Override
     public Array getControlPoints() {
         throw new UnsupportedOperationException("Not supported yet.");
diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultCurvePolygon.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultCurvePolygon.java
index dd808cbac7..c86cc90970 100644
--- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultCurvePolygon.java
+++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultCurvePolygon.java
@@ -87,12 +87,4 @@ public non-sealed class DefaultCurvePolygon extends AbstractGeometry implements
         throw new UnsupportedOperationException("Not supported yet.");
     }
 
-    @Override
-    public String asText() {
-        final StringBuilder sb = new StringBuilder(TYPE).append(" (").append(exterior.asText());
-        for (final Curve interior : interiors) {
-            sb.append(", ").append(interior.asText());
-        }
-        return sb.append(')').toString();
-    }
 }
diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultPolyhedralSurface.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultPolyhedralSurface.java
index d307336018..6d82f4c9f0 100644
--- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultPolyhedralSurface.java
+++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultPolyhedralSurface.java
@@ -95,13 +95,4 @@ public non-sealed class DefaultPolyhedralSurface<T extends Polygon> extends Abst
         return envUnion(patches);
     }
 
-    @Override
-    public String asText() {
-        final StringBuilder sb = new StringBuilder(PolyhedralSurface.TYPE).append(" (");
-        for (int i = 0; i < patches.length; i++) {
-            if (i != 0) sb.append(", ");
-            sb.append(patches[i].asText());
-        }
-        return sb.append(')').toString();
-    }
 }
diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultPolyhedron.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultPolyhedron.java
index 14cc4bb18d..b603651aca 100644
--- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultPolyhedron.java
+++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultPolyhedron.java
@@ -16,7 +16,6 @@
  */
 package org.apache.sis.geometries.internal.shared;
 
-import java.util.ArrayList;
 import java.util.List;
 import java.util.Objects;
 import javax.measure.quantity.Area;
@@ -86,19 +85,6 @@ public non-sealed class DefaultPolyhedron extends AbstractGeometry implements Po
         return exterior.getEnvelope();
     }
 
-    @Override
-    public String asText() {
-        final List<MultiPolygon> shells = new ArrayList<>(interiors.size() + 1);
-        shells.add(exterior);
-        shells.addAll(interiors);
-        final StringBuilder sb = new StringBuilder(TYPE).append(" (");
-        for (int i = 0; i < shells.size(); i++) {
-            if (i != 0) sb.append(", ");
-            sb.append(shells.get(i).asText());
-        }
-        return sb.append(')').toString();
-    }
-
     @Override
     public Geometry getBoundary() {
         throw new UnsupportedOperationException("Not supported yet.");
diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultReversedCurve.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultReversedCurve.java
index d00b5d1488..efcddf5bce 100644
--- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultReversedCurve.java
+++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultReversedCurve.java
@@ -128,11 +128,6 @@ public non-sealed class DefaultReversedCurve extends AbstractGeometry implements
         return base.getEnvelope();
     }
 
-    @Override
-    public String asText() {
-        return base.asText();
-    }
-
     @Override
     public Array getControlPoints() {
         return base.getControlPoints();
diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultReversedSurface.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultReversedSurface.java
index b89cca5147..fa6730f8c1 100644
--- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultReversedSurface.java
+++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultReversedSurface.java
@@ -101,8 +101,4 @@ public non-sealed class DefaultReversedSurface extends AbstractGeometry implemen
         return base.getEnvelope();
     }
 
-    @Override
-    public String asText() {
-        return base.asText();
-    }
 }
diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultTriangle.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultTriangle.java
index 257544b97a..acb9c10426 100644
--- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultTriangle.java
+++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultTriangle.java
@@ -73,9 +73,4 @@ public non-sealed class DefaultTriangle extends DefaultPolygon implements Triang
         return 7 * exterior.hashCode();
     }
 
-    @Override
-    public String asText() {
-        return Triangle.super.asText();
-    }
-
 }
diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultTriangulatedSurface.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultTriangulatedSurface.java
index 4991032ff5..f581ab4cf0 100644
--- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultTriangulatedSurface.java
+++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultTriangulatedSurface.java
@@ -43,12 +43,4 @@ public non-sealed class DefaultTriangulatedSurface extends DefaultPolyhedralSurf
     public String getGeometryType() {
         return TIN.TYPE;
     }
-
-    /**
-     * Delegates to {@link TIN#asText()}, which formats the triangles as a WKT {@code TIN}.
-     */
-    @Override
-    public String asText() {
-        return TIN.super.asText();
-    }
 }
diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/operation/triangulate/delaunay/Delaunay.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/operation/triangulate/delaunay/Delaunay.java
index 192ae45310..b6bee56335 100644
--- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/operation/triangulate/delaunay/Delaunay.java
+++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/operation/triangulate/delaunay/Delaunay.java
@@ -427,7 +427,6 @@ public final class Delaunay {
      * @throws OperationException if an algorithm exception occurs
      */
     private OrientedEdge insertEdge(OrientedEdge A_B, Vertex Z, boolean isContraint) throws OperationException {
-//        System.out.println("ee " + A_B.getTriangle().asTextPolygon() +" LINESTRING(" + A_B.getStart().getPosition().get(0) +" "+  A_B.getStart().getPosition().get(1) + ", " + Z.getPosition().get(0) +" "+ Z.getPosition().get(1) + ")");
 
         //find the starting triangle
         /*
diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/point/MultiPoint.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/point/MultiPoint.java
index 764c6095b4..516c80ae84 100644
--- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/point/MultiPoint.java
+++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/point/MultiPoint.java
@@ -22,7 +22,6 @@ import java.util.Set;
 import org.apache.sis.geometries.GeometryCollection;
 import org.apache.sis.geometries.GeometryType;
 import org.apache.sis.geometries.Point;
-import org.apache.sis.geometries.internal.shared.AbstractGeometry;
 import org.apache.sis.geometries.internal.shared.DefaultMultiPoint;
 import org.apache.sis.geometries.internal.shared.DefaultRawMultiPoint;
 import org.apache.sis.geometries.mesh.MeshPrimitive;
@@ -115,17 +114,4 @@ public sealed interface MultiPoint<T extends Point> extends GeometryCollection<T
         };
     }
 
-    @Override
-    default String asText() {        int dimension = getDimension();
-        final StringBuilder sb = new StringBuilder(TYPE);
-        sb.append('(');
-        for (int i = 0, n = getNumGeometries(); i < n; i++) {
-            T point = getGeometryN(i);
-            if (i > 0) sb.append(',');
-            AbstractGeometry.toText(sb, point.getPosition());
-        }
-        sb.append(')');
-        return sb.toString();
-    }
-
 }
diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/surface/CurvePolygon.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/surface/CurvePolygon.java
index 9b5151bc55..3bf17c75f7 100644
--- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/surface/CurvePolygon.java
+++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/surface/CurvePolygon.java
@@ -20,7 +20,6 @@ import java.util.List;
 import org.apache.sis.geometries.AttributesType;
 import org.apache.sis.geometries.Curve;
 import org.apache.sis.geometries.Surface;
-import org.apache.sis.geometries.internal.shared.AbstractGeometry;
 import org.apache.sis.geometries.internal.shared.DefaultCurvePolygon;
 import static org.opengis.annotation.Specification.ISO_19107;
 import org.opengis.annotation.UML;
@@ -152,19 +151,4 @@ public sealed interface CurvePolygon extends Surface
         throw new UnsupportedOperationException();
     }
 
-    @Override
-    default String asText() {
-        final StringBuilder sb = new StringBuilder("POLYGON ((");
-        AbstractGeometry.toText(sb,  getExteriorRing().asLine(null, null).getDataPoints());
-        sb.append(')');
-        for (int i = 0, n = getNumInteriorRing(); i < n; i++) {
-            if (i != 0) sb.append(',');
-            sb.append('(');
-            AbstractGeometry.toText(sb, getInteriorRingN(i).asLine(null, null).getDataPoints());
-            sb.append(')');
-        }
-        sb.append(')');
-        return sb.toString();
-    }
-
 }
diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/surface/MultiPolygon.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/surface/MultiPolygon.java
index 7008210971..a6828f1581 100644
--- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/surface/MultiPolygon.java
+++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/surface/MultiPolygon.java
@@ -18,7 +18,6 @@ package org.apache.sis.geometries.surface;
 
 import java.util.Set;
 import org.apache.sis.geometries.GeometryType;
-import org.apache.sis.geometries.internal.shared.AbstractGeometry;
 import org.apache.sis.geometries.internal.shared.DefaultMultiPolygon;
 
 
@@ -58,24 +57,4 @@ public sealed interface MultiPolygon extends MultiSurface<Polygon>
         return Set.of(GeometryType.POLYGON);
     }
 
-    @Override
-    default String asText() {
-        final StringBuilder sb = new StringBuilder("MULTIPOLYGON (");
-        for (int k = 0, kn = getNumGeometries(); k < kn; k++){
-            if (k > 0) sb.append(',');
-            final Polygon polygon = getGeometryN(k);
-            sb.append("((");
-            AbstractGeometry.toText(sb,  polygon.getExteriorRing().asLine(null, null).getDataPoints());
-            sb.append(')');
-            for (int i = 0, n = polygon.getNumInteriorRing(); i < n; i++) {
-                if (i != 0) sb.append(',');
-                sb.append('(');
-                AbstractGeometry.toText(sb, polygon.getInteriorRingN(i).getDataPoints());
-                sb.append(')');
-            }
-            sb.append(')');
-        }
-        sb.append(')');
-        return sb.toString();
-    }
 }
diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/surface/Polygon.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/surface/Polygon.java
index af99ba0fb9..cf4dd562d9 100644
--- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/surface/Polygon.java
+++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/surface/Polygon.java
@@ -21,7 +21,6 @@ import org.apache.sis.geometries.AttributesType;
 import org.apache.sis.geometries.Surface;
 import org.apache.sis.geometries.SurfaceInterpolation;
 import org.apache.sis.geometries.curve.LinearRing;
-import org.apache.sis.geometries.internal.shared.AbstractGeometry;
 import org.apache.sis.geometries.internal.shared.DefaultPolygon;
 import static org.opengis.annotation.Specification.ISO_19107;
 import org.opengis.annotation.UML;
@@ -172,19 +171,4 @@ public sealed interface Polygon extends Surface
         throw new UnsupportedOperationException();
     }
 
-    @Override
-    default String asText() {
-        final StringBuilder sb = new StringBuilder("POLYGON ((");
-        AbstractGeometry.toText(sb,  getExteriorRing().asLine(null, null).getDataPoints());
-        sb.append(')');
-        for (int i = 0, n = getNumInteriorRing(); i < n; i++) {
-            if (i != 0) sb.append(',');
-            sb.append('(');
-            AbstractGeometry.toText(sb, getInteriorRingN(i).getDataPoints());
-            sb.append(')');
-        }
-        sb.append(')');
-        return sb.toString();
-    }
-
 }
diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/surface/TIN.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/surface/TIN.java
index 837b8c47bc..98dba1422d 100644
--- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/surface/TIN.java
+++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/surface/TIN.java
@@ -17,12 +17,9 @@
 package org.apache.sis.geometries.surface;
 
 import java.util.List;
-import org.apache.sis.geometries.DataPoints;
 import org.apache.sis.geometries.SurfaceInterpolation;
-import org.apache.sis.geometries.internal.shared.AbstractGeometry;
 import org.apache.sis.geometries.internal.shared.DefaultTriangulatedSurface;
 import org.apache.sis.geometries.mesh.MeshPrimitive;
-import org.apache.sis.maths.Tuple;
 
 
 /**
@@ -40,6 +37,9 @@ public sealed interface TIN extends TriangulatedSurface<Triangle>
 
     static final String TYPE = "TIN";
 
+    /**
+     * Returns {@value #TYPE}.
+     */
     @Override
     default String getGeometryType() {
         return TYPE;
@@ -56,42 +56,4 @@ public sealed interface TIN extends TriangulatedSurface<Triangle>
         return List.of(SurfaceInterpolation.TIN);
     }
 
-    /**
-     * Produce a Well Known Text representation of this TIN.
-     *
-     * @return WKT string
-     */
-    default String asText() {
-        final StringBuilder sb = new StringBuilder("TIN(");
-        boolean first = true;
-        Tuple corner;
-        for (int i = 0, n = getNumPatches(); i < n; i++) {
-            Triangle triangle = getPatchN(i);
-            if (first) {
-                first = false;
-            } else {
-                sb.append(',');
-            }
-            sb.append("((");
-            final DataPoints points = triangle.getExteriorRing().getDataPoints();
-            corner = points.getPosition(0);
-            AbstractGeometry.toText(sb, corner);
-
-            sb.append(',');
-            corner = points.getPosition(1);
-            AbstractGeometry.toText(sb, corner);
-
-            sb.append(',');
-            corner = points.getPosition(2);
-            AbstractGeometry.toText(sb, corner);
-
-            sb.append(',');
-            corner = points.getPosition(0);
-            AbstractGeometry.toText(sb, corner);
-
-            sb.append("))");
-        }
-        sb.append(')');
-        return sb.toString();
-    }
 }
diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/surface/Triangle.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/surface/Triangle.java
index 7465fe162f..d071af9728 100644
--- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/surface/Triangle.java
+++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/surface/Triangle.java
@@ -24,7 +24,6 @@ import org.apache.sis.geometries.BBox;
 import org.apache.sis.geometries.DataPoints;
 import org.apache.sis.geometries.Point;
 import org.apache.sis.geometries.curve.LinearRing;
-import org.apache.sis.geometries.internal.shared.AbstractGeometry;
 import org.apache.sis.geometries.internal.shared.DefaultTriangle;
 import org.apache.sis.geometries.operation.simplify.greedyinsert.WTriangle;
 import org.apache.sis.geometries.operation.triangulate.delaunay.OrientedTriangle;
@@ -72,14 +71,16 @@ public sealed interface Triangle extends Polygon
                 OrientedTriangle
 {
 
+    static final String TYPE = "TRIANGLE";
+
     /**
-     * Returns {@code "TRIANGLE"}.
+     * Returns {@value #TYPE}.
      *
      * @see ISO 19107:2019 - 6.4.4.23
      */
     @Override
     default String getGeometryType() {
-        return "TRIANGLE";
+        return TYPE;
     }
 
     /**
@@ -255,25 +256,6 @@ public sealed interface Triangle extends Polygon
         return null;
     }
 
-    @Override
-    default String asText() {
-        final DataPoints exterior = getExteriorRing().getDataPoints();
-        final StringBuilder sb = new StringBuilder("TRIANGLE ((");
-        AbstractGeometry.toText(sb, exterior.getPosition(0));
-        sb.append(',');
-        AbstractGeometry.toText(sb, exterior.getPosition(1));
-        sb.append(',');
-        AbstractGeometry.toText(sb, exterior.getPosition(2));
-        sb.append("))");
-        return sb.toString();
-    }
-
-    /**
-     */
-    default String asTextPolygon() {
-        return Polygon.super.asText();
-    }
-
     public static final class InterpolatedPoint implements Point {
 
         private static final double[][] CORNERS = new double[][]{
diff --git a/incubator/src/org.apache.sis.geometry/test/org/apache/sis/geometries/PreparedTINTest.java b/incubator/src/org.apache.sis.geometry/test/org/apache/sis/geometries/PreparedTINTest.java
index 4301893b99..4efcec33dc 100644
--- a/incubator/src/org.apache.sis.geometry/test/org/apache/sis/geometries/PreparedTINTest.java
+++ b/incubator/src/org.apache.sis.geometry/test/org/apache/sis/geometries/PreparedTINTest.java
@@ -93,9 +93,9 @@ public class PreparedTINTest {
         assertEquals(2, tin.getPatches(env).count());
 
         assertEquals(4, tin.getNumPatches());
-        assertEquals("TRIANGLE ((0.0 1.0 2.0,3.0 4.0 5.0,6.0 7.0 8.0))", tin.getPatchN(0).asText());
-        assertEquals("TRIANGLE ((6.0 7.0 8.0,9.0 10.0 11.0,3.0 4.0 5.0))", tin.getPatchN(1).asText());
-        assertEquals("TRIANGLE ((0.0 1.0 2.0,3.0 4.0 5.0,6.0 7.0 8.0))", tin.getPatchN(2).asText());
-        assertEquals("TRIANGLE ((6.0 7.0 8.0,9.0 10.0 11.0,3.0 4.0 5.0))", tin.getPatchN(3).asText());
+        assertEquals("TRIANGLE Z ((0 1 2, 3 4 5, 6 7 8, 0 1 2))", tin.getPatchN(0).asText());
+        assertEquals("TRIANGLE Z ((6 7 8, 9 10 11, 3 4 5, 6 7 8))", tin.getPatchN(1).asText());
+        assertEquals("TRIANGLE Z ((0 1 2, 3 4 5, 6 7 8, 0 1 2))", tin.getPatchN(2).asText());
+        assertEquals("TRIANGLE Z ((6 7 8, 9 10 11, 3 4 5, 6 7 8))", tin.getPatchN(3).asText());
     }
 }
diff --git a/incubator/src/org.apache.sis.geometry/test/org/apache/sis/geometries/adapter/WellKnownTextTest.java b/incubator/src/org.apache.sis.geometry/test/org/apache/sis/geometries/adapter/WellKnownTextTest.java
new file mode 100644
index 0000000000..43f4f25ec8
--- /dev/null
+++ b/incubator/src/org.apache.sis.geometry/test/org/apache/sis/geometries/adapter/WellKnownTextTest.java
@@ -0,0 +1,299 @@
+/*
+ * 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.sis.geometries.adapter;
+
+import org.apache.sis.geometries.AttributesType;
+import org.apache.sis.geometries.DataPoints;
+import org.apache.sis.geometries.Empty;
+import org.apache.sis.geometries.Geometries;
+import org.apache.sis.geometries.Geometry;
+import org.apache.sis.geometries.GeometryCollection;
+import org.apache.sis.geometries.GeometryFactory;
+import org.apache.sis.geometries.Point;
+import org.apache.sis.geometries.curve.CircularString;
+import org.apache.sis.geometries.curve.CompoundCurve;
+import org.apache.sis.geometries.curve.LineString;
+import org.apache.sis.geometries.curve.MultiCurve;
+import org.apache.sis.geometries.curve.MultiLineString;
+import org.apache.sis.geometries.point.MultiPoint;
+import org.apache.sis.geometries.surface.CurvePolygon;
+import org.apache.sis.geometries.surface.MultiPolygon;
+import org.apache.sis.geometries.surface.MultiSurface;
+import org.apache.sis.geometries.surface.Polygon;
+import org.apache.sis.geometries.surface.PolyhedralSurface;
+import org.apache.sis.geometries.surface.TIN;
+import org.apache.sis.geometries.surface.Triangle;
+import org.apache.sis.maths.NDArrays;
+import org.apache.sis.maths.SampleSystem;
+import org.opengis.referencing.crs.CoordinateReferenceSystem;
+
+// Test dependencies
+import static org.junit.jupiter.api.Assertions.*;
+import org.junit.jupiter.api.Test;
+
+
+/**
+ * Tests {@link WellKnownText}.
+ *
+ * @author  Johann Sorel (Geomatys)
+ */
+public final class WellKnownTextTest {
+    /**
+     * The codec under test, writing the shortest representation of every ordinate.
+     */
+    private final WellKnownText wkt = new WellKnownText();
+
+    /**
+     * Creates a new test case.
+     */
+    public WellKnownTextTest() {
+    }
+
+    /**
+     * Verifies that the given text decodes to a geometry of the expected type, and that encoding
+     * that geometry gives the text back. Every text given here is in the canonical form the
+     * encoder writes, so the round trip is an equality and not merely an equivalence.
+     */
+    private void assertRoundTrip(final Class<? extends Geometry> type, final String text) {
+        final Geometry geometry = wkt.decode(text);
+        assertInstanceOf(type, geometry, text);
+        assertEquals(text, wkt.encode(geometry), "Round trip of " + text);
+        assertEquals(text, geometry.asText(), "Geometry.asText() of " + text);
+    }
+
+    /**
+     * Tests the two dimensional form of every supported geometry type.
+     */
+    @Test
+    public void testRoundTrip2D() {
+        assertRoundTrip(Point.class,              "POINT (1 2)");
+        assertRoundTrip(LineString.class,         "LINESTRING (0 0, 1 1, 2 0)");
+        assertRoundTrip(CircularString.class,     "CIRCULARSTRING (0 0, 1 1, 2 0)");
+        assertRoundTrip(CompoundCurve.class,      "COMPOUNDCURVE ((0 0, 1 1), CIRCULARSTRING (1 1, 2 2, 3 1))");
+        assertRoundTrip(Polygon.class,            "POLYGON ((0 0, 4 0, 4 4, 0 4, 0 0), (1 1, 2 1, 2 2, 1 2, 1 1))");
+        assertRoundTrip(Triangle.class,           "TRIANGLE ((0 0, 1 0, 0 1, 0 0))");
+        assertRoundTrip(CurvePolygon.class,       "CURVEPOLYGON (CIRCULARSTRING (0 0, 2 0, 2 2, 0 2, 0 0))");
+        assertRoundTrip(MultiPoint.class,         "MULTIPOINT ((1 2), (3 4))");
+        assertRoundTrip(MultiLineString.class,    "MULTILINESTRING ((0 0, 1 1), (2 2, 3 3))");
+        assertRoundTrip(MultiPolygon.class,       "MULTIPOLYGON (((0 0, 1 0, 1 1, 0 0)), ((2 2, 3 2, 3 3, 2 2)))");
+        assertRoundTrip(GeometryCollection.class, "GEOMETRYCOLLECTION (POINT (1 2), LINESTRING (0 0, 1 1))");
+    }
+
+    /**
+     * Tests the types whose members keep their own keyword, and the surface patch types.
+     * Those are the extensions of ISO 13249-3 over OGC Simple Feature Access.
+     */
+    @Test
+    public void testRoundTripCurvedAndPatches() {
+        assertRoundTrip(MultiCurve.class,
+                "MULTICURVE (LINESTRING (0 0, 1 1), CIRCULARSTRING (1 1, 2 2, 3 1))");
+        assertRoundTrip(MultiSurface.class,
+                "MULTISURFACE (POLYGON ((0 0, 1 0, 1 1, 0 0)), CURVEPOLYGON (CIRCULARSTRING (0 0, 2 0, 2 2, 0 2, 0 0)))");
+        assertRoundTrip(PolyhedralSurface.class,
+                "POLYHEDRALSURFACE Z (((0 0 0, 1 0 0, 1 1 0, 0 0 0)), ((0 0 0, 1 1 0, 0 1 0, 0 0 0)))");
+        assertRoundTrip(TIN.class,
+                "TIN Z (((0 0 0, 1 0 0, 1 1 0, 0 0 0)), ((0 0 0, 1 1 0, 0 1 0, 0 0 0)))");
+    }
+
+    /**
+     * Tests the {@code Z}, {@code M} and {@code ZM} flags.
+     */
+    @Test
+    public void testDimensionFlags() {
+        assertRoundTrip(Point.class,      "POINT Z (1 2 3)");
+        assertRoundTrip(Point.class,      "POINT M (1 2 3)");
+        assertRoundTrip(Point.class,      "POINT ZM (1 2 3 4)");
+        assertRoundTrip(LineString.class, "LINESTRING Z (0 0 0, 1 1 1)");
+        assertRoundTrip(LineString.class, "LINESTRING M (0 0 5, 1 1 6)");
+        assertRoundTrip(LineString.class, "LINESTRING ZM (0 0 0 5, 1 1 1 6)");
+        assertRoundTrip(MultiPoint.class, "MULTIPOINT ZM ((1 2 3 4), (5 6 7 8))");
+        assertRoundTrip(Polygon.class,    "POLYGON Z ((0 0 0, 1 0 0, 1 1 0, 0 0 0))");
+        /*
+         * A geometry collection repeats the flag on each of its members, since each member
+         * carries its own keyword.
+         */
+        assertRoundTrip(GeometryCollection.class, "GEOMETRYCOLLECTION Z (POINT Z (1 2 3))");
+    }
+
+    /**
+     * Verifies that the measure is read into the dedicated attribute rather than into the
+     * position, and that the position keeps the number of dimensions the flag announces.
+     */
+    @Test
+    public void testMeasureIsASeparateAttribute() {
+        final LineString line = assertInstanceOf(LineString.class, wkt.decode("LINESTRING ZM (0 1 2 5, 3 4 5 6)"));
+        final DataPoints points = line.getDataPoints();
+        assertEquals(3, points.getDimension(), "Position dimension");
+        assertEquals(2, points.size());
+        assertArrayEquals(new double[] {0, 1, 2}, points.getPosition(0).toArrayDouble());
+        assertArrayEquals(new double[] {3, 4, 5}, points.getPosition(1).toArrayDouble());
+        assertTrue(points.getAttributesType().getAttributeNames().contains(AttributesType.ATT_M));
+        assertEquals(5.0, points.getAttribute(0, AttributesType.ATT_M).get(0));
+        assertEquals(6.0, points.getAttribute(1, AttributesType.ATT_M).get(0));
+        /*
+         * Without the flag, a 2-dimensional text carries no measure at all.
+         */
+        final LineString plain = assertInstanceOf(LineString.class, wkt.decode("LINESTRING (0 1, 3 4)"));
+        assertFalse(plain.getDataPoints().getAttributesType().getAttributeNames().contains(AttributesType.ATT_M));
+    }
+
+    /**
+     * Tests the {@code EMPTY} form of every supported geometry type.
+     */
+    @Test
+    public void testEmpty() {
+        assertRoundTrip(LineString.class,         "LINESTRING EMPTY");
+        assertRoundTrip(CircularString.class,     "CIRCULARSTRING EMPTY");
+        assertRoundTrip(CompoundCurve.class,      "COMPOUNDCURVE EMPTY");
+        assertRoundTrip(Polygon.class,            "POLYGON EMPTY");
+        assertRoundTrip(Triangle.class,           "TRIANGLE EMPTY");
+        assertRoundTrip(CurvePolygon.class,       "CURVEPOLYGON EMPTY");
+        assertRoundTrip(TIN.class,                "TIN EMPTY");
+        assertRoundTrip(MultiPoint.class,         "MULTIPOINT EMPTY");
+        assertRoundTrip(MultiLineString.class,    "MULTILINESTRING EMPTY");
+        assertRoundTrip(MultiPolygon.class,       "MULTIPOLYGON EMPTY");
+        assertRoundTrip(GeometryCollection.class, "GEOMETRYCOLLECTION EMPTY");
+    }
+
+    /**
+     * Verifies that an {@link Empty} geometry, which has no type of its own, is written as the
+     * type-less empty form of the grammar.
+     */
+    @Test
+    public void testEmptyGeometry() {
+        final Geometry empty = GeometryFactory.createEmpty(Geometries.getUndefinedCRS(2));
+        assertEquals("GEOMETRYCOLLECTION EMPTY", wkt.encode(empty));
+    }
+
+    /**
+     * Verifies the one empty form which does not round-trip: the model has no empty point, since
+     * a point holds exactly one position, so {@code POINT EMPTY} becomes an {@link Empty}.
+     */
+    @Test
+    public void testEmptyPoint() {
+        assertInstanceOf(Empty.class, wkt.decode("POINT EMPTY"));
+        assertInstanceOf(Empty.class, wkt.decode("POINT Z EMPTY"));
+        assertEquals("GEOMETRYCOLLECTION EMPTY", wkt.encode(wkt.decode("POINT EMPTY")));
+        assertMalformed("MULTIPOINT (EMPTY, (1 2))");
+    }
+
+    /**
+     * Tests the forms which the grammar allows but the encoder does not write:
+     * lower case keywords, loose whitespace, a multi point without parentheses around its points,
+     * a bare coordinate list as a line string, and the {@code GEOMCOLLECTION} alias.
+     */
+    @Test
+    public void testAlternateForms() {
+        assertEquals("POINT (1 2)", wkt.encode(wkt.decode("point(1 2)")));
+        assertEquals("POINT (1 2)", wkt.encode(wkt.decode("  Point   (  1   2  )  ")));
+        assertEquals("MULTIPOINT ((1 2), (3 4))", wkt.encode(wkt.decode("MULTIPOINT (1 2, 3 4)")));
+        assertEquals("MULTICURVE (LINESTRING (0 0, 1 1))", wkt.encode(wkt.decode("MULTICURVE ((0 0, 1 1))")));
+        assertEquals("MULTISURFACE (POLYGON ((0 0, 1 0, 1 1, 0 0)))",
+                     wkt.encode(wkt.decode("MULTISURFACE (((0 0, 1 0, 1 1, 0 0)))")));
+        assertEquals("GEOMETRYCOLLECTION (POINT (1 2))", wkt.encode(wkt.decode("GEOMCOLLECTION (POINT (1 2))")));
+        /*
+         * Exponents and explicit signs are part of the lexical form of a number.
+         */
+        assertEquals("POINT (1000 -2)", wkt.encode(wkt.decode("POINT (1e3 -2)")));
+        assertEquals("POINT (1.5 2)", wkt.encode(wkt.decode("POINT (+1.5 +2.0)")));
+    }
+
+    /**
+     * Tests the number of decimal digits given to the constructor.
+     */
+    @Test
+    public void testDecimalPrecision() {
+        final Geometry geometry = wkt.decode("POINT (1.23456 2.5)");
+        assertEquals("POINT (1.23456 2.5)", new WellKnownText().encode(geometry));
+        assertEquals("POINT (1.235 2.5)",   new WellKnownText(3).encode(geometry));
+        assertEquals("POINT (1.2 2.5)",     new WellKnownText(1).encode(geometry));
+        assertEquals("POINT (1 3)",         new WellKnownText(0).encode(geometry));
+        assertThrows(IllegalArgumentException.class, () -> new WellKnownText(-1));
+    }
+
+    /**
+     * Verifies that a whole ordinate is written without a fractional part,
+     * which the model would otherwise expose as {@code 1.0}.
+     */
+    @Test
+    public void testWholeNumbers() {
+        final Point point = GeometryFactory.createPoint(Geometries.getUndefinedCRS(3), 1, 2, 3);
+        assertEquals("POINT Z (1 2 3)", wkt.encode(point));
+    }
+
+    /**
+     * Tests decoding in a coordinate reference system given by the caller.
+     */
+    @Test
+    public void testDecodeWithCRS() {
+        final CoordinateReferenceSystem crs = Geometries.getUndefinedCRS(3);
+        final Geometry geometry = wkt.decode("POINT Z (1 2 3)", crs);
+        assertSame(crs, geometry.getCoordinateReferenceSystem());
+        /*
+         * The number of ordinates in the text and the dimension of the system must agree.
+         */
+        assertThrows(IllegalArgumentException.class, () -> wkt.decode("POINT (1 2)", crs));
+    }
+
+    /**
+     * Verifies that the geometry types which the format does not define are rejected
+     * rather than written in an invented syntax.
+     */
+    @Test
+    public void testUnsupportedType() {
+        final DataPoints points = GeometryFactory.createSequence(
+                NDArrays.of(SampleSystem.of(Geometries.getUndefinedCRS(2)), 0, 0, 1, 1));
+        assertThrows(IllegalArgumentException.class, () -> wkt.encode(GeometryFactory.createGeodesic(points)));
+        assertThrows(IllegalArgumentException.class, () -> wkt.encode(GeometryFactory.createRhumb(points)));
+    }
+
+    /**
+     * Verifies that a geometry whose positions have more than three dimensions is rejected,
+     * since the format has no way to tell them from a measure.
+     */
+    @Test
+    public void testTooManyDimensions() {
+        final Point point = GeometryFactory.createPoint(Geometries.getUndefinedCRS(4), 1, 2, 3, 4);
+        assertThrows(IllegalArgumentException.class, () -> wkt.encode(point));
+    }
+
+    /**
+     * Tests the rejection of malformed texts.
+     */
+    @Test
+    public void testMalformed() {
+        assertMalformed("");                                    // No keyword at all.
+        assertMalformed("FOO (1 2)");                           // Unknown keyword.
+        assertMalformed("POINT (1 2");                          // Unbalanced parenthesis.
+        assertMalformed("POINT 1 2)");                          // Missing opening parenthesis.
+        assertMalformed("POINT ()");                            // No coordinate.
+        assertMalformed("POINT (1 2) LINESTRING (0 0, 1 1)");   // Trailing text.
+        assertMalformed("POINT (1 2 3 4)");                     // Ambiguous without a flag.
+        assertMalformed("LINESTRING (0 0, 1 1 1)");             // Inconsistent tuple width.
+        assertMalformed("POINT Z (1 2)");                       // Flag contradicted by the tuple.
+        assertMalformed("GEOMETRYCOLLECTION Z (POINT (1 2))");  // Flag contradicted by a member.
+        assertMalformed("TRIANGLE ((0 0, 1 0, 0 1, 0 0), (0 0, 1 0, 0 1, 0 0))");   // Interior ring.
+        assertMalformed("MULTICURVE (POLYGON ((0 0, 1 0, 1 1, 0 0)))");             // Member is not a curve.
+    }
+
+    /**
+     * Verifies that the given text is rejected as malformed.
+     */
+    private void assertMalformed(final String text) {
+        assertThrows(IllegalArgumentException.class, () -> wkt.decode(text), text);
+    }
+}
diff --git a/incubator/src/org.apache.sis.geometry/test/org/apache/sis/geometries/mesh/PrimitiveVisitorTest.java b/incubator/src/org.apache.sis.geometry/test/org/apache/sis/geometries/mesh/PrimitiveVisitorTest.java
index 5f095c0ba0..9c74f14681 100644
--- a/incubator/src/org.apache.sis.geometry/test/org/apache/sis/geometries/mesh/PrimitiveVisitorTest.java
+++ b/incubator/src/org.apache.sis.geometry/test/org/apache/sis/geometries/mesh/PrimitiveVisitorTest.java
@@ -117,10 +117,10 @@ public class PrimitiveVisitorTest {
                 9,10,11));
 
         final List<String> expected = Arrays.asList(
-                "LINESTRING (0.0 1.0 2.0, 3.0 4.0 5.0)",
+                "LINESTRING Z (0 1 2, 3 4 5)",
                 "V:0 POSITION[0.0, 1.0, 2.0]",
                 "V:1 POSITION[3.0, 4.0, 5.0]",
-                "LINESTRING (6.0 7.0 8.0, 9.0 10.0 11.0)",
+                "LINESTRING Z (6 7 8, 9 10 11)",
                 "V:2 POSITION[6.0, 7.0, 8.0]",
                 "V:3 POSITION[9.0, 10.0, 11.0]"
         );
@@ -129,8 +129,8 @@ public class PrimitiveVisitorTest {
 
         //test geometry collection
         assertEquals(2, geometry.getNumGeometries());
-        assertEquals("LINESTRING (0.0 1.0 2.0, 3.0 4.0 5.0)", geometry.getGeometryN(0).asText());
-        assertEquals("LINESTRING (6.0 7.0 8.0, 9.0 10.0 11.0)", geometry.getGeometryN(1).asText());
+        assertEquals("LINESTRING Z (0 1 2, 3 4 5)", geometry.getGeometryN(0).asText());
+        assertEquals("LINESTRING Z (6 7 8, 9 10 11)", geometry.getGeometryN(1).asText());
     }
 
     /**
@@ -148,13 +148,13 @@ public class PrimitiveVisitorTest {
         geometry.setIndex(NDArrays.ofUnsigned(1, 0, 1, 2, 3, 1, 3));
 
         final List<String> expected = Arrays.asList(
-                "LINESTRING (0.0 1.0 2.0, 3.0 4.0 5.0)",
+                "LINESTRING Z (0 1 2, 3 4 5)",
                 "V:0 POSITION[0.0, 1.0, 2.0]",
                 "V:1 POSITION[3.0, 4.0, 5.0]",
-                "LINESTRING (6.0 7.0 8.0, 9.0 10.0 11.0)",
+                "LINESTRING Z (6 7 8, 9 10 11)",
                 "V:2 POSITION[6.0, 7.0, 8.0]",
                 "V:3 POSITION[9.0, 10.0, 11.0]",
-                "LINESTRING (3.0 4.0 5.0, 9.0 10.0 11.0)",
+                "LINESTRING Z (3 4 5, 9 10 11)",
                 "V:1 POSITION[3.0, 4.0, 5.0]",
                 "V:3 POSITION[9.0, 10.0, 11.0]"
         );
@@ -163,9 +163,9 @@ public class PrimitiveVisitorTest {
 
         //test geometry collection
         assertEquals(3, geometry.getNumGeometries());
-        assertEquals("LINESTRING (0.0 1.0 2.0, 3.0 4.0 5.0)", geometry.getGeometryN(0).asText());
-        assertEquals("LINESTRING (6.0 7.0 8.0, 9.0 10.0 11.0)", geometry.getGeometryN(1).asText());
-        assertEquals("LINESTRING (3.0 4.0 5.0, 9.0 10.0 11.0)", geometry.getGeometryN(2).asText());
+        assertEquals("LINESTRING Z (0 1 2, 3 4 5)", geometry.getGeometryN(0).asText());
+        assertEquals("LINESTRING Z (6 7 8, 9 10 11)", geometry.getGeometryN(1).asText());
+        assertEquals("LINESTRING Z (3 4 5, 9 10 11)", geometry.getGeometryN(2).asText());
     }
 
     /**
@@ -182,13 +182,13 @@ public class PrimitiveVisitorTest {
                 9,10,11));
 
         final List<String> expected = Arrays.asList(
-                "LINESTRING (0.0 1.0 2.0, 3.0 4.0 5.0)",
+                "LINESTRING Z (0 1 2, 3 4 5)",
                 "V:0 POSITION[0.0, 1.0, 2.0]",
                 "V:1 POSITION[3.0, 4.0, 5.0]",
-                "LINESTRING (3.0 4.0 5.0, 6.0 7.0 8.0)",
+                "LINESTRING Z (3 4 5, 6 7 8)",
                 "V:1 POSITION[3.0, 4.0, 5.0]",
                 "V:2 POSITION[6.0, 7.0, 8.0]",
-                "LINESTRING (6.0 7.0 8.0, 9.0 10.0 11.0)",
+                "LINESTRING Z (6 7 8, 9 10 11)",
                 "V:2 POSITION[6.0, 7.0, 8.0]",
                 "V:3 POSITION[9.0, 10.0, 11.0]"
         );
@@ -196,7 +196,7 @@ public class PrimitiveVisitorTest {
 
         //test geometry as LineString
         assertTrue(geometry instanceof LineString);
-        assertEquals("LINESTRING (0.0 1.0 2.0, 3.0 4.0 5.0, 6.0 7.0 8.0, 9.0 10.0 11.0)", geometry.asText());
+        assertEquals("LINESTRING Z (0 1 2, 3 4 5, 6 7 8, 9 10 11)", geometry.asText());
     }
 
     /**
@@ -214,19 +214,19 @@ public class PrimitiveVisitorTest {
         geometry.setIndex(NDArrays.ofUnsigned(1, 0, 1, 2, 3, 0, 2));
 
         final List<String> expected = Arrays.asList(
-                "LINESTRING (0.0 1.0 2.0, 3.0 4.0 5.0)",
+                "LINESTRING Z (0 1 2, 3 4 5)",
                 "V:0 POSITION[0.0, 1.0, 2.0]",
                 "V:1 POSITION[3.0, 4.0, 5.0]",
-                "LINESTRING (3.0 4.0 5.0, 6.0 7.0 8.0)",
+                "LINESTRING Z (3 4 5, 6 7 8)",
                 "V:1 POSITION[3.0, 4.0, 5.0]",
                 "V:2 POSITION[6.0, 7.0, 8.0]",
-                "LINESTRING (6.0 7.0 8.0, 9.0 10.0 11.0)",
+                "LINESTRING Z (6 7 8, 9 10 11)",
                 "V:2 POSITION[6.0, 7.0, 8.0]",
                 "V:3 POSITION[9.0, 10.0, 11.0]",
-                "LINESTRING (9.0 10.0 11.0, 0.0 1.0 2.0)",
+                "LINESTRING Z (9 10 11, 0 1 2)",
                 "V:3 POSITION[9.0, 10.0, 11.0]",
                 "V:0 POSITION[0.0, 1.0, 2.0]",
-                "LINESTRING (0.0 1.0 2.0, 6.0 7.0 8.0)",
+                "LINESTRING Z (0 1 2, 6 7 8)",
                 "V:0 POSITION[0.0, 1.0, 2.0]",
                 "V:2 POSITION[6.0, 7.0, 8.0]"
         );
@@ -234,7 +234,7 @@ public class PrimitiveVisitorTest {
 
         //test geometry as LineString
         assertTrue(geometry instanceof LineString);
-        assertEquals("LINESTRING (0.0 1.0 2.0, 3.0 4.0 5.0, 6.0 7.0 8.0, 9.0 10.0 11.0, 0.0 1.0 2.0, 6.0 7.0 8.0)", geometry.asText());
+        assertEquals("LINESTRING Z (0 1 2, 3 4 5, 6 7 8, 9 10 11, 0 1 2, 6 7 8)", geometry.asText());
     }
 
     /**
@@ -253,11 +253,11 @@ public class PrimitiveVisitorTest {
                 15,16,17));
 
         final List<String> expected = Arrays.asList(
-                "TRIANGLE ((0.0 1.0 2.0,3.0 4.0 5.0,6.0 7.0 8.0))",
+                "TRIANGLE Z ((0 1 2, 3 4 5, 6 7 8, 0 1 2))",
                 "V:0 POSITION[0.0, 1.0, 2.0]",
                 "V:1 POSITION[3.0, 4.0, 5.0]",
                 "V:2 POSITION[6.0, 7.0, 8.0]",
-                "TRIANGLE ((9.0 10.0 11.0,12.0 13.0 14.0,15.0 16.0 17.0))",
+                "TRIANGLE Z ((9 10 11, 12 13 14, 15 16 17, 9 10 11))",
                 "V:3 POSITION[9.0, 10.0, 11.0]",
                 "V:4 POSITION[12.0, 13.0, 14.0]",
                 "V:5 POSITION[15.0, 16.0, 17.0]"
@@ -268,8 +268,8 @@ public class PrimitiveVisitorTest {
         //test geometry as TIN
         assertTrue(geometry instanceof TIN);
         assertEquals(2, geometry.getNumPatches());
-        assertEquals("TRIANGLE ((0.0 1.0 2.0,3.0 4.0 5.0,6.0 7.0 8.0))", geometry.getPatchN(0).asText());
-        assertEquals("TRIANGLE ((9.0 10.0 11.0,12.0 13.0 14.0,15.0 16.0 17.0))", geometry.getPatchN(1).asText());
+        assertEquals("TRIANGLE Z ((0 1 2, 3 4 5, 6 7 8, 0 1 2))", geometry.getPatchN(0).asText());
+        assertEquals("TRIANGLE Z ((9 10 11, 12 13 14, 15 16 17, 9 10 11))", geometry.getPatchN(1).asText());
     }
 
     /**
@@ -287,11 +287,11 @@ public class PrimitiveVisitorTest {
         geometry.setIndex(NDArrays.ofUnsigned(1, 0, 1, 2, 2, 3, 1));
 
         final List<String> expected = Arrays.asList(
-                "TRIANGLE ((0.0 1.0 2.0,3.0 4.0 5.0,6.0 7.0 8.0))",
+                "TRIANGLE Z ((0 1 2, 3 4 5, 6 7 8, 0 1 2))",
                 "V:0 POSITION[0.0, 1.0, 2.0]",
                 "V:1 POSITION[3.0, 4.0, 5.0]",
                 "V:2 POSITION[6.0, 7.0, 8.0]",
-                "TRIANGLE ((6.0 7.0 8.0,9.0 10.0 11.0,3.0 4.0 5.0))",
+                "TRIANGLE Z ((6 7 8, 9 10 11, 3 4 5, 6 7 8))",
                 "V:2 POSITION[6.0, 7.0, 8.0]",
                 "V:3 POSITION[9.0, 10.0, 11.0]",
                 "V:1 POSITION[3.0, 4.0, 5.0]"
@@ -302,8 +302,8 @@ public class PrimitiveVisitorTest {
         //test geometry as TIN
         assertTrue(geometry instanceof TIN);
         assertEquals(2, geometry.getNumPatches());
-        assertEquals("TRIANGLE ((0.0 1.0 2.0,3.0 4.0 5.0,6.0 7.0 8.0))", geometry.getPatchN(0).asText());
-        assertEquals("TRIANGLE ((6.0 7.0 8.0,9.0 10.0 11.0,3.0 4.0 5.0))", geometry.getPatchN(1).asText());
+        assertEquals("TRIANGLE Z ((0 1 2, 3 4 5, 6 7 8, 0 1 2))", geometry.getPatchN(0).asText());
+        assertEquals("TRIANGLE Z ((6 7 8, 9 10 11, 3 4 5, 6 7 8))", geometry.getPatchN(1).asText());
     }
 
     /**
@@ -333,19 +333,19 @@ public class PrimitiveVisitorTest {
                 0,-1,0));
 
         final List<String> expected = Arrays.asList(
-                "TRIANGLE ((0.0 0.0 0.0,0.0 1.0 0.0,1.0 1.0 0.0))",
+                "TRIANGLE Z ((0 0 0, 0 1 0, 1 1 0, 0 0 0))",
                 "V:0 POSITION[0.0, 0.0, 0.0]",
                 "V:1 POSITION[0.0, 1.0, 0.0]",
                 "V:2 POSITION[1.0, 1.0, 0.0]",
-                "TRIANGLE ((0.0 0.0 0.0,1.0 1.0 0.0,1.0 0.0 0.0))",
+                "TRIANGLE Z ((0 0 0, 1 1 0, 1 0 0, 0 0 0))",
                 "V:0 POSITION[0.0, 0.0, 0.0]",
                 "V:2 POSITION[1.0, 1.0, 0.0]",
                 "V:3 POSITION[1.0, 0.0, 0.0]",
-                "TRIANGLE ((0.0 0.0 0.0,1.0 0.0 0.0,1.0 -1.0 0.0))",
+                "TRIANGLE Z ((0 0 0, 1 0 0, 1 -1 0, 0 0 0))",
                 "V:0 POSITION[0.0, 0.0, 0.0]",
                 "V:3 POSITION[1.0, 0.0, 0.0]",
                 "V:4 POSITION[1.0, -1.0, 0.0]",
-                "TRIANGLE ((0.0 0.0 0.0,1.0 -1.0 0.0,0.0 -1.0 0.0))",
+                "TRIANGLE Z ((0 0 0, 1 -1 0, 0 -1 0, 0 0 0))",
                 "V:0 POSITION[0.0, 0.0, 0.0]",
                 "V:4 POSITION[1.0, -1.0, 0.0]",
                 "V:5 POSITION[0.0, -1.0, 0.0]"
@@ -356,10 +356,10 @@ public class PrimitiveVisitorTest {
         //test geometry as TIN
         assertTrue(geometry instanceof TIN);
         assertEquals(4, geometry.getNumPatches());
-        assertEquals("TRIANGLE ((0.0 0.0 0.0,0.0 1.0 0.0,1.0 1.0 0.0))", geometry.getPatchN(0).asText());
-        assertEquals("TRIANGLE ((0.0 0.0 0.0,1.0 1.0 0.0,1.0 0.0 0.0))", geometry.getPatchN(1).asText());
-        assertEquals("TRIANGLE ((0.0 0.0 0.0,1.0 0.0 0.0,1.0 -1.0 0.0))", geometry.getPatchN(2).asText());
-        assertEquals("TRIANGLE ((0.0 0.0 0.0,1.0 -1.0 0.0,0.0 -1.0 0.0))", geometry.getPatchN(3).asText());
+        assertEquals("TRIANGLE Z ((0 0 0, 0 1 0, 1 1 0, 0 0 0))", geometry.getPatchN(0).asText());
+        assertEquals("TRIANGLE Z ((0 0 0, 1 1 0, 1 0 0, 0 0 0))", geometry.getPatchN(1).asText());
+        assertEquals("TRIANGLE Z ((0 0 0, 1 0 0, 1 -1 0, 0 0 0))", geometry.getPatchN(2).asText());
+        assertEquals("TRIANGLE Z ((0 0 0, 1 -1 0, 0 -1 0, 0 0 0))", geometry.getPatchN(3).asText());
     }
 
     /**
@@ -391,23 +391,23 @@ public class PrimitiveVisitorTest {
                 0, 1, 2, 3, 4, 5, 2));
 
         final List<String> expected = Arrays.asList(
-                "TRIANGLE ((0.0 0.0 0.0,0.0 1.0 0.0,1.0 1.0 0.0))",
+                "TRIANGLE Z ((0 0 0, 0 1 0, 1 1 0, 0 0 0))",
                 "V:0 POSITION[0.0, 0.0, 0.0]",
                 "V:1 POSITION[0.0, 1.0, 0.0]",
                 "V:2 POSITION[1.0, 1.0, 0.0]",
-                "TRIANGLE ((0.0 0.0 0.0,1.0 1.0 0.0,1.0 0.0 0.0))",
+                "TRIANGLE Z ((0 0 0, 1 1 0, 1 0 0, 0 0 0))",
                 "V:0 POSITION[0.0, 0.0, 0.0]",
                 "V:2 POSITION[1.0, 1.0, 0.0]",
                 "V:3 POSITION[1.0, 0.0, 0.0]",
-                "TRIANGLE ((0.0 0.0 0.0,1.0 0.0 0.0,1.0 -1.0 0.0))",
+                "TRIANGLE Z ((0 0 0, 1 0 0, 1 -1 0, 0 0 0))",
                 "V:0 POSITION[0.0, 0.0, 0.0]",
                 "V:3 POSITION[1.0, 0.0, 0.0]",
                 "V:4 POSITION[1.0, -1.0, 0.0]",
-                "TRIANGLE ((0.0 0.0 0.0,1.0 -1.0 0.0,0.0 -1.0 0.0))",
+                "TRIANGLE Z ((0 0 0, 1 -1 0, 0 -1 0, 0 0 0))",
                 "V:0 POSITION[0.0, 0.0, 0.0]",
                 "V:4 POSITION[1.0, -1.0, 0.0]",
                 "V:5 POSITION[0.0, -1.0, 0.0]",
-                "TRIANGLE ((0.0 0.0 0.0,0.0 -1.0 0.0,1.0 1.0 0.0))",
+                "TRIANGLE Z ((0 0 0, 0 -1 0, 1 1 0, 0 0 0))",
                 "V:0 POSITION[0.0, 0.0, 0.0]",
                 "V:5 POSITION[0.0, -1.0, 0.0]",
                 "V:2 POSITION[1.0, 1.0, 0.0]"
@@ -418,11 +418,11 @@ public class PrimitiveVisitorTest {
         //test geometry as TIN
         assertTrue(geometry instanceof TIN);
         assertEquals(5, geometry.getNumPatches());
-        assertEquals("TRIANGLE ((0.0 0.0 0.0,0.0 1.0 0.0,1.0 1.0 0.0))", geometry.getPatchN(0).asText());
-        assertEquals("TRIANGLE ((0.0 0.0 0.0,1.0 1.0 0.0,1.0 0.0 0.0))", geometry.getPatchN(1).asText());
-        assertEquals("TRIANGLE ((0.0 0.0 0.0,1.0 0.0 0.0,1.0 -1.0 0.0))", geometry.getPatchN(2).asText());
-        assertEquals("TRIANGLE ((0.0 0.0 0.0,1.0 -1.0 0.0,0.0 -1.0 0.0))", geometry.getPatchN(3).asText());
-        assertEquals("TRIANGLE ((0.0 0.0 0.0,0.0 -1.0 0.0,1.0 1.0 0.0))", geometry.getPatchN(4).asText());
+        assertEquals("TRIANGLE Z ((0 0 0, 0 1 0, 1 1 0, 0 0 0))", geometry.getPatchN(0).asText());
+        assertEquals("TRIANGLE Z ((0 0 0, 1 1 0, 1 0 0, 0 0 0))", geometry.getPatchN(1).asText());
+        assertEquals("TRIANGLE Z ((0 0 0, 1 0 0, 1 -1 0, 0 0 0))", geometry.getPatchN(2).asText());
+        assertEquals("TRIANGLE Z ((0 0 0, 1 -1 0, 0 -1 0, 0 0 0))", geometry.getPatchN(3).asText());
+        assertEquals("TRIANGLE Z ((0 0 0, 0 -1 0, 1 1 0, 0 0 0))", geometry.getPatchN(4).asText());
     }
 
     /**
@@ -451,27 +451,27 @@ public class PrimitiveVisitorTest {
                 3,1,0));
 
         final List<String> expected = Arrays.asList(
-                "TRIANGLE ((0.0 0.0 0.0,0.0 1.0 0.0,1.0 0.0 0.0))",
+                "TRIANGLE Z ((0 0 0, 0 1 0, 1 0 0, 0 0 0))",
                 "V:0 POSITION[0.0, 0.0, 0.0]",
                 "V:1 POSITION[0.0, 1.0, 0.0]",
                 "V:2 POSITION[1.0, 0.0, 0.0]",
-                "TRIANGLE ((1.0 0.0 0.0,0.0 1.0 0.0,1.0 1.0 0.0))",
+                "TRIANGLE Z ((1 0 0, 0 1 0, 1 1 0, 1 0 0))",
                 "V:2 POSITION[1.0, 0.0, 0.0]",
                 "V:1 POSITION[0.0, 1.0, 0.0]",
                 "V:3 POSITION[1.0, 1.0, 0.0]",
-                "TRIANGLE ((1.0 0.0 0.0,1.0 1.0 0.0,2.0 0.0 0.0))",
+                "TRIANGLE Z ((1 0 0, 1 1 0, 2 0 0, 1 0 0))",
                 "V:2 POSITION[1.0, 0.0, 0.0]",
                 "V:3 POSITION[1.0, 1.0, 0.0]",
                 "V:4 POSITION[2.0, 0.0, 0.0]",
-                "TRIANGLE ((2.0 0.0 0.0,1.0 1.0 0.0,2.0 1.0 0.0))",
+                "TRIANGLE Z ((2 0 0, 1 1 0, 2 1 0, 2 0 0))",
                 "V:4 POSITION[2.0, 0.0, 0.0]",
                 "V:3 POSITION[1.0, 1.0, 0.0]",
                 "V:5 POSITION[2.0, 1.0, 0.0]",
-                "TRIANGLE ((2.0 0.0 0.0,2.0 1.0 0.0,3.0 0.0 0.0))",
+                "TRIANGLE Z ((2 0 0, 2 1 0, 3 0 0, 2 0 0))",
                 "V:4 POSITION[2.0, 0.0, 0.0]",
                 "V:5 POSITION[2.0, 1.0, 0.0]",
                 "V:6 POSITION[3.0, 0.0, 0.0]",
-                "TRIANGLE ((3.0 0.0 0.0,2.0 1.0 0.0,3.0 1.0 0.0))",
+                "TRIANGLE Z ((3 0 0, 2 1 0, 3 1 0, 3 0 0))",
                 "V:6 POSITION[3.0, 0.0, 0.0]",
                 "V:5 POSITION[2.0, 1.0, 0.0]",
                 "V:7 POSITION[3.0, 1.0, 0.0]"
@@ -482,12 +482,12 @@ public class PrimitiveVisitorTest {
         //test geometry as TIN
         assertTrue(geometry instanceof TIN);
         assertEquals(6, geometry.getNumPatches());
-        assertEquals("TRIANGLE ((0.0 0.0 0.0,0.0 1.0 0.0,1.0 0.0 0.0))", geometry.getPatchN(0).asText());
-        assertEquals("TRIANGLE ((1.0 0.0 0.0,0.0 1.0 0.0,1.0 1.0 0.0))", geometry.getPatchN(1).asText());
-        assertEquals("TRIANGLE ((1.0 0.0 0.0,1.0 1.0 0.0,2.0 0.0 0.0))", geometry.getPatchN(2).asText());
-        assertEquals("TRIANGLE ((2.0 0.0 0.0,1.0 1.0 0.0,2.0 1.0 0.0))", geometry.getPatchN(3).asText());
-        assertEquals("TRIANGLE ((2.0 0.0 0.0,2.0 1.0 0.0,3.0 0.0 0.0))", geometry.getPatchN(4).asText());
-        assertEquals("TRIANGLE ((3.0 0.0 0.0,2.0 1.0 0.0,3.0 1.0 0.0))", geometry.getPatchN(5).asText());
+        assertEquals("TRIANGLE Z ((0 0 0, 0 1 0, 1 0 0, 0 0 0))", geometry.getPatchN(0).asText());
+        assertEquals("TRIANGLE Z ((1 0 0, 0 1 0, 1 1 0, 1 0 0))", geometry.getPatchN(1).asText());
+        assertEquals("TRIANGLE Z ((1 0 0, 1 1 0, 2 0 0, 1 0 0))", geometry.getPatchN(2).asText());
+        assertEquals("TRIANGLE Z ((2 0 0, 1 1 0, 2 1 0, 2 0 0))", geometry.getPatchN(3).asText());
+        assertEquals("TRIANGLE Z ((2 0 0, 2 1 0, 3 0 0, 2 0 0))", geometry.getPatchN(4).asText());
+        assertEquals("TRIANGLE Z ((3 0 0, 2 1 0, 3 1 0, 3 0 0))", geometry.getPatchN(5).asText());
     }
 
     /**
@@ -518,27 +518,27 @@ public class PrimitiveVisitorTest {
                 0, 1, 2, 3, 4, 5, 6, 3));
 
         final List<String> expected = Arrays.asList(
-                "TRIANGLE ((0.0 0.0 0.0,0.0 1.0 0.0,1.0 0.0 0.0))",
+                "TRIANGLE Z ((0 0 0, 0 1 0, 1 0 0, 0 0 0))",
                 "V:0 POSITION[0.0, 0.0, 0.0]",
                 "V:1 POSITION[0.0, 1.0, 0.0]",
                 "V:2 POSITION[1.0, 0.0, 0.0]",
-                "TRIANGLE ((1.0 0.0 0.0,0.0 1.0 0.0,1.0 1.0 0.0))",
+                "TRIANGLE Z ((1 0 0, 0 1 0, 1 1 0, 1 0 0))",
                 "V:2 POSITION[1.0, 0.0, 0.0]",
                 "V:1 POSITION[0.0, 1.0, 0.0]",
                 "V:3 POSITION[1.0, 1.0, 0.0]",
-                "TRIANGLE ((1.0 0.0 0.0,1.0 1.0 0.0,2.0 0.0 0.0))",
+                "TRIANGLE Z ((1 0 0, 1 1 0, 2 0 0, 1 0 0))",
                 "V:2 POSITION[1.0, 0.0, 0.0]",
                 "V:3 POSITION[1.0, 1.0, 0.0]",
                 "V:4 POSITION[2.0, 0.0, 0.0]",
-                "TRIANGLE ((2.0 0.0 0.0,1.0 1.0 0.0,2.0 1.0 0.0))",
+                "TRIANGLE Z ((2 0 0, 1 1 0, 2 1 0, 2 0 0))",
                 "V:4 POSITION[2.0, 0.0, 0.0]",
                 "V:3 POSITION[1.0, 1.0, 0.0]",
                 "V:5 POSITION[2.0, 1.0, 0.0]",
-                "TRIANGLE ((2.0 0.0 0.0,2.0 1.0 0.0,3.0 0.0 0.0))",
+                "TRIANGLE Z ((2 0 0, 2 1 0, 3 0 0, 2 0 0))",
                 "V:4 POSITION[2.0, 0.0, 0.0]",
                 "V:5 POSITION[2.0, 1.0, 0.0]",
                 "V:6 POSITION[3.0, 0.0, 0.0]",
-                "TRIANGLE ((3.0 0.0 0.0,2.0 1.0 0.0,1.0 1.0 0.0))",
+                "TRIANGLE Z ((3 0 0, 2 1 0, 1 1 0, 3 0 0))",
                 "V:6 POSITION[3.0, 0.0, 0.0]",
                 "V:5 POSITION[2.0, 1.0, 0.0]",
                 "V:3 POSITION[1.0, 1.0, 0.0]"
@@ -549,12 +549,12 @@ public class PrimitiveVisitorTest {
         //test geometry as TIN
         assertTrue(geometry instanceof TIN);
         assertEquals(6, geometry.getNumPatches());
-        assertEquals("TRIANGLE ((0.0 0.0 0.0,0.0 1.0 0.0,1.0 0.0 0.0))", geometry.getPatchN(0).asText());
-        assertEquals("TRIANGLE ((1.0 0.0 0.0,0.0 1.0 0.0,1.0 1.0 0.0))", geometry.getPatchN(1).asText());
-        assertEquals("TRIANGLE ((1.0 0.0 0.0,1.0 1.0 0.0,2.0 0.0 0.0))", geometry.getPatchN(2).asText());
-        assertEquals("TRIANGLE ((2.0 0.0 0.0,1.0 1.0 0.0,2.0 1.0 0.0))", geometry.getPatchN(3).asText());
-        assertEquals("TRIANGLE ((2.0 0.0 0.0,2.0 1.0 0.0,3.0 0.0 0.0))", geometry.getPatchN(4).asText());
-        assertEquals("TRIANGLE ((3.0 0.0 0.0,2.0 1.0 0.0,1.0 1.0 0.0))", geometry.getPatchN(5).asText());
+        assertEquals("TRIANGLE Z ((0 0 0, 0 1 0, 1 0 0, 0 0 0))", geometry.getPatchN(0).asText());
+        assertEquals("TRIANGLE Z ((1 0 0, 0 1 0, 1 1 0, 1 0 0))", geometry.getPatchN(1).asText());
+        assertEquals("TRIANGLE Z ((1 0 0, 1 1 0, 2 0 0, 1 0 0))", geometry.getPatchN(2).asText());
+        assertEquals("TRIANGLE Z ((2 0 0, 1 1 0, 2 1 0, 2 0 0))", geometry.getPatchN(3).asText());
+        assertEquals("TRIANGLE Z ((2 0 0, 2 1 0, 3 0 0, 2 0 0))", geometry.getPatchN(4).asText());
+        assertEquals("TRIANGLE Z ((3 0 0, 2 1 0, 1 1 0, 3 0 0))", geometry.getPatchN(5).asText());
     }
 
     private void visit(MeshPrimitive geometry, List<String> exp) {
diff --git a/incubator/src/org.apache.sis.referencing.dggs/test/org/apache/sis/referencing/dggs/s2/S2Test.java b/incubator/src/org.apache.sis.referencing.dggs/test/org/apache/sis/referencing/dggs/s2/S2Test.java
index 7f7c5714a4..efcd471308 100644
--- a/incubator/src/org.apache.sis.referencing.dggs/test/org/apache/sis/referencing/dggs/s2/S2Test.java
+++ b/incubator/src/org.apache.sis.referencing.dggs/test/org/apache/sis/referencing/dggs/s2/S2Test.java
@@ -50,7 +50,7 @@ public class S2Test extends AbstractDggrsTest {
 
         final GeographicExtent extent = zone.getGeographicExtent();
         final Polygon polygon = DiscreteGlobalGridSystems.toSISPolygon(extent);
-        assertEquals("POLYGON ((-180.0 34.50852298766839, -180.0 22.619864948040426, -169.38034472384487 22.270575488008195, -169.38034472384487 34.04786296943431, -180.0 34.50852298766839))", polygon.asText());
+        assertEquals("POLYGON ((-180 34.50852298766839, -180 22.619864948040426, -169.38034472384487 22.270575488008195, -169.38034472384487 34.04786296943431, -180 34.50852298766839))", polygon.asText());
 
         final Envelope envelope = zone.getEnvelope();
         assertEquals(-180, envelope.getMinimum(0), 0.0);


(sis) 01/03: refactor(Geometry): remove unecessary public keyword on interfaces

Posted by js...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

asf-gitbox-commits pushed a commit to branch geoapi-4.0
in repository https://gitbox.apache.org/repos/asf/sis.git

commit cb2de8f34481f570cc29f68356f5ed0eae690fe2
Author: jsorel <jo...@geomatys.com>
AuthorDate: Thu Sep 10 14:39:44 2026 +0200

    refactor(Geometry): remove unecessary public keyword on interfaces
---
 .../org/apache/sis/geometries/AttributesType.java  | 20 +++++-----
 .../main/org/apache/sis/geometries/Empty.java      |  2 +-
 .../main/org/apache/sis/geometries/Geometry.java   | 46 +++++++++++-----------
 .../apache/sis/geometries/GeometryCollection.java  |  8 ++--
 .../main/org/apache/sis/geometries/Point.java      | 20 +++++-----
 .../main/org/apache/sis/geometries/Prism.java      |  6 +--
 .../main/org/apache/sis/geometries/Solid.java      |  2 +-
 .../apache/sis/geometries/curve/ArcByBulge.java    |  6 +--
 .../sis/geometries/curve/ArcByCenterPoint.java     |  8 ++--
 .../sis/geometries/curve/CircularString.java       |  7 ++--
 .../apache/sis/geometries/curve/CompoundCurve.java |  4 +-
 .../org/apache/sis/geometries/curve/Geodesic.java  | 10 ++---
 .../apache/sis/geometries/curve/LineString.java    | 12 +++---
 .../apache/sis/geometries/curve/LinearRing.java    |  4 +-
 .../apache/sis/geometries/curve/MultiCurve.java    |  4 +-
 .../sis/geometries/curve/MultiLineString.java      |  4 +-
 .../org/apache/sis/geometries/curve/NurbCurve.java |  2 +-
 .../org/apache/sis/geometries/curve/Rhumb.java     | 10 ++---
 .../apache/sis/geometries/point/MultiPoint.java    |  4 +-
 .../sis/geometries/solid/MultiPolyhedron.java      |  4 +-
 .../apache/sis/geometries/solid/Polyhedron.java    |  6 +--
 .../sis/geometries/surface/CurvePolygon.java       |  6 +--
 .../sis/geometries/surface/MultiPolygon.java       |  4 +-
 .../sis/geometries/surface/MultiSurface.java       |  4 +-
 .../apache/sis/geometries/surface/NurbSurface.java |  2 +-
 .../org/apache/sis/geometries/surface/Polygon.java |  6 +--
 .../sis/geometries/surface/PolyhedralSurface.java  |  6 +--
 .../apache/sis/geometries/surface/PreparedTIN.java |  4 +-
 .../org/apache/sis/geometries/surface/TIN.java     |  4 +-
 .../apache/sis/geometries/surface/Triangle.java    |  4 +-
 30 files changed, 114 insertions(+), 115 deletions(-)

diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/AttributesType.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/AttributesType.java
index 2f143201c8..c5939bc4b9 100644
--- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/AttributesType.java
+++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/AttributesType.java
@@ -37,22 +37,22 @@ import org.apache.sis.maths.SampleSystem;
  */
 public interface AttributesType {
 
-    public static final String ATT_POSITION = "POSITION";
-    public static final String ATT_NORMAL = "NORMAL";
-    public static final String ATT_TANGENT = "TANGENT";
-    public static final String ATT_TEXCOORD_0 = "TEXCOORD_0";
+    static final String ATT_POSITION = "POSITION";
+    static final String ATT_NORMAL = "NORMAL";
+    static final String ATT_TANGENT = "TANGENT";
+    static final String ATT_TEXCOORD_0 = "TEXCOORD_0";
 
     //indexed attributes
-    public static final String ATT_TEXCOORD = "TEXCOORD";
-    public static final String ATT_COLOR = "COLOR";
-    public static final String ATT_JOINTS = "JOINTS";
-    public static final String ATT_WEIGHTS = "WEIGHTS";
+    static final String ATT_TEXCOORD = "TEXCOORD";
+    static final String ATT_COLOR = "COLOR";
+    static final String ATT_JOINTS = "JOINTS";
+    static final String ATT_WEIGHTS = "WEIGHTS";
 
     /**
      * Attribute from OGC 3D Tiles.
      * To link primitives to features/batch tables.
      */
-    public static final String ATT_BATCH_ID = "_BATCHID";
+    static final String ATT_BATCH_ID = "_BATCHID";
 
     /**
      * Returns attribute system for given name.
@@ -99,7 +99,7 @@ public interface AttributesType {
     /**
      * Empty attributes type.
      */
-    public static AttributesType EMPTY = new AttributesType() {
+    static AttributesType EMPTY = new AttributesType() {
         @Override
         public SampleSystem getAttributeSystem(String name) {
             return null;
diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/Empty.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/Empty.java
index fff7eafb37..eba8423068 100644
--- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/Empty.java
+++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/Empty.java
@@ -55,7 +55,7 @@ public sealed interface Empty extends Geometry
     /**
      * Well-known text keyword of this geometry type.
      */
-    public static final String TYPE = "EMPTY";
+    static final String TYPE = "EMPTY";
 
     /**
      * Returns {@code true} since this geometry is the empty set.
diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/Geometry.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/Geometry.java
index 57a6d8422d..98ecb67f16 100644
--- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/Geometry.java
+++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/Geometry.java
@@ -568,7 +568,7 @@ public sealed interface Geometry
      *
      * @see GeometryProcessor#buffer(org.apache.sis.geometries.Geometry, double)
      */
-    public default Geometry buffer(double distance) throws OperationException {
+    default Geometry buffer(double distance) throws OperationException {
         return new GeometryProcessor().buffer(this, distance);
     }
 
@@ -595,7 +595,7 @@ public sealed interface Geometry
      */
     @UML(identifier="buffer", specification=ISO_19107)
     //@UML(identifier="3Dbuffer", specification=ISO_19107)
-    public default Geometry buffer(Length radius) throws OperationException {
+    default Geometry buffer(Length radius) throws OperationException {
         return new GeometryProcessor().buffer(this, radius);
     }
 
@@ -618,7 +618,7 @@ public sealed interface Geometry
      */
     @UML(identifier="convexHull", specification=ISO_19107)
     //@UML(identifier="3DconvexHull", specification=ISO_19107)
-    public default Geometry convexHull() throws OperationException {
+    default Geometry convexHull() throws OperationException {
         return new GeometryProcessor().convexHull(this);
     }
 
@@ -641,7 +641,7 @@ public sealed interface Geometry
      */
     @UML(identifier="difference", specification=ISO_19107)
     //@UML(identifier="3Ddifference", specification=ISO_19107)
-    public default Geometry difference(Geometry other) throws OperationException {
+    default Geometry difference(Geometry other) throws OperationException {
         return new GeometryProcessor().difference(this, other);
     }
 
@@ -665,7 +665,7 @@ public sealed interface Geometry
      */
     @UML(identifier="distance", specification=ISO_19107)
     //@UML(identifier="3Ddistance", specification=ISO_19107)
-    public default Length distance(Geometry other) throws OperationException {
+    default Length distance(Geometry other) throws OperationException {
         return new GeometryProcessor().distance(this, other);
     }
 
@@ -688,7 +688,7 @@ public sealed interface Geometry
      */
     @UML(identifier="intersection", specification=ISO_19107)
     //@UML(identifier="3Dintersection", specification=ISO_19107)
-    public default Geometry intersection(Geometry other) throws OperationException {
+    default Geometry intersection(Geometry other) throws OperationException {
         return new GeometryProcessor().intersection(this, other);
     }
 
@@ -712,7 +712,7 @@ public sealed interface Geometry
      */
     @UML(identifier="symDifference", specification=ISO_19107)
     //@UML(identifier="3DsymDifference", specification=ISO_19107)
-    public default Geometry symDifference(Geometry other) throws OperationException {
+    default Geometry symDifference(Geometry other) throws OperationException {
         return new GeometryProcessor().symDifference(this, other);
     }
 
@@ -735,7 +735,7 @@ public sealed interface Geometry
      */
     @UML(identifier="union", specification=ISO_19107)
     //@UML(identifier="3Dunion", specification=ISO_19107)
-    public default Geometry union(Geometry other) throws OperationException {
+    default Geometry union(Geometry other) throws OperationException {
         return new GeometryProcessor().union(this, other);
     }
 
@@ -760,7 +760,7 @@ public sealed interface Geometry
      * @see ISO 19107:2019 - 6.4.4.28
      */
     @UML(identifier="transform", specification=ISO_19107)
-    public default Geometry transform(CoordinateReferenceSystem crs) {
+    default Geometry transform(CoordinateReferenceSystem crs) {
         return new GeometryProcessor().transform(this, crs, null);
     }
 
@@ -778,7 +778,7 @@ public sealed interface Geometry
      */
     @UML(identifier="contains", specification=ISO_19107)
     //@UML(identifier="3Dcontains", specification=ISO_19107)
-    public default boolean contains(DirectPosition element) throws OperationException {
+    default boolean contains(DirectPosition element) throws OperationException {
         return new GeometryProcessor().contains(this, element);
     }
 
@@ -800,7 +800,7 @@ public sealed interface Geometry
      * @see ISO 19107:2019 - 6.4.4.30, 6.4.8.8, 10.8.6.3.2
      */
     @UML(identifier="contains", specification=ISO_19107)
-    public default boolean contains(Geometry other) throws OperationException {
+    default boolean contains(Geometry other) throws OperationException {
         return new GeometryProcessor().contains(this, other);
     }
 
@@ -823,7 +823,7 @@ public sealed interface Geometry
      */
     @UML(identifier="crosses", specification=ISO_19107)
     //@UML(identifier="3Dcrosses", specification=ISO_19107)
-    public default boolean crosses(Geometry other) throws OperationException {
+    default boolean crosses(Geometry other) throws OperationException {
         return new GeometryProcessor().crosses(this, other);
     }
 
@@ -845,7 +845,7 @@ public sealed interface Geometry
      */
     @UML(identifier="disjoint", specification=ISO_19107)
     //@UML(identifier="3Ddisjoint", specification=ISO_19107)
-    public default boolean disjoint(Geometry other) throws OperationException {
+    default boolean disjoint(Geometry other) throws OperationException {
         return new GeometryProcessor().disjoint(this, other);
     }
 
@@ -871,7 +871,7 @@ public sealed interface Geometry
      */
     @UML(identifier="equals", specification=ISO_19107)
     //@UML(identifier="3Dequals", specification=ISO_19107)
-    public default boolean equal(Geometry other) throws OperationException {
+    default boolean equal(Geometry other) throws OperationException {
         return new GeometryProcessor().equal(this, other);
     }
 
@@ -893,7 +893,7 @@ public sealed interface Geometry
      */
     @UML(identifier="intersects", specification=ISO_19107)
     //@UML(identifier="3Dintersects", specification=ISO_19107)
-    public default boolean intersects(Geometry other) throws OperationException {
+    default boolean intersects(Geometry other) throws OperationException {
         return new GeometryProcessor().intersects(this, other);
     }
 
@@ -909,7 +909,7 @@ public sealed interface Geometry
      *
      * @see GeometryProcessor#locateAlong(org.apache.sis.geometries.Geometry, double)
      */
-    public default Geometry locateAlong(double mValue) throws OperationException {
+    default Geometry locateAlong(double mValue) throws OperationException {
         return new GeometryProcessor().locateAlong(this, mValue);
     }
 
@@ -926,7 +926,7 @@ public sealed interface Geometry
      *
      * @see GeometryProcessor#contains(org.apache.sis.geometries.Geometry, double, double)
      */
-    public default Geometry locateBetween(double mStart, double mEnd) throws OperationException {
+    default Geometry locateBetween(double mStart, double mEnd) throws OperationException {
         return new GeometryProcessor().locateBetween(this, mStart, mEnd);
     }
 
@@ -949,7 +949,7 @@ public sealed interface Geometry
      */
     @UML(identifier="overlaps", specification=ISO_19107)
     //@UML(identifier="3Doverlaps", specification=ISO_19107)
-    public default boolean overlaps(Geometry other) throws OperationException {
+    default boolean overlaps(Geometry other) throws OperationException {
         return new GeometryProcessor().overlaps(this, other);
     }
 
@@ -967,7 +967,7 @@ public sealed interface Geometry
      *
      * @see GeometryProcessor#relate(org.apache.sis.geometries.Geometry, org.apache.sis.geometries.Geometry, int)
      */
-    public default boolean relate(Geometry other, int matrix) throws OperationException {
+    default boolean relate(Geometry other, int matrix) throws OperationException {
         return new GeometryProcessor().relate(this, other, matrix);
     }
 
@@ -997,7 +997,7 @@ public sealed interface Geometry
      */
     @UML(identifier="relate", specification=ISO_19107)
     //@UML(identifier="3Drelate", specification=ISO_19107)
-    public default boolean relate(Geometry other, String matrix) throws OperationException {
+    default boolean relate(Geometry other, String matrix) throws OperationException {
         return new GeometryProcessor().relate(this, other, matrix);
     }
 
@@ -1020,7 +1020,7 @@ public sealed interface Geometry
      */
     @UML(identifier="touches", specification=ISO_19107)
     //@UML(identifier="3Dtouches", specification=ISO_19107)
-    public default boolean touches(Geometry other) throws OperationException {
+    default boolean touches(Geometry other) throws OperationException {
         return new GeometryProcessor().touches(this, other);
     }
 
@@ -1043,7 +1043,7 @@ public sealed interface Geometry
      */
     @UML(identifier="within", specification=ISO_19107)
     //@UML(identifier="3Dwithin", specification=ISO_19107)
-    public default boolean within(Geometry other) throws OperationException {
+    default boolean within(Geometry other) throws OperationException {
         return new GeometryProcessor().within(this, other);
     }
 
@@ -1065,7 +1065,7 @@ public sealed interface Geometry
      */
     @UML(identifier="withinDistance", specification=ISO_19107)
     //@UML(identifier="3DwithinDistance", specification=ISO_19107)
-    public default boolean withinDistance(Geometry other, Length distance) throws OperationException {
+    default boolean withinDistance(Geometry other, Length distance) throws OperationException {
         return new GeometryProcessor().withinDistance(this, other, distance);
     }
 
diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/GeometryCollection.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/GeometryCollection.java
index 571e02ab12..9876ede51d 100644
--- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/GeometryCollection.java
+++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/GeometryCollection.java
@@ -70,7 +70,7 @@ public sealed interface GeometryCollection<T extends Geometry> extends Geometry
     /**
      * Well-known text keyword of this geometry type.
      */
-    public static final String TYPE = "GEOMETRYCOLLECTION";
+    static final String TYPE = "GEOMETRYCOLLECTION";
 
     /**
      * Returns {@value #TYPE}.
@@ -78,7 +78,7 @@ public sealed interface GeometryCollection<T extends Geometry> extends Geometry
      * @see ISO 19107:2019 - 6.4.4.23
      */
     @Override
-    public default String getGeometryType() {
+    default String getGeometryType() {
         return TYPE;
     }
 
@@ -147,7 +147,7 @@ public sealed interface GeometryCollection<T extends Geometry> extends Geometry
     T getGeometryN(int n);
 
     @Override
-    public default Envelope getEnvelope() {
+    default Envelope getEnvelope() {
         GeneralEnvelope e = null;
         for (int i = 0, n = getNumGeometries(); i < n; i++) {
             T sn = getGeometryN(i);
@@ -168,7 +168,7 @@ public sealed interface GeometryCollection<T extends Geometry> extends Geometry
     }
 
     @Override
-    public default AttributesType getAttributesType() {
+    default AttributesType getAttributesType() {
         if (getNumGeometries() == 0) return null;
         return getGeometryN(0).getAttributesType();
     }
diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/Point.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/Point.java
index 961a446280..81f077e7b2 100644
--- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/Point.java
+++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/Point.java
@@ -68,7 +68,7 @@ public sealed interface Point extends Primitive
     /**
      * Well-known text keyword of this geometry type.
      */
-    public static final String TYPE = "POINT";
+    static final String TYPE = "POINT";
 
     /**
      * Location of this point in its reference system.
@@ -78,7 +78,7 @@ public sealed interface Point extends Primitive
      * @see ISO 19107:2019 - 6.4.13.2
      */
     @UML(identifier="position", specification=ISO_19107)
-    Tuple getPosition();
+    Tuple<?> getPosition();
 
     /**
      * Returns tuple for given name.
@@ -86,7 +86,7 @@ public sealed interface Point extends Primitive
      * @param name seached attribute name
      * @return attribute or null.
      */
-    Tuple getAttribute(String name);
+    Tuple<?> getAttribute(String name);
 
     /**
      * Sets the value of the attribute of the given name.
@@ -94,7 +94,7 @@ public sealed interface Point extends Primitive
      * @param name  name of the attribute to set.
      * @param tuple new attribute value.
      */
-    void setAttribute(String name, Tuple tuple);
+    void setAttribute(String name, Tuple<?> tuple);
 
     /**
      * View this point as a single point sequence
@@ -131,7 +131,7 @@ public sealed interface Point extends Primitive
             }
 
             @Override
-            public void setPosition(int index, Tuple value) {
+            public void setPosition(int index, Tuple<?> value) {
                 if (index != 0) throw new IndexOutOfBoundsException();
                 Point.this.getPosition().set(value);
             }
@@ -143,7 +143,7 @@ public sealed interface Point extends Primitive
             }
 
             @Override
-            public void setAttribute(int index, String name, Tuple value) {
+            public void setAttribute(int index, String name, Tuple<?> value) {
                 if (index != 0) throw new IndexOutOfBoundsException();
                 Point.this.setAttribute(name, value);
             }
@@ -156,13 +156,13 @@ public sealed interface Point extends Primitive
     }
 
     @Override
-    public default String getGeometryType() {
+    default String getGeometryType() {
         return TYPE;
     }
 
     @Override
     default Envelope getEnvelope() {
-        final Tuple first = getPosition();
+        final Tuple<?> first = getPosition();
         final BBox env = new BBox(first, first);
         env.setCoordinateReferenceSystem(getCoordinateReferenceSystem());
         return env;
@@ -187,7 +187,7 @@ public sealed interface Point extends Primitive
      * @see ISO 19107:2019 - 6.4.13.4
      */
     @UML(identifier="vectorToPoint", specification=ISO_19107)
-    default Vector vectorToPoint(DirectPosition toPoint) {
+    default Vector<?> vectorToPoint(DirectPosition toPoint) {
         //TODO
         throw new UnsupportedOperationException();
     }
@@ -218,7 +218,7 @@ public sealed interface Point extends Primitive
      * @see ISO 19107:2019 - 6.4.13.6
      */
     @UML(identifier="pointAtDistance", specification=ISO_19107)
-    default DirectPosition pointAtDistance(Vector bearing){
+    default DirectPosition pointAtDistance(Vector<?> bearing){
         //TODO
         throw new UnsupportedOperationException();
     }
diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/Prism.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/Prism.java
index a8251953fc..3ed022d992 100644
--- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/Prism.java
+++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/Prism.java
@@ -28,15 +28,15 @@ import org.apache.sis.measure.NumberRange;
  */
 public non-sealed interface Prism extends Geometry {
 
-    public static final String TYPE = "PRISM";
+    static final String TYPE = "PRISM";
 
     @Override
-    public default String getGeometryType() {
+    default String getGeometryType() {
         return TYPE;
     }
 
     @Override
-    public default AttributesType getAttributesType() {
+    default AttributesType getAttributesType() {
         return getBase().getAttributesType();
     }
 
diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/Solid.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/Solid.java
index e922560591..8b200d8fbd 100644
--- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/Solid.java
+++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/Solid.java
@@ -67,7 +67,7 @@ public sealed interface Solid extends Primitive
      * @see ISO 19107:2019 - 6.4.4.22
      */
     @Override
-    public default int getTopologicDimension() {
+    default int getTopologicDimension() {
         return 3;
     }
 
diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/ArcByBulge.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/ArcByBulge.java
index 54402c1391..7ebc030107 100644
--- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/ArcByBulge.java
+++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/ArcByBulge.java
@@ -44,10 +44,10 @@ public sealed interface ArcByBulge extends Curve
         permits DefaultArcByBulge
 {
 
-    public static final String TYPE = "ARCBYBULGE";
+    static final String TYPE = "ARCBYBULGE";
 
     @Override
-    public default String getGeometryType() {
+    default String getGeometryType() {
         return TYPE;
     }
 
@@ -80,7 +80,7 @@ public sealed interface ArcByBulge extends Curve
      * @return null, a ArcByBulge has no control points
      */
     @Override
-    public default Array getControlPoints() {
+    default Array getControlPoints() {
         return null;
     }
 
diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/ArcByCenterPoint.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/ArcByCenterPoint.java
index e6711cb7c4..901daa1db9 100644
--- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/ArcByCenterPoint.java
+++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/ArcByCenterPoint.java
@@ -47,10 +47,10 @@ public sealed interface ArcByCenterPoint extends Curve
         permits DefaultArcByCenterPoint
 {
 
-    public static final String TYPE = "ARCBYCENTERPOINT";
+    static final String TYPE = "ARCBYCENTERPOINT";
 
     @Override
-    public default String getGeometryType() {
+    default String getGeometryType() {
         return TYPE;
     }
 
@@ -106,7 +106,7 @@ public sealed interface ArcByCenterPoint extends Curve
     }
 
     @Override
-    public default DataPoints getDataPoints() {
+    default DataPoints getDataPoints() {
         throw new UnsupportedOperationException("Not supported yet");
     }
 
@@ -114,7 +114,7 @@ public sealed interface ArcByCenterPoint extends Curve
      * @return null, a ArcByCenterPoint has no control points
      */
     @Override
-    public default Array getControlPoints() {
+    default Array getControlPoints() {
         return null;
     }
 
diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/CircularString.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/CircularString.java
index 10b4954056..d5dd382aa5 100644
--- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/CircularString.java
+++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/CircularString.java
@@ -20,7 +20,6 @@ import org.apache.sis.geometries.AttributesType;
 import org.apache.sis.geometries.Curve;
 import org.apache.sis.geometries.CurveInterpolation;
 import org.apache.sis.geometries.DataPoints;
-import org.apache.sis.geometries.Point;
 import org.apache.sis.geometries.internal.shared.DefaultCircularString;
 import org.apache.sis.maths.Array;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
@@ -40,10 +39,10 @@ public sealed interface CircularString extends Curve
         permits DefaultCircularString
 {
 
-    public static final String TYPE = "CIRCULARSTRING";
+    static final String TYPE = "CIRCULARSTRING";
 
     @Override
-    public default String getGeometryType() {
+    default String getGeometryType() {
         return TYPE;
     }
 
@@ -76,7 +75,7 @@ public sealed interface CircularString extends Curve
      * @return null, a CircularString has no control points
      */
     @Override
-    public default Array getControlPoints() {
+    default Array getControlPoints() {
         return null;
     }
 
diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/CompoundCurve.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/CompoundCurve.java
index 3d0dbe0bae..aae8df5e70 100644
--- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/CompoundCurve.java
+++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/CompoundCurve.java
@@ -38,10 +38,10 @@ public sealed interface CompoundCurve extends Curve
         permits DefaultCompoundCurve
 {
 
-    public static final String TYPE = "COMPOUNDCURVE";
+    static final String TYPE = "COMPOUNDCURVE";
 
     @Override
-    public default String getGeometryType() {
+    default String getGeometryType() {
         return TYPE;
     }
 
diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/Geodesic.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/Geodesic.java
index 966844baad..42575abe8c 100644
--- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/Geodesic.java
+++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/Geodesic.java
@@ -59,7 +59,7 @@ public sealed interface Geodesic extends Curve
     /**
      * Well-known text keyword of this geometry type.
      */
-    public static final String TYPE = "GEODESIC";
+    static final String TYPE = "GEODESIC";
 
     /**
      * Returns {@value #TYPE}.
@@ -67,7 +67,7 @@ public sealed interface Geodesic extends Curve
      * @see ISO 19107:2019 - 6.4.4.23
      */
     @Override
-    public default String getGeometryType() {
+    default String getGeometryType() {
         return TYPE;
     }
 
@@ -80,7 +80,7 @@ public sealed interface Geodesic extends Curve
      */
     @UML(identifier="interpolation", specification=ISO_19107)
     @Override
-    public default CurveInterpolation getInterpolation() {
+    default CurveInterpolation getInterpolation() {
         return CurveInterpolation.GEODESIC;
     }
 
@@ -110,7 +110,7 @@ public sealed interface Geodesic extends Curve
      * @see ISO 19107:2019 - 6.4.18.2
      */
     @Override
-    public default Array getControlPoints() {
+    default Array getControlPoints() {
         return null;
     }
 
@@ -125,7 +125,7 @@ public sealed interface Geodesic extends Curve
     }
 
     @Override
-    public default AttributesType getAttributesType() {
+    default AttributesType getAttributesType() {
         return getDataPoints().getAttributesType();
     }
 
diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/LineString.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/LineString.java
index 32c2b5d216..02388c94bd 100644
--- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/LineString.java
+++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/LineString.java
@@ -66,7 +66,7 @@ public sealed interface LineString extends Curve
     /**
      * Well-known text keyword of this geometry type.
      */
-    public static final String TYPE = "LINESTRING";
+    static final String TYPE = "LINESTRING";
 
     /**
      * Returns {@value #TYPE}.
@@ -74,7 +74,7 @@ public sealed interface LineString extends Curve
      * @see ISO 19107:2019 - 6.4.4.23
      */
     @Override
-    public default String getGeometryType() {
+    default String getGeometryType() {
         return TYPE;
     }
 
@@ -85,7 +85,7 @@ public sealed interface LineString extends Curve
      */
     @UML(identifier="interpolation", specification=ISO_19107)
     @Override
-    public default CurveInterpolation getInterpolation() {
+    default CurveInterpolation getInterpolation() {
         return CurveInterpolation.LINEAR;
     }
 
@@ -95,7 +95,7 @@ public sealed interface LineString extends Curve
      * @see ISO 19107:2019 - 6.4.18.17
      */
     @Override
-    public default LineString asLine(Length spacing, Length offset) {
+    default LineString asLine(Length spacing, Length offset) {
         return this;
     }
 
@@ -125,7 +125,7 @@ public sealed interface LineString extends Curve
      * @see ISO 19107:2019 - 6.4.18.2
      */
     @Override
-    public default Array getControlPoints() {
+    default Array getControlPoints() {
         return null;
     }
 
@@ -140,7 +140,7 @@ public sealed interface LineString extends Curve
     }
 
     @Override
-    public default AttributesType getAttributesType() {
+    default AttributesType getAttributesType() {
         return getDataPoints().getAttributesType();
     }
 
diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/LinearRing.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/LinearRing.java
index 1d5b9e0f12..7677dd0446 100644
--- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/LinearRing.java
+++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/LinearRing.java
@@ -31,10 +31,10 @@ public sealed interface LinearRing extends LineString
         permits DefaultLinearRing
 {
 
-    public static final String TYPE = "LINEARRING";
+    static final String TYPE = "LINEARRING";
 
     @Override
-    public default String getGeometryType() {
+    default String getGeometryType() {
         return TYPE;
     }
 
diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/MultiCurve.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/MultiCurve.java
index bdbd19b9cd..39883fc653 100644
--- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/MultiCurve.java
+++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/MultiCurve.java
@@ -50,10 +50,10 @@ public sealed interface MultiCurve<T extends Curve> extends GeometryCollection<T
                 DefaultMultiCurve
 {
 
-    public static final String TYPE = "MULTICURVE";
+    static final String TYPE = "MULTICURVE";
 
     @Override
-    public default String getGeometryType() {
+    default String getGeometryType() {
         return TYPE;
     }
 
diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/MultiLineString.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/MultiLineString.java
index 7910a58539..de80212aa2 100644
--- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/MultiLineString.java
+++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/MultiLineString.java
@@ -33,10 +33,10 @@ public sealed interface MultiLineString extends MultiCurve<LineString>
                 MeshPrimitive.Lines
 {
 
-    public static final String TYPE = "MULTILINESTRING";
+    static final String TYPE = "MULTILINESTRING";
 
     @Override
-    public default String getGeometryType() {
+    default String getGeometryType() {
         return TYPE;
     }
 
diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/NurbCurve.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/NurbCurve.java
index a2853cbcbc..a0519d288f 100644
--- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/NurbCurve.java
+++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/NurbCurve.java
@@ -54,7 +54,7 @@ public sealed interface NurbCurve extends BSplineCurve
     /**
      * Well-known text keyword of this geometry type.
      */
-    public static final String TYPE = "NURBS";
+    static final String TYPE = "NURBS";
 
     /**
      * Returns {@value #TYPE}.
diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/Rhumb.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/Rhumb.java
index 5cb3428e77..958b55d612 100644
--- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/Rhumb.java
+++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/Rhumb.java
@@ -59,7 +59,7 @@ public sealed interface Rhumb extends Curve
     /**
      * Well-known text keyword of this geometry type.
      */
-    public static final String TYPE = "RHUMB";
+    static final String TYPE = "RHUMB";
 
     /**
      * Returns {@value #TYPE}.
@@ -67,7 +67,7 @@ public sealed interface Rhumb extends Curve
      * @see ISO 19107:2019 - 6.4.4.23
      */
     @Override
-    public default String getGeometryType() {
+    default String getGeometryType() {
         return TYPE;
     }
 
@@ -78,7 +78,7 @@ public sealed interface Rhumb extends Curve
      */
     @UML(identifier="interpolation", specification=ISO_19107)
     @Override
-    public default CurveInterpolation getInterpolation() {
+    default CurveInterpolation getInterpolation() {
         return CurveInterpolation.RHUMB;
     }
 
@@ -120,7 +120,7 @@ public sealed interface Rhumb extends Curve
      * @see ISO 19107:2019 - 6.4.18.2
      */
     @Override
-    public default Array getControlPoints() {
+    default Array getControlPoints() {
         return null;
     }
 
@@ -135,7 +135,7 @@ public sealed interface Rhumb extends Curve
     }
 
     @Override
-    public default AttributesType getAttributesType() {
+    default AttributesType getAttributesType() {
         return getDataPoints().getAttributesType();
     }
 
diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/point/MultiPoint.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/point/MultiPoint.java
index 0672976710..3318bc56a9 100644
--- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/point/MultiPoint.java
+++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/point/MultiPoint.java
@@ -44,10 +44,10 @@ public sealed interface MultiPoint<T extends Point> extends GeometryCollection<T
                 MeshPrimitive.Points
 {
 
-    public static final String TYPE = "MULTIPOINT";
+    static final String TYPE = "MULTIPOINT";
 
     @Override
-    public default String getGeometryType() {
+    default String getGeometryType() {
         return TYPE;
     }
 
diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/solid/MultiPolyhedron.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/solid/MultiPolyhedron.java
index 2b77a2ec7d..75a8f5e811 100644
--- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/solid/MultiPolyhedron.java
+++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/solid/MultiPolyhedron.java
@@ -32,10 +32,10 @@ public sealed interface MultiPolyhedron extends GeometryCollection<Polyhedron>
         permits DefaultMultiPolyhedron
 {
 
-    public static final String TYPE = "MULTIPOLYHEDRON";
+    static final String TYPE = "MULTIPOLYHEDRON";
 
     @Override
-    public default String getGeometryType() {
+    default String getGeometryType() {
         return TYPE;
     }
 
diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/solid/Polyhedron.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/solid/Polyhedron.java
index 12c5153c94..d1d5711e69 100644
--- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/solid/Polyhedron.java
+++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/solid/Polyhedron.java
@@ -43,15 +43,15 @@ public sealed interface Polyhedron extends Solid
                 DefaultPolyhedron
 {
 
-    public static final String TYPE = "POLYHEDRON";
+    static final String TYPE = "POLYHEDRON";
 
     @Override
-    public default String getGeometryType() {
+    default String getGeometryType() {
         return TYPE;
     }
 
     @Override
-    public default AttributesType getAttributesType() {
+    default AttributesType getAttributesType() {
         return getExteriorShell().getAttributesType();
     }
 
diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/surface/CurvePolygon.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/surface/CurvePolygon.java
index e16ea3e9ee..9b5151bc55 100644
--- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/surface/CurvePolygon.java
+++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/surface/CurvePolygon.java
@@ -59,7 +59,7 @@ public sealed interface CurvePolygon extends Surface
     /**
      * Well-known text keyword of this geometry type.
      */
-    public static final String TYPE = "CURVEPOLYGON";
+    static final String TYPE = "CURVEPOLYGON";
 
     /**
      * Returns {@value #TYPE}.
@@ -67,7 +67,7 @@ public sealed interface CurvePolygon extends Surface
      * @see ISO 19107:2019 - 6.4.4.23
      */
     @Override
-    public default String getGeometryType() {
+    default String getGeometryType() {
         return TYPE;
     }
 
@@ -75,7 +75,7 @@ public sealed interface CurvePolygon extends Surface
      * Returns the attributes of the exterior ring, which are the attributes of this surface.
      */
     @Override
-    public default AttributesType getAttributesType() {
+    default AttributesType getAttributesType() {
         return getExteriorRing().getAttributesType();
     }
 
diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/surface/MultiPolygon.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/surface/MultiPolygon.java
index 7133f767ca..a295a895d5 100644
--- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/surface/MultiPolygon.java
+++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/surface/MultiPolygon.java
@@ -39,10 +39,10 @@ public sealed interface MultiPolygon extends MultiSurface<Polygon>
         permits DefaultMultiPolygon
 {
 
-    public static final String TYPE = "MULTIPOLYGON";
+    static final String TYPE = "MULTIPOLYGON";
 
     @Override
-    public default String getGeometryType() {
+    default String getGeometryType() {
         return TYPE;
     }
 
diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/surface/MultiSurface.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/surface/MultiSurface.java
index f5718adb2b..4563f9bb21 100644
--- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/surface/MultiSurface.java
+++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/surface/MultiSurface.java
@@ -47,10 +47,10 @@ public sealed interface MultiSurface<T extends Surface> extends GeometryCollecti
                 DefaultMultiSurface
 {
 
-    public static final String TYPE = "MULTISURFACE";
+    static final String TYPE = "MULTISURFACE";
 
     @Override
-    public default String getGeometryType() {
+    default String getGeometryType() {
         return TYPE;
     }
 
diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/surface/NurbSurface.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/surface/NurbSurface.java
index cb9c681ff9..fad2377b05 100644
--- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/surface/NurbSurface.java
+++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/surface/NurbSurface.java
@@ -28,7 +28,7 @@ public sealed interface NurbSurface extends BSplineSurface
         permits DefaultNurbSurface
 {
 
-    public static final String TYPE = "NURBSSURFACE";
+    static final String TYPE = "NURBSSURFACE";
 
     @Override
     default String getGeometryType() {
diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/surface/Polygon.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/surface/Polygon.java
index ecb32ef5eb..3c5ec90384 100644
--- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/surface/Polygon.java
+++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/surface/Polygon.java
@@ -67,7 +67,7 @@ public sealed interface Polygon extends Surface
     /**
      * Well-known text keyword of this geometry type.
      */
-    public static final String TYPE = "POLYGON";
+    static final String TYPE = "POLYGON";
 
     /**
      * Returns {@value #TYPE}.
@@ -75,7 +75,7 @@ public sealed interface Polygon extends Surface
      * @see ISO 19107:2019 - 6.4.4.23
      */
     @Override
-    public default String getGeometryType() {
+    default String getGeometryType() {
         return TYPE;
     }
 
@@ -83,7 +83,7 @@ public sealed interface Polygon extends Surface
      * Returns the attributes of the exterior ring, which are the attributes of this polygon.
      */
     @Override
-    public default AttributesType getAttributesType() {
+    default AttributesType getAttributesType() {
         return getExteriorRing().getAttributesType();
     }
 
diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/surface/PolyhedralSurface.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/surface/PolyhedralSurface.java
index c8d405692d..6c1110c07d 100644
--- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/surface/PolyhedralSurface.java
+++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/surface/PolyhedralSurface.java
@@ -67,7 +67,7 @@ public sealed interface PolyhedralSurface<T extends Polygon> extends /*GeometryC
     /**
      * Well-known text keyword of this geometry type.
      */
-    public static final String TYPE = "POLYHEDRALSURFACE";
+    static final String TYPE = "POLYHEDRALSURFACE";
 
     /**
      * Returns {@value #TYPE}.
@@ -75,7 +75,7 @@ public sealed interface PolyhedralSurface<T extends Polygon> extends /*GeometryC
      * @see ISO 19107:2019 - 6.4.4.23
      */
     @Override
-    public default String getGeometryType() {
+    default String getGeometryType() {
         return TYPE;
     }
 
@@ -117,7 +117,7 @@ public sealed interface PolyhedralSurface<T extends Polygon> extends /*GeometryC
      * @see ISO 19107:2019 - 6.4.25.7
      */
     @Override
-    public default Area getArea() {
+    default Area getArea() {
         final int n = getNumPatches();
         if (n == 0) {
             return Quantities.create(0, Units.SQUARE_METRE);
diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/surface/PreparedTIN.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/surface/PreparedTIN.java
index 208a0fc361..9db95bfd89 100644
--- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/surface/PreparedTIN.java
+++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/surface/PreparedTIN.java
@@ -60,11 +60,11 @@ public sealed interface PreparedTIN extends TIN
      */
     Stream<Triangle> getPatches(org.opengis.geometry.Envelope env) throws TransformException;
 
-    public static PreparedTIN create(Collection<TIN> tin) {
+    static PreparedTIN create(Collection<TIN> tin) {
         return create(tin.toArray(TIN[]::new));
     }
 
-    public static PreparedTIN create(TIN... tin) {
+    static PreparedTIN create(TIN... tin) {
         if (tin.length == 0) throw new IllegalArgumentException("At least one TIN must be provided");
         if (tin.length == 1) return new Single(tin[0]);
         final Single[] singles = new Single[tin.length];
diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/surface/TIN.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/surface/TIN.java
index d45eeba935..3960f8c540 100644
--- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/surface/TIN.java
+++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/surface/TIN.java
@@ -36,10 +36,10 @@ public sealed interface TIN extends TriangulatedSurface<Triangle>
                 MeshPrimitive.TriangleStrip
 {
 
-    public static final String TYPE = "TIN";
+    static final String TYPE = "TIN";
 
     @Override
-    public default String getGeometryType() {
+    default String getGeometryType() {
         return TYPE;
     }
 
diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/surface/Triangle.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/surface/Triangle.java
index f965d45715..7465fe162f 100644
--- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/surface/Triangle.java
+++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/surface/Triangle.java
@@ -183,7 +183,7 @@ public sealed interface Triangle extends Polygon
      * @param nullIfOutside return null if point is outside the triangle
      * @return [a,b,c] weights
      */
-    public static double[] getBarycentricValue2D(double x1, double y1, double x2, double y2, double x3, double y3, double x, double y, double epsilon, boolean nullIfOutside) {
+    static double[] getBarycentricValue2D(double x1, double y1, double x2, double y2, double x3, double y3, double x, double y, double epsilon, boolean nullIfOutside) {
         if (nullIfOutside) {
             if (!Maths.isPointInTriangle_BoundingBox(x1, y1, x2, y2, x3, y3, x, y, epsilon)) {
                 return null;
@@ -229,7 +229,7 @@ public sealed interface Triangle extends Polygon
         }
     }
 
-    public static Double interpolate2D(Vector3D.Double a, Vector3D.Double b, Vector3D.Double c, Vector2D.Double p){
+    static Double interpolate2D(Vector3D.Double a, Vector3D.Double b, Vector3D.Double c, Vector2D.Double p){
         final double v0x = b.x-a.x;
         final double v0y = b.y-a.y;
         final double v1x = c.x-a.x;