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); } /**