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 b6b045f35e76d2fc0047b15f4af389499c03e0a9
Author: jsorel <[email protected]>
AuthorDate: Thu Sep 17 12:27:57 2026 +0200

    feat(Geometry): add default empty geometry classes implementations and 
factory methods
---
 .../org/apache/sis/geometries/GeometryFactory.java | 348 +++++++++++++++++++++
 .../main/org/apache/sis/geometries/Prism.java      |   4 +-
 .../main/org/apache/sis/geometries/curve/Arc.java  |  14 +-
 .../apache/sis/geometries/curve/BSplineCurve.java  |  14 +-
 .../org/apache/sis/geometries/curve/Bezier.java    |  19 +-
 .../org/apache/sis/geometries/curve/Circle.java    |  14 +-
 .../org/apache/sis/geometries/curve/Clothoid.java  |  14 +-
 .../org/apache/sis/geometries/curve/Conic.java     |  14 +-
 .../apache/sis/geometries/curve/CubicSpline.java   |  14 +-
 .../apache/sis/geometries/curve/EllipticArc.java   |  14 +-
 .../apache/sis/geometries/curve/OffsetCurve.java   |  14 +-
 .../sis/geometries/curve/PolynomialSpline.java     |  14 +-
 .../apache/sis/geometries/curve/ProductCurve.java  |  18 +-
 .../org/apache/sis/geometries/curve/Spiral.java    |  14 +-
 .../sis/geometries/internal/shared/DefaultArc.java |  70 +++++
 .../internal/shared/DefaultBSplineCurve.java       | 177 +++++++++++
 .../internal/shared/DefaultBSplineSolid.java       | 177 +++++++++++
 .../internal/shared/DefaultBSplineSurface.java     | 195 ++++++++++++
 .../geometries/internal/shared/DefaultBezier.java  |  58 ++++
 .../internal/shared/DefaultBilinearGrid.java       | 179 +++++++++++
 .../geometries/internal/shared/DefaultCircle.java  |  54 ++++
 .../internal/shared/DefaultClothoid.java           |  55 ++++
 .../geometries/internal/shared/DefaultConic.java   | 107 +++++++
 .../internal/shared/DefaultCubicSpline.java        |  67 ++++
 .../internal/shared/DefaultEllipticArc.java        |  43 +++
 .../internal/shared/DefaultOffsetCurve.java        | 122 ++++++++
 .../internal/shared/DefaultPolynomialSpline.java   | 198 ++++++++++++
 .../geometries/internal/shared/DefaultPrism.java   |  98 ++++++
 .../internal/shared/DefaultProductCurve.java       |  84 +++++
 .../geometries/internal/shared/DefaultSpiral.java  | 134 ++++++++
 .../solid/{BSolidSpline.java => BSplineSolid.java} |  14 +-
 .../sis/geometries/solid/ParametricCurveSolid.java |   2 +-
 .../sis/geometries/surface/BSplineSurface.java     |  14 +-
 .../sis/geometries/surface/BilinearGrid.java       |  14 +-
 34 files changed, 2374 insertions(+), 17 deletions(-)

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 11b8342531..cf42443272 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
@@ -22,24 +22,51 @@ import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 import javax.measure.Unit;
+import javax.measure.quantity.Length;
+import org.apache.sis.geometries.cs.Projection;
+import org.apache.sis.geometries.curve.Arc;
 import org.apache.sis.geometries.curve.ArcByBulge;
 import org.apache.sis.geometries.curve.ArcByCenterPoint;
+import org.apache.sis.geometries.curve.BSplineCurve;
+import org.apache.sis.geometries.curve.Bezier;
 import org.apache.sis.geometries.curve.Circle;
 import org.apache.sis.geometries.curve.CircularString;
 import org.apache.sis.geometries.curve.Clothoid;
 import org.apache.sis.geometries.curve.CompoundCurve;
+import org.apache.sis.geometries.curve.Conic;
+import org.apache.sis.geometries.curve.CubicSpline;
+import org.apache.sis.geometries.curve.EllipticArc;
 import org.apache.sis.geometries.curve.Geodesic;
+import org.apache.sis.geometries.curve.KnotType;
 import org.apache.sis.geometries.curve.LineString;
 import org.apache.sis.geometries.curve.LinearRing;
 import org.apache.sis.geometries.curve.MultiCurve;
 import org.apache.sis.geometries.curve.MultiLineString;
+import org.apache.sis.geometries.curve.NurbCurve;
+import org.apache.sis.geometries.curve.OffsetCurve;
+import org.apache.sis.geometries.curve.PolynomialSpline;
+import org.apache.sis.geometries.curve.ProductCurve;
+import org.apache.sis.geometries.curve.RealFunction;
 import org.apache.sis.geometries.curve.Rhumb;
+import org.apache.sis.geometries.curve.Spiral;
+import org.apache.sis.geometries.curve.SplineCurveForm;
 import org.apache.sis.geometries.internal.shared.ArrayDataPoints;
+import org.apache.sis.geometries.internal.shared.DefaultArc;
 import org.apache.sis.geometries.internal.shared.DefaultArcByBulge;
 import org.apache.sis.geometries.internal.shared.DefaultArcByCenterPoint;
+import org.apache.sis.geometries.internal.shared.DefaultBSplineSolid;
+import org.apache.sis.geometries.internal.shared.DefaultBSplineCurve;
+import org.apache.sis.geometries.internal.shared.DefaultBSplineSurface;
+import org.apache.sis.geometries.internal.shared.DefaultBezier;
+import org.apache.sis.geometries.internal.shared.DefaultBilinearGrid;
+import org.apache.sis.geometries.internal.shared.DefaultCircle;
 import org.apache.sis.geometries.internal.shared.DefaultCircularString;
+import org.apache.sis.geometries.internal.shared.DefaultClothoid;
 import org.apache.sis.geometries.internal.shared.DefaultCompoundCurve;
+import org.apache.sis.geometries.internal.shared.DefaultConic;
+import org.apache.sis.geometries.internal.shared.DefaultCubicSpline;
 import org.apache.sis.geometries.internal.shared.DefaultCurvePolygon;
+import org.apache.sis.geometries.internal.shared.DefaultEllipticArc;
 import org.apache.sis.geometries.internal.shared.DefaultEmpty;
 import org.apache.sis.geometries.internal.shared.DefaultGeodesic;
 import org.apache.sis.geometries.internal.shared.DefaultGeometryCollection;
@@ -51,22 +78,33 @@ import 
org.apache.sis.geometries.internal.shared.DefaultMultiPoint;
 import org.apache.sis.geometries.internal.shared.DefaultMultiPolygon;
 import org.apache.sis.geometries.internal.shared.DefaultMultiPolyhedron;
 import org.apache.sis.geometries.internal.shared.DefaultMultiSurface;
+import org.apache.sis.geometries.internal.shared.DefaultNurbCurve;
+import org.apache.sis.geometries.internal.shared.DefaultNurbSurface;
+import org.apache.sis.geometries.internal.shared.DefaultOffsetCurve;
 import org.apache.sis.geometries.internal.shared.DefaultPoint;
 import org.apache.sis.geometries.internal.shared.DefaultPolygon;
 import org.apache.sis.geometries.internal.shared.DefaultPolyhedralSurface;
 import org.apache.sis.geometries.internal.shared.DefaultPolyhedron;
+import org.apache.sis.geometries.internal.shared.DefaultPolynomialSpline;
+import org.apache.sis.geometries.internal.shared.DefaultPrism;
+import org.apache.sis.geometries.internal.shared.DefaultProductCurve;
 import org.apache.sis.geometries.internal.shared.DefaultRawMultiPoint;
 import org.apache.sis.geometries.internal.shared.DefaultReversedCurve;
 import org.apache.sis.geometries.internal.shared.DefaultReversedSurface;
 import org.apache.sis.geometries.internal.shared.DefaultRhumb;
+import org.apache.sis.geometries.internal.shared.DefaultSpiral;
 import org.apache.sis.geometries.internal.shared.DefaultTriangle;
 import org.apache.sis.geometries.internal.shared.DefaultTriangulatedSurface;
 import org.apache.sis.geometries.point.MultiPoint;
 import org.apache.sis.geometries.solid.MultiPolyhedron;
 import org.apache.sis.geometries.solid.Polyhedron;
+import org.apache.sis.geometries.surface.BSplineSurface;
+import org.apache.sis.geometries.surface.BSplineSurfaceForm;
+import org.apache.sis.geometries.surface.BilinearGrid;
 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.NurbSurface;
 import org.apache.sis.geometries.surface.Polygon;
 import org.apache.sis.geometries.surface.PolyhedralSurface;
 import org.apache.sis.geometries.surface.TIN;
@@ -80,8 +118,13 @@ import org.apache.sis.maths.DataType;
 import org.apache.sis.maths.NDArrays;
 import org.apache.sis.maths.SampleSystem;
 import org.apache.sis.maths.Vector;
+import org.apache.sis.measure.NumberRange;
+import org.apache.sis.measure.Range;
 import org.apache.sis.setup.GeometryLibrary;
