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

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

commit 6fee2463db58c56acf6c3bad522e244987da7d3f
Author: Martin Desruisseaux <[email protected]>
AuthorDate: Thu Jan 16 18:58:03 2020 +0100

    Review of the methods added in Geometries internal class:
    - Be more specific about whether we unwrap GeometryWrapper or not.
    - Keep classical loops in this case. Actually since those loops are very 
small,
      applying lambda in Geometries require about twice more lines of code than 
loops.
    - Revert to package-private access some methods that, when public, 
encouraged the
      writing of code specific to a single geometry library.
    - Change some implementation to have a consistent behavior about whether 
they
      return null or throw an exception.
---
 .../java/org/apache/sis/internal/feature/ESRI.java |  85 +++---
 .../apache/sis/internal/feature/Geometries.java    | 338 ++++++++++++---------
 .../sis/internal/feature/GeometryWrapper.java      |   5 +-
 .../java/org/apache/sis/internal/feature/JTS.java  | 130 ++++----
 .../org/apache/sis/internal/feature/Java2D.java    | 203 +++++--------
 .../apache/sis/internal/feature/MovingFeature.java |   2 +-
 .../sis/internal/feature/j2d/ShapeProperties.java  |  24 +-
 .../org/apache/sis/internal/feature/jts/JTS.java   |  19 +-
 .../apache/sis/internal/feature/package-info.java  |   1 +
 .../sis/internal/filter/FilterGeometryUtils.java   |  11 +-
 .../filter/sqlmm/AbstractSpatialFunction.java      |   9 +
 .../sis/internal/filter/sqlmm/ST_Envelope.java     |   7 +-
 .../org/apache/sis/internal/feature/ESRITest.java  |   2 +-
 .../sis/internal/feature/GeometriesTestCase.java   |  18 +-
 .../org/apache/sis/internal/feature/JTSTest.java   |   2 +-
 .../apache/sis/internal/feature/Java2DTest.java    |   2 +-
 .../sis/internal/netcdf/impl/FeaturesInfo.java     |   2 +-
 .../sis/internal/sql/feature/ANSIInterpreter.java  |  32 +-
 .../sis/internal/sql/feature/EWKBReader.java       |  31 +-
 .../apache/sis/internal/storage/gpx/Writer.java    |   2 +-
 20 files changed, 491 insertions(+), 434 deletions(-)

diff --git 
a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/ESRI.java 
b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/ESRI.java
index 47486eb..7a38721 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/ESRI.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/ESRI.java
@@ -18,7 +18,6 @@ package org.apache.sis.internal.feature;
 
 import java.nio.ByteBuffer;
 import java.util.Iterator;
-import java.util.stream.Stream;
 
 import com.esri.core.geometry.Geometry;
 import com.esri.core.geometry.Envelope2D;
@@ -41,8 +40,6 @@ import org.apache.sis.util.Classes;
 
 import com.esri.core.geometry.*;
 
-import static org.apache.sis.util.ArgumentChecks.ensureNonNull;
-
 
 /**
  * Centralizes some usages of ESRI geometry API by Apache SIS.
@@ -50,7 +47,7 @@ import static 
org.apache.sis.util.ArgumentChecks.ensureNonNull;
  *
  * @author  Johann Sorel (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.0
+ * @version 1.1
  * @since   0.7
  * @module
  */
@@ -94,11 +91,11 @@ final class ESRI extends Geometries<Geometry> {
     }
 
     /**
-     * If the given point is an implementation of this library, returns its 
coordinate.
+     * If the given point is an implementation of this library, returns its 
coordinates.
      * Otherwise returns {@code null}. If non-null, the returned array may 
have a length of 2 or 3.
      */
     @Override
