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 a8edb40767c3d160692038454a9e21c2f05a77f0
Author: Martin Desruisseaux <martin.desruisse...@geomatys.com>
AuthorDate: Thu Dec 9 18:25:53 2021 +0100

    Add an implementation of JTS `CoordinateSequenceFactory` interface which 
stores coordinate values in packed arrays
    (like what JTS `PackedCoordinateSequence` does) but without retaining a 
copy in a `SoftReference<Coordinate[]>`
    (because we want to save memory for bigger uses, such as rendered images).
---
 .../apache/sis/internal/feature/Geometries.java    |   5 +-
 .../apache/sis/internal/feature/jts/Factory.java   | 210 +++++++--
 .../feature/jts/PackedCoordinateSequence.java      | 473 +++++++++++++++++++++
 .../jts/PackedCoordinateSequenceFactory.java       | 142 +++++++
 .../apache/sis/internal/feature/jts/Wrapper.java   |  15 +-
 5 files changed, 797 insertions(+), 48 deletions(-)

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 d650f9a..4a2a9a2 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
@@ -276,9 +276,12 @@ public abstract class Geometries<G> implements 
Serializable {
     /**
      * Returns whether this library can produce geometry backed by the {@code 
float} primitive type
      * instead of the {@code double} primitive type. If single-precision mode 
is supported, using
-     * that mode may reduce memory usage.
+     * that mode may reduce memory usage. This method is used for checking 
whether it is worth to
+     * invoke {@link Vector#isSinglePrecision()} for example.
      *
      * @return whether the library support single-precision values.
+     *
+     * @see Vector#isSinglePrecision()
      */
     public boolean supportSinglePrecision() {
         return false;
diff --git 
a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/jts/Factory.java
 
b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/jts/Factory.java
index f5721cb..1daf3ca 100644
--- 
a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/jts/Factory.java
+++ 
b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/jts/Factory.java
@@ -20,6 +20,8 @@ import java.util.List;
 import java.util.Arrays;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.function.Function;
+import java.util.function.BiFunction;
 import java.nio.ByteBuffer;
 import java.io.ObjectStreamException;
 import org.apache.sis.setup.GeometryLibrary;
@@ -67,6 +69,8 @@ public final class Factory extends Geometries<Geometry> {
 
     /**
      * The singleton instance of this factory.
+     *
+     * @see #factory(boolean)
      */
     public static final Factory INSTANCE = new Factory();
 
@@ -81,17 +85,22 @@ public final class Factory extends Geometries<Geometry> {
     }
 
     /**
-     * The factory to use for creating JTS geometries. Currently set to a 
factory using
-     * double-precision floating point numbers and a spatial-reference ID of 0.
+     * The factory to use for creating JTS geometries. Set to a factory using 
double-precision ({@code factory})
+     * or single-precision ({@code fctry32}) floating point numbers and a 
spatial-reference ID of 0.
+     *
+     * <p>Not serialized because {@link #readResolve()} will replace by {@link 
#INSTANCE}.</p>
+     *
+     * @see #factory(boolean)
      */
-    private final transient GeometryFactory factory;
+    private final transient GeometryFactory factory, fctry32;
 
     /**
      * Creates the singleton instance.
      */
     private Factory() {
         super(GeometryLibrary.JTS, Geometry.class, Point.class, 
LineString.class, Polygon.class);
-        factory = new GeometryFactory();            // Default to double 
precision and SRID of 0.
+        factory = new GeometryFactory(new 
PackedCoordinateSequenceFactory(true));
+        fctry32 = new GeometryFactory(new 
PackedCoordinateSequenceFactory(false));
     }
 
     /**
@@ -138,6 +147,48 @@ public final class Factory extends Geometries<Geometry> {
     }
 
     /**
+     * Returns {@code true} if the given sequence stores coordinates as 
single-precision floating point values.
+     */
+    static boolean isFloat(final CoordinateSequence cs) {
+        return (cs instanceof PackedCoordinateSequence.Float) ||
+               (cs instanceof 
org.locationtech.jts.geom.impl.PackedCoordinateSequence.Float);
+    }
+
+    /**
+     * Returns {@code true} if {@code previous} is {@code true} and the given 
point uses single-precision.
+     * This is a convenience method for less distraction in code using it.
+     *
+     * @param  previous  previous value of {@code isFloat} boolean.
+     * @param  geometry  the geometry to combine with previous value.
+     * @return new value of {@code isFloat} boolean.
+     */
+    static boolean isFloat(final boolean previous, final Point geometry) {
+        return previous && isFloat(geometry.getCoordinateSequence());
+    }
+
+    /**
+     * Returns the geometry factory to use for the given precision.
+     *
+     * @param  isFloat  {@code true} for single-precision, or {@code false} 
for double-precision (default).
+     * @return the JTS geometry factory for the given precision.
+     *
+     * @see #INSTANCE
+     */
+    public final GeometryFactory factory(final boolean isFloat) {
+        return isFloat ? fctry32 : factory;
+    }
+
+    /**
+     * Single-precision variant of {@link #createPoint(double, double)}.
+     *
+     * @return the point for the given coordinate values.
+     */
+    @Override
+    public Object createPoint(final float x, final float y) {
+        return fctry32.createPoint(new CoordinateXY(x, y));
+    }
+
+    /**
      * Creates a two-dimensional point from the given coordinates.
      *
      * @return the point for the given coordinate values.
@@ -173,6 +224,13 @@ public final class Factory extends Geometries<Geometry> {
         if (!is3D && dimension != BIDIMENSIONAL) {
             throw new UnsupportedOperationException(unsupported(dimension));
         }
+        boolean isFloat = true;
+        for (final Vector v : coordinates) {
+            if (v != null && !v.isSinglePrecision()) {
+                isFloat = false;
+                break;
+            }
+        }
         final List<Coordinate> coordList = new ArrayList<>(32);
         final List<Geometry> lines = new ArrayList<>();
         for (final Vector v : coordinates) {
@@ -191,14 +249,14 @@ public final class Factory extends Geometries<Geometry> {
                         coordList.add(c);
                     } else {
                         if (is3D) i++;
-                        toLineString(coordList, lines, polygon);
+                        toLineString(coordList, lines, polygon, isFloat);
                         coordList.clear();
                     }
                 }
             }
         }
-        toLineString(coordList, lines, polygon);
-        return toGeometry(lines, polygon);
+        toLineString(coordList, lines, polygon, isFloat);
+        return toGeometry(lines, polygon, isFloat);
     }
 
     /**
@@ -212,46 +270,59 @@ public final class Factory extends Geometries<Geometry> {
     @Override
     public GeometryWrapper<Geometry> createMultiPolygon(final Object[] 
geometries) {
         final Polygon[] polygons = new Polygon[geometries.length];
-        for (int i=0; i<geometries.length; i++) {
+        boolean isFloat = true;
+        for (int i=0; i < geometries.length; i++) {
             final Object polyline = unwrap(geometries[i]);
             final Polygon polygon;
             if (polyline instanceof Polygon) {
                 polygon = (Polygon) polyline;
-            } else if (polyline instanceof LinearRing) {
-                polygon = factory.createPolygon((LinearRing) polyline);
-                JTS.copyMetadata((Geometry) polyline, polygon);
-            } else if (polyline instanceof LineString) {
-                // Let JTS throws an exception with its own error message if 
the ring is not valid.
-                polygon = factory.createPolygon(((LineString) 
polyline).getCoordinateSequence());
-                JTS.copyMetadata((Geometry) polyline, polygon);
             } else {
-                throw new 
ClassCastException(Errors.format(Errors.Keys.IllegalArgumentClass_3,
-                        Strings.bracket("geometries", i), Polygon.class, 
Classes.getClass(polyline)));
+                final boolean fs;
+                final CoordinateSequence cs;
+                if (polyline instanceof LinearRing) {
+                    final LinearRing ring = (LinearRing) polyline;
+                    cs      = ring.getCoordinateSequence();
+                    fs      = isFloat(cs);
+                    polygon = factory(fs).createPolygon(ring);
+                } else if (polyline instanceof LineString) {
+                    // Let JTS throws an exception with its own error message 
if the ring is not valid.
+                    cs      = ((LineString) polyline).getCoordinateSequence();
+                    fs      = isFloat(cs);
+                    polygon = factory(fs).createPolygon(cs);
+                } else {
+                    throw new 
ClassCastException(Errors.format(Errors.Keys.IllegalArgumentClass_3,
+                            Strings.bracket("geometries", i), Polygon.class, 
Classes.getClass(polyline)));
+                }
+                JTS.copyMetadata((Geometry) polyline, polygon);
+                isFloat &= fs;
             }
             polygons[i] = polygon;
         }
-        return new Wrapper(factory.createMultiPolygon(polygons));
+        return new Wrapper(factory(isFloat).createMultiPolygon(polygons));
     }
 
     /**
      * Makes a line string or linear ring from the given coordinates, and adds 
the line string to the given list.
      * If the {@code polygon} argument is {@code true}, then this method 
creates polygons instead of line strings.
-     * If the given coordinates array is empty, then this method does nothing.
+     * If the given coordinates list is empty, then this method does nothing.
      * This method does not modify the given coordinates list.
      */
-    final void toLineString(final List<Coordinate> coordinates, final 
List<Geometry> addTo, final boolean polygon) {
+    final void toLineString(final List<Coordinate> coordinates, final 
List<Geometry> addTo,
+                            final boolean polygon, final boolean isFloat)
+    {
         final int s = coordinates.size();
         if (s >= 2) {
             final Coordinate[] ca = coordinates.toArray(new Coordinate[s]);
-            final Geometry geom;
+            final GeometryFactory gf = factory(isFloat);
+            final Geometry geometry;
             if (polygon) {
-                geom = factory.createPolygon(ca);
+                geometry = gf.createPolygon(ca);
             } else if (ca.length > 3 && ca[0].equals2D(ca[s-1])) {
-                geom = factory.createLinearRing(ca);
+                geometry = gf.createLinearRing(ca);
             } else {
-                geom = factory.createLineString(ca);        // Throws an 
exception if contains duplicated point.
+                geometry = gf.createLineString(ca);         // Throws an 
exception if contains duplicated point.
             }
-            addTo.add(geom);
+            addTo.add(geometry);
         }
     }
 
@@ -265,26 +336,50 @@ public final class Factory extends Geometries<Geometry> {
      *         of the type specified by the {@code polygon} argument.
      */
     @SuppressWarnings("SuspiciousToArrayCall")      // Type controlled by 
`polygon`.
-    final Geometry toGeometry(final List<Geometry> lines, final boolean 
polygon) {
+    final Geometry toGeometry(final List<Geometry> lines, final boolean 
polygon, final boolean isFloat) {
         final int s = lines.size();
         switch (s) {
             case 0:  {
                 // Create an empty polygon or linear ring.
-                return polygon ? factory.createPolygon   ((Coordinate[]) null)
-                               : factory.createLinearRing((Coordinate[]) null);
+                final GeometryFactory gf = factory(isFloat);
+                return polygon ? gf.createPolygon   ((Coordinate[]) null)
+                               : gf.createLinearRing((Coordinate[]) null);
             }
             case 1: {
                 return lines.get(0);
             }
             default: {
                 // An ArrayStoreException here would be a bug in our use of 
`polygon` boolean.
-                return polygon ? factory.createMultiPolygon   
(lines.toArray(new Polygon   [s]))
-                               : 
factory.createMultiLineString(lines.toArray(new LineString[s]));
+                final GeometryFactory gf = factory(isFloat);
+                return polygon ? gf.createMultiPolygon   (lines.toArray(new 
Polygon   [s]))
+                               : gf.createMultiLineString(lines.toArray(new 
LineString[s]));
             }
         }
     }
 
     /**
+     * Creates a geometry from an array of components, which may be single or 
double-precision.
+     * If all components use single-precision, then the returned geometry will 
use single-precision too.
+     *
+     * <p>We have to use generic type because JTS does not provide an
+     * {@code Geometry.getCoordinateSequence()} abstract method.</p>
+     */
+    private <G extends Geometry> Geometry createFromComponents(
+            final G[] geometries,
+            final Function<G,CoordinateSequence> csGetter,
+            final BiFunction<GeometryFactory, G[], Geometry> builder)
+    {
+        boolean isFloat = true;
+        for (final G geometry : geometries) {
+            if (!isFloat(csGetter.apply(geometry))) {
+                isFloat = false;
+                break;
+            }
+        }
+        return builder.apply(factory(isFloat), geometries);
+    }
+
+    /**
      * Creates a geometry from components.
      * The expected {@code components} type depend on the target geometry type:
      * <ul>
@@ -305,44 +400,75 @@ public final class Factory extends Geometries<Geometry> {
     public GeometryWrapper<Geometry> createFromComponents(final GeometryType 
type, final Object components) {
         final Geometry geometry;
         switch (type) {
-            // The ClassCastException that may happen here is part of method 
contract.
-            case GEOMETRY_COLLECTION: geometry = 
factory.createGeometryCollection((Geometry[]) components); break;
-            case MULTI_LINESTRING:    geometry = 
factory.createMultiLineString((LineString[]) components); break;
-            case MULTI_POLYGON:       geometry = 
factory.createMultiPolygon((Polygon[]) components); break;
+            case GEOMETRY_COLLECTION: {
+                // The ClassCastException that may happen here is part of 
method contract.
+                geometry = factory.createGeometryCollection((Geometry[]) 
components);
+                break;
+            }
+            case MULTI_LINESTRING: {
+                // The ClassCastException that may happen here is part of 
method contract.
+                geometry = createFromComponents((LineString[]) components,
+                            LineString::getCoordinateSequence,
+                            GeometryFactory::createMultiLineString);
+                break;
+            }
+            case MULTI_POLYGON: {
+                // The ClassCastException that may happen here is part of 
method contract.
+                geometry = createFromComponents((Polygon[]) components,
+                            (g) -> g.getExteriorRing().getCoordinateSequence(),
+                            GeometryFactory::createMultiPolygon);
+                break;
+            }
             case MULTI_POINT: {
                 if (components instanceof Point[]) {
-                    geometry = factory.createMultiPoint((Point[]) components);
+                    geometry = createFromComponents((Point[]) components,
+                                Point::getCoordinateSequence,
+                                GeometryFactory::createMultiPoint);
                     break;
                 }
                 // Else fallthrough
             }
             default: {
+                final GeometryFactory gf;
                 final CoordinateSequence cs;
                 if (components instanceof CoordinateSequence) {
                     cs = (CoordinateSequence) components;
+                    gf = factory(isFloat(cs));
                 } else {
                     final Coordinate[] coordinates;
                     if (components instanceof Coordinate[]) {
                         coordinates = (Coordinate[]) components;
+                        gf = factory;
                     } else {
                         // The ClassCastException that may happen here is part 
of method contract.
                         final Collection<?> source = (components instanceof 
Collection<?>)
                                 ? (Collection<?>) components : 
Arrays.asList((Object[]) components);
                         coordinates = new Coordinate[source.size()];
+                        boolean isFloat = true;
                         int n = 0;
                         for (final Object obj : source) {
-                            // The ClassCastException that may happen here is 
part of method contract.
-                            coordinates[n++] = (obj instanceof Point) ? 
((Point) obj).getCoordinate() : (Coordinate) obj;
+                            final Coordinate c;
+                            if (obj instanceof Point) {
+                                final Point p = (Point) obj;
+                                isFloat = isFloat(isFloat, p);
+                                c = p.getCoordinate();
+                            } else {
+                                // The ClassCastException that may happen here 
is part of method contract.
+                                c = (Coordinate) obj;
+                                isFloat = false;
+                            }
+                            coordinates[n++] = c;
                         }
+                        gf = factory(isFloat);
                     }
-                    cs = 
factory.getCoordinateSequenceFactory().create(coordinates);
+                    cs = gf.getCoordinateSequenceFactory().create(coordinates);
                 }
                 switch (type) {
                     case GEOMETRY:    // Default to multi-points for now.
-                    case MULTI_POINT: geometry = factory.createMultiPoint(cs); 
break;
-                    case LINESTRING:  geometry = factory.createLineString(cs); 
break;
-                    case POLYGON:     geometry = factory.createPolygon(cs); 
break;
-                    case POINT:       geometry = 
factory.createMultiPoint(cs).getCentroid(); break;
+                    case MULTI_POINT: geometry = gf.createMultiPoint(cs); 
break;
+                    case LINESTRING:  geometry = gf.createLineString(cs); 
break;
+                    case POLYGON:     geometry = gf.createPolygon(cs); break;
+                    case POINT:       geometry = 
gf.createMultiPoint(cs).getCentroid(); break;
                     default:          throw new AssertionError(type);
                 }
             }
diff --git 
a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/jts/PackedCoordinateSequence.java
 
b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/jts/PackedCoordinateSequence.java
new file mode 100644
index 0000000..de6ac65
--- /dev/null
+++ 
b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/jts/PackedCoordinateSequence.java
@@ -0,0 +1,473 @@
+/*
+ * 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.internal.feature.jts;
+
+import java.io.Serializable;
+import java.util.Arrays;
+import org.apache.sis.util.ArgumentChecks;
+import org.locationtech.jts.geom.Envelope;
+import org.locationtech.jts.geom.Coordinate;
+import org.locationtech.jts.geom.CoordinateXY;
+import org.locationtech.jts.geom.CoordinateXYM;
+import org.locationtech.jts.geom.CoordinateXYZM;
+import org.locationtech.jts.geom.CoordinateSequence;
+import org.locationtech.jts.geom.CoordinateSequences;
+
+
+/**
+ * A JTS coordinate sequence which stores coordinates in a single {@code 
float[]} or {@code double[]} array.
+ * This class serves the same purpose than {@link 
org.locationtech.jts.geom.impl.PackedCoordinateSequence}
+ * but without caching the {@code Coordinate[]} array.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @version 1.2
+ * @since   1.2
+ * @module
+ */
+abstract class PackedCoordinateSequence implements CoordinateSequence, 
Serializable {
+    /**
+     * For cross-version compatibility.
+     */
+    private static final long serialVersionUID = 6323915437380051705L;
+
+    /**
+     * Number of dimensions for this coordinate sequence.
+     *
+     * @see #getDimension()
+     */
+    protected final int dimension;
+
+    /**
+     * Whether this coordinate sequence has <var>z</var> and/or <var>M</var> 
coordinate values.
+     * This is a combination of {@link #Z_MASK} and {@link #M_MASK} bit masks.
+     *
+     * @see #hasZ()
+     * @see #hasM()
+     */
+    private final int hasZM;
+
+    /**
+     * Bit to set to 1 in the {@link #hasZM} mask if this coordinate sequence
+     * has <var>z</var> and/or <var>M</var> coordinate values.
+     */
+    private static final int Z_MASK = 1, M_MASK = 2;      // Z_MASK must be 1 
for bit twiddling reason.
+
+    /**
+     * Creates a new sequence initialized to a copy of the given sequence.
+     * This is for constructors implementing the {@link #copy()} method.
+     */
+    PackedCoordinateSequence(final PackedCoordinateSequence original) {
+        dimension = original.dimension;
+        hasZM     = original.hasZM;
+    }
+
+    /**
+     * Creates a new coordinate sequence for the given number of dimensions.
+     *
+     * @param  dimension  number of dimensions, including the number of 
measures.
+     * @param  measures   number of <var>M</var> coordinates.
+     */
+    PackedCoordinateSequence(final int dimension, final int measures) {
+        ArgumentChecks.ensurePositive("measures", measures);
+        ArgumentChecks.ensureBetween("dimension", Factory.BIDIMENSIONAL + 
measures,
+                       Math.addExact(Factory.TRIDIMENSIONAL, measures), 
dimension);
+        this.dimension = dimension;
+        int hasZM = (measures == 0) ? 0 : M_MASK;
+        if ((dimension - measures) >= Factory.TRIDIMENSIONAL) {
+            hasZM |= Z_MASK;
+        }
+        this.hasZM = hasZM;
+    }
+
+    /**
+     * Returns the number of spatial dimensions,
+     * which is {@value Factory#BIDIMENSIONAL} or {@value 
Factory#TRIDIMENSIONAL}.
+     */
+    private static int getSpatialDimension(final int hasZM) {
+        return Factory.BIDIMENSIONAL | (hasZM & Z_MASK);
+    }
+
+    /**
+     * Returns the number of dimensions for all coordinates in this sequence,
+     * including {@linkplain #getMeasures() measures}.
+     */
+    @Override
+    public final int getDimension() {
+        return dimension;
+    }
+
+    /**
+     * Returns the number of <var>M</var> coordinates.
+     */
+    @Override
+    public final int getMeasures() {
+        return dimension - getSpatialDimension(hasZM);
+    }
+
+    /**
+     * Returns whether this coordinate sequence has <var>z</var> coordinate 
values.
+     */
+    @Override
+    public final boolean hasZ() {
+        return (hasZM & Z_MASK) != 0;
+    }
+
+    /**
+     * Returns whether this coordinate sequence has <var>M</var> coordinate 
values.
+     */
+    @Override
+    public final boolean hasM() {
+        return (hasZM & M_MASK) != 0;
+    }
+
+    /**
+     * Returns the <var>x</var> coordinate value for the tuple at the given 
index.
+     */
+    @Override
+    public final double getX(final int index) {
+        return coordinate(index * dimension + X);
+    }
+
+    /**
+     * Returns the <var>y</var> coordinate value for the tuple at the given 
index.
+     */
+    @Override
+    public final double getY(final int index) {
+        return coordinate(index * dimension + Y);
+    }
+
+    /**
+     * Returns the <var>z</var> coordinate value for the tuple at the given 
index,
+     * or {@link java.lang.Double.NaN} if this sequence has no <var>z</var> 
coordinates.
+     */
+    @Override
+    public final double getZ(final int index) {
+        return (hasZM & Z_MASK) != 0 ? coordinate(index * dimension + Z) : 
java.lang.Double.NaN;
+    }
+
+    /**
+     * Returns the first <var>M</var> coordinate value for the tuple at the 
given index,
+     * or {@link java.lang.Double.NaN} if this sequence has no <var>M</var> 
coordinates.
+     */
+    @Override
+    public final double getM(final int index) {
+        switch (hasZM) {
+            default:              return java.lang.Double.NaN;
+            case M_MASK:          return coordinate(index * dimension + Z);
+            case M_MASK | Z_MASK: return coordinate(index * dimension + M);
+        }
+    }
+
+    /**
+     * Returns the coordinate tuple at the given index.
+     *
+     * @param  index  index of the coordinate tuple.
+     * @return coordinate tuple at the given index.
+     * @throws ArrayIndexOutOfBoundsException if the given index is out of 
bounds.
+     */
+    @Override
+    public final Coordinate getCoordinate(int index) {
+        index *= dimension;
+        final double x = coordinate(  index);
+        final double y = coordinate(++index);
+        switch (hasZM) {
+            default:              return new Coordinate    (x,y);
+            case 0:               return new CoordinateXY  (x,y);
+            case Z_MASK:          return new Coordinate    (x,y, 
coordinate(++index));
+            case M_MASK:          return new CoordinateXYM (x,y, 
coordinate(++index));
+            case Z_MASK | M_MASK: return new CoordinateXYZM(x,y, 
coordinate(++index), coordinate(++index));
+        }
+    }
+
+    /**
+     * Copies the coordinate tuple at the given index into the specified 
target.
+     *
+     * @param  index     index of the coordinate tuple.
+     * @param  dest  where to copy the coordinates.
+     * @throws ArrayIndexOutOfBoundsException if the given index is out of 
bounds.
+     */
+    @Override
+    @SuppressWarnings("fallthrough")
+    public final void getCoordinate(int index, final Coordinate dest) {
+        index *= dimension;
+        dest.x = coordinate(  index);
+        dest.y = coordinate(++index);
+        switch (hasZM) {
+            case Z_MASK:          dest.setZ(coordinate(++index)); break;
+            case Z_MASK | M_MASK: dest.setZ(coordinate(++index)); // Fall 
through
+            case M_MASK:          dest.setM(coordinate(++index)); break;
+        }
+    }
+
+    /**
+     * Returns the coordinate tuple at given index.
+     *
+     * @param  index  index of the coordinate tuple.
+     * @return coordinate tuple at the given index.
+     * @throws ArrayIndexOutOfBoundsException if the given index is out of 
bounds.
+     */
+    @Override
+    public final Coordinate getCoordinateCopy(int index) {
+        return getCoordinate(index);
+    }
+
+    /**
+     * Returns a coordinate value from the coordinate tuple at the given index.
+     * For performance reasons, this method does not check {@code dim} 
validity.
+     *
+     * @param  index  index of the coordinate tuple.
+     * @param  dim    index of the coordinate value in the tuple.
+     * @return value of the specified value in the coordinate tuple.
+     */
+    @Override
+    public final double getOrdinate(final int index, final int dim) {
+        return coordinate(index * dimension + dim);
+    }
+
+    /**
+     * Returns the coordinate value at the given index in the packed array.
+     *
+     * @param  index  index in the packed array.
+     * @return coordinate value at the given index.
+     */
+    abstract double coordinate(int index);
+
+    /**
+     * Sets all coordinates in this sequence. The length of the given array
+     * shall be equal to {@link #size()} (this is not verified).
+     */
+    abstract void setCoordinates(Coordinate[] values);
+
+    /**
+     * Sets all coordinates in this sequence. The size of the given sequence
+     * shall be equal to {@link #size()} (this is not verified).
+     */
+    void setCoordinates(CoordinateSequence values) {
+        setCoordinates(values.toCoordinateArray());
+    }
+
+    /**
+     * Coordinate sequence storing values in a packed {@code double[]} array.
+     */
+    static final class Double extends PackedCoordinateSequence {
+        /** For cross-version compatibility. */
+        private static final long serialVersionUID = 1940132733783453171L;
+
+        /** The packed coordinates. */
+        private final double[] coordinates;
+
+        /** Creates a new sequence initialized to a copy of the given 
sequence. */
+        private Double(final Double original) {
+            super(original);
+            coordinates = original.coordinates.clone();
+        }
+
+        /** Creates a new coordinate sequence for the given number of tuples. 
*/
+        Double(final int size, final int dimension, final int measures) {
+            super(dimension, measures);
+            coordinates = new double[Math.multiplyExact(size, dimension)];
+        }
+
+        /** Returns the number of coordinate tuples in this sequence. */
+        @Override public int size() {
+            return coordinates.length / dimension;
+        }
+
+        /** Returns the coordinate value at the given index in the packed 
array. */
+        @Override double coordinate(int index) {
+            return coordinates[index];
+        }
+
+        /** Sets a coordinate value for the coordinate tuple at the given 
index. */
+        @Override public void setOrdinate(int index, int dim, double value) {
+            coordinates[index * dimension + dim] = value;
+        }
+
+        /** Sets all coordinates in this sequence. */
+        @Override void setCoordinates(final Coordinate[] values) {
+            int t = 0;
+            for (final Coordinate c : values) {
+                for (int i=0; i<dimension; i++) {
+                    coordinates[t++] = c.getOrdinate(i);
+                }
+            }
+            assert t == coordinates.length;
+        }
+
+        /** Sets all coordinates in this sequence. */
+        @Override void setCoordinates(final CoordinateSequence values) {
+            if (values instanceof 
org.locationtech.jts.geom.impl.PackedCoordinateSequence.Double) {
+                
System.arraycopy(((org.locationtech.jts.geom.impl.PackedCoordinateSequence.Double)
 values).getRawCoordinates(), 0, coordinates, 0, coordinates.length);
+            } else {
+                super.setCoordinates(values);
+            }
+        }
+
+        /** Expands the given envelope to include the (x,y) coordinates of 
this sequence. */
+        @Override public Envelope expandEnvelope(final Envelope envelope) {
+            for (int i=0; i < coordinates.length; i += dimension) {
+                envelope.expandToInclude(coordinates[i], coordinates[i+1]);
+            }
+            return envelope;
+        }
+
+        /** Returns a copy of this sequence. */
+        @Override public CoordinateSequence copy() {
+            return new Double(this);
+        }
+
+        /** Returns a hash code value for this sequence. */
+        @Override public int hashCode() {
+            return Arrays.hashCode(coordinates) + super.hashCode();
+        }
+
+        /** Compares the given object with this sequence for equality. */
+        @Override public boolean equals(final Object obj) {
+            return super.equals(obj) && Arrays.equals(((Double) 
obj).coordinates, coordinates);
+        }
+    }
+
+    /**
+     * Coordinate sequence storing values in a packed {@code float[]} array.
+     */
+    static final class Float extends PackedCoordinateSequence {
+        /** For cross-version compatibility. */
+        private static final long serialVersionUID = 2625498691139718968L;
+
+        /** The packed coordinates. */
+        private final float[] coordinates;
+
+        /** Creates a new sequence initialized to a copy of the given 
sequence. */
+        private Float(final Float original) {
+            super(original);
+            coordinates = original.coordinates.clone();
+        }
+
+        /** Creates a new coordinate sequence for the given number of tuples. 
*/
+        Float(final int size, final int dimension, final int measures) {
+            super(dimension, measures);
+            coordinates = new float[Math.multiplyExact(size, dimension)];
+        }
+
+        /** Returns the number of coordinate tuples in this sequence. */
+        @Override public int size() {
+            return coordinates.length / dimension;
+        }
+
+        /** Returns the coordinate value at the given index in the packed 
array. */
+        @Override double coordinate(int index) {
+            return coordinates[index];
+        }
+
+        /** Sets a coordinate value for the coordinate tuple at the given 
index. */
+        @Override public void setOrdinate(int index, int dim, double value) {
+            coordinates[index * dimension + dim] = (float) value;
+        }
+
+        /** Sets all coordinates in this sequence. */
+        @Override void setCoordinates(final Coordinate[] values) {
+            int t = 0;
+            for (final Coordinate c : values) {
+                for (int i=0; i<dimension; i++) {
+                    coordinates[t++] = (float) c.getOrdinate(i);
+                }
+            }
+            assert t == coordinates.length;
+        }
+
+        /** Sets all coordinates in this sequence. */
+        @Override void setCoordinates(final CoordinateSequence values) {
+            if (values instanceof 
org.locationtech.jts.geom.impl.PackedCoordinateSequence.Float) {
+                
System.arraycopy(((org.locationtech.jts.geom.impl.PackedCoordinateSequence.Float)
 values).getRawCoordinates(), 0, coordinates, 0, coordinates.length);
+            } else {
+                super.setCoordinates(values);
+            }
+        }
+
+        /** Expands the given envelope to include the (x,y) coordinates of 
this sequence. */
+        @Override public Envelope expandEnvelope(final Envelope envelope) {
+            for (int i=0; i < coordinates.length; i += dimension) {
+                envelope.expandToInclude(coordinates[i], coordinates[i+1]);
+            }
+            return envelope;
+        }
+
+        /** Returns a copy of this sequence. */
+        @Override public CoordinateSequence copy() {
+            return new Float(this);
+        }
+
+        /** Returns a hash code value for this sequence. */
+        @Override public int hashCode() {
+            return Arrays.hashCode(coordinates) + super.hashCode();
+        }
+
+        /** Compares the given object with this sequence for equality. */
+        @Override public boolean equals(final Object obj) {
+            return super.equals(obj) && Arrays.equals(((Float) 
obj).coordinates, coordinates);
+        }
+    }
+
+    /**
+     * Returns a copy of all coordinates in this sequence.
+     */
+    @Override
+    public final Coordinate[] toCoordinateArray() {
+        final Coordinate[] coordinates = new Coordinate[size()];
+        for (int i=0; i < coordinates.length; i++) {
+            coordinates[i] = getCoordinate(i);
+        }
+        return coordinates;
+    }
+
+    /**
+     * Returns a string representation of this coordinate sequence.
+     */
+    public final String toString() {
+        return CoordinateSequences.toString(this);
+    }
+
+    /**
+     * Returns a hash code value for this sequence.
+     */
+    @Override
+    public int hashCode() {
+        return (37 * dimension) ^ hasZM;
+    }
+
+    /**
+     * Compares the given object with this sequence for equality.
+     */
+    @Override
+    public boolean equals(final Object obj) {
+        if (obj != null && obj.getClass() == getClass()) {
+            final PackedCoordinateSequence other = (PackedCoordinateSequence) 
obj;
+            return other.dimension == dimension && other.hasZM == hasZM;
+        }
+        return false;
+    }
+
+    /**
+     * Returns a copy of this sequence.
+     *
+     * @deprecated Inherits the deprecation status from JTS.
+     */
+    @Deprecated
+    public final Object clone() {
+        return copy();
+    }
+}
diff --git 
a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/jts/PackedCoordinateSequenceFactory.java
 
b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/jts/PackedCoordinateSequenceFactory.java
new file mode 100644
index 0000000..08a7a56
--- /dev/null
+++ 
b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/jts/PackedCoordinateSequenceFactory.java
@@ -0,0 +1,142 @@
+/*
+ * 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.internal.feature.jts;
+
+import java.io.Serializable;
+import org.locationtech.jts.geom.Coordinate;
+import org.locationtech.jts.geom.Coordinates;
+import org.locationtech.jts.geom.CoordinateSequence;
+import org.locationtech.jts.geom.CoordinateSequenceFactory;
+
+
+/**
+ * A factory of JTS coordinate sequence storing coordinates in a single {@code 
float[]} or {@code double[]} array.
+ * This class serves the same purpose than {@link 
org.locationtech.jts.geom.impl.PackedCoordinateSequenceFactory}
+ * but without caching the {@code Coordinate[]} array.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @version 1.2
+ * @since   1.2
+ * @module
+ */
+final class PackedCoordinateSequenceFactory implements 
CoordinateSequenceFactory, Serializable {
+    /**
+     * For cross-version compatibility.
+     */
+    private static final long serialVersionUID = 8160800176640629524L;
+
+    /**
+     * Whether to use double-precision floating point numbers.
+     */
+    protected final boolean doublePrecision;
+
+    /**
+     * Creates a new factory.
+     *
+     * @param  doublePrecision  whether to use double-precision floating point 
numbers.
+     */
+    PackedCoordinateSequenceFactory(final boolean doublePrecision) {
+        this.doublePrecision = doublePrecision;
+    }
+
+    /**
+     * Creates a new sequence with the given coordinates. All values are 
copied.
+     * The number of dimensions of the sequence is the minimal number of 
dimensions found in all coordinates.
+     * We use the minimal number because requesting a inexistent dimension or 
measure in {@link Coordinate}
+     * may cause an exception to be thrown.
+     *
+     * @param  coordinates  the coordinate values, or {@code null} for an 
empty sequence.
+     * @return a newly created coordinate sequence with the given coordinate 
values.
+     */
+    @Override
+    public CoordinateSequence create(final Coordinate[] coordinates) {
+        int dimension = Factory.TRIDIMENSIONAL;
+        int measures  = -1;
+        int size      =  0;
+        if (coordinates != null) {
+            size = coordinates.length;
+            for (final Coordinate c : coordinates) {
+                final int m = Coordinates.measures(c);
+                dimension = Math.min(dimension, Coordinates.dimension(c) - m);
+                if (m < measures || measures < 0) measures = m;
+            }
+        }
+        dimension += measures;
+        final PackedCoordinateSequence cs = create(size, dimension, measures);
+        if (size != 0) {
+            cs.setCoordinates(coordinates);
+        }
+        return cs;
+    }
+
+    /**
+     * Creates a new sequence as a copy of the given sequence.
+     *
+     * @param  original  the sequence to copy, or {@code null} for an empty 
sequence.
+     * @return a newly created coordinate sequence with the values of the 
given sequence.
+     */
+    @Override
+    public CoordinateSequence create(final CoordinateSequence original) {
+        if (original instanceof PackedCoordinateSequence) {
+            return original.copy();
+        }
+        final int dimension, measures, size;
+        if (original != null) {
+            dimension = original.getDimension();
+            measures  = original.getMeasures();
+            size      = original.size();
+        } else {
+            dimension = Factory.TRIDIMENSIONAL;
+            measures  = 0;
+            size      = 0;
+        }
+        final PackedCoordinateSequence cs = create(size, dimension, measures);
+        if (size != 0) {
+            cs.setCoordinates(original);
+        }
+        return cs;
+    }
+
+    /**
+     * Creates a new coordinate sequence for the given number of dimensions.
+     *
+     * @param  size       number of coordinate tuples.
+     * @param  dimension  number of dimensions, {@value Factory#BIDIMENSIONAL} 
or {@value Factory#TRIDIMENSIONAL}.
+     * @return a newly created coordinate sequence with all values initialized 
to zero.
+     */
+    @Override
+    public CoordinateSequence create(final int size, final int dimension) {
+        return create(size, dimension, 0);
+    }
+
+    /**
+     * Creates a new coordinate sequence for the given number of dimensions 
and measures.
+     *
+     * @param  size       number of coordinate tuples.
+     * @param  dimension  number of dimensions, including the number of 
measures.
+     * @param  measures   number of <var>M</var> coordinates.
+     * @return a newly created coordinate sequence with all values initialized 
to zero.
+     */
+    @Override
+    public PackedCoordinateSequence create(final int size, final int 
dimension, final int measures) {
+        if (doublePrecision) {
+            return new PackedCoordinateSequence.Double(size, dimension, 
measures);
+        } else {
+            return new PackedCoordinateSequence.Float(size, dimension, 
measures);
+        }
+    }
+}
diff --git 
a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/jts/Wrapper.java
 
b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/jts/Wrapper.java
index e49357f..e6c0b50 100644
--- 
a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/jts/Wrapper.java
+++ 
b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/jts/Wrapper.java
@@ -285,14 +285,17 @@ final class Wrapper extends GeometryWrapper<Geometry> {
     protected Geometry mergePolylines(final Iterator<?> polylines) {
         final List<Coordinate> coordinates = new ArrayList<>();
         final List<Geometry> lines = new ArrayList<>();
+        boolean isFloat = true;
 add:    for (Geometry next = geometry;;) {
             if (next instanceof Point) {
                 final Coordinate pt = ((Point) next).getCoordinate();
                 if (!Double.isNaN(pt.x) && !Double.isNaN(pt.y)) {
+                    isFloat = Factory.isFloat(isFloat, (Point) next);
                     coordinates.add(pt);
                 } else {
-                    Factory.INSTANCE.toLineString(coordinates, lines, false);
+                    Factory.INSTANCE.toLineString(coordinates, lines, false, 
isFloat);
                     coordinates.clear();
+                    isFloat = true;
                 }
             } else {
                 final int n = next.getNumGeometries();
@@ -301,21 +304,23 @@ add:    for (Geometry next = geometry;;) {
                     if (coordinates.isEmpty()) {
                         lines.add(ls);
                     } else {
+                        if (isFloat) isFloat = 
Factory.isFloat(ls.getCoordinateSequence());
                         coordinates.addAll(Arrays.asList(ls.getCoordinates()));
-                        Factory.INSTANCE.toLineString(coordinates, lines, 
false);
+                        Factory.INSTANCE.toLineString(coordinates, lines, 
false, isFloat);
                         coordinates.clear();
+                        isFloat = true;
                     }
                 }
             }
             /*
-             * 'polylines.hasNext()' check is conceptually part of 'for' 
instruction,
+             * `polylines.hasNext()` check is conceptually part of `for` 
instruction,
              * except that we need to skip this condition during the first 
iteration.
              */
             do if (!polylines.hasNext()) break add;
             while ((next = (Geometry) polylines.next()) == null);
         }
-        Factory.INSTANCE.toLineString(coordinates, lines, false);
-        return Factory.INSTANCE.toGeometry(lines, false);
+        Factory.INSTANCE.toLineString(coordinates, lines, false, isFloat);
+        return Factory.INSTANCE.toGeometry(lines, false, isFloat);
     }
 
     /**

Reply via email to