+import org.opengis.geometry.DirectPosition;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
+import org.opengis.referencing.crs.SingleCRS;
+import org.apache.sis.geometries.solid.BSplineSolid;
 
 
 /**
@@ -251,6 +294,218 @@ public final class GeometryFactory extends 
org.apache.sis.geometry.wrapper.Geome
         return new DefaultArcByBulge(points, bulge, normal);
     }
 
+    /*
+     * Conics, spirals and splines. These are the curves whose interpolation 
is neither linear nor
+     * a simple chain of circular arcs, and which ISO 19107 defines by a 
mathematical construction
+     * rather than by a list of positions alone.
+     */
+
+    /**
+     * Creates a chain of conic section arcs, each of them determined by five 
data points.
+     *
+     * @param  points         points lying on the conic. At least five of 
them, the first one also
+     *                        acting as the last one when {@code cycle} is 
{@code true}.
+     * @param  controlPoints  centres of the exponential maps in which the 
arcs are constructed,
+     *                        one per arc, or {@code null} if none.
+     * @param  cycle          whether the conic closes on itself.
+     */
+    public static Conic createConic(DataPoints points, Array controlPoints, 
boolean cycle) {
+        return new DefaultConic(points, controlPoints, cycle);
+    }
+
+    /**
+     * Creates a chain of circular arcs, each of them centred on a control 
point and joining two
+     * consecutive data points.
+     *
+     * @param  points         start and end points of the arcs, two 
consecutive arcs sharing a point.
+     *                        There is one more point than there are arcs.
+     * @param  controlPoints  centres of the circles carrying the arcs, one 
per arc.
+     * @param  radius         radius vectors giving the plane and the rotation 
direction of each arc,
+     *                        possibly empty.
+     * @param  cycle          whether the chain closes on itself.
+     */
+    public static Arc createArc(DataPoints points, Array controlPoints, 
List<Vector> radius, boolean cycle) {
+        return new DefaultArc(points, controlPoints, radius, cycle);
+    }
+
+    /**
+     * Creates a complete circle, i.e. a chain of circular arcs sharing a 
single centre and closing
+     * on itself.
+     *
+     * @param  points         points of the circle, all at the same distance 
from the centre,
+     *                        the first and the last ones being equal.
+     * @param  controlPoints  the centre of the circle, repeated once per arc. 
Because a single arc
+     *                        must stay below a full turn, at least two of 
them are needed.
+     * @param  radius         radius vectors of the arcs, possibly empty.
+     */
+    public static Circle createCircle(DataPoints points, Array controlPoints, 
List<Vector> radius) {
+        return new DefaultCircle(points, controlPoints, radius);
+    }
+
+    /**
+     * Creates a conic without a cross term, therefore an arc of ellipse, each 
arc being determined
+     * by four data points instead of five.
+     *
+     * @param  points         points lying on the ellipse.
+     * @param  controlPoints  centres of the ellipses of the arcs, or {@code 
null} if none.
+     * @param  cycle          whether the arc closes on itself, making it a 
complete ellipse.
+     */
+    public static EllipticArc createEllipticArc(DataPoints points, Array 
controlPoints, boolean cycle) {
+        return new DefaultEllipticArc(points, controlPoints, cycle);
+    }
+
+    /**
+     * Creates a curve defined indirectly by its curvature, and by its torsion 
when it is not planar.
+     *
+     * @param  points      points of the spiral, the first one being its start 
point.
+     * @param  curvature   curvature as a function of arc length.
+     * @param  torsion     torsion as a function of arc length, or {@code 
null} if the spiral is planar.
+     * @param  startFrame  two or three mutually orthogonal unit vectors 
forming a right-handed frame
+     *                     at the start point.
+     */
+    public static Spiral createSpiral(DataPoints points, RealFunction 
curvature,
+            RealFunction torsion, List<Vector> startFrame)
+    {
+        return new DefaultSpiral(points, curvature, torsion, startFrame);
+    }
+
+    /**
+     * Creates a spiral whose curvature varies linearly with arc length, also 
called a Cornu spiral.
+     *
+     * @param  points      points of the clothoid, the first one being its 
start point.
+     * @param  curvature   curvature as a function of arc length. It shall be 
linear in the arc
+     *                     length measured from the point where the infinite 
clothoid has zero
+     *                     curvature.
+     * @param  startFrame  two mutually orthogonal unit vectors forming a 
right-handed frame at the
+     *                     start point, a clothoid being planar.
+     */
+    public static Clothoid createClothoid(DataPoints points, RealFunction 
curvature, List<Vector> startFrame) {
+        return new DefaultClothoid(points, curvature, startFrame);
+    }
+
+    /**
+     * Creates a spline which interpolates its data points, i.e. a polynomial 
curve passing through them.
+     *
+     * @param  points              points the spline passes through, in order.
+     * @param  controlPoints       control points of the spline, or {@code 
null} if none.
+     * @param  knots               knot values, strictly increasing, one per 
data point.
+     * @param  degree              degree of the interpolating polynomials.
+     * @param  curveForm           kind of curve approximated by the spline, 
or {@code null} if none.
+     * @param  knotSpec            distribution of the knots, or {@code null} 
if unspecified.
+     * @param  derivativeAtStart   derivative imposed at the start point, or 
{@code null} if none.
+     * @param  derivativeAtEnd     derivative imposed at the end point, or 
{@code null} if none.
+     * @param  derivativeInterior  number of continuous derivatives at the 
interior knots,
+     *                             at most {@code degree} − 1.
+     */
+    public static PolynomialSpline createPolynomialSpline(DataPoints points, 
Array controlPoints,
+            double[] knots, int degree, SplineCurveForm curveForm, KnotType 
knotSpec,
+            Vector derivativeAtStart, Vector derivativeAtEnd, int 
derivativeInterior)
+    {
+        return new DefaultPolynomialSpline(points, controlPoints, knots, 
degree, curveForm,
+                knotSpec, derivativeAtStart, derivativeAtEnd, 
derivativeInterior);
+    }
+
+    /**
+     * Creates a polynomial spline of degree 3, C² everywhere and passing 
through its data points.
+     *
+     * @param  points             points the spline passes through, in order.
+     * @param  controlPoints      control points of the spline, or {@code 
null} if none.
+     * @param  knots              knot values, strictly increasing, one per 
data point.
+     * @param  curveForm          kind of curve approximated by the spline, or 
{@code null} if none.
+     * @param  knotSpec           distribution of the knots, or {@code null} 
if unspecified.
+     * @param  derivativeAtStart  tangent imposed at the start point, or 
{@code null} if none.
+     * @param  derivativeAtEnd    tangent imposed at the end point, or {@code 
null} if none.
+     */
+    public static CubicSpline createCubicSpline(DataPoints points, Array 
controlPoints, double[] knots,
+            SplineCurveForm curveForm, KnotType knotSpec, Vector 
derivativeAtStart, Vector derivativeAtEnd)
+    {
+        return new DefaultCubicSpline(points, controlPoints, knots, curveForm, 
knotSpec,
+                derivativeAtStart, derivativeAtEnd);
+    }
+
+    /**
+     * Creates an approximating spline using the Bézier (Bernstein) 
polynomials as partition of unity.
+     *
+     * @param  points             end points of the segments of the curve.
+     * @param  controlPoints      control points of the curve, each 
subsequence of {@code degree} + 1
+     *                            points starting at a multiple of {@code 
degree} defining one segment.
+     * @param  knots              knot values. For a Bézier curve the only 
knots are 0 and 1.
+     * @param  degree             degree of the Bernstein polynomials.
+     * @param  curveForm          kind of curve approximated by the spline, or 
{@code null} if none.
+     * @param  knotSpec           distribution of the knots, or {@code null} 
if unspecified.
+     * @param  derivativeAtStart  derivative imposed at the start point, or 
{@code null} if none.
+     * @param  derivativeAtEnd    derivative imposed at the end point, or 
{@code null} if none.
+     */
+    public static Bezier createBezier(DataPoints points, Array controlPoints, 
double[] knots, int degree,
+            SplineCurveForm curveForm, KnotType knotSpec, Vector 
derivativeAtStart, Vector derivativeAtEnd)
+    {
+        return new DefaultBezier(points, controlPoints, knots, degree, 
curveForm, knotSpec,
+                derivativeAtStart, derivativeAtEnd);
+    }
+
+    /**
+     * Creates an approximating spline using the b-spline basis functions as 
partition of unity.
+     * For the rational flavour carrying a weight per control point, see
+     * {@link #createNurbCurve(DataPoints, double[], double[], int)}.
+     *
+     * @param  points         points of the curve.
+     * @param  controlPoints  control points of the curve, or {@code null} if 
none.
+     * @param  knots          knot values, strictly increasing, repeated knots 
being expressed by
+     *                        their multiplicity rather than by repetition.
+     * @param  degree         degree of the b-spline basis functions.
+     * @param  curveForm      kind of curve approximated by the spline, or 
{@code null} if none.
+     * @param  knotSpec       distribution of the knots, or {@code null} if 
unspecified.
+     * @param  rational       whether the control points are expressed in 
homogeneous coordinates.
+     */
+    public static BSplineCurve createBSplineCurve(DataPoints points, Array 
controlPoints, double[] knots,
+            int degree, SplineCurveForm curveForm, KnotType knotSpec, boolean 
rational)
+    {
+        return new DefaultBSplineCurve(points, controlPoints, knots, degree, 
curveForm, knotSpec, rational);
+    }
+
+    /**
+     * Creates a curve defined by control points, weights, a knot vector and a 
degree. Depending on
+     * the given values it is a Bézier curve, a b-spline or a NURBS.
+     *
+     * @param  points   control points of the curve.
+     * @param  weights  weight of each control point, making the curve 
rational.
+     * @param  knots    knot values, whose multiplicities make the curve 
clamped or periodic.
+     * @param  degree   degree of the basis functions.
+     */
+    public static NurbCurve createNurbCurve(DataPoints points, double[] 
weights, double[] knots, int degree) {
+        return new DefaultNurbCurve(points, weights, knots, degree);
+    }
+
+    /**
+     * Creates a curve at a constant distance and bearing from a base curve.
+     *
+     * @param  baseCurve     curve from which the returned curve is offset. It 
shall have a
+     *                       well-defined tangent at every position.
+     * @param  distance      constant offset distance. In a 2-dimensional 
coordinate system, a
+     *                       positive distance designates the left side of the 
base curve with
+     *                       respect to the tangent, and a negative distance 
its right side.
+     * @param  refDirection  reference direction of the offset in a 
3-dimensional coordinate system,
+     *                       or {@code null} if the spatial dimension is 2.
+     */
+    public static OffsetCurve createOffsetCurve(Curve baseCurve, Length 
distance, Bearing refDirection) {
+        return new DefaultOffsetCurve(baseCurve, distance, refDirection);
+    }
+
+    /**
+     * Creates a curve composed of other curves which all share the same 
parameter space, each of
+     * them covering a disjoint projection of the coordinate system.
+     *
+     * @param  parameterRange  interval of the construction parameter shared 
by all the elements.
+     * @param  projections     projections of the coordinate system matching 
the element curves.
+     *                         They are disjoint and together cover the whole 
coordinate system.
+     * @param  elements        projections of the curve, one per projection of 
the coordinate system.
+     */
+    public static ProductCurve createProductCurve(Range<?> parameterRange,
+            List<Projection> projections, Curve ... elements)
+    {
+        return new DefaultProductCurve(parameterRange, projections, elements);
+    }
+
     public static CurvePolygon createCurvePolygon(Curve exterior, List<Curve> 
interiors) {
         return new DefaultCurvePolygon(exterior, interiors);
     }
@@ -283,6 +538,99 @@ public final class GeometryFactory extends 
org.apache.sis.geometry.wrapper.Geome
         return new DefaultMultiPolyhedron(crs, solids);
     }
 
