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]));