-    final double[] tryGetCoordinate(final Object point) {
+    final double[] tryGetPointCoordinates(final Object point) {
         if (point instanceof Point) {
             final Point pt = (Point) point;
             final double z = pt.getZ();
@@ -123,6 +120,25 @@ final class ESRI extends Geometries<Geometry> {
     }
 
     /**
+     * If the given geometry is an implementation of this library, returns all 
its coordinate tuples.
+     * Otherwise returns {@code null}.
+     */
+    @Override
+    final double[] tryGetAllCoordinates(final Object geometry) {
+        if (geometry instanceof MultiVertexGeometry) {
+            final Point2D[] points = ((MultiVertexGeometry) 
geometry).getCoordinates2D();
+            final double[] coordinates = new double[points.length * 2];
+            int i = 0;
+            for (final Point2D p : points) {
+                coordinates[i++] = p.x;
+                coordinates[i++] = p.y;
+            }
+            return coordinates;
+        }
+        return null;
+    }
+
+    /**
      * If the given object is an ESRI geometry, returns its centroid. 
Otherwise returns {@code null}.
      */
     @Override
@@ -150,7 +166,7 @@ final class ESRI extends Geometries<Geometry> {
      * @throws UnsupportedOperationException if this operation is not 
implemented for the given number of dimensions.
      */
     @Override
-    public Geometry createPolyline(final int dimension, final Vector... 
coordinates) {
+    public Geometry createPolyline(final boolean polygon, final int dimension, 
final Vector... coordinates) {
         if (dimension != 2) {
             throw new UnsupportedOperationException(unsupported(dimension));
         }
@@ -173,13 +189,28 @@ final class ESRI extends Geometries<Geometry> {
                 }
             }
         }
+        if (polygon) {
+            final Polygon p = new Polygon();
+            p.add(path, false);
+            return p;
+        }
         return path;
     }
 
+    /**
+     * Creates a multi-polygon from an array of geometries.
+     * Callers must ensure that the given objects are ESRI geometries.
+     *
+     * @param  geometries  the polygons or linear rings to put in a 
multi-polygons.
+     * @throws ClassCastException if an element in the array is not an ESRI 
geometry.
+     */
     @Override
-    public Geometry toPolygon(Geometry polyline) throws 
IllegalArgumentException {
-        if (polyline instanceof Polygon) return polyline;
-        return createMultiPolygon(Stream.of(polyline));
+    public Polygon createMultiPolygon(final Object[] geometries) {
+        final Polygon polygon = new Polygon();
+        for (final Object geometry : geometries) {
+            polygon.add((MultiPath) unwrap(geometry), false);
+        }
+        return polygon;
     }
 
     /**
@@ -188,7 +219,7 @@ final class ESRI extends Geometries<Geometry> {
      * @throws ClassCastException if an element in the iterator is not an ESRI 
geometry.
      */
     @Override
-    public final Geometry tryMergePolylines(Object next, final Iterator<?> 
polylines) {
+    final Geometry tryMergePolylines(Object next, final Iterator<?> polylines) 
{
         if (!(next instanceof MultiPath || next instanceof Point)) {
             return null;
         }
@@ -223,38 +254,6 @@ add:    for (;;) {
         return path;
     }
 
-    @Override
-    public double[] getPoints(Object geometry) {
-        if (geometry instanceof GeometryWrapper) geometry = ((GeometryWrapper) 
geometry).geometry;
-        ensureNonNull("Geometry", geometry);
-        if (geometry instanceof MultiVertexGeometry) {
-            MultiVertexGeometry vertices = (MultiVertexGeometry) geometry;
-            final Point2D[] coords = vertices.getCoordinates2D();
-            final double[] ordinates = new double[coords.length*2];
-            int idx = 0;
-            for (Point2D coord : coords) {
-                ordinates[idx++] = coord.x;
-                ordinates[idx++] = coord.y;
-            }
-            return ordinates;
-        }
-        throw new UnsupportedOperationException("Unsupported geometry type: 
"+geometry.getClass().getCanonicalName());
-    }
-
-    @Override
-    public Polygon createMultiPolygon(Stream<?> polygonsOrLinearRings) {
-        return polygonsOrLinearRings.map(ESRI::toMultiPath).reduce(
-                new Polygon(),
-                (p, m) -> {p.add(m, false); return p;},
-                (p1, p2) -> {p1.add(p2, false); return p1;}
-        );
-    }
-
-    private static MultiPath toMultiPath(Object polr) {
-        if (polr instanceof MultiPath) return (MultiPath) polr;
-        else throw new UnsupportedOperationException("Unsupported geometry 
type: "+polr == null ? "null" : polr.getClass().getCanonicalName());
-    }
-
     /**
      * Parses the given WKT.
      */
diff --git 
a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/Geometries.java
 
b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/Geometries.java
index 2ec6c21..24545f7 100644
--- 
a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/Geometries.java
+++ 
b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/Geometries.java
@@ -17,14 +17,10 @@
 package org.apache.sis.internal.feature;
 
 import java.util.Iterator;
-import java.util.Optional;
-import java.util.function.Function;
 import java.util.logging.Level;
 import java.util.logging.LogRecord;
-import java.util.stream.Stream;
 import org.opengis.geometry.DirectPosition;
 import org.opengis.geometry.Envelope;
-import org.opengis.geometry.Geometry;
 import org.opengis.util.FactoryException;
 import org.opengis.referencing.operation.TransformException;
 import org.opengis.referencing.operation.CoordinateOperation;
@@ -37,7 +33,6 @@ import org.apache.sis.internal.system.Loggers;
 import org.apache.sis.math.Vector;
 import org.apache.sis.referencing.CRS;
 import org.apache.sis.setup.GeometryLibrary;
-import org.apache.sis.util.collection.BackingStoreException;
 import org.apache.sis.util.logging.Logging;
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.util.UnsupportedImplementationException;
@@ -51,11 +46,21 @@ import org.apache.sis.util.Classes;
  * This gives us a single place to review if we want to support different 
geometry libraries,
  * or if Apache SIS come with its own implementation.
  *
+ * <h2>Object types</h2>
+ * Unless other specified in documentation, arguments of type {@link Object} 
in this class shall
+ * be instances of a geometry library (JTS, ESRI, …) or {@link 
GeometryWrapper}. If an object is
+ * an instance of {@link GeometryWrapper} and the Javadoc does not said 
otherwise, the geometry
+ * will be automatically unwrapped before processing and re-wrapped after the 
processing is done.
+ * The reason for this design choice is to ensure consistency between 
arguments type and return type:
+ * all methods having a geometry as input and output should return a geometry 
of the same library than
+ * the argument (considering {@link GeometryWrapper} as a kind of library).
+ *
  * @param   <G>  the base class of all geometry objects (except point in some 
implementations).
  *
  * @author  Johann Sorel (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.0
+ * @author  Alexis Manin (Geomatys)
+ * @version 1.1
  * @since   0.7
  * @module
  */
@@ -104,7 +109,7 @@ public abstract class Geometries<G> {
      * Creates a new adapter for the given root geometry class.
      */
     Geometries(final GeometryLibrary library, final Class<G> rootClass, final 
Class<?> pointClass,
-            final Class<? extends G> polylineClass, final Class<? extends G> 
polygonClass)
+               final Class<? extends G> polylineClass, final Class<? extends 
G> polygonClass)
     {
         this.library       = library;
         this.rootClass     = rootClass;
@@ -177,24 +182,32 @@ public abstract class Geometries<G> {
     }
 
     /**
-     * Transforms an envelope to a polygon whose start point is lower corner, 
and points composing result are the
-     * envelope corners in clockwise order.
-     * @param env The envelope to convert.
-     * @param wraparound How to resolve wrap-around ambiguities on the 
envelope.
-     * @return If any geometric implementation is installed, return a polygon 
(or two polygons in case of
-     * {@link WraparoundStrategy#SPLIT split handling of wrap-around}.
+     * If the given object is an instance of {@link GeometryWrapper}, returns 
the wrapped geometry implementation.
+     * Otherwise returns the given geometry unchanged.
+     *
+     * @param  geometry  the geometry to unwrap (can be {@code null}).
+     * @return the geometry implementation, or the given geometry as-is.
+     */
+    static Object unwrap(final Object geometry) {
+        return (geometry instanceof GeometryWrapper) ? ((GeometryWrapper) 
geometry).geometry : geometry;
+    }
+
+    /**
+     * If an input argument was a {@link GeometryWrapper}, rewrap the 
operation result.
+     * Otherwise returns the geometry as-is.
+     *
+     * @param  geometry  the geometry created by an operation.
+     * @param  wrap      whether to rewrap the geometry.
+     * @return the potentially wrapped geometry.
      */
-    public static Optional<Geometry> toGeometry(final Envelope env, 
WraparoundStrategy wraparound) {
-        return findStrategy(g -> g.tryConvertToGeometry(env, wraparound))
-                .map(result -> new GeometryWrapper(result, env));
+    private Object rewrap(final Object geometry, final boolean wrap) {
+        return wrap ? new GeometryWrapper(geometry, tryGetEnvelope(geometry)) 
: geometry;
     }
 
     /**
      * If the given geometry is an implementation of this library, returns its 
coordinate reference system.
      * Otherwise returns {@code null}. The default implementation returns 
{@code null} because only a few
      * geometry implementations can store CRS information.
-     *
-     * @see #tryTransform(Object, CoordinateOperation, 
CoordinateReferenceSystem)
      */
     CoordinateReferenceSystem tryGetCoordinateReferenceSystem(Object point) 
throws FactoryException {
         return null;
@@ -207,10 +220,9 @@ public abstract class Geometries<G> {
      * @param  geometry the geometry from which to get the CRS, or {@code 
null}.
      * @return the coordinate reference system, or {@code null}.
      * @throws FactoryException if the CRS is defined by a SRID code and that 
code can not be used.
-     *
-     * @see #transform(Object, CoordinateReferenceSystem)
      */
-    public static CoordinateReferenceSystem getCoordinateReferenceSystem(final 
Object geometry) throws FactoryException {
+    public static CoordinateReferenceSystem 
getCoordinateReferenceSystem(Object geometry) throws FactoryException {
+        geometry = unwrap(geometry);
         for (Geometries<?> g = implementation; g != null; g = g.fallback) {
             CoordinateReferenceSystem crs = 
g.tryGetCoordinateReferenceSystem(geometry);
             if (crs != null) return crs;
@@ -219,13 +231,45 @@ public abstract class Geometries<G> {
     }
 
     /**
-     * If the given point is an implementation of this library, returns its 
coordinate.
+     * If the given geometry is an implementation of this library, sets its 
coordinate reference system
+     * and returns {@code true}. Otherwise returns {@code false}. The default 
implementation returns
+     * {@code false} because only a few geometry implementations can store CRS 
information.
+     *
+     * @see #tryTransform(Object, CoordinateOperation, 
CoordinateReferenceSystem)
+     */
+    boolean trySetCoordinateReferenceSystem(Object geometry, 
CoordinateReferenceSystem crs) {
+        return false;
+    }
+
+    /**
+     * Associates the given coordinate reference system to the specified 
geometry if possible.
+     * The given CRS replace any previous referencing information. Note that 
not all geometry
+     * implementations can store CRS information.
+     *
+     * @param  geometry  the geometry on which to set CRS information.
+     * @param  crs       the coordinate reference system to set.
+     * @return {@code true} if the CRS has been set.
+     *
+     * @see #transform(Object, CoordinateReferenceSystem)
+     */
+    public static boolean setCoordinateReferenceSystem(Object geometry, final 
CoordinateReferenceSystem crs) {
+        geometry = unwrap(geometry);
+        for (Geometries<?> g = implementation; g != null; g = g.fallback) {
+            if (g.trySetCoordinateReferenceSystem(geometry, crs)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * If the given point is an implementation of this library, returns its 
coordinates.
      * Otherwise returns {@code null}.
      */
-    abstract double[] tryGetCoordinate(Object point);
+    abstract double[] tryGetPointCoordinates(Object point);
 
     /**
-     * If the given object is one of the recognized point implementation, 
returns its coordinate.
+     * If the given object is one of the recognized point implementations, 
returns its coordinates.
      * Otherwise returns {@code null}. If non-null, the returned array may 
have a length of 2 or 3.
      * If the CRS is geographic, then the (x,y) values should be (longitude, 
latitude) for compliance
      * with usage in ESRI and JTS libraries.
@@ -237,11 +281,22 @@ public abstract class Geometries<G> {
      * @see #getCoordinateReferenceSystem(Object)
      * @see #createPoint(double, double)
      */
-    public static double[] getCoordinate(final Object point) {
-        return findStrategy(g -> g.tryGetCoordinate(point)).orElse(null);
+    public static double[] getPointCoordinates(Object point) {
+        point = unwrap(point);
+        for (Geometries<?> g = implementation; g != null; g = g.fallback) {
+            final double[] coord = g.tryGetPointCoordinates(point);
+            if (coord != null) return coord;
+        }
+        return null;
     }
 
     /**
+     * If the given object is one of the recognized geometry implementations, 
returns all its coordinate tuples.
+     * Otherwise returns {@code null}. This method is currently used only for 
testing purpose.
+     */
+    abstract double[] tryGetAllCoordinates(Object geometry);
+
+    /**
      * If the given geometry is the type supported by this {@code Geometries} 
instance,
      * returns its envelope if non-empty. Otherwise returns {@code null}. We 
currently
      * do not distinguish the reasons why this method may return null.
@@ -255,9 +310,16 @@ public abstract class Geometries<G> {
      * @param  geometry  the geometry from which to get the envelope, or 
{@code null}.
      * @return the envelope of the given geometry, or {@code null} if the 
given object
      *         is not a recognized geometry or its envelope is empty.
+     *
+     * @see #toGeometry(Envelope, WraparoundStrategy)
      */
-    public static GeneralEnvelope getEnvelope(final Object geometry) {
-        return findStrategy(g -> g.tryGetEnvelope(geometry)).orElse(null);
+    public static GeneralEnvelope getEnvelope(Object geometry) {
+        geometry = unwrap(geometry);
+        for (Geometries<?> g = implementation; g != null; g = g.fallback) {
+            final GeneralEnvelope env = g.tryGetEnvelope(geometry);
+            if (env != null) return env;
+        }
+        return null;
     }
 
     /**
@@ -276,9 +338,12 @@ public abstract class Geometries<G> {
      *         is not a recognized geometry.
      */
     public static Object getCentroid(final Object geometry) {
+        final Object impl = unwrap(geometry);
         for (Geometries<?> g = implementation; g != null; g = g.fallback) {
-            Object center = g.tryGetCentroid(geometry);
-            if (center != null) return center;
+            final Object center = g.tryGetCentroid(impl);
+            if (center != null) {
+                return g.rewrap(center, impl != geometry);
+            }
         }
         return null;
     }
@@ -297,20 +362,20 @@ public abstract class Geometries<G> {
      * @return a short string representation of the given geometry, or {@code 
null} if the given
      *         object is not a recognized geometry.
      */
-    public static String toString(final Object geometry) {
-        return findStrategy(g -> g.tryToString(geometry)).orElse(null);
-    }
-
-    private String tryToString(Object geometry) {
-        String s = tryGetLabel(geometry);
-        if (s != null) {
-            GeneralEnvelope env = tryGetEnvelope(geometry);
-            if (env != null) {
-                final String bbox = env.toString();
-                s += bbox.substring(bbox.indexOf('('));
+    public static String toString(Object geometry) {
+        geometry = unwrap(geometry);
+        for (Geometries<?> g = implementation; g != null; g = g.fallback) {
+            String s = g.tryGetLabel(geometry);
+            if (s != null) {
+                final GeneralEnvelope env = g.tryGetEnvelope(geometry);
+                if (env != null) {
+                    final String bbox = env.toString();
+                    s += bbox.substring(bbox.indexOf('('));
+                }
+                return s;
             }
         }
-        return s;
+        return null;
     }
 
     /**
@@ -324,20 +389,12 @@ public abstract class Geometries<G> {
      * @return the Well Known Text for the given geometry, or {@code null} if 
the given object is unrecognized.
      */
     public static String formatWKT(Object geometry, double flatness) {
-        if (geometry instanceof GeometryWrapper) geometry = ((GeometryWrapper) 
geometry).geometry;
-        final Object fGeom = geometry;
-        return findStrategy(g -> g.tryFormatWKT(fGeom, flatness))
-                .orElse(null);
-    }
-
-    public static Optional<?> fromWkt(String wkt) {
-        return findStrategy(g -> {
-            try {
-                return g.parseWKT(wkt);
-            } catch (Exception e) {
-                throw new BackingStoreException(e);
-            }
-        });
+        geometry = unwrap(geometry);
+        for (Geometries<?> g = implementation; g != null; g = g.fallback) {
+            final String wkt = g.tryFormatWKT(geometry, flatness);
+            if (wkt != null) return wkt;
+        }
+        return null;
     }
 
     /**
@@ -347,21 +404,22 @@ public abstract class Geometries<G> {
     abstract String tryFormatWKT(Object geometry, double flatness);
 
     /**
-     * Parses the given WKT.
+     * Parses the given Well Known Text (WKT).
      *
-     * @param  wkt  the WKT to parse.
-     * @return the geometry object for the given WKT.
+     * @param  wkt  the WKT to parse. Can not be null.
+     * @return the geometry object for the given WKT (never {@code null}).
      * @throws Exception if the WKT can not be parsed. The exception sub-class 
depends on the implementation.
      */
     public abstract G parseWKT(String wkt) throws Exception;
 
     /**
-     * Try to read given bytes as a WKB encoded geometry.
-     * @param source Contains the WKB data. Must not be null.
-     * @return Decoded Geometry, never null.
-     * @throws RuntimeException If given byte array is not a consistent WKB, 
or denote some unsupported geometry type.
+     * Reads the given bytes as a Well Known Binary (WKB) encoded geometry.
+     *
+     * @param  data  the binary data in WKB format. Can not be null.
+     * @return decoded geometry (never {@code null}).
+     * @throws Exception if the WKB can not be parsed. The exception sub-class 
depends on the implementation.
      */
-    public abstract G parseWKB(byte[] source);
+    public abstract G parseWKB(byte[] data) throws Exception;
 
     /**
      * Creates a two-dimensional point from the given coordinate. If the CRS 
is geographic, then the
@@ -371,33 +429,46 @@ public abstract class Geometries<G> {
      * @param  y  the second coordinate value.
      * @return the point for the given coordinate values.
      *
-     * @see #getCoordinate(Object)
+     * @see #getPointCoordinates(Object)
      */
     public abstract Object createPoint(double x, double y);
 
     /**
-     * Creates a path or polyline from the given coordinate values.
-     * The array of coordinate values will be handled as if all vectors were 
concatenated in a single vector,
-     * ignoring {@code null} array elements.
+     * Creates a path, polyline or polygon from the given coordinate values.
+     * The array of coordinate values will be handled as if all vectors were
+     * concatenated in a single vector, ignoring {@code null} array elements.
      * Each {@link Double#NaN} coordinate value in the concatenated vector 
starts a new path.
      * The implementation returned by this method is an instance of {@link 
#rootClass}.
      *
+     * <p>If the {@code polygon} argument is {@code true}, then the 
coordinates should
+     * make a closed line (e.g: a linear ring), otherwise an exception is 
thrown.
+     *
+     * @param  polygon      whether to return the path as a polygon instead 
than polyline.
      * @param  dimension    the number of dimensions (2 or 3).
      * @param  coordinates  sequence of (x,y) or (x,y,z) tuples.
      * @return the geometric object for the given points.
      * @throws UnsupportedOperationException if the geometry library can not 
create the requested path.
+     * @throws IllegalArgumentException if a polygon was requested but the 
given coordinates do not make
+     *         a closed shape (linear ring).
      */
-    public abstract G createPolyline(int dimension, Vector... coordinates);
+    public abstract G createPolyline(final boolean polygon, int dimension, 
Vector... coordinates);
 
     /**
-     * Force conversion of input geometry to a polygon. If input is a closed 
line (e.g: Linear ring), it should be
-     * converted to polygon object. Otherwise, an error should be thrown.
+     * Creates a multi-polygon from an array of geometries (polygons or linear 
rings).
+     * Callers must ensure that the given objects are instance of geometric 
objects of this library.
+     *
+     * If some geometries are actually linear rings, current behavior is not 
well defined.
+     * Some implementation may convert polylines to polygons but this is not 
guaranteed.
+     *
+     * @param  geometries  the polygons or linear rings to put in a 
multi-polygons.
+     * @return the multi-polygon.
+     * @throws ClassCastException if an element in the array is not an 
implementation of this library.
      *
-     * @param polyline The polyline to see as a polygon.
-     * @return A polygon object.
-     * @throws IllegalArgumentException If given object is not a closed line 
(linear ring).
+     * @todo Consider a more general method creating a multi-polygon or 
multi-line depending on object types,
+     *       or returning a more primitive geometry type if the given array 
contains only one element.
+     *       We may want to return null if the array is empty (to be decided 
later).
      */
-    public abstract G toPolygon(G polyline) throws IllegalArgumentException;
+    public abstract G createMultiPolygon(final Object[] geometries);
 
     /**
      * Merges a sequence of polyline instances if the first instance is an 
implementation of this library.
@@ -407,13 +478,17 @@ public abstract class Geometries<G> {
      * @return the merged polyline, or {@code null} if the first instance is 
not an implementation of this library.
      * @throws ClassCastException if an element in the iterator is not an 
implementation of this library.
      */
-    public abstract G tryMergePolylines(Object first, Iterator<?> polylines);
+    abstract G tryMergePolylines(Object first, Iterator<?> polylines);
 
     /**
      * Merges a sequence of points or polylines into a single polyline 
instances.
      * Each previous polyline will be a separated path in the new polyline 
instances.
      * The implementation returned by this method is an instance of {@link 
#rootClass}.
      *
+     * <p>Contrarily to other methods in this class, this method does 
<strong>not</strong> unwrap
+     * the geometries contained in {@link GeometryWrapper}. It is caller 
responsibility to do so
+     * if needed.</p>
+     *
      * @param  paths  the points or polylines to merge in a single polyline 
object.
      * @return the merged polyline, or {@code null} if the given iterator has 
no element.
      * @throws ClassCastException if collection elements are not instances of 
a supported library,
@@ -423,8 +498,18 @@ public abstract class Geometries<G> {
         while (paths.hasNext()) {
             final Object first = paths.next();
             if (first != null) {
-                return findStrategy(g -> g.tryMergePolylines(first, paths))
-                        .orElseThrow(() -> new 
ClassCastException(unsupportedImplementation(first)));
+                for (Geometries<?> g = implementation; g != null; g = 
g.fallback) {
+                    final Object merged = g.tryMergePolylines(first, paths);
+                    if (merged != null) {
+                        return merged;
+                    }
+                }
+                /*
+                 * Use the same exception type than `tryMergePolylines(…)` 
implementations.
+                 * Also the same type than exception occurring elsewhere in 
the code of the
+                 * caller (GroupAsPolylineOperation).
+                 */
+                throw new ClassCastException(unsupportedImplementation(first));
             }
         }
         return null;
@@ -434,7 +519,7 @@ public abstract class Geometries<G> {
      * If the given geometry is the type supported by this {@code Geometries} 
instance, computes
      * its buffer as a geometry instance of the same library. Otherwise 
returns {@code null}.
      */
-    Object tryBuffer(Object geometry, double distance) {
+    G tryBuffer(Object geometry, double distance) {
         if (rootClass.isInstance(geometry)) {
             throw new 
UnsupportedImplementationException(unsupported("buffer"));
         }
@@ -450,9 +535,12 @@ public abstract class Geometries<G> {
      * @return the buffer of the given geometry, or {@code null} if the given 
object is not recognized.
      */
     public static Object buffer(final Object geometry, final double distance) {
+        final Object impl = unwrap(geometry);
         for (Geometries<?> g = implementation; g != null; g = g.fallback) {
-            Object center = g.tryBuffer(geometry, distance);
-            if (center != null) return center;
+            final Object center = g.tryBuffer(impl, distance);
+            if (center != null) {
+                return g.rewrap(center, impl != geometry);
+            }
         }
         return null;
     }
@@ -473,7 +561,7 @@ public abstract class Geometries<G> {
      *
      * @see #tryGetCoordinateReferenceSystem(Object)
      */
-    public G tryTransform(Object geometry, CoordinateOperation operation, 
CoordinateReferenceSystem targetCRS)
+    G tryTransform(Object geometry, CoordinateOperation operation, 
CoordinateReferenceSystem targetCRS)
             throws FactoryException, TransformException
     {
         if (rootClass.isInstance(geometry)) {
@@ -507,10 +595,11 @@ public abstract class Geometries<G> {
          * the geometry CRS. This verification will be done by tryTransform(…) 
implementation.
          */
         if (geometry != null && operation != null) {
+            final Object impl = unwrap(geometry);
             for (Geometries<?> g = implementation; g != null; g = g.fallback) {
-                final Object result = g.tryTransform(geometry, operation, 
null);
+                final Object result = g.tryTransform(impl, operation, null);
                 if (result != null) {
-                    return result;
+                    return g.rewrap(result, impl != geometry);
                 }
             }
             if (!operation.getMathTransform().isIdentity()) {
@@ -543,10 +632,11 @@ public abstract class Geometries<G> {
             throws FactoryException, TransformException
     {
         if (geometry != null && targetCRS != null) {
+            final Object impl = unwrap(geometry);
             for (Geometries<?> g = implementation; g != null; g = g.fallback) {
-                final Object result = g.tryTransform(geometry, null, 
targetCRS);
+                final Object result = g.tryTransform(impl, null, targetCRS);
                 if (result != null) {
-                    return result;
+                    return g.rewrap(result, impl != geometry);
                 }
             }
             return null;
@@ -581,19 +671,18 @@ public abstract class Geometries<G> {
         return Errors.format(Errors.Keys.UnsupportedType_1, 
Classes.getClass(geometry));
     }
 
-    private static <T> Optional<T> findStrategy(final Function<Geometries<?>, 
T> op) {
-        for (Geometries<?> g = implementation; g != null; g = g.fallback) {
-            final T result = op.apply(g);
-            if (result != null) return Optional.of(result);
-        }
-
-        return Optional.empty();
-    }
-
     /**
-     * See {@link Geometries#toGeometry(Envelope, WraparoundStrategy)}.
+     * Transforms an envelope to a polygon whose start point is lower corner,
+     * and points making result are the envelope corners in clockwise order.
+     *
+     * @param  env       the envelope to convert.
+     * @param  strategy  how to resolve wrap-around ambiguities on the 
envelope.
+     * @return if any geometric implementation is installed, return a polygon 
(or two polygons
+     *         in case of {@linkplain WraparoundStrategy#SPLIT split handling 
of wrap-around}).
+     *
+     * @see #getEnvelope(Object)
      */
-    public G tryConvertToGeometry(final Envelope env, WraparoundStrategy 
resolution) {
+    public G toGeometry(final Envelope env, WraparoundStrategy strategy) {
         // Ensure that we can isolate an horizontal part in the given envelope.
         final int x;
         if (env.getDimension() == 2) {
@@ -616,7 +705,7 @@ public abstract class Geometries<G> {
         double maxY = uc.getOrdinate(y);
         double[] splittedLeft = null;
         // We start by short-circuiting simplest case for minor 
simplicity/performance reason.
-        if (!WraparoundStrategy.NONE.equals(resolution)) {
+        if (!WraparoundStrategy.NONE.equals(strategy)) {
             // ensure the envelope is correctly defined, by forcing 
non-authorized wrapped axes to take entire crs span.
             final GeneralEnvelope fixedEnv = new GeneralEnvelope(env);
             fixedEnv.normalize();
@@ -629,7 +718,7 @@ public abstract class Geometries<G> {
                 if (crs == null) throw new IllegalArgumentException("Cannot 
resolve wrap-around for an envelope without any system defined");
                 final CoordinateSystemAxis axis = 
crs.getCoordinateSystem().getAxis(wrapAxis);
                 final double wrapRange = axis.getMaximumValue() - 
axis.getMinimumValue();
-                switch (resolution) {
+                switch (strategy) {
                     case EXPAND:
                         // simpler and more performant than a call to 
GeneralEnvelope.simplify()
                         if (wrapAxis == x) {
@@ -655,29 +744,29 @@ public abstract class Geometries<G> {
                         else maxY += wrapRange;
                         break;
                     default:
-                        throw new IllegalArgumentException("Unknown or unset 
wrap resolution: " + resolution);
+                        throw new IllegalArgumentException("Unknown or unset 
wrap resolution: " + strategy);
                 }
             }
         }
 
         Vector[] points = clockwiseRing(minX, minY, maxX, maxY);
 
-        final G mainRect = createPolyline(2, points);
+        final G mainRect = createPolyline(true, 2, points);
         if (splittedLeft != null) {
             minX = splittedLeft[0];
             minY = splittedLeft[1];
             maxX = splittedLeft[2];
             maxY = splittedLeft[3];
             Vector[] points2 = clockwiseRing(minX, minY, maxX, maxY);
-            final G secondRect = createPolyline(2, points2);
-            return createMultiPolygon(Stream.of(mainRect, secondRect));
+            final G secondRect = createPolyline(true, 2, points2);
+            return createMultiPolygon(new Object[] {mainRect, secondRect});
         }
 
         /* Geotk original method had an option to insert a median point on 
wrappped around axis, but we have not ported
          * it, because in an orthonormal space, I don't see any case where it 
could be useful. However, in case it
          * have to be added, we can do it here by amending created ring(s).
          */
-        return toPolygon(mainRect);
+        return mainRect;
     }
 
     /**
@@ -692,41 +781,12 @@ public abstract class Geometries<G> {
      * @return A set of 5 points describing given rectangle.
      */
     private static Vector[] clockwiseRing(final double minX, final double 
minY, final double maxX, final double maxY) {
-        return new Vector[]{
-                Vector.create(new double[]{minX, minY}),
-                Vector.create(new double[]{minX, maxY}),
-                Vector.create(new double[]{maxX, maxY}),
-                Vector.create(new double[]{maxX, minY}),
-                Vector.create(new double[]{minX, minY})
+        return new Vector[] {
+                Vector.create(new double[] {minX, minY}),
+                Vector.create(new double[] {minX, maxY}),
+                Vector.create(new double[] {maxX, maxY}),
+                Vector.create(new double[] {maxX, minY}),
+                Vector.create(new double[] {minX, minY})
         };
     }
-
-    /**
-     * Extract all points from input geometry, and return them as a contiguous 
set of ordinates.
-     * For rings, point order (clockwise/counter-clockwise) is implementation 
dependant.
-     *
-     * @param geometry The geometry to extract point from.
-     */
-    public static Optional<double[]> getOrdinates(Geometry geometry) {
-        return findStrategy(g -> g.getPoints(geometry));
-    }
-
-    public abstract double[] getPoints(Object geometry);
-
-    public abstract G createMultiPolygon(final Stream<?> 
polygonsOrLinearRings);
-
-    public static Object createMultiPolygon_(final Stream 
polygonsOrLinearRings) {
-        return findStrategy(g -> g.createMultiPolygon(polygonsOrLinearRings));
-    }
-
-    /**
-     * Try and associate given coordinate reference system to the specified 
geometry. It should replace any previously
-     * set referencing information.
-     *
-     * @param target The geometry to embed referencing information into.
-     * @param toApply Referencing information to add.
-     */
-    public void setCRS(G target, CoordinateReferenceSystem toApply) {
-        throw new UnsupportedOperationException("Not supported yet");
-    }
 }
diff --git 
a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/GeometryWrapper.java
 
b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/GeometryWrapper.java
index 62db662..bd71914 100644
--- 
a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/GeometryWrapper.java
+++ 
b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/GeometryWrapper.java
@@ -33,7 +33,7 @@ import org.opengis.referencing.operation.MathTransform;
  * This is a temporary class to be refactored later as a more complete 
geometry framework.
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 0.8
+ * @version 1.1
  * @since   0.8
  * @module
  */
@@ -45,6 +45,7 @@ public final class GeometryWrapper implements Geometry {
 
     /**
      * Geometry bounding box, together with its coordinate reference system.
+     * May be {@code null} if the envelope is unknown.
      */
     private final Envelope envelope;
 
@@ -66,7 +67,7 @@ public final class GeometryWrapper implements Geometry {
      */
     @Override
     public CoordinateReferenceSystem getCoordinateReferenceSystem() {
-        return envelope.getCoordinateReferenceSystem();
+        return (envelope != null) ? envelope.getCoordinateReferenceSystem() : 
null;
     }
 
     /**
diff --git 
a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/JTS.java 
b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/JTS.java
index 0c9920b..9146584 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/JTS.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/JTS.java
@@ -21,7 +21,6 @@ import java.util.List;
 import java.util.Arrays;
 import java.util.ArrayList;
 import java.util.Iterator;
-import java.util.stream.Stream;
 
 import org.opengis.referencing.operation.TransformException;
 import org.opengis.referencing.operation.CoordinateOperation;
@@ -32,7 +31,6 @@ import org.apache.sis.geometry.GeneralEnvelope;
 import org.apache.sis.math.Vector;
 import org.apache.sis.setup.GeometryLibrary;
 import org.apache.sis.util.Classes;
-import org.apache.sis.util.collection.BackingStoreException;
 
 import org.locationtech.jts.geom.Coordinate;
 import org.locationtech.jts.geom.Envelope;
@@ -56,7 +54,7 @@ import org.locationtech.jts.io.WKTReader;
  *
  * @author  Johann Sorel (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.0
+ * @version 1.1
  * @since   0.7
  * @module
  */
@@ -87,12 +85,8 @@ final class JTS extends Geometries<Geometry> {
     }
 
     @Override
-    public Geometry parseWKB(byte[] source) {
-        try {
-            return new WKBReader(factory).read(source);
-        } catch (ParseException e) {
-            throw new BackingStoreException("Cannot decode given bytes as a 
WKB geometry", e);
-        }
+    public Geometry parseWKB(byte[] source) throws ParseException {
+        return new WKBReader(factory).read(source);
     }
 
     /**
@@ -134,11 +128,11 @@ final class JTS extends Geometries<Geometry> {
     }
 
     /**
-     * If the given point is an implementation of this library, returns its 
coordinate.
+     * If the given point is an implementation of this library, returns its 
coordinates.
      * Otherwise returns {@code null}. If non-null, the returned array may 
have a length of 2 or 3.
      */
     @Override
-    final double[] tryGetCoordinate(final Object point) {
+    final double[] tryGetPointCoordinates(final Object point) {
         final Coordinate pt;
         if (point instanceof Point) {
             pt = ((Point) point).getCoordinate();
@@ -161,6 +155,25 @@ final class JTS extends Geometries<Geometry> {
     }
 
     /**
+     * If the given geometry is an implementation of this library, returns all 
its coordinate tuples.
+     * Otherwise returns {@code null}.
+     */
+    @Override
+    final double[] tryGetAllCoordinates(Object geometry) {
+        if (geometry instanceof Geometry) {
+            final Coordinate[] points = ((Geometry) geometry).getCoordinates();
+            final double[] coordinates = new double[points.length * 2];
+            int i = 0;
+            for (final Coordinate p : points) {
+                coordinates[i++] = p.x;
+                coordinates[i++] = p.y;
+            }
+            return coordinates;
+        }
+        return null;
+    }
+
+    /**
      * If the given geometry is an implementation of this library, returns its 
coordinate reference system.
      * Otherwise returns {@code null}.
      *
@@ -175,6 +188,16 @@ final class JTS extends Geometries<Geometry> {
         }
     }
 
+    @Override
+    final boolean trySetCoordinateReferenceSystem(final Object geometry, final 
CoordinateReferenceSystem crs) {
+        if (geometry instanceof Geometry) {
+            
org.apache.sis.internal.feature.jts.JTS.setCoordinateReferenceSystem((Geometry) 
geometry, crs);
+            return true;
+        } else {
+            return super.trySetCoordinateReferenceSystem(geometry, crs);
+        }
+    }
+
     /**
      * Copies coordinate reference system information from the given source 
geometry to the target geometry.
      * Current implementation copies only CRS information, but future 
implementations could copy some other
@@ -228,7 +251,7 @@ final class JTS extends Geometries<Geometry> {
      * @throws UnsupportedOperationException if this operation is not 
implemented for the given number of dimensions.
      */
     @Override
-    public Geometry createPolyline(final int dimension, final Vector... 
coords) {
+    public Geometry createPolyline(final boolean polygon, final int dimension, 
final Vector... coords) {
         final boolean is3D = (dimension == 3);
         if (!is3D && dimension != 2) {
             throw new UnsupportedOperationException(unsupported(dimension));
@@ -258,13 +281,32 @@ final class JTS extends Geometries<Geometry> {
             }
         }
         toLineString(coordinates, lines);
+        if (polygon) {
+            return toPolygon(toGeometry(lines));    // TODO: create polygon 
directly instead.
+        }
         return toGeometry(lines);
     }
 
+    /**
+     * Creates a multi-polygon from an array of JTS {@link Polygon} or {@link 
LinearRing}.
+     * If some geometries are actually linear rings, they will be converted to 
polygons.
+     *
+     * @param  geometries  the polygons or linear rings to put in a 
multi-polygons.
+     * @throws ClassCastException if an element in the array is not a JTS 
geometry.
+     */
     @Override
-    public Polygon toPolygon(Geometry polyline) throws 
IllegalArgumentException {
-        if (polyline instanceof Polygon) return (Polygon) polyline;
+    public MultiPolygon createMultiPolygon(final Object[] geometries) {
+        final Polygon[] polygons = new Polygon[geometries.length];
+        for (int i=0; i<geometries.length; i++) {
+            polygons[i] = toPolygon((Geometry) unwrap(geometries[i]));
+        }
+        return factory.createMultiPolygon(polygons);
+    }
 
+    private Polygon toPolygon(Geometry polyline) throws 
IllegalArgumentException {
+        if (polyline instanceof Polygon) {
+            return (Polygon) polyline;
+        }
         Polygon result = null;
         if (polyline instanceof LinearRing) {
             result = factory.createPolygon((LinearRing) polyline);
@@ -274,16 +316,10 @@ final class JTS extends Geometries<Geometry> {
                 result = factory.createPolygon(myLine.getCoordinateSequence());
             }
         }
-
-        if (result == null) throw new IllegalArgumentException("Input is not a 
closed line.");
-
-        try {
-            final CoordinateReferenceSystem crs = 
org.apache.sis.internal.feature.jts.JTS.getCoordinateReferenceSystem(polyline);
-            
org.apache.sis.internal.feature.jts.JTS.setCoordinateReferenceSystem(result, 
crs);
-        } catch (FactoryException e) {
-            throw new BackingStoreException("Cannot extract CRS from 
geometry", e);
+        if (result == null) {
+            throw new IllegalArgumentException("Input is not a closed line.");
         }
-
+        copyMetadata(polyline, result);
         return result;
     }
 
@@ -324,7 +360,7 @@ final class JTS extends Geometries<Geometry> {
      * @throws ClassCastException if an element in the iterator is not a JTS 
geometry.
      */
     @Override
-    public final Geometry tryMergePolylines(Object next, final Iterator<?> 
polylines) {
+    final Geometry tryMergePolylines(Object next, final Iterator<?> polylines) 
{
         if (!(next instanceof MultiLineString || next instanceof LineString || 
next instanceof Point)) {
             return null;
         }
@@ -364,53 +400,11 @@ add:    for (;;) {
         return toGeometry(lines);
     }
 
-    @Override
-    public double[] getPoints(Object geometry) {
-        if (geometry instanceof GeometryWrapper) {
-            geometry = ((GeometryWrapper) geometry).geometry;
-        }
-
-        if (geometry instanceof Geometry) {
-            Geometry geom = (Geometry) geometry;
-            final int nbPts = geom.getNumPoints();
-            final Coordinate[] coords = geom.getCoordinates();
-            double[] ordinates = new double[nbPts * 2];
-            for (int i = 0, j=0; i < nbPts ; i++) {
-                final Coordinate coord = coords[i];
-                ordinates[j++] = coord.x;
-                ordinates[j++] = coord.y;
-            }
-            return ordinates;
-        }
-
-        return null;
-    }
-
-    @Override
-    public MultiPolygon createMultiPolygon(Stream<?> polygonsOrLinearRings) {
-        final Polygon[] polys = polygonsOrLinearRings
-                .map(this::castToPolygon)
-                .toArray(size -> new Polygon[size]);
-        return factory.createMultiPolygon(polys);
-    }
-
-    private Polygon castToPolygon(Object input) {
-        if (input instanceof GeometryWrapper) input = ((GeometryWrapper) 
input).geometry;
-
-        if (input instanceof Geometry) return toPolygon((Geometry) input);
-        else throw new IllegalArgumentException("Given argument cannot be cast 
to polygon");
-    }
-
-    @Override
-    public void setCRS(Geometry target, CoordinateReferenceSystem toApply) {
-        
org.apache.sis.internal.feature.jts.JTS.setCoordinateReferenceSystem(target, 
toApply);
-    }
-
     /**
      * If the given geometry is a JTS geometry, computes its buffer. Otherwise 
returns {@code null}.
      */
     @Override
-    Object tryBuffer(final Object geometry, final double distance) {
+    Geometry tryBuffer(final Object geometry, final double distance) {
         if (geometry instanceof Geometry) {
             final Geometry jts = (Geometry) geometry;
             final Geometry buffer = jts.buffer(distance);
@@ -433,7 +427,7 @@ add:    for (;;) {
      * @see #tryGetCoordinateReferenceSystem(Object)
      */
     @Override
-    public Geometry tryTransform(final Object geometry, final 
CoordinateOperation operation, final CoordinateReferenceSystem targetCRS)
+    final Geometry tryTransform(final Object geometry, final 
CoordinateOperation operation, final CoordinateReferenceSystem targetCRS)
             throws FactoryException, TransformException
     {
         if (geometry instanceof Geometry) {
diff --git 
a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/Java2D.java 
b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/Java2D.java
index 5f1fcc5..0adf7b6 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/Java2D.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/Java2D.java
@@ -24,25 +24,21 @@ import java.awt.geom.Point2D;
 import java.awt.geom.Rectangle2D;
 import java.util.Arrays;
 import java.util.Iterator;
+import java.util.List;
 import java.util.Spliterator;
 import java.util.function.Consumer;
 import java.util.stream.DoubleStream;
-import java.util.stream.Stream;
-import java.util.stream.StreamSupport;
-
 import java.awt.geom.RectangularShape;
-
 import org.apache.sis.geometry.GeneralEnvelope;
 import org.apache.sis.internal.feature.j2d.ShapeProperties;
 import org.apache.sis.internal.referencing.j2d.ShapeUtilities;
 import org.apache.sis.math.Vector;
 import org.apache.sis.setup.GeometryLibrary;
+import org.apache.sis.util.ArraysExt;
 import org.apache.sis.util.Classes;
 import org.apache.sis.util.Numbers;
 import org.apache.sis.util.UnsupportedImplementationException;
 
-import static org.apache.sis.util.ArgumentChecks.ensureNonNull;
-
 
 /**
  * Centralizes usages of some (not all) Java2D geometry API by Apache SIS.
@@ -50,7 +46,7 @@ import static 
org.apache.sis.util.ArgumentChecks.ensureNonNull;
  *
  * @author  Johann Sorel (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.0
+ * @version 1.1
  * @since   0.7
  * @module
  */
@@ -100,11 +96,11 @@ final class Java2D extends Geometries<Shape> {
     }
 
     /**
-     * If the given point is an implementation of this library, returns its 
coordinate.
+     * If the given point is an implementation of this library, returns its 
coordinates.
      * Otherwise returns {@code null}.
      */
     @Override
-    final double[] tryGetCoordinate(final Object point) {
+    final double[] tryGetPointCoordinates(final Object point) {
         if (point instanceof Point2D) {
             final Point2D pt = (Point2D) point;
             return new double[] {
@@ -116,6 +112,38 @@ final class Java2D extends Geometries<Shape> {
     }
 
     /**
+     * If the given geometry is an implementation of this library, returns all 
its coordinate tuples.
+     * Otherwise returns {@code null}.
+     */
+    @Override
+    final double[] tryGetAllCoordinates(final Object geometry) {
+        if (geometry instanceof Point2D) {
+            final Point2D pt = (Point2D) geometry;
+            return new double[] {
+                pt.getX(),
+                pt.getY()
+            };
+        }
+        if (geometry instanceof Shape) {
+            final List<double[]> coordinates = new ShapeProperties((Shape) 
geometry).coordinatesAsDoubles();
+            switch (coordinates.size()) {
+                case 0:  return ArraysExt.EMPTY_DOUBLE;
+                case 1:  return coordinates.get(0);
+                default: {
+                    final double[] tgt = new 
double[coordinates.stream().mapToInt((a) -> a.length).sum()];
+                    int p = 0;
+                    for (final double[] src : coordinates) {
+                        System.arraycopy(src, 0, tgt, p, src.length);
+                        p += src.length;
+                    }
+                    return tgt;
+                }
+            }
+        }
+        return null;
+    }
+
+    /**
      * If the given object is a Java2D geometry, returns its centroid. 
Otherwise returns {@code null}.
      */
     @Override
@@ -137,6 +165,18 @@ final class Java2D extends Geometries<Shape> {
     }
 
     /**
+     * Creates an initially empty Java2D path.
+     *
+     * @param  isFloat {@code true} for {@code float} type, {@code false} for 
{@code double} type.
+     * @param  length  initial capacity.
+     * @return an initially empty path of the given type.
+     */
+    private Path2D createPath(final boolean isFloat, final int length) {
+        return isFloat ? new Path2D.Float (Path2D.WIND_NON_ZERO, length)
+                       : new Path2D.Double(Path2D.WIND_NON_ZERO, length);
+    }
+
+    /**
      * Creates a path from the given coordinate values.
      * Each {@link Double#NaN} coordinate value starts a new path.
      * The geometry may be backed by {@code float} or {@code double} primitive 
type,
@@ -146,7 +186,7 @@ final class Java2D extends Geometries<Shape> {
      * @throws UnsupportedOperationException if this operation is not 
implemented for the given number of dimensions.
      */
     @Override
-    public Shape createPolyline(final int dimension, final Vector... 
coordinates) {
+    public Shape createPolyline(final boolean polygon, final int dimension, 
final Vector... coordinates) {
         if (dimension != 2) {
             throw new UnsupportedOperationException(unsupported(dimension));
         }
@@ -181,11 +221,10 @@ final class Java2D extends Geometries<Shape> {
                 return path;
             }
         }
-        final Path2D path = isFloat ? new Path2D.Float (Path2D.WIND_NON_ZERO, 
length)
-                                    : new Path2D.Double(Path2D.WIND_NON_ZERO, 
length);
-        boolean lineTo = false;
+        final Path2D path = createPath(isFloat, length);
         double startX = Double.NaN, startY = Double.NaN;
-        double lastX = Double.NaN, lastY = Double.NaN;
+        double  lastX = Double.NaN,  lastY = Double.NaN;
+        boolean lineTo = false;
         for (final Vector v : coordinates) {
             final int size = v.size();
             for (int i=0; i<size;) {
@@ -208,19 +247,45 @@ final class Java2D extends Geometries<Shape> {
                 lastX = x; lastY = y;
             }
         }
-
-        if (lastX == startX && lastY == startY) path.closePath();
-
+        if (polygon) {
+            path.closePath();
+        }
         return ShapeUtilities.toPrimitive(path);
     }
 
     /**
+     * Creates a multi-polygon from an array of geometries.
+     * Callers must ensure that the given objects are Java2D geometries.
+     *
+     * @param  geometries  the polygons or linear rings to put in a 
multi-polygons.
+     * @throws ClassCastException if an element in the array is not a Java2D 
geometry.
+     */
+    @Override
+    public Shape createMultiPolygon(final Object[] geometries) {
+        if (geometries.length == 1) {
+            return (Shape) geometries[0];
+        }
+        boolean isFloat = true;
+        for (final Object geometry : geometries) {
+            if (!ShapeUtilities.isFloat(geometry)) {
+                isFloat = false;
+                break;
+            }
+        }
+        final Path2D path = createPath(isFloat, 20);
+        for (final Object geometry : geometries) {
+            path.append((Shape) geometry, false);
+        }
+        return path;
+    }
+
+    /**
      * Merges a sequence of points or paths if the first instance is an 
implementation of this library.
      *
      * @throws ClassCastException if an element in the iterator is not a 
{@link Shape} or a {@link Point2D}.
      */
     @Override
-    public final Shape tryMergePolylines(Object next, final Iterator<?> 
polylines) {
+    final Shape tryMergePolylines(Object next, final Iterator<?> polylines) {
         if (!(next instanceof Shape || next instanceof Point2D)) {
             return null;
         }
@@ -260,34 +325,6 @@ add:    for (;;) {
         return ShapeUtilities.toPrimitive(path);
     }
 
-    @Override
-    public double[] getPoints(Object geometry) {
-        if (geometry instanceof GeometryWrapper) geometry = ((GeometryWrapper) 
geometry).geometry;
-        ensureNonNull("Geometry", geometry);
-        if (geometry instanceof Point2D) return getCoordinate(geometry);
-        if (geometry instanceof Shape) {
-            final PathIterator it = ((Shape) geometry).getPathIterator(null);
-            return StreamSupport.stream(new PathSpliterator(it), false)
-                    .flatMapToDouble(Segment::ordinates)
-                    .toArray();
-        }
-
-        throw new UnsupportedOperationException("Unsupported geometry type: 
"+geometry.getClass().getCanonicalName());
-    }
-
-    @Override
-    public Shape createMultiPolygon(Stream<?> polygonsOrLinearRings) {
-        final Iterator<?> it = polygonsOrLinearRings.iterator();
-        if (it.hasNext()) return tryMergePolylines(it.next(), it);
-        throw new IllegalArgumentException("Empty input");
-    }
-
-    @Override
-    public Shape toPolygon(Shape polyline) throws IllegalArgumentException {
-        // TODO: check that path ends with close.
-        return polyline;
-    }
-
     /**
      * If the given object is a Java2D shape, builds its WKT representation.
      * Current implementation assumes that all closed shapes are polygons and 
that polygons have no hole
@@ -312,78 +349,4 @@ add:    for (;;) {
     public Shape parseWKB(byte[] source) {
         throw new UnsupportedImplementationException(unsupported("parseWKB"));
     }
-
-    /**
-     * An abstraction over {@link PathIterator} to use it in a streaming 
context.
-     */
-    private static final class PathSpliterator implements Spliterator<Segment> 
{
-
-        private final PathIterator source;
-
-        private PathSpliterator(PathIterator source) {
-            this.source = source;
-        }
-
-        @Override
-        public boolean tryAdvance(Consumer<? super Segment> action) {
-            if (source.isDone()) return false;
-            final double[] coords = new double[6];
-            final int segmentType = source.currentSegment(coords);
-            action.accept(new Segment(segmentType, coords));
-            source.next();
-            return true;
-        }
-
-        @Override
-        public Spliterator<Segment> trySplit() {
-            return null;
-        }
-
-        @Override
-        public long estimateSize() {
-            return Long.MAX_VALUE;
-        }
-
-        @Override
-        public int characteristics() {
-            return Spliterator.NONNULL | Spliterator.ORDERED | 
Spliterator.IMMUTABLE;
-        }
-    }
-
-    /**
-     * Describe a path segment as described by {@link 
PathIterator#currentSegment(double[]) AWT path iteration API}.
-     * Basically, the awt abstraction is really poor, so we made a wrapper 
around it to describe segments.
-     */
-    private static class Segment {
-        /**
-         * This segment type ({@link PathIterator#SEG_CLOSE}, etc.).
-         */
-        final int type;
-        /**
-         * Brut points composing the segment, as returned by {@link 
PathIterator#currentSegment(double[])}.
-         */
-        private final double[] points;
-
-        Segment(int type, double[] points) {
-            this.type = type;
-            this.points = points;
-        }
-
-        /**
-         *
-         * @return points composing this segment, as a contiguous set of 
ordinates. Points are all 2D, so every two
-         * elements in backed stream describe a point. Can be empty in case of 
{@link PathIterator#SEG_CLOSE closing segment}.
-         */
-        DoubleStream ordinates() {
-            switch (type) {
-                case PathIterator.SEG_CLOSE: return DoubleStream.empty();
-                case PathIterator.SEG_QUADTO: return Arrays.stream(points, 0, 
4);
-                case PathIterator.SEG_CUBICTO: return Arrays.stream(points);
-                case PathIterator.SEG_LINETO:
-                case PathIterator.SEG_MOVETO:
-                        return Arrays.stream(points, 0, 2);
-                default: throw new IllegalStateException("Unknown segment 
type: "+type);
-            }
-        }
-    }
 }
diff --git 
a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/MovingFeature.java
 
b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/MovingFeature.java
index c60dbd9..e6cb9c5 100644
--- 
a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/MovingFeature.java
+++ 
b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/MovingFeature.java
@@ -291,7 +291,7 @@ public final class MovingFeature {
         /*
          * Store the geometry and characteristics in the attribute.
          */
-        dest.setValue(factory.createPolyline(dimension, vectors));
+        dest.setValue(factory.createPolyline(false, dimension, vectors));
         final Attribute<Instant> c = TIME.newInstance();
         c.setValues(new DateList(times));
         dest.characteristics().values().add(c);
diff --git 
a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/j2d/ShapeProperties.java
 
b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/j2d/ShapeProperties.java
index dcc2a0d..6ea9617 100644
--- 
a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/j2d/ShapeProperties.java
+++ 
b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/j2d/ShapeProperties.java
@@ -31,7 +31,7 @@ import org.apache.sis.util.StringBuilders;
  * Provides some information about a {@link Shape} object.
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.0
+ * @version 1.1
  * @since   1.0
  * @module
  */
@@ -104,8 +104,26 @@ public final class ShapeProperties {
     }
 
     /**
+     * Returns coordinates of the given geometry as a list of 
(<var>x</var>,<var>y</var>) tuples
+     * in {@code double[]} arrays. This method guarantees that all arrays have 
at least 2 points (4 coordinates).
+     * It should be invoked only for small or medium shapes. For large shapes, 
the path iterator should be used
+     * directly without copy to arrays.
+     *
+     * @return coordinate points as (<var>x</var>,<var>y</var>) tuples.
+     * @throws IllegalPathStateException if the given path iterator contains 
curves.
+     */
+    public List<double[]> coordinatesAsDoubles() {
+        isPolygon = true;
+        return coordinatesAsDoubles(geometry.getPathIterator(null));
+    }
+
+    /**
      * {@link #coordinates(double)} implementation for the double-precision 
case.
      * The {@link #isPolygon} field needs to be set before to invoke this 
method.
+     *
+     * @param  it  path iterator of the geometry for which to get the 
coordinate points.
+     * @return coordinate points as (<var>x</var>,<var>y</var>) tuples.
+     * @throws IllegalPathStateException if the given path iterator contains 
curves.
      */
     private List<double[]> coordinatesAsDoubles(final PathIterator it) {
         final List<double[]> polylines = new ArrayList<>();
@@ -158,6 +176,10 @@ public final class ShapeProperties {
     /**
      * {@link #coordinates(double)} implementation for the single-precision 
case.
      * The {@link #isPolygon} field needs to be set before to invoke this 
method.
+     *
+     * @param  it  path iterator of the geometry for which to get the 
coordinate points.
+     * @return coordinate points as (<var>x</var>,<var>y</var>) tuples.
+     * @throws IllegalPathStateException if the given path iterator contains 
curves.
      */
     private List<float[]> coordinatesAsFloats(final PathIterator it) {
         final List<float[]> polylines = new ArrayList<>();
diff --git 
a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/jts/JTS.java 
b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/jts/JTS.java
index ada49fb..86813e1 100644
--- 
a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/jts/JTS.java
+++ 
b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/jts/JTS.java
@@ -80,13 +80,13 @@ public final class JTS extends Static {
      *
      * If none of the above is valid, {@code null} is returned.
      *
-     * @param  geometry the geometry from which to get the CRS, or {@code 
null}.
+     * @param  source  the geometry from which to get the CRS, or {@code null}.
      * @return the coordinate reference system, or {@code null} if none.
      * @throws FactoryException if the CRS can not be created from the SRID 
code.
      */
-    public static CoordinateReferenceSystem getCoordinateReferenceSystem(final 
Geometry geometry) throws FactoryException {
-        if (geometry != null) {
-            final Object userData = geometry.getUserData();
+    public static CoordinateReferenceSystem getCoordinateReferenceSystem(final 
Geometry source) throws FactoryException {
+        if (source != null) {
+            final Object userData = source.getUserData();
             if (userData instanceof CoordinateReferenceSystem) {
                 return (CoordinateReferenceSystem) userData;
             } else if (userData instanceof Map<?,?>) {
@@ -99,7 +99,7 @@ public final class JTS extends Static {
             /*
              * Fallback on SRID with the assumption that they are EPSG codes.
              */
-            final int srid = geometry.getSRID();
+            final int srid = source.getSRID();
             if (srid > 0) {
                 return CRS.forCode(Constants.EPSG + ':' + srid);
             }
@@ -107,27 +107,26 @@ public final class JTS extends Static {
         return null;
     }
 
-    public static Optional<CoordinateReferenceSystem> 
setCoordinateReferenceSystem(final Geometry target, final 
CoordinateReferenceSystem toSet) {
+    public static Optional<CoordinateReferenceSystem> 
setCoordinateReferenceSystem(final Geometry target, final 
CoordinateReferenceSystem crs) {
         ensureNonNull("Target geometry", target);
         final Object ud = target.getUserData();
         if (ud == null) {
             // By security, we reset SRID in case old CRS was defined this way.
             target.setSRID(0);
-            target.setUserData(toSet);
+            target.setUserData(crs);
             return Optional.empty();
         } else if (ud instanceof CoordinateReferenceSystem) {
-            target.setUserData(toSet);
+            target.setUserData(crs);
             return Optional.of((CoordinateReferenceSystem) ud);
         } else if (ud instanceof Map) {
             final Map asMap = (Map) ud;
             // In case user-data contains other useful data, we don't switch 
from map to CRS. We also reset SRID.
-            final Object oldVal = asMap.put(CRS_KEY, toSet);
+            final Object oldVal = asMap.put(CRS_KEY, crs);
             // By security, we reset SRID in case old CRS was defined this way.
             if (oldVal == null) {
                 target.setSRID(0);
             }
         }
-
         throw new IllegalArgumentException("Cannot modify input geometry, 
because user-data does not comply with SIS convention (should be a map or null, 
but was "+ud.getClass().getCanonicalName()+").");
     }
 
diff --git 
a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/package-info.java
 
b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/package-info.java
index dfd97c4..2cfcc6e 100644
--- 
a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/package-info.java
+++ 
b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/package-info.java
@@ -27,6 +27,7 @@
  *
  * @author  Johann Sorel (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
+ * @author  Alexis Manin (Geomatys)
  * @version 1.1
  * @since   0.7
  * @module
diff --git 
a/core/sis-feature/src/main/java/org/apache/sis/internal/filter/FilterGeometryUtils.java
 
b/core/sis-feature/src/main/java/org/apache/sis/internal/filter/FilterGeometryUtils.java
index 8069b26..4278bcf 100644
--- 
a/core/sis-feature/src/main/java/org/apache/sis/internal/filter/FilterGeometryUtils.java
+++ 
b/core/sis-feature/src/main/java/org/apache/sis/internal/filter/FilterGeometryUtils.java
@@ -168,7 +168,7 @@ public final class FilterGeometryUtils {
         if (value instanceof GridCoverage) {
             //use the coverage envelope
             final GridCoverage coverage = (GridCoverage) value;
-            candidate = 
SIS_GEOMETRY_FACTORY.tryConvertToGeometry(coverage.getGridGeometry().getEnvelope(),
 WraparoundStrategy.SPLIT);
+            candidate = 
SIS_GEOMETRY_FACTORY.toGeometry(coverage.getGridGeometry().getEnvelope(), 
WraparoundStrategy.SPLIT);
         } else {
             try {
                 candidate = ObjectConverters.convert(value, Geometry.class);
@@ -193,10 +193,11 @@ public final class FilterGeometryUtils {
                 .left(leftCRS)
                 .right(rightCRS);
 
-        final CRSMatching.Transformer<Geometry> projectGeom = (g, op) -> 
SIS_GEOMETRY_FACTORY.tryTransform(g, op, null);
-        return new Geometry[]{
-            match.transformLeftToCommon(leftGeom, projectGeom),
-            match.transformRightToCommon(rightGeom, projectGeom)
+        // TODO: avoid casts.
+        final CRSMatching.Transformer<Object> projectGeom = (g, op) -> 
Geometries.transform(g, op);
+        return new Geometry[] {
+            (Geometry) match.transformLeftToCommon(leftGeom, projectGeom),
+            (Geometry) match.transformRightToCommon(rightGeom, projectGeom)
         };
     }
 
diff --git 
a/core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/AbstractSpatialFunction.java
 
b/core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/AbstractSpatialFunction.java
index bf6eddf..1410ab3 100644
--- 
a/core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/AbstractSpatialFunction.java
+++ 
b/core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/AbstractSpatialFunction.java
@@ -22,8 +22,10 @@ import org.apache.sis.feature.Features;
 import org.apache.sis.feature.builder.FeatureTypeBuilder;
 import org.apache.sis.internal.feature.AttributeConvention;
 import org.apache.sis.internal.feature.FeatureExpression;
+import org.apache.sis.internal.feature.Geometries;
 import org.apache.sis.internal.feature.jts.JTS;
 import org.apache.sis.internal.filter.NamedFunction;
+import org.apache.sis.setup.GeometryLibrary;
 import org.locationtech.jts.geom.Geometry;
 import org.opengis.feature.AttributeType;
 import org.opengis.feature.FeatureType;
@@ -44,6 +46,13 @@ public abstract class AbstractSpatialFunction extends 
NamedFunction implements F
         super(parameters);
     }
 
+    /**
+     * @todo allow the geometry library to be specified.
+     */
+    static Geometries<?> getGeometryImplementation() {
+        return Geometries.implementation((GeometryLibrary) null);
+    }
+
     protected int getMinParams() {
         return 1;
     }
diff --git 
a/core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/ST_Envelope.java
 
b/core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/ST_Envelope.java
index cab9c4b..4c5925f 100644
--- 
a/core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/ST_Envelope.java
+++ 
b/core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/ST_Envelope.java
@@ -181,10 +181,11 @@ public class ST_Envelope extends NamedFunction implements 
FeatureExpression {
             return new GeneralEnvelope((GeographicBoundingBox) value);
         } else if (value instanceof Envelope) {
             return (Envelope) value;
-        } else if (value instanceof CharSequence) {
+        } else if (value instanceof CharSequence) try {
             // Maybe it's a WKT format, so we will try to read it
-            value = Geometries.fromWkt(value.toString())
-                    .orElseThrow(() -> new IllegalArgumentException("No 
geometry provider found to read WKT"));
+            value = 
AbstractSpatialFunction.getGeometryImplementation().parseWKT(value.toString());
+        } catch (Exception e) {
+            throw new IllegalArgumentException("Can not parse WKT", e);
         }
 
         // First, we check if the envelope is already available. If not, we 
try to compute it.
diff --git 
a/core/sis-feature/src/test/java/org/apache/sis/internal/feature/ESRITest.java 
b/core/sis-feature/src/test/java/org/apache/sis/internal/feature/ESRITest.java
index 62ae691..e45395f 100644
--- 
a/core/sis-feature/src/test/java/org/apache/sis/internal/feature/ESRITest.java
+++ 
b/core/sis-feature/src/test/java/org/apache/sis/internal/feature/ESRITest.java
@@ -40,7 +40,7 @@ public final strictfp class ESRITest extends 
GeometriesTestCase {
     }
 
     /**
-     * Tests {@link ESRI#createPolyline(int, Vector...)}.
+     * Tests {@link ESRI#createPolyline(boolean, int, Vector...)}.
      */
     @Test
     @Override
diff --git 
a/core/sis-feature/src/test/java/org/apache/sis/internal/feature/GeometriesTestCase.java
 
b/core/sis-feature/src/test/java/org/apache/sis/internal/feature/GeometriesTestCase.java
index 9a12f11..8797547 100644
--- 
a/core/sis-feature/src/test/java/org/apache/sis/internal/feature/GeometriesTestCase.java
+++ 
b/core/sis-feature/src/test/java/org/apache/sis/internal/feature/GeometriesTestCase.java
@@ -62,23 +62,23 @@ public abstract strictfp class GeometriesTestCase extends 
TestCase {
     }
 
     /**
-     * Tests {@link Geometries#createPoint(double, double)} followed by {@link 
Geometries#tryGetCoordinate(Object)}.
+     * Tests {@link Geometries#createPoint(double, double)} followed by {@link 
Geometries#tryGetPointCoordinates(Object)}.
      */
     @Test
-    public void testTryGetCoordinate() {
+    public void testTryGetPointCoordinates() {
         geometry = factory.createPoint(4, 5);
         assertNotNull("createPoint", geometry);
-        assertArrayEquals("tryGetCoordinate", new double[] {4, 5}, 
factory.tryGetCoordinate(geometry), STRICT);
+        assertArrayEquals("tryGetPointCoordinates", new double[] {4, 5}, 
factory.tryGetPointCoordinates(geometry), STRICT);
     }
 
     /**
-     * Tests {@link Geometries#createPolyline(int, Vector...)}.
+     * Tests {@link Geometries#createPolyline(boolean, int, Vector...)}.
      * This method verifies the polylines by a call to {@link 
Geometries#tryGetEnvelope(Object)}.
      * Subclasses should perform more extensive tests by verifying the {@link 
#geometry} field.
      */
     @Test
     public void testCreatePolyline() {
-        geometry = factory.createPolyline(2, Vector.create(new double[] {
+        geometry = factory.createPolyline(false, 2, Vector.create(new double[] 
{
                   4,   5,
                   7,   9,
                   9,   3,
@@ -134,7 +134,7 @@ public abstract strictfp class GeometriesTestCase extends 
TestCase {
     @Test
     @DependsOnMethod("testCreatePolyline")
     public void testTryFormatWKT() {
-        geometry = factory.createPolyline(2, Vector.create(new double[] {4,5, 
7,9, 9,3, -1,-6}));
+        geometry = factory.createPolyline(false, 2, Vector.create(new double[] 
{4,5, 7,9, 9,3, -1,-6}));
         final String text = factory.tryFormatWKT(geometry, 0);
         assertNotNull(text);
         assertWktEquals("LINESTRING (4 5, 7 9, 9 3, -1 -6)", text);
@@ -179,12 +179,12 @@ public abstract strictfp class GeometriesTestCase extends 
TestCase {
         Stream.of(WraparoundStrategy.CONTIGUOUS, WraparoundStrategy.EXPAND, 
WraparoundStrategy.SPLIT)
                 .forEach(method
                         -> expectFailFast(()
-                                -> factory.tryConvertToGeometry(wrapped, 
method)
+                                -> factory.toGeometry(wrapped, method)
                         , IllegalArgumentException.class)
                 );
 
         final GeneralEnvelope wrapped3d = new GeneralEnvelope(3);
-        expectFailFast(() -> factory.tryConvertToGeometry(wrapped3d, 
WraparoundStrategy.NONE), IllegalArgumentException.class);
+        expectFailFast(() -> factory.toGeometry(wrapped3d, 
WraparoundStrategy.NONE), IllegalArgumentException.class);
     }
 
     public static void expectFailFast(Callable whichMustFail, Class<? extends 
Exception>... expectedErrorTypes) {
@@ -212,7 +212,7 @@ public abstract strictfp class GeometriesTestCase extends 
TestCase {
     }
 
     private void assertConversion(final Envelope source, final 
WraparoundStrategy method, final double[] expectedOrdinates) {
-        final double[] result = 
factory.getPoints(factory.tryConvertToGeometry(source, method));
+        final double[] result = 
factory.tryGetAllCoordinates(factory.toGeometry(source, method));
         assertArrayEquals("Point list for: "+method, expectedOrdinates, 
result, 1e-9);
     }
 
diff --git 
a/core/sis-feature/src/test/java/org/apache/sis/internal/feature/JTSTest.java 
b/core/sis-feature/src/test/java/org/apache/sis/internal/feature/JTSTest.java
index 8347c6b..94a1f28 100644
--- 
a/core/sis-feature/src/test/java/org/apache/sis/internal/feature/JTSTest.java
+++ 
b/core/sis-feature/src/test/java/org/apache/sis/internal/feature/JTSTest.java
@@ -41,7 +41,7 @@ public final strictfp class JTSTest extends 
GeometriesTestCase {
     }
 
     /**
-     * Tests {@link JTS#createPolyline(int, Vector...)}.
+     * Tests {@link JTS#createPolyline(boolean, int, Vector...)}.
      */
     @Test
     @Override
diff --git 
a/core/sis-feature/src/test/java/org/apache/sis/internal/feature/Java2DTest.java
 
b/core/sis-feature/src/test/java/org/apache/sis/internal/feature/Java2DTest.java
index 7df9a29..2cdc9c7 100644
--- 
a/core/sis-feature/src/test/java/org/apache/sis/internal/feature/Java2DTest.java
+++ 
b/core/sis-feature/src/test/java/org/apache/sis/internal/feature/Java2DTest.java
@@ -39,7 +39,7 @@ public final strictfp class Java2DTest extends 
GeometriesTestCase {
     }
 
     /**
-     * Tests {@link Java2D#createPolyline(int, Vector...)}.
+     * Tests {@link Java2D#createPolyline(boolean, int, Vector...)}.
      */
     @Test
     @Override
diff --git 
a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/impl/FeaturesInfo.java
 
b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/impl/FeaturesInfo.java
index 81d9283..86cf5e8 100644
--- 
a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/impl/FeaturesInfo.java
+++ 
b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/impl/FeaturesInfo.java
@@ -387,7 +387,7 @@ search: for (final VariableInfo counts : decoder.variables) 
{
             for (int i=0; i<tmp.length; i++) {
                 tmp[i] = coords[i % dimension].doubleValue(i / dimension);
             }
-            feature.setPropertyValue("trajectory", 
factory.createPolyline(dimension, Vector.create(tmp)));
+            feature.setPropertyValue("trajectory", 
factory.createPolyline(false, dimension, Vector.create(tmp)));
             action.accept(feature);
             position = Math.addExact(position, length);
             return ++index < counts.size();
diff --git 
a/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/ANSIInterpreter.java
 
b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/ANSIInterpreter.java
index 0976e36..1bc056b 100644
--- 
a/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/ANSIInterpreter.java
+++ 
b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/ANSIInterpreter.java
@@ -25,8 +25,10 @@ import java.util.stream.Collectors;
 import java.util.stream.IntStream;
 import org.apache.sis.geometry.GeneralEnvelope;
 import org.apache.sis.internal.feature.Geometries;
+import org.apache.sis.internal.feature.GeometryWrapper;
 import org.apache.sis.internal.feature.WraparoundStrategy;
-import static org.apache.sis.util.ArgumentChecks.ensureNonNull;
+import org.apache.sis.setup.GeometryLibrary;
+import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.util.iso.Names;
 import org.opengis.filter.*;
 import org.opengis.filter.expression.Add;
@@ -76,6 +78,10 @@ import org.opengis.util.LocalName;
  * @module
  */
 public class ANSIInterpreter implements FilterVisitor, ExpressionVisitor {
+    /**
+     * TODO
+     */
+    private static final GeometryLibrary LIBRARY = null;
 
     private final java.util.function.Function<Literal, CharSequence> 
valueFormatter;
 
@@ -87,10 +93,10 @@ public class ANSIInterpreter implements FilterVisitor, 
ExpressionVisitor {
 
     public ANSIInterpreter(
             java.util.function.Function<Literal, CharSequence> valueFormatter,
-            java.util.function.Function<PropertyName, CharSequence> 
nameFormatter
-    ) {
-        ensureNonNull("Literal value formatter", valueFormatter);
-        ensureNonNull("Property name formatter", nameFormatter);
+            java.util.function.Function<PropertyName, CharSequence> 
nameFormatter)
+    {
+        ArgumentChecks.ensureNonNull("valueFormatter", valueFormatter);
+        ArgumentChecks.ensureNonNull("nameFormatter", nameFormatter);
         this.valueFormatter = valueFormatter;
         this.nameFormatter = nameFormatter;
     }
@@ -403,7 +409,6 @@ public class ANSIInterpreter implements FilterVisitor, 
ExpressionVisitor {
         if (value instanceof Geometry) {
             return format((Geometry) value);
         }
-
         throw new UnsupportedOperationException("Not supported yet: Literal 
value of type "+value.getClass());
     }
 
@@ -451,8 +456,8 @@ public class ANSIInterpreter implements FilterVisitor, 
ExpressionVisitor {
     protected CharSequence join(
             Supplier<Expression> leftOperand,
             Supplier<Expression> rightOperand,
-            String operator, Object extraData
-    ) {
+            String operator, Object extraData)
+    {
         return "("
                 + evaluateMandatory(leftOperand.get(), extraData)
                 + operator
@@ -500,8 +505,9 @@ public class ANSIInterpreter implements FilterVisitor, 
ExpressionVisitor {
         ensureMatchCase(filter::isMatchingCase);
     }
     private static void ensureMatchCase(BooleanSupplier filter) {
-        if (!filter.getAsBoolean())
+        if (!filter.getAsBoolean()) {
             throw new UnsupportedOperationException("case insensitive match is 
not defined by ANSI SQL");
+        }
     }
 
     protected static CharSequence append(CharSequence toAdd, Object extraData) 
{
@@ -521,8 +527,11 @@ public class ANSIInterpreter implements FilterVisitor, 
ExpressionVisitor {
         }
         final GeneralEnvelope env = new GeneralEnvelope(lower, upper);
         
env.setCoordinateReferenceSystem(source.getCoordinateReferenceSystem());
-        return Geometries.toGeometry(env, WraparoundStrategy.SPLIT)
-                .orElseThrow(() -> new UnsupportedOperationException("No 
geometry implementation available"));
+        final Object g = Geometries.implementation(LIBRARY).toGeometry(env, 
WraparoundStrategy.SPLIT);
+        if (g == null) {
+            throw new UnsupportedOperationException("No geometry 
implementation available");
+        }
+        return new GeometryWrapper(g, env);
     }
 
     protected static CharSequence format(final Geometry source) {
@@ -543,7 +552,6 @@ public class ANSIInterpreter implements FilterVisitor, 
ExpressionVisitor {
         } else if (candidate == Double.POSITIVE_INFINITY) {
             return Double.MAX_VALUE;
         }
-
         return candidate;
     }
 }
diff --git 
a/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/EWKBReader.java
 
b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/EWKBReader.java
index 6b0e690..a4ca24d 100644
--- 
a/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/EWKBReader.java
+++ 
b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/EWKBReader.java
@@ -19,7 +19,6 @@ package org.apache.sis.internal.sql.feature;
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
 import java.util.Arrays;
-import java.util.Iterator;
 import java.util.function.Function;
 import java.util.function.IntFunction;
 import java.util.stream.IntStream;
@@ -95,7 +94,7 @@ class EWKBReader {
         if (defaultCrs == null) return new EWKBReader(factory);
         else return new EWKBReader(factory, bytes -> {
             final Object geom = new Reader(factory, bytes).read();
-            if (geom != null) factory.setCRS(geom, defaultCrs);
+            Geometries.setCoordinateReferenceSystem(geom, defaultCrs);
             return geom;
         });
     }
@@ -106,7 +105,9 @@ class EWKBReader {
             final Object geom = reader.read();
             if (reader.srid > 0) {
                 final CoordinateReferenceSystem crs = 
fromPgSridToCrs.apply(reader.srid);
-                if (crs != null) factory.setCRS(geom, crs);
+                if (crs != null) {
+                    Geometries.setCoordinateReferenceSystem(geom, crs);
+                }
             }
             return geom;
         });
@@ -156,26 +157,25 @@ class EWKBReader {
                 case MULTIPOLYGON:       return readMultiPolygon();
                 case GEOMETRYCOLLECTION: return readCollection();
             }
-
             throw new UnsupportedOperationException("Unsupported geometry 
type: "+geomType);
         }
 
         private Object readMultiLineString() {
-            final Iterator<Object> it = IntStream.range(0, readCount())
+            final Object geometry = 
Geometries.mergePolylines(IntStream.range(0, readCount())
                     .mapToObj(i -> new Reader(factory, buffer).read())
-                    .iterator();
-            if (it.hasNext()) {
-                final Object first = it.next();
-                return factory.tryMergePolylines(first, it);
+                    .iterator());
+            if (geometry != null) {
+                return geometry;
             }
             throw new IllegalStateException("No geometry decoded");
         }
 
         private Object readMultiPolygon() {
-            return factory.createMultiPolygon(
-                    IntStream.range(0, readCount())
-                            .mapToObj(i -> new Reader(factory, buffer).read())
-            );
+            final Object[] polygons = new Object[readCount()];
+            for (int i=0; i<polygons.length; i++) {
+                polygons[i] = new Reader(factory, buffer).read();
+            }
+            return factory.createMultiPolygon(polygons);
         }
 
         private Object readCollection() {
@@ -190,7 +190,7 @@ class EWKBReader {
 
         final Object readLineString() {
             final double[] ordinates = readCoordinateSequence();
-            return factory.createPolyline(dimension, Vector.create(ordinates));
+            return factory.createPolyline(false, dimension, 
Vector.create(ordinates));
         }
 
         private Object readPolygon() {
@@ -206,8 +206,7 @@ class EWKBReader {
                 allShells[i++] = separator;
                 allShells[i++] = Vector.create(readCoordinateSequence());
             }
-
-            return factory.toPolygon(factory.createPolyline(dimension, 
outerShell));
+            return factory.createPolyline(true, dimension, outerShell);
         }
 
         private double[] readMultiPoint() {
diff --git 
a/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/Writer.java
 
b/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/Writer.java
index 710df49..b018e99 100644
--- 
a/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/Writer.java
+++ 
b/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/Writer.java
@@ -192,7 +192,7 @@ final class Writer extends StaxStreamWriter {
      */
     private void writeWayPoint(final Feature feature, final String tagName) 
throws XMLStreamException, JAXBException {
         if (feature != null) {
-            final double[] pt = 
Geometries.getCoordinate(feature.getPropertyValue(AttributeConvention.GEOMETRY));
+            final double[] pt = 
Geometries.getPointCoordinates(feature.getPropertyValue(AttributeConvention.GEOMETRY));
             if (pt != null && pt.length >= 2) {
                 writer.writeStartElement(tagName);
                 writer.writeAttribute(Attributes.LATITUDE,  
Double.toString(pt[1]));

Reply via email to