+    /*
+     * Surfaces and solids defined over a rectangular parameter space, in 
which fixing all the
+     * parameters but one yields a family of section curves.
+     */
+
+    /**
+     * Creates a parametric curve surface using polylines as both horizontal 
and vertical curves,
+     * each cell of the parameter grid being interpolated bilinearly over the 
unit square.
+     *
+     * @param  points         positions at the knots of the parameter grid, in 
row-major order.
+     *                        There shall be {@code rows} × {@code columns} of 
them.
+     * @param  controlPoints  control points of the section curves in 
row-major order,
+     *                        or {@code null} if none.
+     * @param  rows           number of rows in the parameter grid.
+     * @param  columns        number of columns in the parameter grid.
+     * @param  knots          knot values, one sequence per surface parameter, 
or {@code null} if none.
+     */
+    public static BilinearGrid createBilinearGrid(DataPoints points, 
List<DirectPosition> controlPoints,
+            int rows, int columns, List<double[]> knots)
+    {
+        return new DefaultBilinearGrid(points, controlPoints, rows, columns, 
knots);
+    }
+
+    /**
+     * Creates a rational or polynomial parametric surface represented by 
control points, b-spline
+     * basis functions and possibly weights. For the rational flavour carrying 
a weight per control
+     * point, see {@link #createNurbSurface(Vector[][], double[][], double[], 
double[], int)}.
+     *
+     * @param  points         positions at the knots of the parameter grid, in 
row-major order.
+     *                        There shall be {@code rows} × {@code columns} of 
them.
+     * @param  controlPoints  control points in row-major order, or {@code 
null} if none.
+     * @param  rows           number of rows in the parameter grid.
+     * @param  columns        number of columns in the parameter grid.
+     * @param  knots          exactly two knot sequences, one per surface 
parameter, knots with a
+     *                        multiplicity greater than one being repeated in 
the sequence.
+     * @param  degree         algebraic degree of the b-spline basis functions.
+     * @param  knotSpec       distribution of the knots, or {@code null} if 
unspecified.
+     * @param  surfaceForm    kind of surface approximated by the spline, or 
{@code null} if none.
+     * @param  polynomial     {@code true} if the surface is polynomial, 
{@code false} if the control
+     *                        points are expressed in homogeneous coordinates, 
making it rational.
+     */
+    public static BSplineSurface createBSplineSurface(DataPoints points, 
List<DirectPosition> controlPoints,
+            int rows, int columns, List<double[]> knots, int degree, KnotType 
knotSpec,
+            BSplineSurfaceForm surfaceForm, boolean polynomial)
+    {
+        return new DefaultBSplineSurface(points, controlPoints, rows, columns, 
knots, degree,
+                knotSpec, surfaceForm, polynomial);
+    }
+
+    /**
+     * Creates a NURBS surface, i.e. the tensor product of two directions each 
having its own knot
+     * vector, over a grid of weighted control points.
+     *
+     * @param  controlPoints  control points as a grid, the first index 
running along <var>u</var>
+     *                        and the second one along <var>v</var>.
+     * @param  weights        weight of each control point, in the same layout 
as the control points.
+     * @param  knotsU         knot values along the <var>u</var> parameter.
+     * @param  knotsV         knot values along the <var>v</var> parameter.
+     * @param  degree         degree of the basis functions.
+     */
+    public static NurbSurface createNurbSurface(Vector<?>[][] controlPoints, 
double[][] weights,
+            double[] knotsU, double[] knotsV, int degree)
+    {
+        return new DefaultNurbSurface(controlPoints, weights, knotsU, knotsV, 
degree);
+    }
+
+    /**
+     * Creates a parametric curve solid whose three families of curves are 
b-splines.
+     *
+     * @param  points         positions at the knots of the parameter grid, in 
row-major order.
+     *                        There shall be {@code rows} × {@code columns} × 
{@code files} of them.
+     * @param  controlPoints  control points in row-major order, or {@code 
null} if none.
+     * @param  rows           number of horizontal rows in the parameter grid.
+     * @param  columns        number of vertical columns in the parameter grid.
+     * @param  files          number of depth files in the parameter grid.
+     */
+    public static BSplineSolid createBSolidSpline(DataPoints points, 
List<DirectPosition> controlPoints,
+            int rows, int columns, int files)
+    {
+        return new DefaultBSplineSolid(points, controlPoints, rows, columns, 
files);
+    }
+
+    /**
+     * Creates a prism by extruding the given base shape between two limits.
+     *
+     * @param  base            base shape of the prism, for example a polygon 
or a circle.
+     * @param  extrusionRange  lower and upper limits of the extrusion.
+     * @param  extrusionCrs    coordinate reference system of the extrusion 
range.
+     */
+    public static Prism createPrism(Geometry base, NumberRange<?> 
extrusionRange, SingleCRS extrusionCrs) {
+        return new DefaultPrism(base, extrusionRange, extrusionCrs);
+    }
+
     /**
      * Returns a curve traversed in the opposite direction to the given one.
      * This is what a GML {@code gml:OrientableCurve} with {@code 
orientation="-"} describes.
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 3ed022d992..e046881e46 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
@@ -16,6 +16,7 @@
  */
 package org.apache.sis.geometries;
 
+import org.apache.sis.geometries.internal.shared.DefaultPrism;
 import org.opengis.referencing.crs.SingleCRS;
 import org.apache.sis.measure.NumberRange;
 
@@ -26,7 +27,8 @@ import org.apache.sis.measure.NumberRange;
  * @author Johann Sorel (Geomatys)
  * @see https://docs.ogc.org/DRAFTS/21-045r1.html#prism
  */
