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 5fb125cf40ea5fa7a9fe5fe1cfa0d4c6b3c6808f Author: Martin Desruisseaux <[email protected]> AuthorDate: Wed Jul 16 18:33:06 2025 +0200 If `Feature` property contains a geometry without CRS, fallback on the CRS defined in the `FeatureType`. --- .../org/apache/sis/feature/EnvelopeOperation.java | 2 +- .../apache/sis/filter/BinaryGeometryFilter.java | 34 +-- .../org/apache/sis/filter/BinarySpatialFilter.java | 5 +- .../main/org/apache/sis/filter/DistanceFilter.java | 5 +- .../apache/sis/filter/InvalidXPathException.java | 2 +- .../sis/filter/internal/GeometryConverter.java | 50 +++- .../sis/filter/internal/GeometryFromFeature.java | 108 +++++++ .../main/org/apache/sis/filter/internal/Node.java | 28 +- .../apache/sis/filter/sqlmm/FunctionWithSRID.java | 4 +- .../apache/sis/filter/sqlmm/GeometryParser.java | 3 +- .../org/apache/sis/filter/sqlmm/ST_Transform.java | 6 +- .../apache/sis/geometry/wrapper/Geometries.java | 74 ++++- .../sis/geometry/wrapper/GeometryWithCRS.java | 92 ------ .../sis/geometry/wrapper/GeometryWrapper.java | 316 ++++++++++++--------- .../apache/sis/geometry/wrapper/esri/Wrapper.java | 3 +- .../apache/sis/geometry/wrapper/j2d/Factory.java | 2 +- .../sis/geometry/wrapper/j2d/PointWrapper.java | 13 +- .../apache/sis/geometry/wrapper/j2d/Wrapper.java | 8 +- .../apache/sis/geometry/wrapper/jts/Factory.java | 28 +- .../org/apache/sis/geometry/wrapper/jts/JTS.java | 45 +-- .../apache/sis/geometry/wrapper/jts/Wrapper.java | 79 +++--- .../apache/sis/geometry/wrapper/jts/JTSTest.java | 6 +- .../storage/sql/feature/GeometryGetterTest.java | 8 +- .../sis/storage/geopackage/GpkgStoreTest.java | 2 +- 24 files changed, 520 insertions(+), 403 deletions(-) diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/EnvelopeOperation.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/EnvelopeOperation.java index fc464e7c87..349c759a98 100644 --- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/EnvelopeOperation.java +++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/EnvelopeOperation.java @@ -436,7 +436,7 @@ final class EnvelopeOperation extends AbstractOperation { * the actual CRS. */ if (sourceCRS != null && targetCRS != null) try { - if (op == null) { + if (op == null || sourceCRS != op.getSourceCRS()) { op = CRS.findOperation(sourceCRS, targetCRS, null); } if (!op.getMathTransform().isIdentity()) { diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/BinaryGeometryFilter.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/BinaryGeometryFilter.java index 685c3ea900..92502c388f 100644 --- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/BinaryGeometryFilter.java +++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/BinaryGeometryFilter.java @@ -18,8 +18,6 @@ package org.apache.sis.filter; import java.util.List; import javax.measure.Unit; -import javax.measure.IncommensurableException; -import org.opengis.util.FactoryException; import org.opengis.referencing.crs.CoordinateReferenceSystem; import org.opengis.referencing.operation.TransformException; import org.apache.sis.geometry.wrapper.Geometries; @@ -27,6 +25,7 @@ import org.apache.sis.geometry.wrapper.GeometryWrapper; import org.apache.sis.geometry.wrapper.SpatialOperationContext; import org.apache.sis.feature.privy.AttributeConvention; import org.apache.sis.filter.internal.Node; +import org.apache.sis.util.Exceptions; // Specific to the geoapi-3.1 and geoapi-4.0 branches: import org.opengis.filter.Filter; @@ -81,6 +80,7 @@ abstract class BinaryGeometryFilter<R> extends Node implements SpatialOperator<R /** * Creates a new binary function. * + * @param library the geometry library to use. * @param geometry1 the first of the two expressions to be used by this function. * @param geometry2 the second of the two expressions to be used by this function. * @param systemUnit if the CRS needs to be in some units of measurement, the {@link Unit#getSystemUnit()} value. @@ -102,20 +102,20 @@ abstract class BinaryGeometryFilter<R> extends Node implements SpatialOperator<R final int index; final Literal<R,?> literal; final GeometryWrapper value; - if (geometry2 instanceof Literal<?,?>) { - literal = (Literal<R,?>) geometry2; - value = expression2.apply(null); - index = 1; - } else if (geometry1 instanceof Literal<?,?>) { - literal = (Literal<R,?>) geometry1; - value = expression1.apply(null); - index = 0; - } else { - literal = null; - value = null; - index = -1; - } try { + if (geometry2 instanceof Literal<?,?>) { + literal = (Literal<R,?>) geometry2; + value = expression2.apply(null); + index = 1; + } else if (geometry1 instanceof Literal<?,?>) { + literal = (Literal<R,?>) geometry1; + value = expression1.apply(null); + index = 0; + } else { + literal = null; + value = null; + index = -1; + } context = new SpatialOperationContext(null, value, systemUnit, index); if (value != null) { final GeometryWrapper gt = context.transform(value); @@ -128,8 +128,8 @@ abstract class BinaryGeometryFilter<R> extends Node implements SpatialOperator<R } } } - } catch (FactoryException | TransformException | IncommensurableException e) { - throw new InvalidFilterValueException(e); + } catch (Exception e) { + throw new InvalidFilterValueException(Exceptions.unwrap(e)); } this.expression1 = expression1; this.expression2 = expression2; diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/BinarySpatialFilter.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/BinarySpatialFilter.java index 240cd00afc..3e03ba7213 100644 --- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/BinarySpatialFilter.java +++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/BinarySpatialFilter.java @@ -23,6 +23,7 @@ import org.apache.sis.geometry.WraparoundMethod; import org.apache.sis.geometry.wrapper.SpatialOperationContext; import org.apache.sis.geometry.wrapper.GeometryWrapper; import org.apache.sis.geometry.wrapper.Geometries; +import org.apache.sis.util.Exceptions; // Specific to the geoapi-3.1 and geoapi-4.0 branches: import org.opengis.filter.Expression; @@ -143,8 +144,8 @@ final class BinarySpatialFilter<R> extends BinaryGeometryFilter<R> implements Bi final GeometryWrapper right = expression2.apply(object); if (right != null) try { return left.predicate(operatorType, right, context); - } catch (RuntimeException e) { - warning(e, true); + } catch (Exception e) { + warning(Exceptions.unwrap(e), true); } } return emptyResult(); diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/DistanceFilter.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/DistanceFilter.java index 72d5a396fc..3fd8a10447 100644 --- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/DistanceFilter.java +++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/DistanceFilter.java @@ -25,6 +25,7 @@ import org.opengis.geometry.Geometry; import org.apache.sis.geometry.wrapper.Geometries; import org.apache.sis.geometry.wrapper.GeometryWrapper; import org.apache.sis.geometry.wrapper.SpatialOperationContext; +import org.apache.sis.util.Exceptions; // Specific to the geoapi-3.1 and geoapi-4.0 branches: import org.opengis.filter.Literal; @@ -160,8 +161,8 @@ final class DistanceFilter<R> extends BinaryGeometryFilter<R> implements Distanc final GeometryWrapper right = expression2.apply(object); if (right != null) try { return left.predicate(operatorType, right, distance, context); - } catch (RuntimeException e) { - warning(e, true); + } catch (Exception e) { + warning(Exceptions.unwrap(e), true); } } return emptyResult(); diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/InvalidXPathException.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/InvalidXPathException.java index 3a49222670..39c1a5d428 100644 --- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/InvalidXPathException.java +++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/InvalidXPathException.java @@ -21,7 +21,7 @@ import org.opengis.filter.InvalidFilterValueException; /** - * Exceptions thrown when the XPath in an expression is invalid or unsupported. + * Exception thrown when the XPath in an expression is invalid or unsupported. * Apache SIS currently supports only a small subset of XPath syntax, mostly paths of * the form {@code "a/b/c"} (and not everywhere) and the {@code "Q{namespace}"} syntax. * diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/internal/GeometryConverter.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/internal/GeometryConverter.java index fbdff72b61..5e77717b8c 100644 --- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/internal/GeometryConverter.java +++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/internal/GeometryConverter.java @@ -29,17 +29,18 @@ import org.apache.sis.geometry.ImmutableEnvelope; import org.apache.sis.geometry.WraparoundMethod; import org.apache.sis.geometry.wrapper.Geometries; import org.apache.sis.geometry.wrapper.GeometryWrapper; +import org.apache.sis.feature.internal.Resources; import org.apache.sis.filter.Optimization; // Specific to the geoapi-3.1 and geoapi-4.0 branches: -import org.opengis.coordinate.MismatchedDimensionException; import org.opengis.filter.Expression; import org.opengis.filter.InvalidFilterValueException; +import org.opengis.coordinate.MismatchedDimensionException; /** - * Expression whose results is a geometry wrapper. This converter evaluates another expression, - * which is given at construction time, potentially converts the result then wraps it. + * Expression whose result is a geometry wrapper. This converter evaluates another expression, + * which is given at construction time, then wraps the result in a {@link GeometryWrapper}. * * @author Martin Desruisseaux (Geomatys) * @author Alexis Manin (Geomatys) @@ -49,7 +50,7 @@ import org.opengis.filter.InvalidFilterValueException; * * @see org.apache.sis.filter.ConvertFunction */ -final class GeometryConverter<R,G> extends Node implements Optimization.OnExpression<R, GeometryWrapper> { +class GeometryConverter<R,G> extends Node implements Optimization.OnExpression<R, GeometryWrapper> { /** * For cross-version compatibility. */ @@ -66,7 +67,10 @@ final class GeometryConverter<R,G> extends Node implements Optimization.OnExpres final Geometries<G> library; /** - * The expression to be used by this operator. + * The expression which returns instances of a geometry library such as <abbr>JTS</abbr>. + * The objects returned by this expression shall be recognized by the {@linkplain #library}, + * with the addition of the following special cases: {@link DirectPosition}, {@link Envelope}, + * {@link GeographicBoundingBox} and (in some cases) {@code Feature}. * * @see #getParameters() */ @@ -74,14 +78,40 @@ final class GeometryConverter<R,G> extends Node implements Optimization.OnExpres final Expression<R,?> expression; /** - * Creates a new converter expression. + * Creates a new converter for the given expression producing library-specific objects. + * This constructor is for subclasses. Use {@link #create(Geometries, Expression)} instead. * * @param library the geometry library to use. - * @param expression the expression providing source values. + * @param expression the expression providing geometric objects of the given library. */ - public GeometryConverter(final Geometries<G> library, final Expression<R,?> expression) { - this.expression = Objects.requireNonNull(expression); + GeometryConverter(final Geometries<G> library, final Expression<R,?> expression) { this.library = Objects.requireNonNull(library); + this.expression = Objects.requireNonNull(expression); + } + + /** + * Creates a new converter for the given expression producing library-specific objects. + * + * @param library the geometry library to use. + * @param expression the expression providing geometric objects of the given library. + * @return the geometry converter. + */ + @SuppressWarnings("unchecked") + public static <R,G> GeometryConverter<R,G> create(final Geometries<G> library, final Expression<R,?> expression) { + if (expression instanceof GeometryConverter<?,?>) { + final var candidate = (GeometryConverter<R,?>) expression; + if (library.equals(candidate.library)) { + return (GeometryConverter<R,G>) expression; + } + throw new InvalidFilterValueException(Resources.format( + Resources.Keys.MixedGeometryImplementation_2, + library.library, candidate.library.library)); + } + GeometryConverter<?,G> candidate = GeometryFromFeature.tryCreate(library, expression); + if (candidate == null) { + return new GeometryConverter<>(library, expression); + } + return (GeometryConverter<R,G>) candidate; } /** @@ -90,7 +120,7 @@ final class GeometryConverter<R,G> extends Node implements Optimization.OnExpres */ @Override public Expression<R, GeometryWrapper> recreate(final Expression<R,?>[] effective) { - return new GeometryConverter<>(library, effective[0]); + return create(library, effective[0]); } /** diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/internal/GeometryFromFeature.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/internal/GeometryFromFeature.java new file mode 100644 index 0000000000..178a668113 --- /dev/null +++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/internal/GeometryFromFeature.java @@ -0,0 +1,108 @@ +/* + * 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.filter.internal; + +import org.opengis.referencing.crs.CoordinateReferenceSystem; +import org.apache.sis.geometry.wrapper.Geometries; +import org.apache.sis.geometry.wrapper.GeometryWrapper; +import org.apache.sis.feature.privy.AttributeConvention; +import org.apache.sis.filter.privy.XPath; + +// Specific to the geoapi-3.1 and geoapi-4.0 branches: +import org.opengis.feature.Feature; +import org.opengis.filter.Expression; +import org.opengis.filter.ValueReference; +import org.opengis.filter.InvalidFilterValueException; + + +/** + * Expression whose input is a feature instance and output is a geometry wrapper. + * This converter evaluates another expression, which is given at construction time, + * then wraps the result in a {@link GeometryWrapper}. + * + * @author Martin Desruisseaux (Geomatys) + * + * @param <G> the geometry implementation type. + */ +final class GeometryFromFeature<G> extends GeometryConverter<Feature, G> { + /** + * For cross-version compatibility. + */ + private static final long serialVersionUID = -550060050444279386L; + + /** + * Name of the property from which the geometry object is read, or {@code null} if none. + * This is used for fetching the default <abbr>CRS</abbr> when the geometry has none. + */ + private final String propertyName; + + /** + * Creates a new converter for the given expression producing library-specific objects. + * + * @param library the geometry library to use. + * @param expression the expression providing geometric objects of the given library. + */ + private GeometryFromFeature(Geometries<G> library, Expression<Feature,?> expression, String propertyName) { + super(library, expression); + this.propertyName = propertyName; + } + + /** + * Tries to create a new converter for the given expression. + * + * @param library the geometry library to use. + * @param expression the expression providing geometric objects of the given library. + * @return the geometry converter, or {@code null} if the given expression cannot be used. + */ + static <G> GeometryFromFeature<G> tryCreate(final Geometries<G> library, final Expression<?,?> expression) { + if (Feature.class.isAssignableFrom(expression.getResourceClass()) && expression instanceof ValueReference<?,?>) { + final var xpath = new XPath(((ValueReference<?,?>) expression).getXPath()); + if (xpath.path == null) { + /* + * The expression type is actually <? extends R>, so it is not really correct to cast to <R>. + * However, we are going to use <R> as input only, not as output. In such case, it is okay to + * ignore the fact that <R> may be a subtype of `Feature`. + */ + @SuppressWarnings("unchecked") + final var ve = (Expression<Feature,?>) expression; + return new GeometryFromFeature<>(library, ve, xpath.tip); + } + } + return null; + } + + /** + * Evaluates the expression and converts the value to a geometry wrapper. + * If the geometry library does not store the <abbr>CRS</abbr>, + * the coordinate reference system is taken from the feature. + * + * @param input the geometry to evaluate with this expression. + * @return the geometry wrapper, or {@code null} if the evaluated value is null. + * @throws InvalidFilterValueException if the expression result is not an instance of a supported type. + */ + @Override + public GeometryWrapper apply(final Feature input) { + final GeometryWrapper wrapper = super.apply(input); + if (wrapper.getCoordinateReferenceSystem() == null) { + final CoordinateReferenceSystem crs = AttributeConvention.getCRSCharacteristic(input, propertyName); + if (crs != null) { + wrapper.setCoordinateReferenceSystem(crs); + } + } + return wrapper; + } +} diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/internal/Node.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/internal/Node.java index 18788702b8..5be96dd66d 100644 --- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/internal/Node.java +++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/internal/Node.java @@ -36,6 +36,7 @@ import org.apache.sis.filter.privy.WarningEvent; import org.apache.sis.geometry.wrapper.Geometries; import org.apache.sis.geometry.wrapper.GeometryWrapper; import org.apache.sis.util.iso.Names; +import org.apache.sis.util.collection.BackingStoreException; import org.apache.sis.util.collection.DefaultTreeTable; import org.apache.sis.util.collection.TableColumn; import org.apache.sis.util.collection.TreeTable; @@ -51,9 +52,8 @@ import org.opengis.feature.AttributeType; /** - * Base class of Apache SIS implementation of OGC expressions, comparators or filters. - * {@code Node} instances are associated together in a tree, which can be formatted - * by {@link #toString()}. + * Base class of Apache <abbr>SIS</abbr> implementations of <abbr>OGC</abbr> expressions, comparators and filters. + * {@code Node} instances are organized in a tree which can be formatted by {@link #toString()}. * * @author Johann Sorel (Geomatys) * @author Martin Desruisseaux (Geomatys) @@ -94,15 +94,15 @@ public abstract class Node implements Serializable { * @see Expression#getFunctionName() */ protected static <T> AttributeType<T> createType(final Class<T> type, final Object name) { - // We do not use `Map.of(…)` for letting the attribute type constructor do the null check. + // We do not use `Map.of(…)` for better exception message in case of null name. return new DefaultAttributeType<>(Collections.singletonMap(DefaultAttributeType.NAME_KEY, name), type, 1, 1, null, (AttributeType<?>[]) null); } /** - * Returns the most specialized class of the given pair of class. A specialized class is guaranteed to exist + * Returns the most specialized class of the given pair of classes. A specialized class is guaranteed to exist * if parametrized type safety has not been bypassed with unchecked casts, because {@code <R>} is always valid. - * However this method is not guaranteed to be able to find that specialized type, because it could be none of + * However, this method is not guaranteed to be able to find that specialized type, because it could be none of * the given arguments if {@code t1}, {@code t2} and {@code <R>} are interfaces with {@code <R>} extending both * {@code t1} and {@code t2}. * @@ -169,33 +169,25 @@ public abstract class Node implements Serializable { /** * Returns an expression whose results is a geometry wrapper. + * Note that the {@code apply(R)} method of the returned expression may throw {@link BackingStoreException}. * * @param <R> the type of resources (e.g. {@link org.opengis.feature.Feature}) used as inputs. * @param <G> the geometry implementation type. * @param library the geometry library to use. - * @param expression the expression providing source values. + * @param expression the expression providing geometry instances of the given library. * @return an expression whose results is a geometry wrapper. * @throws InvalidFilterValueException if the given expression is already a wrapper * but for another geometry implementation. */ - @SuppressWarnings("unchecked") protected static <R,G> Expression<R, GeometryWrapper> toGeometryWrapper( final Geometries<G> library, final Expression<R,?> expression) { - if (expression instanceof GeometryConverter<?,?>) { - final Geometries<?> other = ((GeometryConverter<?,?>) expression).library; - if (library.equals(other)) { - return (GeometryConverter<R,G>) expression; - } - throw new InvalidFilterValueException(Resources.format( - Resources.Keys.MixedGeometryImplementation_2, library.library, other.library)); - } - return new GeometryConverter<>(library, expression); + return GeometryConverter.create(library, expression); } /** * If the given exception was wrapped by {@link #toGeometryWrapper(Geometries, Expression)}, - * returns the original expression. Otherwise returns the given expression. + * returns the original expression. Otherwise, returns the given expression as-is. * * @param <R> the type of resources (e.g. {@link org.opengis.feature.Feature}) used as inputs. * @param expression the expression to unwrap. diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/FunctionWithSRID.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/FunctionWithSRID.java index 2d751dd31e..fc3b821449 100644 --- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/FunctionWithSRID.java +++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/FunctionWithSRID.java @@ -87,10 +87,10 @@ abstract class FunctionWithSRID<R> extends SpatialFunction<R> { * * @param operation identification of the SQLMM operation. * @param parameters sub-expressions that will be evaluated to provide the parameters to the function. - * @param hasSRID whether the SRID is expected as one of {@link #PRESENT}, {@link #ABSENT} or {@link #MAYBE}. + * @param hasSRID whether the <abbr>SRID</abbr> is expected: {@link #PRESENT}, {@link #ABSENT} or {@link #MAYBE}. * * @todo The {@code MAYBE} flag could be removed if we know the type of value evaluated by the expression. - * For now it exists mostly because the last parameter given to {@code ST_Point} can be of various types. + * For now, it exists mostly because the last parameter given to {@code ST_Point} can be of various types. */ FunctionWithSRID(final SQLMM operation, final Expression<R,?>[] parameters, int hasSRID) { super(operation, parameters); diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/GeometryParser.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/GeometryParser.java index af5ac750ac..c9fe68215e 100644 --- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/GeometryParser.java +++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/GeometryParser.java @@ -20,6 +20,7 @@ import org.opengis.referencing.crs.CoordinateReferenceSystem; import org.apache.sis.geometry.wrapper.Geometries; import org.apache.sis.geometry.wrapper.GeometryWrapper; import org.apache.sis.util.Classes; +import org.apache.sis.util.Exceptions; import org.apache.sis.util.resources.Errors; // Specific to the geoapi-3.1 and geoapi-4.0 branches: @@ -112,7 +113,7 @@ abstract class GeometryParser<R,G> extends GeometryConstructor<R,G> { } return library.getGeometry(result); } catch (Exception e) { - warning(e, false); + warning(Exceptions.unwrap(e), false); } return null; } diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/ST_Transform.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/ST_Transform.java index 3d7b299ea4..0a3b7c8847 100644 --- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/ST_Transform.java +++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/ST_Transform.java @@ -49,7 +49,7 @@ import org.opengis.filter.InvalidFilterValueException; * </li> * </ol> * - * <h2>Limitation</h2> + * <h2>Limitations</h2> * <ul> * <li>Current implementation ignores the <var>z</var> and <var>m</var> values.</li> * <li>If the SRID is an integer, it is interpreted as an EPSG code. @@ -122,8 +122,8 @@ final class ST_Transform<R> extends FunctionWithSRID<R> { * by the second expression and returns the result. * * @param input the object from which to get a geometry. - * @return the transformed geometry, or {@code null} if the given object is not an instance of - * a supported geometry library (JTS, ERSI, Java2D…). + * @return the transformed geometry, or {@code null} if the given object is not an instance + * of a supported geometry library (<abbr>JTS</abbr>, <abbr>ERSI</abbr>, Java2D…). */ @Override public Object apply(final R input) { diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/geometry/wrapper/Geometries.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/geometry/wrapper/Geometries.java index fd33834c06..00c44ba879 100644 --- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/geometry/wrapper/Geometries.java +++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/geometry/wrapper/Geometries.java @@ -31,6 +31,7 @@ import org.apache.sis.geometry.AbstractEnvelope; import org.apache.sis.geometry.GeneralEnvelope; import org.apache.sis.geometry.WraparoundMethod; import org.apache.sis.feature.internal.Resources; +import org.apache.sis.feature.privy.AttributeConvention; import org.apache.sis.referencing.CRS; import org.apache.sis.referencing.cs.AxesConvention; import org.apache.sis.referencing.privy.AxisDirections; @@ -38,15 +39,19 @@ import org.apache.sis.system.Loggers; import org.apache.sis.math.Vector; import org.apache.sis.setup.GeometryLibrary; import org.apache.sis.util.resources.Errors; +import org.apache.sis.util.collection.BackingStoreException; + +// Specific to the geoapi-3.1 and 4.0 branches: +import org.opengis.feature.Feature; // Specific to the geoapi-4.0 branch: import org.opengis.coordinate.MismatchedDimensionException; /** - * Utility methods on geometric objects defined in libraries outside Apache SIS. + * Utility methods on geometric objects defined in libraries outside Apache <abbr>SIS</abbr>. * We use this class for isolating dependencies from the {@code org.apache.feature} package - * to ESRI's API or to Java Topology Suite (JTS) API. + * to <abbr>ESRI</abbr>'s <abbr>API</abbr> or to Java Topology Suite (<abbr>JTS</abbr>) API. * 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. * @@ -96,8 +101,8 @@ public abstract class Geometries<G> implements Serializable { /** * The fallback implementation to use if the default one is not available. * This is set by {@link GeometryFactories} and should not change after initialization. - * We do not synchronize accesses to this field because we keep it stable after - * {@link GeometryFactories} class initialization. + * We do not synchronize the accesses to this field because we keep the field value stable + * after {@link GeometryFactories} class initialization. * * <h4>Temporarily permitted change</h4> * {@link GeometryFactories#setStandard(Geometries)} temporarily permits a change of this field, @@ -108,7 +113,7 @@ public abstract class Geometries<G> implements Serializable { /** * {@code true} if {@link #pointClass} is not a subtype of {@link #rootClass}. - * This is true for Java2D and false for JTS and ESRI libraries. + * This is true for Java2D and false for <abbr>JTS</abbr> and <abbr>ESRI</abbr> libraries. */ private final transient boolean isPointClassDistinct; @@ -212,6 +217,7 @@ public abstract class Geometries<G> implements Serializable { * @param wrapper the wrapper for which to get the geometry, or {@code null}. * @return the geometry instance of the library requested by user, or {@code null} if the given wrapper was null. * @throws ClassCastException if the given wrapper is not an instance of the class expected by this factory. + * @throws BackingStoreException if the operation failed because of a checked exception. * * @see #getGeometryClass(GeometryType) * @see #implementation(Object) @@ -227,13 +233,56 @@ public abstract class Geometries<G> implements Serializable { return wrapper.implementation(); } + /** + * Wraps the geometry stored in a property of the given feature. This method should be used + * instead of {@link #wrap(Object)} when the value come from a feature instance in order to + * allow <abbr>SIS</abbr> to fetch a default <abbr>CRS</abbr> when the geometry object does + * not specify the <abbr>CRS</abbr> itself. + * + * @param feature the feature from which wrap a geometry, or {@code null} if none. + * @param property the name of the property from which to get the default <abbr>CRS</abbr>. + * @return a wrapper for the geometry implementation of the given feature, or empty value. + * @throws BackingStoreException if the operation failed because of a checked exception. + */ + public static Optional<GeometryWrapper> wrap(final Feature feature, final String property) { + if (feature == null) { + return Optional.empty(); + } + final Optional<GeometryWrapper> value = wrap(feature.getPropertyValue(property)); + value.ifPresent((wrapper) -> { + if (wrapper.crs == null) { + wrapper.crs = AttributeConvention.getCRSCharacteristic(feature, property); + } + }); + return value; + } + + /** + * Wraps the default geometry of the given feature. This method should be used instead of {@link #wrap(Object)} + * when possible because it allows <abbr>SIS</abbr> to fetch a default <abbr>CRS</abbr> when the geometry object + * does not specify the <abbr>CRS</abbr> itself. + * + * @param feature the feature from which wrap the default geometry, or {@code null} if none. + * @return a wrapper for the geometry implementation of the given feature, or empty value. + * @throws BackingStoreException if the operation failed because of a checked exception. + */ + public static Optional<GeometryWrapper> wrap(final Feature feature) { + return wrap(feature, AttributeConvention.GEOMETRY); + } + /** * Wraps the given geometry implementation if recognized. * If the given object is already an instance of {@link GeometryWrapper}, then it is returned as-is. * If the given object is not recognized, then this method returns an empty value. * + * <h4>Recommended alternative</h4> + * Prefers {@link #wrap(Feature)} for wrapping the default geometry of a feature instance. + * This is preferred for allowing <abbr>SIS</abbr> to fetch the default <abbr>CRS</abbr> + * from the feature type when that information was not present in the geometry object. + * * @param geometry the geometry instance to wrap (can be {@code null}). * @return a wrapper for the given geometry implementation, or empty value. + * @throws BackingStoreException if the operation failed because of a checked exception. * * @see #castOrWrap(Object) * @see #implementation(Object) @@ -271,6 +320,7 @@ public abstract class Geometries<G> implements Serializable { * @return a wrapper for the given geometry implementation, or {@code null} if the given object was null. * @throws ClassCastException if the given object is not a wrapper or a geometry object * of the implementation of the library identified by {@link #library}. + * @throws BackingStoreException if the operation failed because of a checked exception. * * @see #wrap(Object) */ @@ -325,6 +375,7 @@ public abstract class Geometries<G> implements Serializable { * * @param point the point to convert to a geometry. * @return the given point converted to a geometry. + * @throws BackingStoreException if the operation failed because of a checked exception. */ public final GeometryWrapper createPoint(final DirectPosition point) { final Object geometry; @@ -350,6 +401,7 @@ public abstract class Geometries<G> implements Serializable { * @param x the first coordinate value. * @param y the second coordinate value. * @return the point for the given coordinate values. + * @throws BackingStoreException if the operation failed because of a checked exception. * * @see Capability#SINGLE_PRECISION */ @@ -365,6 +417,7 @@ public abstract class Geometries<G> implements Serializable { * @param x the first coordinate value. * @param y the second coordinate value. * @return the point for the given coordinate values. + * @throws BackingStoreException if the operation failed because of a checked exception. * * @see GeometryWrapper#getPointCoordinates() */ @@ -379,6 +432,7 @@ public abstract class Geometries<G> implements Serializable { * @param y the second coordinate value. * @param z the third coordinate value. * @return the point for the given coordinate values. + * @throws BackingStoreException if the operation failed because of a checked exception. * * @see Capability#Z_COORDINATE * @see GeometryWrapper#getPointCoordinates() @@ -396,6 +450,7 @@ public abstract class Geometries<G> implements Serializable { * @param dimensions the dimensions of the coordinate tuple. * @param coordinates a (x,y), (x,y,z), (x,y,m) or (x,y,z,m) coordinate tuple. * @return the point for the given coordinate values. + * @throws BackingStoreException if the operation failed because of a checked exception. */ public abstract Object createPoint(boolean isFloat, Dimensions dimensions, DoubleBuffer coordinates); @@ -412,6 +467,7 @@ public abstract class Geometries<G> implements Serializable { * @param dimensions the dimensions of the coordinate tuples. * @param coordinates sequence of (x,y), (x,y,z), (x,y,m) or (x,y,z,m) coordinate tuples. * @return the collection of points for the given points. + * @throws BackingStoreException if the operation failed because of a checked exception. */ public abstract G createMultiPoint(boolean isFloat, Dimensions dimensions, DoubleBuffer coordinates); @@ -438,6 +494,7 @@ public abstract class Geometries<G> implements Serializable { * @throws UnsupportedOperationException if the geometry library cannot create the requested collection. * @throws IllegalArgumentException if a polygon was requested but the given coordinates do not make * a closed shape (linear ring). + * @throws BackingStoreException if the operation failed because of a checked exception. */ public abstract G createPolyline(boolean polygon, boolean isFloat, Dimensions dimensions, DoubleBuffer... coordinates); @@ -453,6 +510,7 @@ public abstract class Geometries<G> implements Serializable { * @throws UnsupportedOperationException if the geometry library cannot create the requested collection. * @throws IllegalArgumentException if a polygon was requested but the given coordinates do not make * a closed shape (linear ring). + * @throws BackingStoreException if the operation failed because of a checked exception. */ public final G createPolyline(final boolean polygon, final Dimensions dimensions, final Vector... coordinates) { boolean isFloat = true; @@ -485,6 +543,7 @@ public abstract class Geometries<G> implements Serializable { * @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 backing library. + * @throws BackingStoreException if the operation failed because of a checked exception. * * @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. @@ -512,6 +571,7 @@ public abstract class Geometries<G> implements Serializable { * @throws IllegalArgumentException if the given geometry type is not supported. * @throws ClassCastException if {@code components} is not an array or a collection of supported geometry components. * @throws ArrayStoreException if {@code components} is an array with invalid component type. + * @throws BackingStoreException if the operation failed because of a checked exception. */ public abstract GeometryWrapper createFromComponents(GeometryType type, Object components); @@ -523,6 +583,7 @@ public abstract class Geometries<G> implements Serializable { * @throws IllegalArgumentException if the library has no specific type for the given components. * @throws ClassCastException if {@code components} is not an array or a collection of supported geometry components. * @throws ArrayStoreException if {@code components} is an array with invalid component type. + * @throws BackingStoreException if the operation failed because of a checked exception. */ protected final GeometryWrapper createFromComponents(final Object components) { final Class<?> c = components.getClass(); @@ -544,6 +605,7 @@ public abstract class Geometries<G> implements Serializable { * @param expand whether to expand the envelope to full axis range if there is a wraparound. * @param addPts whether to allow insertion of intermediate points on edges of axis domains. * @return a polyline made of a sequence of at least 5 points describing the given rectangle. + * @throws BackingStoreException if the operation failed because of a checked exception. */ private GeometryWrapper createGeometry2D(final Envelope envelope, final int xd, final int yd, final boolean expand, final boolean addPts) @@ -622,6 +684,7 @@ public abstract class Geometries<G> implements Serializable { * @param envelope the envelope to convert. * @param strategy how to resolve wrap-around ambiguities on the envelope. * @return the envelope as a polygon, or potentially as two polygons in {@link WraparoundMethod#SPLIT} case. + * @throws BackingStoreException if the operation failed because of a checked exception. */ public GeometryWrapper toGeometry2D(final Envelope envelope, final WraparoundMethod strategy) { int xd = 0, yd = 1; @@ -694,6 +757,7 @@ public abstract class Geometries<G> implements Serializable { * @param geometry the geometry to wrap. * @return wrapper for the given geometry. * @throws ClassCastException if the given geometry is not an instance of valid type. + * @throws BackingStoreException if the operation failed because of a checked exception. * * @see #castOrWrap(Object) */ diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/geometry/wrapper/GeometryWithCRS.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/geometry/wrapper/GeometryWithCRS.java deleted file mode 100644 index 7b594beff1..0000000000 --- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/geometry/wrapper/GeometryWithCRS.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * 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.geometry.wrapper; - -import org.opengis.referencing.crs.CoordinateReferenceSystem; -import org.apache.sis.geometry.GeneralEnvelope; -import org.apache.sis.util.ArgumentChecks; - - -/** - * A geometry wrapper with a field for CRS information. This base class is used when the geometry implementation - * to wrap does not store CRS information by itself. See {@link GeometryWrapper} for more information. - * - * @author Martin Desruisseaux (Geomatys) - */ -public abstract class GeometryWithCRS extends GeometryWrapper { - /** - * The coordinate reference system, or {@code null} if unspecified. - */ - private CoordinateReferenceSystem crs; - - /** - * Creates a new instance initialized with null CRS. - */ - protected GeometryWithCRS() { - } - - /** - * Gets the Coordinate Reference System (CRS) of this geometry. - * - * @return the geometry CRS, or {@code null} if unknown. - */ - @Override - public final CoordinateReferenceSystem getCoordinateReferenceSystem() { - return crs; - } - - /** - * Sets the coordinate reference system, which shall be two-dimensional. - * - * @param crs the coordinate reference system to set. - */ - @Override - public final void setCoordinateReferenceSystem(final CoordinateReferenceSystem crs) { - ArgumentChecks.ensureDimensionMatches("crs", Geometries.BIDIMENSIONAL, crs); - this.crs = crs; - } - - /** - * Returns {@code true} if the given geometry use the same CRS as this geometry, or conservatively - * returns {@code false} in case of doubt. This method should perform only a cheap test; it is used - * as a way to filter rapidly if {@link #transform(CoordinateReferenceSystem)} needs to be invoked. - * - * @param other the second geometry. - * @return {@code true} if the two geometries use equivalent CRS or if the CRS is undefined on both side, - * or {@code false} in case of doubt. - */ - @Override - public final boolean isSameCRS(final GeometryWrapper other) { - /* - * Identity comparison is often sufficient since all geometries typically share the same CRS. - * If they are not the same instance, a more expensive `equalsIgnoreMetadata(…)` method here - * would probably duplicate the work done later by the `transform(Geometry, …)` method. - */ - return crs == ((GeometryWithCRS) other).crs; - } - - /** - * Creates an initially empty envelope with the CRS of this geometry. - * If this geometry has no CRS, then a two-dimensional envelope is created. - * This is a convenience method for {@link #getEnvelope()} implementations. - * - * @return an initially empty envelope. - */ - protected final GeneralEnvelope createEnvelope() { - return (crs != null) ? new GeneralEnvelope(crs) : new GeneralEnvelope(Geometries.BIDIMENSIONAL); - } -} diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/geometry/wrapper/GeometryWrapper.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/geometry/wrapper/GeometryWrapper.java index 252da84b90..0ccc9fb2c4 100644 --- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/geometry/wrapper/GeometryWrapper.java +++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/geometry/wrapper/GeometryWrapper.java @@ -33,10 +33,13 @@ import org.opengis.referencing.operation.TransformException; import org.opengis.util.FactoryException; import org.apache.sis.geometry.GeneralEnvelope; import org.apache.sis.filter.sqlmm.SQLMM; +import org.apache.sis.referencing.CRS; +import org.apache.sis.referencing.operation.transform.MathTransforms; import org.apache.sis.referencing.privy.ReferencingUtilities; -import org.apache.sis.util.UnconvertibleObjectException; +import org.apache.sis.util.ArgumentChecks; import org.apache.sis.util.Classes; import org.apache.sis.util.Debug; +import org.apache.sis.util.UnconvertibleObjectException; import org.apache.sis.util.collection.BackingStoreException; import org.apache.sis.util.resources.Errors; @@ -47,23 +50,44 @@ import org.opengis.geometry.TransfiniteSet; import org.opengis.geometry.complex.Complex; import org.opengis.filter.SpatialOperatorName; import org.opengis.filter.DistanceOperatorName; -import org.opengis.filter.InvalidFilterValueException; + +// Specific to the geoapi-4.0 branch: +import org.opengis.coordinate.MismatchedDimensionException; /** - * Wraps a JTS, ESRI or Java2D geometry behind a {@code Geometry} interface. + * Wraps a <abbr>JTS</abbr>, <abbr>ESRI</abbr> or Java2D geometry behind a {@code Geometry} interface. + * + * <h4>Future plans</h4> * This is a temporary class to be refactored later as a more complete geometry framework. * The methods provided in this class are not committed API, and often not even clean API. - * They are only utilities added for very specific Apache SIS needs and will certainly - * change without warning in future Apache SIS version. + * They are only utilities added for very specific Apache <abbr>SIS</abbr> needs and will + * certainly change without warning in future Apache <abbr>SIS</abbr> versions. * * @author Martin Desruisseaux (Geomatys) * * @see Geometries#wrap(Object) */ public abstract class GeometryWrapper extends AbstractGeometry implements Geometry { + /** + * The coordinate reference system, or {@code null} if unspecified. + * The value of this field should be set by subclass constructors. + * + * <h4>Where this value come from</h4> + * This information is sometime redundant with information stored in the geometry itself. + * However, even when the geometry library supports some kind of <abbr>SRID</abbr> field, + * the value in geometry instances is sometime 0 or null. In such case, this {@code crs} + * field may come from other sources such as characteristics of the {@code FeatureType}. + * + * @see #getCoordinateDimension() + * @see #getCoordinateReferenceSystem() + * @see #setCoordinateReferenceSystem(CoordinateReferenceSystem) + */ + protected CoordinateReferenceSystem crs; + /** * Creates a new geometry object. + * Subclasses should set the {@link #crs} field if known. */ protected GeometryWrapper() { } @@ -89,11 +113,11 @@ public abstract class GeometryWrapper extends AbstractGeometry implements Geomet protected abstract Object implementation(); /** - * Returns the Spatial Reference System Identifier (SRID) if available. - * The SRID is used in database such as PostGIS and is generally database-dependent. - * This is <em>not</em> necessarily an EPSG code, even if it is a common practice - * to use the same numerical values as EPSG. Note that the absence of SRID does - * not mean that {@link #getCoordinateReferenceSystem()} would return no CRS. + * Returns the Spatial Reference System Identifier (<abbr>SRID</abbr>) if available. + * The <abbr>SRID</abbr> is used in databases such as PostGIS and is generally database-dependent. + * This is <em>not</em> necessarily an <abbr>EPSG</abbr> code, even if it is a common practice to + * use the same numerical values as <abbr>EPSG</abbr>. Note that the absence of <abbr>SRID</abbr> + * does not mean that {@link #getCoordinateReferenceSystem()} would return no <abbr>CRS</abbr>. * * <p>Users should invoke the {@link #getCoordinateReferenceSystem()} method instead. * This {@code getSRID()} method is provided for classes such as {@code DataStore} backed by an SQL database. @@ -108,26 +132,54 @@ public abstract class GeometryWrapper extends AbstractGeometry implements Geomet } /** - * Gets the Coordinate Reference System (CRS) of this geometry. In some libraries (for example JTS) the CRS - * is stored in the {@link Geometries#rootClass} instances of that library. In other libraries (e.g. Java2D) - * the CRS is stored only in this {@code GeometryWrapper} instance. + * Gets the Coordinate Reference System (<abbr>CRS</abbr>) of the geometry. + * In some libraries such as <abbr>JTS</abbr>, some <abbr>CRS</abbr> information can be stored in the + * geometry object of that library. In other libraries such as Java2D, the <abbr>CRS</abbr> is stored + * only in this {@code GeometryWrapper} instance. * - * @return the geometry CRS, or {@code null} if unknown. - * @throws BackingStoreException if the CRS is defined by a SRID code and that code cannot be used. + * @return the geometry <abbr>CRS</abbr>, or {@code null} if unknown. */ @Override - public abstract CoordinateReferenceSystem getCoordinateReferenceSystem(); + public final CoordinateReferenceSystem getCoordinateReferenceSystem() { + return crs; + } /** * Sets the coordinate reference system. * This method should be invoked only for newly created geometries. If the geometry library supports - * user objects (e.g. JTS), there is no guarantee that this method will not overwrite user setting. + * user objects (e.g. JTS), there is no guarantee that this method will not overwrite user's setting. * * @param crs the coordinate reference system to set. + * @throws MismatchedDimensionException if the <abbr>CRS</abbr> does not have the expected number of dimensions. * * @see #transform(CoordinateReferenceSystem) */ - public abstract void setCoordinateReferenceSystem(CoordinateReferenceSystem crs); + public void setCoordinateReferenceSystem(final CoordinateReferenceSystem crs) { + ArgumentChecks.ensureDimensionMatches("crs", getCoordinateDimension(), crs); + this.crs = crs; + } + + /** + * Returns the dimension of the coordinates that define this geometry. + * It must be the same as the dimension of the coordinate reference system for this geometry. + * + * @return the coordinate dimension. + */ + @Override + public int getCoordinateDimension() { + return Geometries.BIDIMENSIONAL; + } + + /** + * Creates an initially empty envelope with the <abbr>CRS</abbr> of this geometry. + * If this geometry has no <abbr>CRS</abbr>, then a two- or three-dimensional envelope is created. + * This is a convenience method for {@link #getEnvelope()} implementations. + * + * @return an initially empty envelope. + */ + protected final GeneralEnvelope createEnvelope() { + return (crs != null) ? new GeneralEnvelope(crs) : new GeneralEnvelope(getCoordinateDimension()); + } /** * Returns the geometry bounding box, together with its coordinate reference system. @@ -185,6 +237,7 @@ public abstract class GeometryWrapper extends AbstractGeometry implements Geomet * @param paths the points or polylines to merge in a single polyline instance. * @return the merged polyline (may be the underlying geometry of {@code this} but never {@code null}). * @throws ClassCastException if collection elements are not instances of the point or geometry class. + * @throws BackingStoreException if the operation failed because of a checked exception. */ public abstract Object mergePolylines(final Iterator<?> paths); @@ -198,23 +251,23 @@ public abstract class GeometryWrapper extends AbstractGeometry implements Geomet * @param context the preferred CRS and other context to use if geometry transformations are needed. * @return result of applying the specified predicate. * @throws UnsupportedOperationException if the operation cannot be performed with current implementation. - * @throws InvalidFilterValueException if an error occurred while executing the operation on given geometries. + * @throws FactoryException if transformation to the target <abbr>CRS</abbr> cannot be found. + * @throws TransformException if a geometry cannot be transformed. + * @throws IncommensurableException if a unit conversion was necessary but failed. + * @throws BackingStoreException if the operation failed because of another checked exception. */ public final boolean predicate(final DistanceOperatorName type, final GeometryWrapper other, final Quantity<Length> distance, final SpatialOperationContext context) + throws FactoryException, TransformException, IncommensurableException { - final GeometryWrapper[] geometries = new GeometryWrapper[] {this, other}; - try { - if (context.transform(geometries)) { - double dv = distance.getValue().doubleValue(); - final Unit<?> unit = ReferencingUtilities.getUnit(context.commonCRS); - if (unit != null) { - dv = distance.getUnit().getConverterToAny(unit).convert(dv); - } - return geometries[0].predicateSameCRS(type, geometries[1], dv); + final var geometries = new GeometryWrapper[] {this, other}; + if (context.transform(geometries)) { + double dv = distance.getValue().doubleValue(); + final Unit<?> unit = ReferencingUtilities.getUnit(context.commonCRS); + if (unit != null) { + dv = distance.getUnit().getConverterToAny(unit).convert(dv); } - } catch (FactoryException | TransformException | IncommensurableException e) { - throw new InvalidFilterValueException(e); + return geometries[0].predicateSameCRS(type, geometries[1], dv); } /* * No common CRS. Consider that we have no intersection, no overlap, etc. @@ -235,18 +288,18 @@ public abstract class GeometryWrapper extends AbstractGeometry implements Geomet * @param context the preferred CRS and other context to use if geometry transformations are needed. * @return result of applying the specified predicate. * @throws UnsupportedOperationException if the operation cannot be performed with current implementation. - * @throws InvalidFilterValueException if an error occurred while executing the operation on given geometries. + * @throws FactoryException if transformation to the target <abbr>CRS</abbr> cannot be found. + * @throws TransformException if a geometry cannot be transformed. + * @throws IncommensurableException if a unit conversion was necessary but failed. + * @throws BackingStoreException if the operation failed because of another checked exception. */ public final boolean predicate(final SpatialOperatorName type, final GeometryWrapper other, final SpatialOperationContext context) + throws FactoryException, TransformException, IncommensurableException { - final GeometryWrapper[] geometries = new GeometryWrapper[] {this, other}; - try { - if (context.transform(geometries)) { - return geometries[0].predicateSameCRS(type, geometries[1]); - } - } catch (FactoryException | TransformException | IncommensurableException e) { - throw new InvalidFilterValueException(e); + final var geometries = new GeometryWrapper[] {this, other}; + if (context.transform(geometries)) { + return geometries[0].predicateSameCRS(type, geometries[1]); } /* * No common CRS. Consider that we have no intersection, no overlap, etc. @@ -256,7 +309,7 @@ public abstract class GeometryWrapper extends AbstractGeometry implements Geomet } /** - * Applies a SQLMM operation on this geometry. + * Applies a <abbr>SQLMM</abbr> operation on this geometry. * This method shall be invoked only for operations without non-geometric parameters. * * @param operation the SQLMM operation to apply. @@ -264,6 +317,7 @@ public abstract class GeometryWrapper extends AbstractGeometry implements Geomet * @throws UnsupportedOperationException if the operation cannot be performed with current implementation. * @throws ClassCastException if the operation can only be executed on some specific geometry subclasses * (for example polylines) and the wrapped geometry is not of that class. + * @throws BackingStoreException if the operation failed because of a checked exception. */ public final Object operation(final SQLMM operation) { assert operation.geometryCount() == 1 && operation.maxParamCount == 1 : operation; @@ -273,7 +327,7 @@ public abstract class GeometryWrapper extends AbstractGeometry implements Geomet } /** - * Applies a SQLMM operation on two geometries. + * Applies a <abbr>SQLMM</abbr> operation on two geometries. * This method shall be invoked only for operations without non-geometric parameters. * The second geometry is transformed to the same CRS as this geometry for conformance with SQLMM standard. * @@ -284,18 +338,17 @@ public abstract class GeometryWrapper extends AbstractGeometry implements Geomet * @throws ClassCastException if the operation can only be executed on some specific geometry subclasses * (for example polylines) and the wrapped geometry is not of that class. * @throws TransformException if it was necessary to transform the other geometry and that transformation failed. + * @throws BackingStoreException if the operation failed because of another checked exception. */ - public final Object operation(final SQLMM operation, final GeometryWrapper other) - throws TransformException - { + public final Object operation(final SQLMM operation, final GeometryWrapper other) throws TransformException { assert operation.geometryCount() == 2 && operation.maxParamCount == 2 : operation; - final Object result = operationSameCRS(operation, toSameCRS(other), null); + final Object result = operationSameCRS(operation, other.transform(crs), null); assert isInstance(operation, result) : result; return result; } /** - * Applies a SQLMM operation on this geometry with one operation-specific argument. + * Applies a <abbr>SQLMM</abbr> operation on this geometry with one operation-specific argument. * The argument shall be non-null, unless the argument is optional. * * @param operation the SQLMM operation to apply. @@ -304,12 +357,13 @@ public abstract class GeometryWrapper extends AbstractGeometry implements Geomet * @throws UnsupportedOperationException if the operation cannot be performed with current implementation. * @throws ClassCastException if the operation can only be executed on some specific geometry subclasses * (for example polylines) and the wrapped geometry is not of that class. + * @throws BackingStoreException if the operation failed because of a checked exception. */ public final Object operationWithArgument(final SQLMM operation, final Object argument) { assert operation.geometryCount() == 1 && operation.maxParamCount == 2 : operation; - if (argument == null && operation.minParamCount > 1) { + if (operation.minParamCount > 1) { // TODO: fetch argument name. - throw new NullPointerException(Errors.format(Errors.Keys.NullArgument_1, "arg1")); + ArgumentChecks.ensureNonNull("arg1", argument); } final Object result = operationSameCRS(operation, null, argument); assert isInstance(operation, result) : result; @@ -317,7 +371,7 @@ public abstract class GeometryWrapper extends AbstractGeometry implements Geomet } /** - * Applies a SQLMM operation on two geometries with one operation-specific argument. + * Applies a <abbr>SQLMM</abbr> operation on two geometries with one operation-specific argument. * The argument shall be non-null, unless the argument is optional. * The second geometry is transformed to the same CRS as this geometry for conformance with SQLMM standard. * @@ -329,6 +383,7 @@ public abstract class GeometryWrapper extends AbstractGeometry implements Geomet * @throws ClassCastException if the operation can only be executed on some specific geometry subclasses * (for example polylines) and the wrapped geometry is not of that class. * @throws TransformException if it was necessary to transform the other geometry and that transformation failed. + * @throws BackingStoreException if the operation failed because of another checked exception. */ public final Object operationWithArgument(final SQLMM operation, final GeometryWrapper other, final Object argument) throws TransformException @@ -338,32 +393,11 @@ public abstract class GeometryWrapper extends AbstractGeometry implements Geomet // TODO: fetch argument name. throw new NullPointerException(Errors.format(Errors.Keys.NullArgument_1, "arg2")); } - final Object result = operationSameCRS(operation, toSameCRS(other), argument); + final Object result = operationSameCRS(operation, other.transform(crs), argument); assert isInstance(operation, result) : result; return result; } - /** - * Transforms the {@code other} geometry to the same CRS as this geometry. - * This method should be cheap for the common case where the other geometry - * already uses the CRS, in which case it is returned unchanged. - * - * <p>If this geometry does not define a CRS, then current implementation - * returns the other geometry unchanged.</p> - * - * @param other the other geometry. - * @return the other geometry in the same CRS as this geometry. - * @throws TransformException if the other geometry cannot be transformed. - * If may be because the other geometry does not define its CRS. - */ - private GeometryWrapper toSameCRS(final GeometryWrapper other) throws TransformException { - if (isSameCRS(other)) { - return other; - } - final CoordinateReferenceSystem crs = getCoordinateReferenceSystem(); - return (crs != null) ? other.transform(crs) : this; - } - /** * Returns {@code true} if a result is of the expected type. * This is used for assertion purposes only. @@ -374,7 +408,8 @@ public abstract class GeometryWrapper extends AbstractGeometry implements Geomet /** * Applies a filter predicate between this geometry and another geometry. - * This method assumes that the two geometries are in the same CRS (this is not verified). + * This method assumes that the two geometries are in the same <abbr>CRS</abbr> (this is not verified). + * Conversions, if needed, shall be done by the caller. * * <p><b>Note:</b> {@link SpatialOperatorName#BBOX} is implemented by {@code NOT DISJOINT}. * It is caller's responsibility to ensure that one of the geometries is rectangular.</p> @@ -383,6 +418,7 @@ public abstract class GeometryWrapper extends AbstractGeometry implements Geomet * @param other the other geometry to test with this geometry. * @return result of applying the specified predicate. * @throws UnsupportedOperationException if the operation cannot be performed with current implementation. + * @throws BackingStoreException if the operation failed because of a checked exception. */ protected boolean predicateSameCRS(SpatialOperatorName type, GeometryWrapper other) { throw new UnsupportedOperationException(Geometries.unsupported(type.name())); @@ -390,21 +426,25 @@ public abstract class GeometryWrapper extends AbstractGeometry implements Geomet /** * Applies a filter predicate between this geometry and another geometry within a given distance. - * This method assumes that the two geometries are in the same CRS and that the unit of measurement - * is the same for {@code distance} than for axes (this is not verified). + * This method assumes that the two geometries are in the same <abbr>CRS</abbr> and that the unit + * of measurement is the same for {@code distance} than for axes (this is not verified). + * Conversions, if needed, shall be done by the caller. * * @param type the predicate operation to apply. * @param other the other geometry to test with this geometry. * @param distance distance to test between the geometries. * @return result of applying the specified predicate. * @throws UnsupportedOperationException if the operation cannot be performed with current implementation. + * @throws BackingStoreException if the operation failed because of a checked exception. */ protected boolean predicateSameCRS(DistanceOperatorName type, GeometryWrapper other, double distance) { throw new UnsupportedOperationException(Geometries.unsupported(type.name())); } /** - * Applies a SQLMM operation on this geometry. + * Applies a <abbr>SQLMM</abbr> operation on this geometry. + * This method assumes that the two geometries are in the same <abbr>CRS</abbr> (this is not verified). + * Conversions, if needed, shall be done by the caller. * * @param operation the SQLMM operation to apply. * @param other the other geometry, or {@code null} if the operation requires only one geometry. @@ -413,6 +453,7 @@ public abstract class GeometryWrapper extends AbstractGeometry implements Geomet * @throws UnsupportedOperationException if the operation cannot be performed with current implementation. * @throws ClassCastException if the operation can only be executed on some specific geometry subclasses * (for example polylines) and the wrapped geometry is not of that class. + * @throws BackingStoreException if the operation failed because of a checked exception. */ protected Object operationSameCRS(SQLMM operation, GeometryWrapper other, Object argument) { throw new UnsupportedOperationException(Geometries.unsupported(operation.name())); @@ -439,6 +480,7 @@ public abstract class GeometryWrapper extends AbstractGeometry implements Geomet * @param target the desired type. * @return the converted geometry. * @throws IllegalArgumentException if the geometry cannot be converted to the specified type. + * @throws BackingStoreException if the operation failed because of a checked exception. */ public GeometryWrapper toGeometryType(final GeometryType target) { final Class<?> type = factory().getGeometryClass(target); @@ -451,85 +493,90 @@ public abstract class GeometryWrapper extends AbstractGeometry implements Geomet /** * Transforms this geometry using the given coordinate operation. - * If the operation is {@code null}, then the geometry is returned unchanged. - * If the geometry uses a different CRS than the source CRS of the given operation - * and {@code validate} is {@code true}, - * then a new operation to the target CRS will be automatically computed. + * If the geometry uses a different <abbr>CRS</abbr> than the source <abbr>CRS</abbr> + * of the given {@code operation} and if the {@code validate} argument is {@code true}, + * then a new operation to the target <abbr>CRS</abbr> will be automatically computed. * - * <p>This method is preferred to {@link #transform(CoordinateReferenceSystem)} - * when possible because not all geometry libraries store the CRS of their objects.</p> + * <p>This method is preferred to the {@link #transform(CoordinateReferenceSystem)} method + * because not all geometry libraries store the <abbr>CRS</abbr> of their objects.</p> * - * @param operation the coordinate operation to apply, or {@code null}. - * @param validate whether to validate the operation source CRS. + * @param operation the coordinate operation to apply. + * @param validate whether to validate the operation source <abbr>CRS</abbr>. * @return the transformed geometry (may be the same geometry instance, but never {@code null}). * @throws UnsupportedOperationException if this operation is not supported for current implementation. - * @throws FactoryException if transformation to the target CRS cannot be found. + * @throws FactoryException if transformation to the target <abbr>CRS</abbr> cannot be found. * @throws TransformException if the geometry cannot be transformed. + * @throws BackingStoreException if the operation failed because of another checked exception. */ - public GeometryWrapper transform(CoordinateOperation operation, boolean validate) + public GeometryWrapper transform(final CoordinateOperation operation, boolean validate) throws FactoryException, TransformException { - throw new UnsupportedOperationException(Geometries.unsupported("transform")); + MathTransform transform = operation.getMathTransform(); + if (validate && crs != null) { + CoordinateOperation step = CRS.findOperation(crs, operation.getSourceCRS(), null); + transform = MathTransforms.concatenate(step.getMathTransform(), transform); + } + final GeometryWrapper wrapper = transform(transform); + wrapper.setCoordinateReferenceSystem(operation.getTargetCRS()); + return wrapper; } /** * Transforms this geometry using the given transform. - * If the transform is {@code null}, then the geometry is returned unchanged. - * Otherwise, a new geometry is returned without CRS. + * If the transform is identity, then the geometry is returned unchanged. + * Otherwise, a new geometry is returned without <abbr>CRS</abbr>. * - * @param transform the math transform to apply, or {@code null}. + * @param transform the math transform to apply. * @return the transformed geometry (may be the same geometry instance, but never {@code null}). * @throws UnsupportedOperationException if this operation is not supported for current implementation. * @throws TransformException if the geometry cannot be transformed. + * @throws FactoryException if a problem happened while setting the <abbr>CRS</abbr>. + * @throws BackingStoreException if the operation failed because of another checked exception. */ - public GeometryWrapper transform(MathTransform transform) throws TransformException { - if (transform == null || transform.isIdentity()) { + public GeometryWrapper transform(final MathTransform transform) + throws FactoryException, TransformException + { + if (transform.isIdentity()) { return this; } throw new UnsupportedOperationException(Geometries.unsupported("transform")); } /** - * Transforms this geometry to the specified Coordinate Reference System (CRS). - * If the given CRS is null, then the geometry is returned unchanged. - * If this geometry has no Coordinate Reference System, a {@link TransformException} is thrown. + * Transforms this geometry to the specified Coordinate Reference System (<abbr>CRS</abbr>). + * If the given <abbr>CRS</abbr> is {@code null} or the same as the current <abbr>CRS</abbr>, + * or if the geometry has no <abbr>CRS</abbr>, then this wrapper is returned unchanged. * - * <p>Consider using {@link #transform(CoordinateOperation, boolean)} instead of this method as much as possible, - * both for performance reasons and because not all geometry libraries provide information about the CRS - * of their geometries.</p> + * <p>Consider using {@link #transform(CoordinateOperation, boolean)} instead of this method, + * for performance reasons and because not all geometry libraries associate <abbr>CRS</abbr> + * with their geometric objects.</p> * * @param targetCRS the target coordinate reference system, or {@code null}. * @return the transformed geometry (may be the same geometry but never {@code null}). * @throws UnsupportedOperationException if this operation is not supported for current implementation. * @throws TransformException if the given geometry has no CRS or cannot be transformed. + * @throws BackingStoreException if the operation failed because of another checked exception. * * @see #getCoordinateReferenceSystem() */ @Override public GeometryWrapper transform(final CoordinateReferenceSystem targetCRS) throws TransformException { - if (targetCRS == null) { + if (targetCRS == null || targetCRS == crs || crs == null) { return this; } - throw new UnsupportedOperationException(Geometries.unsupported("transform")); + try { + return transform(CRS.findOperation(crs, targetCRS, null), false); + } catch (FactoryException e) { + /* + * We wrap that exception because `Geometry.transform(…)` does not declare `FactoryException`. + * We may revisit in a future version if `Geometry.transform(…)` method declaration is updated. + */ + throw new TransformException(e); + } } /** - * Returns {@code true} if the given geometry use the same CRS as this geometry, or conservatively - * returns {@code false} in case of doubt. This method should perform only a cheap test; it is used - * as a way to filter rapidly if {@link #transform(CoordinateReferenceSystem)} needs to be invoked. - * If this method wrongly returned {@code false}, the {@code transform(…)} method will return the - * geometry unchanged anyway. - * - * <p>If both CRS are undefined (null), then they are considered the same.</p> - * - * @param other the second geometry. - * @return {@code true} if the two geometries use equivalent CRS or if the CRS is undefined on both side, - * or {@code false} in case of doubt. - */ - public abstract boolean isSameCRS(GeometryWrapper other); - - /** - * Formats the wrapped geometry in Well Known Text (WKT). + * Formats the wrapped geometry in Well Known Text (<abbr>WKT</abbr>) format. * If the geometry contains curves, then the {@code flatness} parameter specifies the maximum distance that * the line segments used in the Well Known Text are allowed to deviate from any point on the original curve. * This parameter is ignored if the geometry does not contain curves. @@ -559,27 +606,26 @@ public abstract class GeometryWrapper extends AbstractGeometry implements Geomet * for reducing the risk of compilation failures during the upcoming revision of GeoAPI interfaces since * some of those methods will be removed. */ - @Deprecated public final Geometry getMbRegion() {throw new UnsupportedOperationException();} - @Deprecated public final DirectPosition getRepresentativePoint() {throw new UnsupportedOperationException();} - @Deprecated public final Boundary getBoundary() {throw new UnsupportedOperationException();} - @Deprecated public final Complex getClosure() {throw new UnsupportedOperationException();} - @Deprecated public final boolean isSimple() {throw new UnsupportedOperationException();} - @Deprecated public final boolean isCycle() {throw new UnsupportedOperationException();} - @Deprecated public final double distance(Geometry geometry) {throw new UnsupportedOperationException();} - @Deprecated public final int getDimension(DirectPosition point) {throw new UnsupportedOperationException();} - @Deprecated public final int getCoordinateDimension() {throw new UnsupportedOperationException();} - @Deprecated public final Set<Complex> getMaximalComplex() {throw new UnsupportedOperationException();} - @Deprecated public final Geometry getConvexHull() {throw new UnsupportedOperationException();} - @Deprecated public final Geometry getBuffer(double distance) {throw new UnsupportedOperationException();} - @Deprecated public final Geometry clone() throws CloneNotSupportedException {throw new CloneNotSupportedException();} - @Deprecated public final boolean contains(TransfiniteSet pointSet) {throw new UnsupportedOperationException();} - @Deprecated public final boolean contains(DirectPosition point) {throw new UnsupportedOperationException();} - @Deprecated public final boolean intersects(TransfiniteSet pointSet) {throw new UnsupportedOperationException();} - @Deprecated public final boolean equals(TransfiniteSet pointSet) {throw new UnsupportedOperationException();} - @Deprecated public final TransfiniteSet union(TransfiniteSet pointSet) {throw new UnsupportedOperationException();} - @Deprecated public final TransfiniteSet intersection(TransfiniteSet pointSet) {throw new UnsupportedOperationException();} - @Deprecated public final TransfiniteSet difference(TransfiniteSet pointSet) {throw new UnsupportedOperationException();} - @Deprecated public final TransfiniteSet symmetricDifference(TransfiniteSet ps) {throw new UnsupportedOperationException();} + @Deprecated @Override public final Geometry getMbRegion() {throw new UnsupportedOperationException();} + @Deprecated @Override public final DirectPosition getRepresentativePoint() {throw new UnsupportedOperationException();} + @Deprecated @Override public final Boundary getBoundary() {throw new UnsupportedOperationException();} + @Deprecated @Override public final Complex getClosure() {throw new UnsupportedOperationException();} + @Deprecated @Override public final boolean isSimple() {throw new UnsupportedOperationException();} + @Deprecated @Override public final boolean isCycle() {throw new UnsupportedOperationException();} + @Deprecated @Override public final double distance(Geometry geometry) {throw new UnsupportedOperationException();} + @Deprecated @Override public final int getDimension(DirectPosition point) {throw new UnsupportedOperationException();} + @Deprecated @Override public final Set<Complex> getMaximalComplex() {throw new UnsupportedOperationException();} + @Deprecated @Override public final Geometry getConvexHull() {throw new UnsupportedOperationException();} + @Deprecated @Override public final Geometry getBuffer(double distance) {throw new UnsupportedOperationException();} + @Deprecated @Override public final Geometry clone() throws CloneNotSupportedException {throw new CloneNotSupportedException();} + @Deprecated @Override public final boolean contains(TransfiniteSet pointSet) {throw new UnsupportedOperationException();} + @Deprecated @Override public final boolean contains(DirectPosition point) {throw new UnsupportedOperationException();} + @Deprecated @Override public final boolean intersects(TransfiniteSet pointSet) {throw new UnsupportedOperationException();} + @Deprecated @Override public final boolean equals(TransfiniteSet pointSet) {throw new UnsupportedOperationException();} + @Deprecated @Override public final TransfiniteSet union(TransfiniteSet pointSet) {throw new UnsupportedOperationException();} + @Deprecated @Override public final TransfiniteSet intersection(TransfiniteSet pointSet) {throw new UnsupportedOperationException();} + @Deprecated @Override public final TransfiniteSet difference(TransfiniteSet pointSet) {throw new UnsupportedOperationException();} + @Deprecated @Override public final TransfiniteSet symmetricDifference(TransfiniteSet ps) {throw new UnsupportedOperationException();} /** * Returns {@code true} if the given object is a wrapper of the same class diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/geometry/wrapper/esri/Wrapper.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/geometry/wrapper/esri/Wrapper.java index f2fb605459..f956791357 100644 --- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/geometry/wrapper/esri/Wrapper.java +++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/geometry/wrapper/esri/Wrapper.java @@ -39,7 +39,6 @@ import org.opengis.geometry.DirectPosition; import org.apache.sis.geometry.DirectPosition2D; import org.apache.sis.geometry.GeneralEnvelope; import org.apache.sis.geometry.wrapper.Geometries; -import org.apache.sis.geometry.wrapper.GeometryWithCRS; import org.apache.sis.geometry.wrapper.GeometryWrapper; import org.apache.sis.filter.sqlmm.SQLMM; import org.apache.sis.util.Debug; @@ -55,7 +54,7 @@ import org.opengis.filter.SpatialOperatorName; * @author Martin Desruisseaux (Geomatys) * @author Alexis Manin (Geomatys) */ -final class Wrapper extends GeometryWithCRS { +final class Wrapper extends GeometryWrapper { /** * The wrapped implementation. */ diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/geometry/wrapper/j2d/Factory.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/geometry/wrapper/j2d/Factory.java index 29b53f6ea6..54111c7fbd 100644 --- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/geometry/wrapper/j2d/Factory.java +++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/geometry/wrapper/j2d/Factory.java @@ -183,7 +183,7 @@ public final class Factory extends Geometries<Shape> { */ @Override public Shape createMultiPoint(boolean isFloat, Dimensions dimensions, DoubleBuffer coordinates) { - throw new UnsupportedOperationException(); + throw new UnsupportedImplementationException(unsupported("createMultiPoint")); } /** diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/geometry/wrapper/j2d/PointWrapper.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/geometry/wrapper/j2d/PointWrapper.java index 43fe93c380..da3ce14fc3 100644 --- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/geometry/wrapper/j2d/PointWrapper.java +++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/geometry/wrapper/j2d/PointWrapper.java @@ -27,7 +27,6 @@ import org.opengis.geometry.DirectPosition; import org.apache.sis.geometry.DirectPosition2D; import org.apache.sis.geometry.GeneralEnvelope; import org.apache.sis.geometry.wrapper.Geometries; -import org.apache.sis.geometry.wrapper.GeometryWithCRS; import org.apache.sis.geometry.wrapper.GeometryWrapper; import org.apache.sis.filter.sqlmm.SQLMM; import org.apache.sis.util.Debug; @@ -42,7 +41,7 @@ import org.opengis.filter.SpatialOperatorName; * * @author Martin Desruisseaux (Geomatys) */ -final class PointWrapper extends GeometryWithCRS { +final class PointWrapper extends GeometryWrapper { /** * The wrapped implementation. */ @@ -176,19 +175,19 @@ final class PointWrapper extends GeometryWithCRS { case ST_Envelope: return getEnvelope(); case ST_Boundary: { if (point instanceof Point) { - final Point p = (Point) point; - final Rectangle r = new Rectangle(); + final var p = (Point) point; + final var r = new Rectangle(); r.x = p.x; r.y = p.y; return r; } else if (point instanceof Point2D.Float) { - final Point2D.Float p = (Point2D.Float) point; - final Rectangle2D.Float r = new Rectangle2D.Float(); + final var p = (Point2D.Float) point; + final var r = new Rectangle2D.Float(); r.x = p.x; r.y = p.y; return r; } else { - final Rectangle2D.Double r = new Rectangle2D.Double(); + final var r = new Rectangle2D.Double(); r.x = point.getX(); r.y = point.getY(); return r; diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/geometry/wrapper/j2d/Wrapper.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/geometry/wrapper/j2d/Wrapper.java index 08bcc9eddb..f71187ce1d 100644 --- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/geometry/wrapper/j2d/Wrapper.java +++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/geometry/wrapper/j2d/Wrapper.java @@ -31,7 +31,6 @@ import org.opengis.geometry.DirectPosition; import org.apache.sis.geometry.GeneralEnvelope; import org.apache.sis.geometry.DirectPosition2D; import org.apache.sis.geometry.wrapper.Geometries; -import org.apache.sis.geometry.wrapper.GeometryWithCRS; import org.apache.sis.geometry.wrapper.GeometryWrapper; import org.apache.sis.filter.sqlmm.SQLMM; import org.apache.sis.referencing.privy.ShapeUtilities; @@ -50,7 +49,7 @@ import org.opengis.filter.SpatialOperatorName; * @author Martin Desruisseaux (Geomatys) * @author Alexis Manin (Geomatys) */ -final class Wrapper extends GeometryWithCRS { +final class Wrapper extends GeometryWrapper { /** * The wrapped implementation. */ @@ -166,8 +165,9 @@ final class Wrapper extends GeometryWithCRS { boolean lineTo = false; add: for (;;) { if (next instanceof Point2D) { - final double x = ((Point2D) next).getX(); - final double y = ((Point2D) next).getY(); + final var p = (Point2D) next; + final double x = p.getX(); + final double y = p.getY(); if (Double.isNaN(x) || Double.isNaN(y)) { lineTo = false; } else if (lineTo) { diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/geometry/wrapper/jts/Factory.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/geometry/wrapper/jts/Factory.java index 378a434ae8..2ef54b6da2 100644 --- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/geometry/wrapper/jts/Factory.java +++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/geometry/wrapper/jts/Factory.java @@ -39,6 +39,7 @@ import org.locationtech.jts.geom.Polygon; import org.locationtech.jts.io.ParseException; import org.locationtech.jts.io.WKBReader; import org.locationtech.jts.io.WKTReader; +import org.opengis.util.FactoryException; import org.apache.sis.setup.GeometryLibrary; import org.apache.sis.geometry.wrapper.Capability; import org.apache.sis.geometry.wrapper.Dimensions; @@ -48,6 +49,7 @@ import org.apache.sis.geometry.wrapper.GeometryWrapper; import org.apache.sis.util.Classes; import org.apache.sis.util.privy.Strings; import org.apache.sis.util.resources.Errors; +import org.apache.sis.util.collection.BackingStoreException; /** @@ -144,13 +146,14 @@ public final class Factory extends Geometries<Geometry> { * @param geometry the geometry instance to wrap (can be {@code null}). * @return a wrapper for the given geometry implementation, or {@code null}. * @throws ClassCastException if the given geometry is not an instance of valid type. + * @throws BackingStoreException if the <abbr>CRS</abbr> cannot be created from the <abbr>SRID</abbr> code. */ @Override public GeometryWrapper castOrWrap(final Object geometry) { if (geometry == null || geometry instanceof Wrapper) { return (Wrapper) geometry; } else { - return new Wrapper((Geometry) geometry); + return createWrapper((Geometry) geometry); } } @@ -159,10 +162,15 @@ public final class Factory extends Geometries<Geometry> { * * @param geometry the geometry to wrap. * @return wrapper for the given geometry. + * @throws BackingStoreException if the <abbr>CRS</abbr> cannot be created from the <abbr>SRID</abbr> code. */ @Override protected GeometryWrapper createWrapper(final Geometry geometry) { - return new Wrapper(geometry); + try { + return new Wrapper(geometry); + } catch (FactoryException e) { + throw new BackingStoreException(e); + } } /** @@ -423,6 +431,7 @@ public final class Factory extends Geometries<Geometry> { * @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. * @throws IllegalArgumentException if an element is a non-closed linear string. + * @throws BackingStoreException if the <abbr>CRS</abbr> cannot be created from the <abbr>SRID</abbr> code. */ @Override public GeometryWrapper createMultiPolygon(final Object[] geometries) { @@ -455,7 +464,7 @@ public final class Factory extends Geometries<Geometry> { } polygons[i] = polygon; } - return new Wrapper(factory(isFloat).createMultiPolygon(polygons)); + return createWrapper(factory(isFloat).createMultiPolygon(polygons)); } /** @@ -476,6 +485,7 @@ public final class Factory extends Geometries<Geometry> { * @throws IllegalArgumentException if the given geometry type is not supported. * @throws ClassCastException if {@code components} is not an array or a collection of supported geometry components. * @throws ArrayStoreException if {@code components} is an array with invalid component type. + * @throws BackingStoreException if the <abbr>CRS</abbr> cannot be created from the <abbr>SRID</abbr> code. */ @Override public GeometryWrapper createFromComponents(final GeometryType type, final Object components) { @@ -535,7 +545,7 @@ public final class Factory extends Geometries<Geometry> { throw new IllegalArgumentException(Errors.format(Errors.Keys.UnsupportedArgumentValue_1, type)); } } - return new Wrapper(geometry); + return createWrapper(geometry); } /** @@ -618,10 +628,11 @@ public final class Factory extends Geometries<Geometry> { * * @param wkt the Well Known Text to parse. * @return the geometry object for the given WKT. - * @throws ParseException if the WKT cannot be parsed. + * @throws ParseException if the <abbr>WKT</abbr> cannot be parsed. + * @throws FactoryException if the <abbr>CRS</abbr> cannot be created from the <abbr>SRID</abbr> code. */ @Override - public GeometryWrapper parseWKT(final String wkt) throws ParseException { + public GeometryWrapper parseWKT(final String wkt) throws ParseException, FactoryException { // WKTReader(GeometryFactory) constructor is cheap. final WKTReader reader = new WKTReader(factory); reader.setIsOldJtsCoordinateSyntaxAllowed(false); @@ -634,10 +645,11 @@ public final class Factory extends Geometries<Geometry> { * * @param data the sequence of bytes to parse. * @return the geometry object for the given WKB. - * @throws ParseException if the WKB cannot be parsed. + * @throws ParseException if the <abbr>WKB</abbr> cannot be parsed. + * @throws FactoryException if the <abbr>CRS</abbr> cannot be created from the <abbr>SRID</abbr> code. */ @Override - public GeometryWrapper parseWKB(final ByteBuffer data) throws ParseException { + public GeometryWrapper parseWKB(final ByteBuffer data) throws ParseException, FactoryException { byte[] array; if (data.hasArray()) { /* diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/geometry/wrapper/jts/JTS.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/geometry/wrapper/jts/JTS.java index 810e741785..114d41572b 100644 --- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/geometry/wrapper/jts/JTS.java +++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/geometry/wrapper/jts/JTS.java @@ -74,43 +74,6 @@ public final class JTS extends Static { private JTS() { } - /** - * Returns {@code true} if the two geometries use the same CRS, based on very cheap comparison. - * A value of {@code false} does not necessarily means that the CRS are different, but it means - * that a more expensive comparison is required. If CRS are specified by SRID codes, then this - * method assumes that the two SRID codes are defined by the same authority (e.g. EPSG). - * - * <p>If both CRS are undefined (null), then they are considered the same.</p> - * - * @param first the first geometry. - * @param second the second geometry. - * @return {@code true} if the two geometries use equivalent CRS, or {@code false} in case of doubt. - */ - static boolean isSameCRS(final Geometry first, final Geometry second) { - final int id1 = first.getSRID(); - final int id2 = second.getSRID(); - if ((id1 | id2) != 0) { - return id1 == id2; - } - /* - * Identity comparison is often sufficient since all geometries typically share the same CRS. - * If they are not the same instance, a more expensive `equalsIgnoreMetadata(…)` method here - * would probably duplicate the work done later by the `transform(Geometry, …)` method. - */ - Object c1 = first.getUserData(); - if (c1 != null && !(c1 instanceof CoordinateReferenceSystem)) { - c1 = (c1 instanceof Map<?,?>) ? ((Map<?,?>) c1).get(CRS_KEY) : null; - } - Object c2 = second.getUserData(); - if (c1 == c2) { - return true; // Quick check for common case. - } - if (c2 != null && !(c2 instanceof CoordinateReferenceSystem)) { - c2 = (c2 instanceof Map<?,?>) ? ((Map<?,?>) c2).get(CRS_KEY) : null; - } - return c1 == c2; - } - /** * Gets the Coordinate Reference System (CRS) from the given geometry. * This method expects the CRS to be stored in one the following ways: @@ -202,7 +165,7 @@ public final class JTS extends Static { /** * Finds an operation between the given CRS valid in the given area of interest. - * This method does not verify the CRS of the given geometry. + * This method does not verify the <abbr>CRS</abbr> of the given geometry. * * @param sourceCRS the CRS of source coordinates. * @param targetCRS the CRS of target coordinates. @@ -233,9 +196,9 @@ public final class JTS extends Static { } /** - * Transforms the given geometry to the specified Coordinate Reference System (CRS). - * If the given CRS or the given geometry is null or is the same as current CRS, - * then the geometry is returned unchanged. + * Transforms the given geometry to the specified Coordinate Reference System (<abbr>CRS</abbr>). + * If the given geometry is {@code null}, or if the given <abbr>CRS</abbr> is {@code null} or the + * same as the current geometry <abbr>CRS</abbr>, then the given geometry is returned unchanged. * If the geometry has no Coordinate Reference System, then the geometry is returned unchanged. * * <p><b>This operation may be slow!</b> diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/geometry/wrapper/jts/Wrapper.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/geometry/wrapper/jts/Wrapper.java index 6e2baa24a1..520150bf1f 100644 --- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/geometry/wrapper/jts/Wrapper.java +++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/geometry/wrapper/jts/Wrapper.java @@ -58,7 +58,6 @@ import org.apache.sis.util.UnconvertibleObjectException; import org.apache.sis.util.ArgumentChecks; import org.apache.sis.util.ArraysExt; import org.apache.sis.util.Debug; -import org.apache.sis.util.collection.BackingStoreException; import org.apache.sis.util.resources.Errors; import org.apache.sis.filter.sqlmm.SQLMM; import static org.apache.sis.geometry.wrapper.GeometryType.POINT; @@ -83,20 +82,25 @@ final class Wrapper extends GeometryWrapper { /** * Creates a new wrapper around the given geometry. + * + * @param geometry the geometry to wrap. + * @throws FactoryException if the <abbr>CRS</abbr> cannot be created from the <abbr>SRID</abbr> code. */ - Wrapper(final Geometry geometry) { + Wrapper(final Geometry geometry) throws FactoryException { this.geometry = geometry; + crs = JTS.getCoordinateReferenceSystem(geometry); } /** - * Returns the given geometry in new wrapper, - * or {@code this} if {@code g} is same as current geometry. + * Creates a new wrapper with the same <abbr>CRS</abbr> than the given wrapper. * - * @param result the geometry computed by a JTS operation. - * @return wrapper for the given geometry. May be {@code this}. + * @param source the source wrapper from which is derived the geometry. + * @param geometry the geometry to wrap. */ - private Wrapper rewrap(final Geometry result) { - return (result != geometry) ? new Wrapper(result) : this; + private Wrapper(final Wrapper source, final Geometry geometry) { + this.geometry = geometry; + this.crs = source.crs; + JTS.copyMetadata(source.geometry, geometry); } /** @@ -127,21 +131,6 @@ final class Wrapper extends GeometryWrapper { return (srid != 0) ? OptionalInt.of(srid) : OptionalInt.empty(); } - /** - * Returns the geometry coordinate reference system, or {@code null} if none. - * - * @return the coordinate reference system, or {@code null} if none. - * @throws BackingStoreException if the CRS cannot be created from the SRID code. - */ - @Override - public CoordinateReferenceSystem getCoordinateReferenceSystem() { - try { - return JTS.getCoordinateReferenceSystem(geometry); - } catch (FactoryException e) { - throw new BackingStoreException(e); - } - } - /** * Sets the coordinate reference system. This method overwrites any previous user object. * This is okay for the context in which Apache SIS uses this method, which is only for @@ -149,10 +138,18 @@ final class Wrapper extends GeometryWrapper { */ @Override public void setCoordinateReferenceSystem(final CoordinateReferenceSystem crs) { - ArgumentChecks.ensureDimensionMatches("crs", getCoordinatesDimension(geometry), crs); + super.setCoordinateReferenceSystem(crs); JTS.setCoordinateReferenceSystem(geometry, crs); } + /** + * Returns the dimension of the coordinates that define this geometry. + */ + @Override + public int getCoordinateDimension() { + return getCoordinatesDimension(geometry); + } + /** * Gets the number of dimensions of geometry vertex (sequence of coordinate tuples), which can be 2 or 3. * Note that this is different than the {@linkplain Geometry#getDimension() geometry topological dimension}, @@ -215,6 +212,7 @@ final class Wrapper extends GeometryWrapper { @Override public DirectPosition getCentroid() { final Coordinate c = geometry.getCentroid().getCoordinate(); + @SuppressWarnings("LocalVariableHidesMemberVariable") final CoordinateReferenceSystem crs = getCoordinateReferenceSystem(); if (crs == null) { final double z = c.getZ(); @@ -555,8 +553,7 @@ add: for (Geometry next = geometry;;) { if (!getGeometryClass(target).isInstance(geometry)) { final Geometry result = convert(target); if (result != geometry) { - JTS.copyMetadata(geometry, result); - return new Wrapper(result); + return new Wrapper(this, result); } } return this; @@ -774,34 +771,32 @@ add: for (Geometry next = geometry;;) { * @throws TransformException if the geometry cannot be transformed. */ @Override - public GeometryWrapper transform(final MathTransform transform) throws TransformException { + public GeometryWrapper transform(final MathTransform transform) throws FactoryException, TransformException { return rewrap(JTS.transform(geometry, transform)); } /** - * Returns a view over the JTS geometry as a Java2D shape. Changes in the JTS geometry - * after this method call may be reflected in the returned shape in an unspecified way. + * Returns the given geometry in a new wrapper, or {@code this} if {@code result} is same as current geometry. + * If a new wrapper is created, then its <abbr>CRS</abbr> will be inferred from the geometry <abbr>SRID</abbr> + * or user properties. * - * @return a view over the geometry as a Java2D shape. + * @param result the geometry computed by a JTS operation. + * @return wrapper for the given geometry. May be {@code this}. + * @throws FactoryException if the <abbr>CRS</abbr> cannot be created from the <abbr>SRID</abbr> code. */ - @Override - public Shape toJava2D() { - return JTS.asShape(geometry); + private Wrapper rewrap(final Geometry result) throws FactoryException { + return (result == geometry) ? this : new Wrapper(result); } /** - * Returns {@code true} if the given geometry use the same CRS as this geometry, or conservatively - * returns {@code false} in case of doubt. This method should perform only a cheap test; it is used - * as a way to filter rapidly if {@link #transform(CoordinateReferenceSystem)} needs to be invoked. + * Returns a view over the JTS geometry as a Java2D shape. Changes in the JTS geometry + * after this method call may be reflected in the returned shape in an unspecified way. * - * @param other the second geometry. - * @return {@code true} if the two geometries use equivalent CRS or if the CRS is undefined on both side, - * or {@code false} in case of doubt. - * @throws ClassCastException if the given wrapper is not for the same geometry library. + * @return a view over the geometry as a Java2D shape. */ @Override - public boolean isSameCRS(final GeometryWrapper other) { - return JTS.isSameCRS(geometry, ((Wrapper) other).geometry); + public Shape toJava2D() { + return JTS.asShape(geometry); } /** diff --git a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/geometry/wrapper/jts/JTSTest.java b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/geometry/wrapper/jts/JTSTest.java index 8aec3d6a9e..2998c4ecdd 100644 --- a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/geometry/wrapper/jts/JTSTest.java +++ b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/geometry/wrapper/jts/JTSTest.java @@ -95,7 +95,7 @@ public final class JTSTest extends TestCase { * Test setting a 2D CRS on a 2 dimensional geometry. */ final Geometry geometry = factory.createPoint(new CoordinateXY(5, 6)); - final GeometryWrapper wrapper = Geometries.wrap(geometry).get(); + final GeometryWrapper wrapper = Geometries.wrap(geometry).orElseThrow(); wrapper.setCoordinateReferenceSystem(crs2D); assertEquals(crs2D, wrapper.getCoordinateReferenceSystem()); } @@ -150,7 +150,7 @@ public final class JTSTest extends TestCase { */ final CoordinateReferenceSystem crs = CommonCRS.WGS84.geographic(); final Geometry geometry = factory.createPoint(new CoordinateXY(5, 6)); - final GeometryWrapper wrapper = Geometries.wrap(geometry).get(); + final GeometryWrapper wrapper = Geometries.wrap(geometry).orElseThrow(); wrapper.setCoordinateReferenceSystem(crs); final GeneralEnvelope envelope = wrapper.getEnvelope(); assertEquals(crs, envelope.getCoordinateReferenceSystem()); @@ -166,7 +166,7 @@ public final class JTSTest extends TestCase { */ final CoordinateReferenceSystem crs = CommonCRS.WGS84.geographic3D(); final Geometry geometry = factory.createPoint(new Coordinate(5, 6, 7)); - final GeometryWrapper wrapper = Geometries.wrap(geometry).get(); + final GeometryWrapper wrapper = Geometries.wrap(geometry).orElseThrow(); wrapper.setCoordinateReferenceSystem(crs); final GeneralEnvelope envelope = wrapper.getEnvelope(); assertEquals(crs, envelope.getCoordinateReferenceSystem()); diff --git a/endorsed/src/org.apache.sis.storage.sql/test/org/apache/sis/storage/sql/feature/GeometryGetterTest.java b/endorsed/src/org.apache.sis.storage.sql/test/org/apache/sis/storage/sql/feature/GeometryGetterTest.java index 9e13d21269..5963cbc2b3 100644 --- a/endorsed/src/org.apache.sis.storage.sql/test/org/apache/sis/storage/sql/feature/GeometryGetterTest.java +++ b/endorsed/src/org.apache.sis.storage.sql/test/org/apache/sis/storage/sql/feature/GeometryGetterTest.java @@ -26,7 +26,6 @@ import org.opengis.referencing.NoSuchAuthorityCodeException; import org.opengis.referencing.crs.CoordinateReferenceSystem; import org.apache.sis.geometry.wrapper.Geometries; import org.apache.sis.geometry.wrapper.GeometryWrapper; -import org.apache.sis.geometry.wrapper.GeometryWithCRS; import org.apache.sis.referencing.CRS; import org.apache.sis.referencing.CommonCRS; import org.apache.sis.setup.GeometryLibrary; @@ -109,11 +108,10 @@ public final class GeometryGetterTest extends TestCase { assertEquals(GF.createPoint(42.2, 43.3), geometry); final GeometryWrapper wrapper = Geometries.factory(library).castOrWrap(geometry); /* - * If the wrapper is an instance of `GeometryWithCRS`, then the CRS is stored - * with the wrapper instead of the geometry implementation. In such case, the - * CRS is lost on `GeometryWrapper.geometry()` and cannot be tested. + * The following assertion can be executed only with library that store the CRS + * in the geometry object itself rather than as a field of the geometry wrapper. */ - if (!(wrapper instanceof GeometryWithCRS)) { + if (library == GeometryLibrary.JTS) { assertSame(HardCodedCRS.WGS84, wrapper.getCoordinateReferenceSystem()); } } diff --git a/incubator/src/org.apache.sis.storage.geopackage/test/org/apache/sis/storage/geopackage/GpkgStoreTest.java b/incubator/src/org.apache.sis.storage.geopackage/test/org/apache/sis/storage/geopackage/GpkgStoreTest.java index 15eb2bd9b1..6ed52ec5ed 100644 --- a/incubator/src/org.apache.sis.storage.geopackage/test/org/apache/sis/storage/geopackage/GpkgStoreTest.java +++ b/incubator/src/org.apache.sis.storage.geopackage/test/org/apache/sis/storage/geopackage/GpkgStoreTest.java @@ -281,7 +281,7 @@ public final class GpkgStoreTest { assertEquals(1, features.size()); final Feature feature = features.get(0); - GeometryWrapper geometry = Geometries.wrap(feature.getPropertyValue("sis:geometry")).get(); + GeometryWrapper geometry = Geometries.wrap(feature).orElseThrow(); assertEquals(expectedCRS, IdentifiedObjects.getIdentifierOrName(geometry.getCoordinateReferenceSystem())); assertEquals(expectedWKT, geometry.formatWKT(1)); assertEquals(1, feature.getPropertyValue("fid"));