-public non-sealed interface Prism extends Geometry {
+public sealed interface Prism extends Geometry
+        permits DefaultPrism {
 
     static final String TYPE = "PRISM";
 
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 c87fe99bb4..3b87d7c683 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
@@ -19,6 +19,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.geometries.internal.shared.DefaultArc;
 import org.apache.sis.maths.Array;
 import org.apache.sis.maths.Vector;
 import static org.opengis.annotation.Specification.ISO_19107;
@@ -35,9 +36,20 @@ import org.opengis.annotation.UML;
  */
 @UML(identifier="Arc", specification=ISO_19107)
 public sealed interface Arc extends Conic
-        permits Circle
+        permits Circle,
+                DefaultArc
 {
 
+    /**
+     * Well-known text keyword of this geometry type.
+     */
+    static final String TYPE = "ARC";
+
+    @Override
+    default String getGeometryType() {
+        return TYPE;
+    }
+
     /**
      * Returns {@link CurveInterpolation#CIRCULAR}.
      *
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 00e3eb48b3..7a0d8215f0 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
@@ -17,6 +17,7 @@
 package org.apache.sis.geometries.curve;
 
 import org.apache.sis.geometries.CurveInterpolation;
+import org.apache.sis.geometries.internal.shared.DefaultBSplineCurve;
 import static org.opengis.annotation.Specification.ISO_19107;
 import org.opengis.annotation.UML;
 
@@ -46,9 +47,20 @@ import org.opengis.annotation.UML;
 @UML(identifier="BSplineCurve", specification=ISO_19107)
 public sealed interface BSplineCurve extends SplineCurve
         permits Bezier,
-                NurbCurve
+                NurbCurve,
+                DefaultBSplineCurve
 {
 
+    /**
+     * Well-known text keyword of this geometry type.
+     */
+    static final String TYPE = "BSPLINECURVE";
+
+    @Override
+    default String getGeometryType() {
+        return TYPE;
+    }
+
     /**
      * Returns {@link 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 3b1f487336..4416db9578 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
@@ -17,6 +17,7 @@
 package org.apache.sis.geometries.curve;
 
 import org.apache.sis.geometries.CurveInterpolation;
+import org.apache.sis.geometries.internal.shared.DefaultBezier;
 import static org.opengis.annotation.Specification.ISO_19107;
 import org.opengis.annotation.UML;
 
@@ -47,7 +48,23 @@ import org.opengis.annotation.UML;
  * @see ISO 19107:2019 - 7.13.7
  */
 @UML(identifier="Bezier", specification=ISO_19107)
-public non-sealed interface Bezier extends PolynomialSpline, BSplineCurve {
+public sealed interface Bezier extends PolynomialSpline, BSplineCurve
+        permits DefaultBezier{
+
+    /**
+     * Well-known text keyword of this geometry type.
+     */
+    static final String TYPE = "BEZIER";
+
+    /**
+     * Returns {@link #TYPE}.
+     * This value takes precedence over the types declared by the two parent 
interfaces,
+     * a Bézier curve being at the same time a polynomial spline and a 
b-spline.
+     */
+    @Override
+    default String getGeometryType() {
+        return TYPE;
+    }
 
     /**
      * Returns {@link 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 2b35034f6d..2c2fdfb015 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
@@ -16,6 +16,7 @@
  */
 package org.apache.sis.geometries.curve;
 
+import org.apache.sis.geometries.internal.shared.DefaultCircle;
 import static org.opengis.annotation.Specification.ISO_19107;
 import org.opengis.annotation.UML;
 
@@ -41,7 +42,18 @@ import org.opengis.annotation.UML;
  * @see ISO 19107:2019 - 7.9.4
  */
 @UML(identifier="Circle", specification=ISO_19107)
-public non-sealed interface Circle extends Arc {
+public sealed interface Circle extends Arc
+        permits DefaultCircle {
+
+    /**
+     * Well-known text keyword of this geometry type.
+     */
+    static final String TYPE = "CIRCLE";
+
+    @Override
+    default String getGeometryType() {
+        return TYPE;
+    }
 
     /**
      * Returns {@code true}: a circle is a complete curve closing on itself.
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 12be0b05e7..56a1be372e 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
@@ -17,6 +17,7 @@
 package org.apache.sis.geometries.curve;
 
 import org.apache.sis.geometries.CurveInterpolation;
+import org.apache.sis.geometries.internal.shared.DefaultClothoid;
 import static org.opengis.annotation.Specification.ISO_19107;
 import org.opengis.annotation.UML;
 
@@ -46,7 +47,18 @@ import org.opengis.annotation.UML;
  * @see ISO 19107:2019 - 7.11.3
  */
 @UML(identifier="Clothoid", specification=ISO_19107)
-public non-sealed interface Clothoid extends Spiral {
+public sealed interface Clothoid extends Spiral
+        permits DefaultClothoid {
+
+    /**
+     * Well-known text keyword of this geometry type.
+     */
+    static final String TYPE = "CLOTHOID";
+
+    @Override
+    default String getGeometryType() {
+        return TYPE;
+    }
 
     /**
      * Returns {@link CurveInterpolation#CLOTHOID}.
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 e8d7ec1461..ccf2b1040a 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
@@ -19,6 +19,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.geometries.internal.shared.DefaultConic;
 import org.apache.sis.maths.Array;
 import static org.opengis.annotation.Specification.ISO_19107;
 import org.opengis.annotation.UML;
@@ -49,9 +50,20 @@ import org.opengis.annotation.UML;
 @UML(identifier="Conic", specification=ISO_19107)
 public sealed interface Conic extends Curve
         permits Arc,
-                EllipticArc
+                EllipticArc,
+                DefaultConic
 {
 
+    /**
+     * Well-known text keyword of this geometry type.
+     */
+    static final String TYPE = "CONIC";
+
+    @Override
+    default String getGeometryType() {
+        return TYPE;
+    }
+
     /**
      * Returns {@link CurveInterpolation#CONIC}.
      *
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 e68a005f5d..4f21b9e3f2 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
@@ -16,6 +16,7 @@
  */
 package org.apache.sis.geometries.curve;
 
+import org.apache.sis.geometries.internal.shared.DefaultCubicSpline;
 import static org.opengis.annotation.Specification.ISO_19107;
 import org.opengis.annotation.UML;
 
@@ -40,7 +41,18 @@ import org.opengis.annotation.UML;
  * @see ISO 19107:2019 - 7.13.6
  */
 @UML(identifier="CubicSpline", specification=ISO_19107)
-public non-sealed interface CubicSpline extends PolynomialSpline {
+public sealed interface CubicSpline extends PolynomialSpline
+        permits DefaultCubicSpline {
+
+    /**
+     * Well-known text keyword of this geometry type.
+     */
+    static final String TYPE = "CUBICSPLINE";
+
+    @Override
+    default String getGeometryType() {
+        return TYPE;
+    }
 
     /**
      * Returns 3: a cubic spline is defined by polynomials of degree 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 5e5ad6116c..6ae57b52f2 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
@@ -17,6 +17,7 @@
 package org.apache.sis.geometries.curve;
 
 import org.apache.sis.geometries.CurveInterpolation;
+import org.apache.sis.geometries.internal.shared.DefaultEllipticArc;
 import static org.opengis.annotation.Specification.ISO_19107;
 import org.opengis.annotation.UML;
 
@@ -37,7 +38,18 @@ import org.opengis.annotation.UML;
  * @see ISO 19107:2019 - 7.9.6
  */
 @UML(identifier="EllipticArc", specification=ISO_19107)
-public non-sealed interface EllipticArc extends Conic {
+public sealed interface EllipticArc extends Conic
+        permits DefaultEllipticArc {
+
+    /**
+     * Well-known text keyword of this geometry type.
+     */
+    static final String TYPE = "ELLIPTICARC";
+
+    @Override
+    default String getGeometryType() {
+        return TYPE;
+    }
 
     /**
      * Returns {@link CurveInterpolation#ELLIPTICAL}.
diff --git 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/OffsetCurve.java
 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/OffsetCurve.java
index c1a0942324..5182e2b2c4 100644
--- 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/OffsetCurve.java
+++ 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/OffsetCurve.java
@@ -19,6 +19,7 @@ package org.apache.sis.geometries.curve;
 import javax.measure.quantity.Length;
 import org.apache.sis.geometries.Bearing;
 import org.apache.sis.geometries.Curve;
+import org.apache.sis.geometries.internal.shared.DefaultOffsetCurve;
 import static org.opengis.annotation.Specification.ISO_19107;
 import org.opengis.annotation.UML;
 
@@ -45,7 +46,18 @@ import org.opengis.annotation.UML;
  * @see ISO 19107:2019 - 6.4.20
  */
 @UML(identifier="OffsetCurve", specification=ISO_19107)
-public non-sealed interface OffsetCurve extends Curve {
+public sealed interface OffsetCurve extends Curve
+        permits DefaultOffsetCurve {
+
+    /**
+     * Well-known text keyword of this geometry type.
+     */
+    static final String TYPE = "OFFSETCURVE";
+
+    @Override
+    default String getGeometryType() {
+        return TYPE;
+    }
 
     /**
      * Distance at which this curve is generated from the {@linkplain 
#getBaseCurve() base curve}.
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 95d5cd090a..4446248fa3 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
@@ -17,6 +17,7 @@
 package org.apache.sis.geometries.curve;
 
 import org.apache.sis.geometries.CurveInterpolation;
+import org.apache.sis.geometries.internal.shared.DefaultPolynomialSpline;
 import org.apache.sis.maths.Vector;
 import static org.opengis.annotation.Specification.ISO_19107;
 import org.opengis.annotation.UML;
@@ -46,9 +47,20 @@ import org.opengis.annotation.UML;
 @UML(identifier="PolynomialSpline", specification=ISO_19107)
 public sealed interface PolynomialSpline extends SplineCurve
         permits CubicSpline,
-                Bezier
+                Bezier,
+                DefaultPolynomialSpline
 {
 
+    /**
+     * Well-known text keyword of this geometry type.
+     */
+    static final String TYPE = "POLYNOMIALSPLINE";
+
+    @Override
+    default String getGeometryType() {
+        return TYPE;
+    }
+
     /**
      * Returns {@link CurveInterpolation#POLYNOMIAL_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 3ad62d5fe8..3afd8210f6 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
@@ -23,6 +23,7 @@ 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.geometries.internal.shared.DefaultProductCurve;
 import org.apache.sis.measure.Range;
 import static org.opengis.annotation.Specification.ISO_19107;
 import org.opengis.annotation.UML;
@@ -52,7 +53,22 @@ import org.opengis.annotation.UML;
  * @see ISO 19107:2019 - 6.4.22
  */
 @UML(identifier="ProductCurve", specification=ISO_19107)
-public non-sealed interface ProductCurve extends Curve, 
GeometryCollection<Curve> {
+public sealed interface ProductCurve extends Curve, GeometryCollection<Curve>
+        permits DefaultProductCurve {
+
+    /**
+     * Well-known text keyword of this geometry type.
+     */
+    static final String TYPE = "PRODUCTCURVE";
+
+    /**
+     * Returns {@link #TYPE} rather than the keyword of a plain geometry 
collection.
+     * This value resolves the ambiguity between {@link Curve} and {@link 
GeometryCollection}.
+     */
+    @Override
+    default String getGeometryType() {
+        return TYPE;
+    }
 
     /**
      * Returns {@link CurveInterpolation#PRODUCT_CURVE}: the interpolation of 
a product curve is
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 9981805242..87dbb7b1bc 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
@@ -19,6 +19,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.geometries.internal.shared.DefaultSpiral;
 import org.apache.sis.maths.Vector;
 import static org.opengis.annotation.Specification.ISO_19107;
 import org.opengis.annotation.UML;
@@ -47,9 +48,20 @@ import org.opengis.annotation.UML;
  */
 @UML(identifier="Spiral", specification=ISO_19107)
 public sealed interface Spiral extends Curve
-        permits Clothoid
+        permits Clothoid,
+                DefaultSpiral
 {
 
+    /**
+     * Well-known text keyword of this geometry type.
+     */
+    static final String TYPE = "SPIRAL";
+
+    @Override
+    default String getGeometryType() {
+        return TYPE;
+    }
+
     /**
      * Returns {@link CurveInterpolation#SPIRAL}.
      *
diff --git 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultArc.java
 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultArc.java
new file mode 100644
index 0000000000..59aa3b46fe
--- /dev/null
+++ 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultArc.java
@@ -0,0 +1,70 @@
+/*
+ * 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.internal.shared;
+
+import java.util.List;
+import org.apache.sis.geometries.DataPoints;
+import org.apache.sis.geometries.curve.Arc;
+import org.apache.sis.maths.Array;
+import org.apache.sis.maths.Vector;
+
+
+/**
+ * A chain of circular arcs, each of them centred on a control point and 
joining two consecutive
+ * data points.
+ *
+ * @author Johann Sorel (Geomatys)
+ */
+public non-sealed class DefaultArc extends DefaultConic implements Arc {
+
+    /**
+     * Radius vectors giving, together with the two end points, the plane and 
the rotation direction
+     * of each arc.
+     */
+    protected final List<Vector> radius;
+
+    /**
+     * Creates a chain of circular arcs.
+     *
+     * @param  points         start and end points of the arcs, two 
consecutive arcs sharing a point.
+     *                        There is one more point than there are arcs.
+     * @param  controlPoints  centres of the circles carrying the arcs, one 
per arc.
+     * @param  radius         radius vectors of the arcs, possibly empty.
+     * @param  cycle          whether this chain closes on itself.
+     */
+    public DefaultArc(final DataPoints points, final Array controlPoints,
+            final List<Vector> radius, final boolean cycle)
+    {
+        super(points, controlPoints, cycle);
+        this.radius = (radius == null) ? List.of() : List.copyOf(radius);
+    }
+
+    /**
+     * Returns the number of arcs in this chain, which is one less than the 
number of data points.
+     */
+    @Override
+    public int getNumArc() {
+        final int size = points.size();
+        return (size == 0) ? 0 : size - 1;
+    }
+
+    @Override
+    public List<Vector> getRadius() {
+        return radius;
+    }
+
+}
diff --git 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultBSplineCurve.java
 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultBSplineCurve.java
new file mode 100644
index 0000000000..34f150e7fe
--- /dev/null
+++ 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultBSplineCurve.java
@@ -0,0 +1,177 @@
+/*
+ * 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.internal.shared;
+
+import java.util.Objects;
+import org.apache.sis.geometries.AttributesType;
+import org.apache.sis.geometries.DataPoints;
+import org.apache.sis.geometries.curve.BSplineCurve;
+import org.apache.sis.geometries.curve.FunctionArc;
+import org.apache.sis.geometries.curve.KnotType;
+import org.apache.sis.geometries.curve.SplineCurveForm;
+import org.apache.sis.maths.Array;
+import org.opengis.geometry.Envelope;
+import org.opengis.referencing.crs.CoordinateReferenceSystem;
+
+
+/**
+ * An approximating spline using the b-spline basis functions as partition of 
unity,
+ * so that the curve stays inside the convex hull of the control points whose 
weight is
+ * currently non-zero.
+ *
+ * <p>For the rational flavour, which carries a weight per control point and 
can therefore be
+ * evaluated, see {@link DefaultNurbCurve}.</p>
+ *
+ * @author Johann Sorel (Geomatys)
+ */
+public non-sealed class DefaultBSplineCurve extends AbstractGeometry 
implements BSplineCurve {
+
+    /**
+     * Points of this curve.
+     */
+    protected final DataPoints points;
+
+    /**
+     * Control points of this curve, or {@code null} if none.
+     */
+    protected final Array controlPoints;
+
+    /**
+     * Knot values defining the parameter space and the basis functions, 
strictly increasing.
+     */
+    protected final double[] knots;
+
+    /**
+     * Degree of the b-spline basis functions.
+     */
+    protected final int degree;
+
+    /**
+     * Kind of curve which this spline approximates, or {@code null} if none.
+     */
+    protected final SplineCurveForm curveForm;
+
+    /**
+     * Distribution of the knots, given for information only.
+     */
+    protected final KnotType knotSpec;
+
+    /**
+     * Whether this spline uses rational functions, i.e. whether its control 
points are expressed
+     * in homogeneous coordinates.
+     */
+    protected final boolean rational;
+
+    /**
+     * Creates a b-spline curve.
+     *
+     * @param  points         points of this curve.
+     * @param  controlPoints  control points of this curve, or {@code null} if 
none.
+     * @param  knots          knot values, strictly increasing, repeated knots 
being expressed by
+     *                        their multiplicity rather than by repetition.
+     * @param  degree         degree of the b-spline basis functions.
+     * @param  curveForm      kind of curve approximated by this spline, or 
{@code null} if none.
+     * @param  knotSpec       distribution of the knots, or {@code null} if 
unspecified.
+     * @param  rational       whether the control points are expressed in 
homogeneous coordinates.
+     */
+    public DefaultBSplineCurve(final DataPoints points, final Array 
controlPoints, final double[] knots,
+            final int degree, final SplineCurveForm curveForm, final KnotType 
knotSpec,
+            final boolean rational)
+    {
+        this.points        = Objects.requireNonNull(points);
+        this.controlPoints = controlPoints;
+        this.knots         = (knots == null) ? new double[0] : knots.clone();
+        this.degree        = degree;
+        this.curveForm     = curveForm;
+        this.knotSpec      = knotSpec;
+        this.rational      = rational;
+    }
+
+    @Override
+    public SplineCurveForm getCurveForm() {
+        return curveForm;
+    }
+
+    @Override
+    public KnotType getKnotSpec() {
+        return knotSpec;
+    }
+
+    @Override
+    public boolean isRational() {
+        return rational;
+    }
+
+    @Override
+    public int getDegree() {
+        return degree;
+    }
+
+    @Override
+    public double[] getKnots() {
+        return knots.clone();
+    }
+
+    /**
+     * Returns the number of intervals in the knot array, which is the number 
of arcs of this curve.
+     */
+    @Override
+    public Integer getNumArc() {
+        return Math.max(0, knots.length - 1);
+    }
+
+    @Override
+    public FunctionArc getSegment(int idx) {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+    @Override
+    public DataPoints getDataPoints() {
+        return points;
+    }
+
+    @Override
+    public Array getControlPoints() {
+        return controlPoints;
+    }
+
+    @Override
+    public CoordinateReferenceSystem getCoordinateReferenceSystem() {
+        return points.getCoordinateReferenceSystem();
+    }
+
+    @Override
+    public void setCoordinateReferenceSystem(CoordinateReferenceSystem crs) 
throws IllegalArgumentException {
+        points.setCoordinateReferenceSystem(crs);
+    }
+
+    @Override
+    public AttributesType getAttributesType() {
+        return points.getAttributesType();
+    }
+
+    @Override
+    public boolean isEmpty() {
+        return points.isEmpty();
+    }
+
+    @Override
+    public Envelope getEnvelope() {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+}
diff --git 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultBSplineSolid.java
 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultBSplineSolid.java
new file mode 100644
index 0000000000..431bcb5f99
--- /dev/null
+++ 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultBSplineSolid.java
@@ -0,0 +1,177 @@
+/*
+ * 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.internal.shared;
+
+import java.util.List;
+import java.util.Objects;
+import org.apache.sis.geometries.AttributesType;
+import org.apache.sis.geometries.Curve;
+import org.apache.sis.geometries.DataPoints;
+import org.apache.sis.geometries.GeometryType;
+import org.opengis.geometry.DirectPosition;
+import org.opengis.geometry.Envelope;
+import org.opengis.referencing.crs.CoordinateReferenceSystem;
+import org.apache.sis.geometries.solid.BSplineSolid;
+
+
+/**
+ * A parametric curve solid whose three families of curves are b-splines.
+ *
+ * @author Johann Sorel (Geomatys)
+ */
+public non-sealed class DefaultBSplineSolid extends AbstractGeometry 
implements BSplineSolid {
+
+    /**
+     * Positions of this solid at the knots of the parameter grid, in 
row-major order.
+     */
+    protected final DataPoints points;
+
+    /**
+     * Control points of the section curves, in row-major order, or an empty 
list if none.
+     */
+    protected final List<DirectPosition> controlPoints;
+
+    /**
+     * Number of horizontal rows in the parameter grid.
+     */
+    protected final int rows;
+
+    /**
+     * Number of vertical columns in the parameter grid.
+     */
+    protected final int columns;
+
+    /**
+     * Number of depth files in the parameter grid.
+     */
+    protected final int files;
+
+    /**
+     * Creates a b-spline solid over the given parameter grid.
+     *
+     * @param  points         positions at the knots of the parameter grid, in 
row-major order.
+     *                        There shall be {@code rows} × {@code columns} × 
{@code files} of them.
+     * @param  controlPoints  control points in row-major order, or {@code 
null} if none.
+     * @param  rows           number of horizontal rows in the parameter grid.
+     * @param  columns        number of vertical columns in the parameter grid.
+     * @param  files          number of depth files in the parameter grid.
+     */
+    public DefaultBSplineSolid(final DataPoints points, final 
List<DirectPosition> controlPoints,
+            final int rows, final int columns, final int files)
+    {
+        this.points        = Objects.requireNonNull(points);
+        this.controlPoints = (controlPoints == null) ? List.of() : 
List.copyOf(controlPoints);
+        this.rows          = rows;
+        this.columns       = columns;
+        this.files         = files;
+    }
+
+    @Override
+    public int getRows() {
+        return rows;
+    }
+
+    @Override
+    public int getColumns() {
+        return columns;
+    }
+
+    @Override
+    public int getFiles() {
+        return files;
+    }
+
+    @Override
+    public DataPoints getDataPoints() {
+        return points;
+    }
+
+    @Override
+    public List<DirectPosition> getControlPoints() {
+        return controlPoints;
+    }
+
+    /**
+     * Returns {@link GeometryType#SPLINECURVE}: the horizontal sections are 
b-splines.
+     */
+    @Override
+    public GeometryType getHorizontalCurveType() {
+        return GeometryType.SPLINECURVE;
+    }
+
+    /**
+     * Returns {@link GeometryType#SPLINECURVE}: the vertical sections are 
b-splines.
+     */
+    @Override
+    public GeometryType getVerticalCurveType() {
+        return GeometryType.SPLINECURVE;
+    }
+
+    /**
+     * Returns {@link GeometryType#SPLINECURVE}: the depth sections are 
b-splines.
+     */
+    @Override
+    public GeometryType getDepthCurveType() {
+        return GeometryType.SPLINECURVE;
+    }
+
+    @Override
+    public Curve getHorizontalCurve(double b, double c) {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+    @Override
+    public Curve getVerticalCurve(double a, double c) {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+    @Override
+    public Curve getDepthCurve(double a, double b) {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+    @Override
+    public DirectPosition getSurface(double a, double b, double c) {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+    @Override
+    public CoordinateReferenceSystem getCoordinateReferenceSystem() {
+        return points.getCoordinateReferenceSystem();
+    }
+
+    @Override
+    public void setCoordinateReferenceSystem(CoordinateReferenceSystem crs) 
throws IllegalArgumentException {
+        points.setCoordinateReferenceSystem(crs);
+    }
+
+    @Override
+    public AttributesType getAttributesType() {
+        return points.getAttributesType();
+    }
+
+    @Override
+    public boolean isEmpty() {
+        return points.isEmpty();
+    }
+
+    @Override
+    public Envelope getEnvelope() {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+}
diff --git 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultBSplineSurface.java
 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultBSplineSurface.java
new file mode 100644
index 0000000000..2774c512ce
--- /dev/null
+++ 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultBSplineSurface.java
@@ -0,0 +1,195 @@
+/*
+ * 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.internal.shared;
+
+import java.util.List;
+import java.util.Objects;
+import javax.measure.quantity.Area;
+import org.apache.sis.geometries.Curve;
+import org.apache.sis.geometries.DataPoints;
+import org.apache.sis.geometries.curve.KnotType;
+import org.apache.sis.geometries.surface.BSplineSurface;
+import org.apache.sis.geometries.surface.BSplineSurfaceForm;
+import org.opengis.geometry.DirectPosition;
+import org.opengis.geometry.Envelope;
+import org.opengis.metadata.Identifier;
+
+
+/**
+ * A rational or polynomial parametric surface represented by control points, 
b-spline basis
+ * functions and possibly weights.
+ *
+ * <p>For the rational flavour, which carries a weight per control point and 
can therefore be
+ * evaluated, see {@link DefaultNurbSurface}.</p>
+ *
+ * @author Johann Sorel (Geomatys)
+ */
+public non-sealed class DefaultBSplineSurface extends AbstractGeometry 
implements BSplineSurface {
+
+    /**
+     * Positions of this surface at the knots of the parameter grid, in 
row-major order.
+     */
+    protected final DataPoints points;
+
+    /**
+     * Control points of the section curves, in row-major order, or an empty 
list if none.
+     */
+    protected final List<DirectPosition> controlPoints;
+
+    /**
+     * Number of rows in the parameter grid.
+     */
+    protected final int rows;
+
+    /**
+     * Number of columns in the parameter grid.
+     */
+    protected final int columns;
+
+    /**
+     * The two knot sequences used to define the basis functions, one per 
surface parameter.
+     */
+    protected final List<double[]> knots;
+
+    /**
+     * Algebraic degree of the b-spline basis functions.
+     */
+    protected final int degree;
+
+    /**
+     * Distribution of the knots, given for information only.
+     */
+    protected final KnotType knotSpec;
+
+    /**
+     * Kind of surface which this spline approximates.
+     */
+    protected final BSplineSurfaceForm surfaceForm;
+
+    /**
+     * Whether this surface is a polynomial spline rather than a rational one.
+     */
+    protected final boolean polynomial;
+
+    /**
+     * Creates a b-spline surface over the given parameter grid.
+     *
+     * @param  points         positions at the knots of the parameter grid, in 
row-major order.
+     *                        There shall be {@code rows} × {@code columns} of 
them.
+     * @param  controlPoints  control points in row-major order, or {@code 
null} if none.
+     * @param  rows           number of rows in the parameter grid.
+     * @param  columns        number of columns in the parameter grid.
+     * @param  knots          exactly two knot sequences, one per surface 
parameter, knots with a
+     *                        multiplicity greater than one being repeated in 
the sequence.
+     * @param  degree         algebraic degree of the b-spline basis functions.
+     * @param  knotSpec       distribution of the knots, or {@code null} if 
unspecified.
+     * @param  surfaceForm    kind of surface approximated by this spline, or 
{@code null} if none.
+     * @param  polynomial     {@code true} if this surface is polynomial, 
{@code false} if the
+     *                        control points are expressed in homogeneous 
coordinates, which makes
+     *                        it rational.
+     */
+    public DefaultBSplineSurface(final DataPoints points, final 
List<DirectPosition> controlPoints,
+            final int rows, final int columns, final List<double[]> knots, 
final int degree,
+            final KnotType knotSpec, final BSplineSurfaceForm surfaceForm, 
final boolean polynomial)
+    {
+        this.points        = Objects.requireNonNull(points);
+        this.controlPoints = (controlPoints == null) ? List.of() : 
List.copyOf(controlPoints);
+        this.rows          = rows;
+        this.columns       = columns;
+        this.knots         = (knots == null) ? List.of() : List.copyOf(knots);
+        this.degree        = degree;
+        this.knotSpec      = knotSpec;
+        this.surfaceForm   = surfaceForm;
+        this.polynomial    = polynomial;
+    }
+
+    @Override
+    public int getDegree() {
+        return degree;
+    }
+
+    @Override
+    public List<double[]> getKnots() {
+        return knots;
+    }
+
+    @Override
+    public KnotType getKnotSpec() {
+        return knotSpec;
+    }
+
+    @Override
+    public BSplineSurfaceForm getSurfaceForm() {
+        return surfaceForm;
+    }
+
+    @Override
+    public boolean isPolynomial() {
+        return polynomial;
+    }
+
+    @Override
+    public int getRows() {
+        return rows;
+    }
+
+    @Override
+    public int getColumns() {
+        return columns;
+    }
+
+    @Override
+    public DataPoints getDataPoints() {
+        return points;
+    }
+
+    @Override
+    public List<DirectPosition> getControlPoints() {
+        return controlPoints;
+    }
+
+    @Override
+    public Curve getHorizontalCurve(double v) {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+    @Override
+    public Curve getVerticalCurve(double u) {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+    @Override
+    public DirectPosition getSurface(double u, double v) {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+    @Override
+    public Area getArea() {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+    @Override
+    public Envelope getEnvelope() {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+    @Override
+    public Identifier getName() {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+}
diff --git 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultBezier.java
 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultBezier.java
new file mode 100644
index 0000000000..9e88f0d055
--- /dev/null
+++ 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultBezier.java
@@ -0,0 +1,58 @@
+/*
+ * 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.internal.shared;
+
+import org.apache.sis.geometries.DataPoints;
+import org.apache.sis.geometries.curve.Bezier;
+import org.apache.sis.geometries.curve.KnotType;
+import org.apache.sis.geometries.curve.SplineCurveForm;
+import org.apache.sis.maths.Array;
+import org.apache.sis.maths.Vector;
+
+
+/**
+ * An approximating spline using the Bézier (Bernstein) polynomials as 
partition of unity,
+ * the curve passing only through its first and last control point.
+ *
+ * @author Johann Sorel (Geomatys)
+ */
+public non-sealed class DefaultBezier extends DefaultPolynomialSpline 
implements Bezier {
+
+    /**
+     * Creates a Bézier curve of the given degree.
+     *
+     * @param  points             end points of the segments of this curve.
+     * @param  controlPoints      control points of this curve. For a uniform 
knot sequence of
+     *                            degree <var>n</var> there are 
<var>s</var>⋅<var>n</var> + 1 of
+     *                            them, each subsequence of <var>n</var> + 1 
points starting at a
+     *                            multiple of <var>n</var> defining one 
segment.
+     * @param  knots              knot values. For a Bézier curve the only 
knots are 0 and 1.
+     * @param  degree             degree of the Bernstein polynomials.
+     * @param  curveForm          kind of curve approximated by this spline, 
or {@code null} if none.
+     * @param  knotSpec           distribution of the knots, or {@code null} 
if unspecified.
+     * @param  derivativeAtStart  derivative imposed at the start point, or 
{@code null} if none.
+     * @param  derivativeAtEnd    derivative imposed at the end point, or 
{@code null} if none.
+     */
+    public DefaultBezier(final DataPoints points, final Array controlPoints, 
final double[] knots,
+            final int degree, final SplineCurveForm curveForm, final KnotType 
knotSpec,
+            final Vector derivativeAtStart, final Vector derivativeAtEnd)
+    {
+        super(points, controlPoints, knots, degree, curveForm, knotSpec,
+              derivativeAtStart, derivativeAtEnd, Math.max(0, degree - 1));
+    }
+
+}
diff --git 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultBilinearGrid.java
 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultBilinearGrid.java
new file mode 100644
index 0000000000..41fe91c035
--- /dev/null
+++ 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultBilinearGrid.java
@@ -0,0 +1,179 @@
+/*
+ * 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.internal.shared;
+
+import java.util.List;
+import java.util.Objects;
+import javax.measure.quantity.Area;
+import org.apache.sis.geometries.AttributesType;
+import org.apache.sis.geometries.Curve;
+import org.apache.sis.geometries.DataPoints;
+import org.apache.sis.geometries.GeometryType;
+import org.apache.sis.geometries.surface.BilinearGrid;
+import org.opengis.geometry.DirectPosition;
+import org.opengis.geometry.Envelope;
+import org.opengis.metadata.Identifier;
+import org.opengis.referencing.crs.CoordinateReferenceSystem;
+
+
+/**
+ * A parametric curve surface using polylines as both horizontal and vertical 
curves, each cell of
+ * the parameter grid being interpolated bilinearly over the unit square.
+ *
+ * @author Johann Sorel (Geomatys)
+ */
+public non-sealed class DefaultBilinearGrid extends AbstractGeometry 
implements BilinearGrid {
+
+    /**
+     * Positions of this surface at the knots of the parameter grid, in 
row-major order.
+     */
+    protected final DataPoints points;
+
+    /**
+     * Control points of the section curves, in row-major order, or an empty 
list if none.
+     */
+    protected final List<DirectPosition> controlPoints;
+
+    /**
+     * Number of rows in the parameter grid.
+     */
+    protected final int rows;
+
+    /**
+     * Number of columns in the parameter grid.
+     */
+    protected final int columns;
+
+    /**
+     * Construction parameter values matching the data points, the first 
sequence being for the
+     * <var>u</var> parameter and the second one for <var>v</var>.
+     */
+    protected final List<double[]> knots;
+
+    /**
+     * Creates a bilinear grid over the given parameter grid.
+     *
+     * @param  points         positions at the knots of the parameter grid, in 
row-major order.
+     *                        There shall be {@code rows} × {@code columns} of 
them.
+     * @param  controlPoints  control points of the section curves in 
row-major order,
+     *                        or {@code null} if none.
+     * @param  rows           number of rows in the parameter grid.
+     * @param  columns        number of columns in the parameter grid.
+     * @param  knots          knot values, one sequence per surface parameter, 
or {@code null} if none.
+     */
+    public DefaultBilinearGrid(final DataPoints points, final 
List<DirectPosition> controlPoints,
+            final int rows, final int columns, final List<double[]> knots)
+    {
+        this.points        = Objects.requireNonNull(points);
+        this.controlPoints = (controlPoints == null) ? List.of() : 
List.copyOf(controlPoints);
+        this.rows          = rows;
+        this.columns       = columns;
+        this.knots         = (knots == null) ? List.of() : List.copyOf(knots);
+    }
+
+    @Override
+    public int getRows() {
+        return rows;
+    }
+
+    @Override
+    public int getColumns() {
+        return columns;
+    }
+
+    @Override
+    public DataPoints getDataPoints() {
+        return points;
+    }
+
+    @Override
+    public List<DirectPosition> getControlPoints() {
+        return controlPoints;
+    }
+
+    @Override
+    public List<double[]> getKnots() {
+        return knots;
+    }
+
+    /**
+     * Returns {@link GeometryType#LINE}: the horizontal sections of a 
bilinear grid are polylines.
+     */
+    @Override
+    public GeometryType getHorizontalCurveType() {
+        return GeometryType.LINE;
+    }
+
+    /**
+     * Returns {@link GeometryType#LINE}: the vertical sections of a bilinear 
grid are polylines.
+     */
+    @Override
+    public GeometryType getVerticalCurveType() {
+        return GeometryType.LINE;
+    }
+
+    @Override
+    public Curve getHorizontalCurve(double v) {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+    @Override
+    public Curve getVerticalCurve(double u) {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+    @Override
+    public DirectPosition getSurface(double u, double v) {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+    @Override
+    public CoordinateReferenceSystem getCoordinateReferenceSystem() {
+        return points.getCoordinateReferenceSystem();
+    }
+
+    @Override
+    public void setCoordinateReferenceSystem(CoordinateReferenceSystem crs) 
throws IllegalArgumentException {
+        points.setCoordinateReferenceSystem(crs);
+    }
+
+    @Override
+    public AttributesType getAttributesType() {
+        return points.getAttributesType();
+    }
+
+    @Override
+    public boolean isEmpty() {
+        return points.isEmpty();
+    }
+
+    @Override
+    public Area getArea() {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+    @Override
+    public Envelope getEnvelope() {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+    @Override
+    public Identifier getName() {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+}
diff --git 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultCircle.java
 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultCircle.java
new file mode 100644
index 0000000000..e55be9dccc
--- /dev/null
+++ 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultCircle.java
@@ -0,0 +1,54 @@
+/*
+ * 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.internal.shared;
+
+import java.util.List;
+import org.apache.sis.geometries.DataPoints;
+import org.apache.sis.geometries.curve.Circle;
+import org.apache.sis.maths.Array;
+import org.apache.sis.maths.Vector;
+
+
+/**
+ * A complete circle, i.e. a chain of circular arcs sharing a single centre 
and closing on itself.
+ *
+ * @author Johann Sorel (Geomatys)
+ */
+public non-sealed class DefaultCircle extends DefaultArc implements Circle {
+
+    /**
+     * Creates a complete circle.
+     *
+     * @param  points         points of the circle, all at the same distance 
from the centre,
+     *                        the first and the last ones being equal.
+     * @param  controlPoints  the centre of the circle, repeated once per arc. 
Because a single arc
+     *                        must stay below a full turn, at least two of 
them are needed.
+     * @param  radius         radius vectors of the arcs, possibly empty.
+     */
+    public DefaultCircle(final DataPoints points, final Array controlPoints, 
final List<Vector> radius) {
+        super(points, controlPoints, radius, true);
+    }
+
+    /**
+     * Returns {@code true}: a circle is a complete curve closing on itself.
+     */
+    @Override
+    public boolean isCycle() {
+        return true;
+    }
+
+}
diff --git 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultClothoid.java
 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultClothoid.java
new file mode 100644
index 0000000000..89e15ee4e9
--- /dev/null
+++ 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultClothoid.java
@@ -0,0 +1,55 @@
+/*
+ * 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.internal.shared;
+
+import java.util.List;
+import org.apache.sis.geometries.DataPoints;
+import org.apache.sis.geometries.curve.Clothoid;
+import org.apache.sis.geometries.curve.RealFunction;
+import org.apache.sis.maths.Vector;
+
+
+/**
+ * A spiral whose curvature varies linearly with arc length, also called a 
Cornu spiral.
+ *
+ * @author Johann Sorel (Geomatys)
+ */
+public non-sealed class DefaultClothoid extends DefaultSpiral implements 
Clothoid {
+
+    /**
+     * Creates a clothoid from its curvature and the frame at its start point.
+     *
+     * @param  points      points of the clothoid, the first one being its 
start point.
+     * @param  curvature   curvature as a function of arc length. It shall be 
linear in the arc
+     *                     length measured from the point where the infinite 
clothoid has zero
+     *                     curvature.
+     * @param  startFrame  two mutually orthogonal unit vectors forming a 
right-handed frame at the
+     *                     start point, a clothoid being planar.
+     */
+    public DefaultClothoid(final DataPoints points, final RealFunction 
curvature, final List<Vector> startFrame) {
+        super(points, curvature, null, startFrame);
+    }
+
+    /**
+     * Returns {@code null}: a clothoid is a planar spiral.
+     */
+    @Override
+    public RealFunction getTorsion() {
+        return null;
+    }
+
+}
diff --git 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultConic.java
 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultConic.java
new file mode 100644
index 0000000000..1050eca09e
--- /dev/null
+++ 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultConic.java
@@ -0,0 +1,107 @@
+/*
+ * 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.internal.shared;
+
+import java.util.Objects;
+import org.apache.sis.geometries.AttributesType;
+import org.apache.sis.geometries.DataPoints;
+import org.apache.sis.geometries.curve.Conic;
+import org.apache.sis.maths.Array;
+import org.opengis.geometry.Envelope;
+import org.opengis.referencing.crs.CoordinateReferenceSystem;
+
+
+/**
+ * A chain of conic section arcs, each of them determined by five data points 
and drawn in the
+ * tangent plane at one control point.
+ *
+ * @author Johann Sorel (Geomatys)
+ */
+public non-sealed class DefaultConic extends AbstractGeometry implements Conic 
{
+
+    /**
+     * Points lying on the conic, four of them being consumed by each arc plus 
the one shared with
+     * the previous arc.
+     */
+    protected final DataPoints points;
+
+    /**
+     * Centers of the exponential maps in which the arcs are constructed, one 
per arc.
+     * May be {@code null} if this conic declares no control point.
+     */
+    protected final Array controlPoints;
+
+    /**
+     * Whether this conic closes on itself.
+     */
+    protected final boolean cycle;
+
+    /**
+     * Creates a conic from the given data points and control points.
+     *
+     * @param  points         points lying on the conic. At least five of 
them, the first one also
+     *                        acting as the last one when {@code cycle} is 
{@code true}.
+     * @param  controlPoints  centers of the exponential maps of the arcs, or 
{@code null} if none.
+     * @param  cycle          whether this conic closes on itself.
+     */
+    public DefaultConic(final DataPoints points, final Array controlPoints, 
final boolean cycle) {
+        this.points        = Objects.requireNonNull(points);
+        this.controlPoints = controlPoints;
+        this.cycle         = cycle;
+    }
+
+    @Override
+    public DataPoints getDataPoints() {
+        return points;
+    }
+
+    @Override
+    public Array getControlPoints() {
+        return controlPoints;
+    }
+
+    @Override
+    public boolean isCycle() {
+        return cycle;
+    }
+
+    @Override
+    public CoordinateReferenceSystem getCoordinateReferenceSystem() {
+        return points.getCoordinateReferenceSystem();
+    }
+
+    @Override
+    public void setCoordinateReferenceSystem(CoordinateReferenceSystem crs) 
throws IllegalArgumentException {
+        points.setCoordinateReferenceSystem(crs);
+    }
+
+    @Override
+    public AttributesType getAttributesType() {
+        return points.getAttributesType();
+    }
+
+    @Override
+    public boolean isEmpty() {
+        return points.isEmpty();
+    }
+
+    @Override
+    public Envelope getEnvelope() {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+}
diff --git 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultCubicSpline.java
 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultCubicSpline.java
new file mode 100644
index 0000000000..56823c435d
--- /dev/null
+++ 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultCubicSpline.java
@@ -0,0 +1,67 @@
+/*
+ * 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.internal.shared;
+
+import org.apache.sis.geometries.DataPoints;
+import org.apache.sis.geometries.curve.CubicSpline;
+import org.apache.sis.geometries.curve.KnotType;
+import org.apache.sis.geometries.curve.SplineCurveForm;
+import org.apache.sis.maths.Array;
+import org.apache.sis.maths.Vector;
+
+
+/**
+ * A polynomial spline of degree 3, C² everywhere and passing through its data 
points.
+ *
+ * @author Johann Sorel (Geomatys)
+ */
+public non-sealed class DefaultCubicSpline extends DefaultPolynomialSpline 
implements CubicSpline {
+
+    /**
+     * Number of continuous derivatives at the interior knots of a cubic 
spline,
+     * which is its degree minus one.
+     */
+    private static final int DERIVATIVE_INTERIOR = 2;
+
+    /**
+     * Creates a cubic spline interpolating the given data points.
+     *
+     * @param  points             points this spline passes through, in order.
+     * @param  controlPoints      control points of this spline, or {@code 
null} if none.
+     * @param  knots              knot values, strictly increasing, one per 
data point.
+     * @param  curveForm          kind of curve approximated by this spline, 
or {@code null} if none.
+     * @param  knotSpec           distribution of the knots, or {@code null} 
if unspecified.
+     * @param  derivativeAtStart  tangent imposed at the start point, or 
{@code null} if none.
+     * @param  derivativeAtEnd    tangent imposed at the end point, or {@code 
null} if none.
+     */
+    public DefaultCubicSpline(final DataPoints points, final Array 
controlPoints, final double[] knots,
+            final SplineCurveForm curveForm, final KnotType knotSpec,
+            final Vector derivativeAtStart, final Vector derivativeAtEnd)
+    {
+        super(points, controlPoints, knots, 3, curveForm, knotSpec,
+              derivativeAtStart, derivativeAtEnd, DERIVATIVE_INTERIOR);
+    }
+
+    /**
+     * Returns 3: a cubic spline is defined by polynomials of degree 3.
+     */
+    @Override
+    public int getDegree() {
+        return 3;
+    }
+
+}
diff --git 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultEllipticArc.java
 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultEllipticArc.java
new file mode 100644
index 0000000000..1082193172
--- /dev/null
+++ 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultEllipticArc.java
@@ -0,0 +1,43 @@
+/*
+ * 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.internal.shared;
+
+import org.apache.sis.geometries.DataPoints;
+import org.apache.sis.geometries.curve.EllipticArc;
+import org.apache.sis.maths.Array;
+
+
+/**
+ * A conic without a cross term, therefore an arc of ellipse, each arc being 
determined by four data
+ * points instead of the five needed by a general conic.
+ *
+ * @author Johann Sorel (Geomatys)
+ */
+public non-sealed class DefaultEllipticArc extends DefaultConic implements 
EllipticArc {
+
+    /**
+     * Creates an elliptic arc from the given data points and control points.
+     *
+     * @param  points         points lying on the ellipse. Four of them 
determine each arc.
+     * @param  controlPoints  centers of the ellipses of the arcs, or {@code 
null} if none.
+     * @param  cycle          whether this arc closes on itself, making it a 
complete ellipse.
+     */
+    public DefaultEllipticArc(final DataPoints points, final Array 
controlPoints, final boolean cycle) {
+        super(points, controlPoints, cycle);
+    }
+
+}
diff --git 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultOffsetCurve.java
 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultOffsetCurve.java
new file mode 100644
index 0000000000..b89bf050c6
--- /dev/null
+++ 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultOffsetCurve.java
@@ -0,0 +1,122 @@
+/*
+ * 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.internal.shared;
+
+import java.util.Objects;
+import javax.measure.quantity.Length;
+import org.apache.sis.geometries.AttributesType;
+import org.apache.sis.geometries.Bearing;
+import org.apache.sis.geometries.Curve;
+import org.apache.sis.geometries.DataPoints;
+import org.apache.sis.geometries.curve.OffsetCurve;
+import org.apache.sis.maths.Array;
+import org.opengis.geometry.Envelope;
+import org.opengis.referencing.crs.CoordinateReferenceSystem;
+
+
+/**
+ * A curve at a constant distance and bearing from a base curve. All the 
non-positional attributes,
+ * such as the coordinate reference system, are those of the base curve.
+ *
+ * @author Johann Sorel (Geomatys)
+ */
+public non-sealed class DefaultOffsetCurve extends AbstractGeometry implements 
OffsetCurve {
+
+    /**
+     * Curve from which this curve is defined as an offset.
+     */
+    protected final Curve baseCurve;
+
+    /**
+     * Distance at which this curve is generated from the base curve.
+     */
+    protected final Length distance;
+
+    /**
+     * Direction in which this curve is offset, or {@code null} if the spatial 
dimension is 2.
+     */
+    protected final Bearing refDirection;
+
+    /**
+     * Creates a curve offset from the given base curve.
+     *
+     * @param  baseCurve     curve from which this curve is offset. It shall 
have a well-defined
+     *                       tangent at every position.
+     * @param  distance      constant offset distance. In a 2-dimensional 
coordinate system, a
+     *                       positive distance designates the left side of the 
base curve with
+     *                       respect to the tangent, and a negative distance 
its right side.
+     * @param  refDirection  reference direction of the offset, whose cross 
product with the tangent
+     *                       of the base curve gives the offset direction in a 
3-dimensional
+     *                       coordinate system, or {@code null} if the spatial 
dimension is 2.
+     */
+    public DefaultOffsetCurve(final Curve baseCurve, final Length distance, 
final Bearing refDirection) {
+        this.baseCurve    = Objects.requireNonNull(baseCurve);
+        this.distance     = Objects.requireNonNull(distance);
+        this.refDirection = refDirection;
+    }
+
+    @Override
+    public Curve getBaseCurve() {
+        return baseCurve;
+    }
+
+    @Override
+    public Length getDistance() {
+        return distance;
+    }
+
+    @Override
+    public Bearing getRefDirection() {
+        return refDirection;
+    }
+
+    @Override
+    public DataPoints getDataPoints() {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+    @Override
+    public Array getControlPoints() {
+        return baseCurve.getControlPoints();
+    }
+
+    @Override
+    public CoordinateReferenceSystem getCoordinateReferenceSystem() {
+        return baseCurve.getCoordinateReferenceSystem();
+    }
+
+    @Override
+    public void setCoordinateReferenceSystem(CoordinateReferenceSystem crs) 
throws IllegalArgumentException {
+        baseCurve.setCoordinateReferenceSystem(crs);
+    }
+
+    @Override
+    public AttributesType getAttributesType() {
+        return baseCurve.getAttributesType();
+    }
+
+    @Override
+    public boolean isEmpty() {
+        return baseCurve.isEmpty();
+    }
+
+    @Override
+    public Envelope getEnvelope() {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+}
diff --git 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultPolynomialSpline.java
 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultPolynomialSpline.java
new file mode 100644
index 0000000000..71719bbe04
--- /dev/null
+++ 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultPolynomialSpline.java
@@ -0,0 +1,198 @@
+/*
+ * 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.internal.shared;
+
+import java.util.Objects;
+import org.apache.sis.geometries.AttributesType;
+import org.apache.sis.geometries.DataPoints;
+import org.apache.sis.geometries.curve.FunctionArc;
+import org.apache.sis.geometries.curve.KnotType;
+import org.apache.sis.geometries.curve.PolynomialSpline;
+import org.apache.sis.geometries.curve.SplineCurveForm;
+import org.apache.sis.maths.Array;
+import org.apache.sis.maths.Vector;
+import org.opengis.geometry.Envelope;
+import org.opengis.referencing.crs.CoordinateReferenceSystem;
+
+
+/**
+ * A spline which interpolates its data points, i.e. a polynomial curve 
passing through them,
+ * defined piecewise between knot values by a polynomial per coordinate offset.
+ *
+ * @author Johann Sorel (Geomatys)
+ */
+public non-sealed class DefaultPolynomialSpline extends AbstractGeometry 
implements PolynomialSpline {
+
+    /**
+     * Points this spline passes through.
+     */
+    protected final DataPoints points;
+
+    /**
+     * Control points of this spline, or {@code null} if none.
+     */
+    protected final Array controlPoints;
+
+    /**
+     * Knot values defining the parameter space, strictly increasing, one per 
data point.
+     */
+    protected final double[] knots;
+
+    /**
+     * Degree of the polynomials defining the interpolation.
+     */
+    protected final int degree;
+
+    /**
+     * Kind of curve which this spline approximates, or {@code null} if none.
+     */
+    protected final SplineCurveForm curveForm;
+
+    /**
+     * Distribution of the knots, given for information only.
+     */
+    protected final KnotType knotSpec;
+
+    /**
+     * Derivative imposed at the start point.
+     */
+    protected final Vector derivativeAtStart;
+
+    /**
+     * Derivative imposed at the end point.
+     */
+    protected final Vector derivativeAtEnd;
+
+    /**
+     * Number of continuous derivatives guaranteed at the interior knots.
+     */
+    protected final int derivativeInterior;
+
+    /**
+     * Creates a polynomial spline interpolating the given data points.
+     *
+     * @param  points              points this spline passes through, in order.
+     * @param  controlPoints       control points of this spline, or {@code 
null} if none.
+     * @param  knots               knot values, strictly increasing, one per 
data point.
+     * @param  degree              degree of the interpolating polynomials.
+     * @param  curveForm           kind of curve approximated by this spline, 
or {@code null} if none.
+     * @param  knotSpec            distribution of the knots, or {@code null} 
if unspecified.
+     * @param  derivativeAtStart   derivative imposed at the start point, or 
{@code null} if none.
+     * @param  derivativeAtEnd     derivative imposed at the end point, or 
{@code null} if none.
+     * @param  derivativeInterior  number of continuous derivatives at the 
interior knots,
+     *                             at most {@code degree} − 1.
+     */
+    public DefaultPolynomialSpline(final DataPoints points, final Array 
controlPoints,
+            final double[] knots, final int degree, final SplineCurveForm 
curveForm,
+            final KnotType knotSpec, final Vector derivativeAtStart, final 
Vector derivativeAtEnd,
+            final int derivativeInterior)
+    {
+        this.points             = Objects.requireNonNull(points);
+        this.controlPoints      = controlPoints;
+        this.knots              = (knots == null) ? new double[0] : 
knots.clone();
+        this.degree             = degree;
+        this.curveForm          = curveForm;
+        this.knotSpec           = knotSpec;
+        this.derivativeAtStart  = derivativeAtStart;
+        this.derivativeAtEnd    = derivativeAtEnd;
+        this.derivativeInterior = derivativeInterior;
+    }
+
+    @Override
+    public Vector getDerivativeAtStart() {
+        return derivativeAtStart;
+    }
+
+    @Override
+    public Vector getDerivativeAtEnd() {
+        return derivativeAtEnd;
+    }
+
+    @Override
+    public int getDerivativeInterior() {
+        return derivativeInterior;
+    }
+
+    @Override
+    public SplineCurveForm getCurveForm() {
+        return curveForm;
+    }
+
+    @Override
+    public KnotType getKnotSpec() {
+        return knotSpec;
+    }
+
+    @Override
+    public int getDegree() {
+        return degree;
+    }
+
+    @Override
+    public double[] getKnots() {
+        return knots.clone();
+    }
+
+    /**
+     * Returns the number of intervals in the knot array, which is the number 
of arcs of this spline.
+     */
+    @Override
+    public Integer getNumArc() {
+        return Math.max(0, knots.length - 1);
+    }
+
+    @Override
+    public FunctionArc getSegment(int idx) {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+    @Override
+    public DataPoints getDataPoints() {
+        return points;
+    }
+
+    @Override
+    public Array getControlPoints() {
+        return controlPoints;
+    }
+
+    @Override
+    public CoordinateReferenceSystem getCoordinateReferenceSystem() {
+        return points.getCoordinateReferenceSystem();
+    }
+
+    @Override
+    public void setCoordinateReferenceSystem(CoordinateReferenceSystem crs) 
throws IllegalArgumentException {
+        points.setCoordinateReferenceSystem(crs);
+    }
+
+    @Override
+    public AttributesType getAttributesType() {
+        return points.getAttributesType();
+    }
+
+    @Override
+    public boolean isEmpty() {
+        return points.isEmpty();
+    }
+
+    @Override
+    public Envelope getEnvelope() {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+}
diff --git 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultPrism.java
 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultPrism.java
new file mode 100644
index 0000000000..01f768a578
--- /dev/null
+++ 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultPrism.java
@@ -0,0 +1,98 @@
+/*
+ * 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.internal.shared;
+
+import java.util.Objects;
+import org.apache.sis.geometries.Geometry;
+import org.apache.sis.geometries.Prism;
+import org.apache.sis.measure.NumberRange;
+import org.opengis.geometry.Envelope;
+import org.opengis.referencing.crs.CoordinateReferenceSystem;
+import org.opengis.referencing.crs.SingleCRS;
+
+
+/**
+ * A base shape extruded from an optional lower limit to an upper limit.
+ *
+ * @author Johann Sorel (Geomatys)
+ */
+public non-sealed class DefaultPrism extends AbstractGeometry implements Prism 
{
+
+    /**
+     * Base shape which is extruded.
+     */
+    protected final Geometry base;
+
+    /**
+     * Lower and upper limits of the extrusion.
+     */
+    protected final NumberRange<?> extrusionRange;
+
+    /**
+     * Coordinate reference system in which the extrusion range is expressed.
+     */
+    protected final SingleCRS extrusionCrs;
+
+    /**
+     * Creates a prism by extruding the given base shape.
+     *
+     * @param  base            base shape of the prism, for example a polygon 
or a circle.
+     * @param  extrusionRange  lower and upper limits of the extrusion.
+     * @param  extrusionCrs    coordinate reference system of the extrusion 
range.
+     */
+    public DefaultPrism(final Geometry base, final NumberRange<?> 
extrusionRange, final SingleCRS extrusionCrs) {
+        this.base           = Objects.requireNonNull(base);
+        this.extrusionRange = Objects.requireNonNull(extrusionRange);
+        this.extrusionCrs   = extrusionCrs;
+    }
+
+    @Override
+    public Geometry getBase() {
+        return base;
+    }
+
+    @Override
+    public NumberRange<?> getExtrusionRange() {
+        return extrusionRange;
+    }
+
+    @Override
+    public SingleCRS getExtrusionCrs() {
+        return extrusionCrs;
+    }
+
+    @Override
+    public CoordinateReferenceSystem getCoordinateReferenceSystem() {
+        return base.getCoordinateReferenceSystem();
+    }
+
+    @Override
+    public void setCoordinateReferenceSystem(CoordinateReferenceSystem crs) 
throws IllegalArgumentException {
+        base.setCoordinateReferenceSystem(crs);
+    }
+
+    @Override
+    public boolean isEmpty() {
+        return base.isEmpty();
+    }
+
+    @Override
+    public Envelope getEnvelope() {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+}
diff --git 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultProductCurve.java
 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultProductCurve.java
new file mode 100644
index 0000000000..6be8db6890
--- /dev/null
+++ 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultProductCurve.java
@@ -0,0 +1,84 @@
+/*
+ * 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.internal.shared;
+
+import java.util.List;
+import org.apache.sis.geometries.Curve;
+import org.apache.sis.geometries.DataPoints;
+import org.apache.sis.geometries.cs.Projection;
+import org.apache.sis.geometries.curve.ProductCurve;
+import org.apache.sis.maths.Array;
+import org.apache.sis.measure.Range;
+
+
+/**
+ * A curve composed of other curves which all share the same parameter space, 
each of them covering
+ * a disjoint projection of the coordinate system. It is therefore both a set 
of curves in the
+ * various projections of the coordinate reference system, and a single curve 
in the complete system.
+ *
+ * @author Johann Sorel (Geomatys)
+ */
+public non-sealed class DefaultProductCurve extends 
DefaultGeometryCollection<Curve> implements ProductCurve {
+
+    /**
+     * Interval of the construction parameter shared by this curve and its 
projections.
+     */
+    protected final Range<?> parameterRange;
+
+    /**
+     * Projections of the coordinate system, in the same order as the element 
curves.
+     */
+    protected final List<Projection> projections;
+
+    /**
+     * Creates a product curve from the given element curves.
+     *
+     * @param  parameterRange  interval of the construction parameter shared 
by all the elements.
+     * @param  projections     projections of the coordinate system matching 
the element curves.
+     *                         The projections are disjoint and together cover 
the whole system.
+     * @param  elements        projections of this curve, one per projection 
of the coordinate system.
+     *                         They all share the knot array and the 
construction parameters of this curve.
+     */
+    public DefaultProductCurve(final Range<?> parameterRange, final 
List<Projection> projections,
+            final Curve... elements)
+    {
+        super(elements);
+        this.parameterRange = parameterRange;
+        this.projections    = (projections == null) ? List.of() : 
List.copyOf(projections);
+    }
+
+    @Override
+    public Range<?> getParameterRange() {
+        return parameterRange;
+    }
+
+    @Override
+    public List<Projection> getProjection() {
+        return projections;
+    }
+
+    @Override
+    public DataPoints getDataPoints() {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+    @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/DefaultSpiral.java
 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultSpiral.java
new file mode 100644
index 0000000000..4f8b8d8881
--- /dev/null
+++ 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultSpiral.java
@@ -0,0 +1,134 @@
+/*
+ * 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.internal.shared;
+
+import java.util.List;
+import java.util.Objects;
+import org.apache.sis.geometries.AttributesType;
+import org.apache.sis.geometries.DataPoints;
+import org.apache.sis.geometries.curve.RealFunction;
+import org.apache.sis.geometries.curve.Spiral;
+import org.apache.sis.maths.Array;
+import org.apache.sis.maths.Vector;
+import org.opengis.geometry.Envelope;
+import org.opengis.referencing.crs.CoordinateReferenceSystem;
+
+
+/**
+ * A curve defined indirectly by its curvature, and by its torsion when it is 
not planar.
+ * The spiral is built in the tangent space at its start point, then projected 
on the geometric
+ * reference surface.
+ *
+ * @author Johann Sorel (Geomatys)
+ */
+public non-sealed class DefaultSpiral extends AbstractGeometry implements 
Spiral {
+
+    /**
+     * Points of the spiral, the first one being the start point in whose 
tangent space the spiral
+     * is constructed.
+     */
+    protected final DataPoints points;
+
+    /**
+     * Curvature of this spiral as a function of arc length.
+     */
+    protected final RealFunction curvature;
+
+    /**
+     * Torsion of this spiral as a function of arc length, or {@code null} if 
this spiral is planar.
+     */
+    protected final RealFunction torsion;
+
+    /**
+     * Orthonormal frame located at the start point, made of two vectors if 
this spiral is planar
+     * and of three vectors otherwise.
+     */
+    protected final List<Vector> startFrame;
+
+    /**
+     * Creates a spiral from its curvature, its torsion and the frame at its 
start point.
+     *
+     * @param  points      points of the spiral, the first one being its start 
point.
+     * @param  curvature   curvature as a function of arc length, measured 
from a fixed point of the
+     *                     infinite spiral which is not necessarily on this 
curve.
+     * @param  torsion     torsion as a function of arc length, or {@code 
null} if this spiral is planar.
+     * @param  startFrame  two or three mutually orthogonal unit vectors 
forming a right-handed frame
+     *                     at the start point.
+     */
+    public DefaultSpiral(final DataPoints points, final RealFunction curvature,
+            final RealFunction torsion, final List<Vector> startFrame)
+    {
+        this.points     = Objects.requireNonNull(points);
+        this.curvature  = Objects.requireNonNull(curvature);
+        this.torsion    = torsion;
+        this.startFrame = (startFrame == null) ? List.of() : 
List.copyOf(startFrame);
+    }
+
+    @Override
+    public RealFunction getCurvature() {
+        return curvature;
+    }
+
+    @Override
+    public RealFunction getTorsion() {
+        return torsion;
+    }
+
+    @Override
+    public List<Vector> getStartFrame() {
+        return startFrame;
+    }
+
+    @Override
+    public DataPoints getDataPoints() {
+        return points;
+    }
+
+    /**
+     * Returns {@code null}: a spiral is defined by its curvature, not by 
control points.
+     */
+    @Override
+    public Array getControlPoints() {
+        return null;
+    }
+
+    @Override
+    public CoordinateReferenceSystem getCoordinateReferenceSystem() {
+        return points.getCoordinateReferenceSystem();
+    }
+
+    @Override
+    public void setCoordinateReferenceSystem(CoordinateReferenceSystem crs) 
throws IllegalArgumentException {
+        points.setCoordinateReferenceSystem(crs);
+    }
+
+    @Override
+    public AttributesType getAttributesType() {
+        return points.getAttributesType();
+    }
+
+    @Override
+    public boolean isEmpty() {
+        return points.isEmpty();
+    }
+
+    @Override
+    public Envelope getEnvelope() {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+}
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/BSplineSolid.java
similarity index 84%
rename from 
incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/solid/BSolidSpline.java
rename to 
incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/solid/BSplineSolid.java
index bf3a72c6af..5417895337 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/BSplineSolid.java
@@ -17,6 +17,7 @@
 package org.apache.sis.geometries.solid;
 
 import org.apache.sis.geometries.SolidInterpolation;
+import org.apache.sis.geometries.internal.shared.DefaultBSplineSolid;
 import static org.opengis.annotation.Specification.ISO_19107;
 import org.opengis.annotation.UML;
 
@@ -38,7 +39,18 @@ import org.opengis.annotation.UML;
  * @see ISO 19107:2019 - 9.3.2
  */
 @UML(identifier="BSolidSpline", specification=ISO_19107)
-public non-sealed interface BSolidSpline extends ParametricCurveSolid {
+public sealed interface BSplineSolid extends ParametricCurveSolid
+        permits DefaultBSplineSolid {
+
+    /**
+     * Well-known text keyword of this geometry type.
+     */
+    static final String TYPE = "BSOLIDSPLINE";
+
+    @Override
+    default String getGeometryType() {
+        return TYPE;
+    }
 
     /**
      * Returns {@link SolidInterpolation#BSPLINE}: the three families of 
curves defining the
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 2feff2cc92..ebeec96f2d 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
@@ -46,7 +46,7 @@ import org.opengis.geometry.DirectPosition;
  */
 @UML(identifier="ParametricCurveSolid", specification=ISO_19107)
 public sealed interface ParametricCurveSolid extends Solid
-        permits BSolidSpline
+        permits BSplineSolid
 {
 
     /**
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 8dbbe73c00..cf22e0fc66 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
@@ -21,6 +21,7 @@ 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 org.apache.sis.geometries.internal.shared.DefaultBSplineSurface;
 import static org.opengis.annotation.Specification.ISO_19107;
 import org.opengis.annotation.UML;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
@@ -47,9 +48,20 @@ import org.opengis.referencing.crs.CoordinateReferenceSystem;
  */
 @UML(identifier="BSplineSurface", specification=ISO_19107)
 public sealed interface BSplineSurface extends ParametricCurveSurface
-        permits NurbSurface
+        permits NurbSurface,
+                DefaultBSplineSurface
 {
 
+    /**
+     * Well-known text keyword of this geometry type.
+     */
+    static final String TYPE = "BSPLINESURFACE";
+
+    @Override
+    default String getGeometryType() {
+        return TYPE;
+    }
+
     /**
      * Algebraic degree of the b-spline basis functions of this surface.
      *
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 fda24ad98b..1b155b057e 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
@@ -18,6 +18,7 @@ package org.apache.sis.geometries.surface;
 
 import java.util.List;
 import org.apache.sis.geometries.SurfaceInterpolation;
+import org.apache.sis.geometries.internal.shared.DefaultBilinearGrid;
 import static org.opengis.annotation.Specification.ISO_19107;
 import org.opengis.annotation.UML;
 
@@ -49,7 +50,18 @@ import org.opengis.annotation.UML;
  * @see ISO 19107:2019 - 8.3.4
  */
 @UML(identifier="BilinearGrid", specification=ISO_19107)
-public non-sealed interface BilinearGrid extends ParametricCurveSurface {
+public sealed interface BilinearGrid extends ParametricCurveSurface
+        permits DefaultBilinearGrid {
+
+    /**
+     * Well-known text keyword of this geometry type.
+     */
+    static final String TYPE = "BILINEARGRID";
+
+    @Override
+    default String getGeometryType() {
+        return TYPE;
+    }
 
     /**
      * Returns {@link SurfaceInterpolation#LINEAR}: each cell of the grid is 
interpolated

Reply via email to