This is an automated email from the ASF dual-hosted git repository. desruisseaux pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/sis.git
commit 95d808c4049730bd9a3e0c99b23d39197ebebaf6 Merge: 3d377745a9 3abad8c520 Author: Martin Desruisseaux <[email protected]> AuthorDate: Wed Jun 11 13:06:46 2025 +0200 Merge branch 'geoapi-3.1'. README.md | 4 +- .../org/apache/sis/cloud/aws/s3/FileService.java | 2 +- .../sis/coverage/grid/ResampledGridCoverage.java | 2 +- .../apache/sis/feature/AbstractIdentifiedType.java | 90 ++- .../org/apache/sis/feature/AbstractOperation.java | 67 +- .../apache/sis/feature/DefaultAssociationRole.java | 15 +- .../apache/sis/feature/DefaultAttributeType.java | 15 +- .../org/apache/sis/feature/EnvelopeOperation.java | 194 +++-- .../org/apache/sis/feature/FeatureOperations.java | 18 +- .../main/org/apache/sis/feature/Features.java | 4 +- .../sis/feature/GroupAsPolylineOperation.java | 40 +- .../main/org/apache/sis/feature/LinkOperation.java | 15 + .../apache/sis/feature/StringJoinOperation.java | 80 ++- .../feature/builder/AssociationRoleBuilder.java | 2 +- .../sis/feature/builder/AttributeTypeBuilder.java | 2 +- .../feature/builder/CharacteristicTypeBuilder.java | 2 +- .../sis/feature/builder/FeatureTypeBuilder.java | 6 +- .../sis/feature/builder/OperationWrapper.java | 37 + .../sis/feature/builder/PropertyTypeBuilder.java | 13 + .../apache/sis/feature/builder/TypeBuilder.java | 2 +- .../org/apache/sis/feature/internal/Resources.java | 5 + .../sis/feature/internal/Resources.properties | 1 + .../sis/feature/internal/Resources_fr.properties | 1 + .../feature/privy/FeatureProjectionBuilder.java | 51 +- .../main/org/apache/sis/xml/XML.java | 2 +- ...g.opengis.referencing.operation.OperationMethod | 2 + .../main/module-info.java | 2 + .../apache/sis/io/wkt/GeodeticObjectParser.java | 2 +- .../org/apache/sis/parameter/ParameterFormat.java | 2 +- .../org/apache/sis/parameter/Parameterized.java | 3 + .../sis/referencing/cs/CoordinateSystems.java | 63 +- .../sis/referencing/cs/DefaultCompoundCS.java | 7 +- .../sis/referencing/datum/BursaWolfParameters.java | 2 +- .../referencing/datum/DefaultDatumEnsemble.java | 2 +- .../sis/referencing/datum/DefaultEllipsoid.java | 55 +- .../referencing/datum/DefaultGeodeticDatum.java | 2 +- .../org/apache/sis/referencing/datum/Sphere.java | 15 +- .../internal/ParameterizedTransformBuilder.java | 235 +++--- .../operation/CoordinateOperationFinder.java | 210 ++---- .../operation/CoordinateOperationRegistry.java | 2 +- .../referencing/operation/DefaultConversion.java | 2 +- .../DefaultCoordinateOperationFactory.java | 11 +- .../operation/MathTransformContext.java | 90 ++- .../operation/matrix/GeneralMatrix.java | 22 +- .../sis/referencing/operation/matrix/Matrices.java | 3 + .../operation/provider/AbstractProvider.java | 52 +- .../GeocentricAffineBetweenGeographic.java | 53 +- .../operation/provider/GeocentricToGeographic.java | 4 +- .../provider/GeocentricToTopocentric.java | 16 +- .../provider/GeocentricTranslation3D.java | 1 + .../operation/provider/Geographic2Dto3D.java | 26 +- .../operation/provider/Geographic3Dto2D.java | 19 +- .../operation/provider/GeographicToGeocentric.java | 40 +- .../operation/provider/MapProjection.java | 28 +- ...{Geographic2Dto3D.java => Spherical2Dto3D.java} | 59 +- ...{Geographic2Dto3D.java => Spherical3Dto2D.java} | 63 +- .../operation/transform/AbstractMathTransform.java | 183 ++++- .../operation/transform/CartesianToPolar.java | 6 +- .../operation/transform/CartesianToSpherical.java | 6 +- .../operation/transform/ConcatenatedTransform.java | 24 +- .../operation/transform/ContextualParameters.java | 80 ++- .../transform/CoordinateSystemTransform.java | 34 +- .../CoordinateSystemTransformBuilder.java | 350 ++++++--- .../operation/transform/CopyTransform.java | 25 +- .../operation/transform/DatumShiftTransform.java | 3 +- .../transform/DefaultMathTransformFactory.java | 8 +- .../transform/EllipsoidToCentricTransform.java | 787 ++++++++++++--------- .../transform/EllipsoidToRadiusTransform.java | 505 +++++++++++++ .../transform/InterpolatedGeocentricTransform.java | 9 +- .../operation/transform/LinearTransform1D.java | 5 +- .../operation/transform/MathTransforms.java | 20 +- .../operation/transform/MolodenskyTransform.java | 1 + .../operation/transform/OnewayLinearTransform.java | 190 +++++ .../operation/transform/PolarToCartesian.java | 6 +- .../operation/transform/PoleRotation.java | 13 +- .../operation/transform/SphericalToCartesian.java | 8 +- .../operation/transform/TransformSeparator.java | 6 +- .../org/apache/sis/referencing/privy/Formulas.java | 26 +- .../referencing/privy/ReferencingUtilities.java | 27 + .../ParameterizedTransformBuilderTest.java | 2 +- .../operation/CoordinateOperationFinderTest.java | 64 ++ .../provider/GeocentricTranslationTest.java | 10 +- .../operation/provider/Geographic3Dto2DTest.java | 5 +- .../operation/provider/ProvidersTest.java | 2 + .../transform/EllipsoidToCentricTransformTest.java | 135 ++-- .../transform/EllipsoidToRadiusTransformTest.java | 162 +++++ .../EllipsoidToSphericalTransformTest.java | 198 ++++++ .../operation/transform/MathTransformWrapper.java | 4 +- .../transform/TransformSeparatorTest.java | 4 +- .../apache/sis/storage/geotiff/GeoTiffStore.java | 2 +- .../apache/sis/storage/sql/feature/Relation.java | 2 +- .../main/org/apache/sis/storage/gpx/Store.java | 17 - .../main/org/apache/sis/storage/gpx/Types.java | 26 +- .../apache/sis/io/stream/FileCacheByteChannel.java | 24 +- .../main/org/apache/sis/storage/FeatureQuery.java | 12 +- .../main/org/apache/sis/storage/FeatureSet.java | 1 - .../main/org/apache/sis/storage/FeatureSubset.java | 4 +- .../org/apache/sis/storage/StorageConnector.java | 2 +- .../sis/storage/UnsupportedQueryException.java | 12 + .../sis/storage/base/FeatureCatalogBuilder.java | 81 --- .../apache/sis/storage/base/MetadataBuilder.java | 3 - .../org/apache/sis/storage/FeatureQueryTest.java | 2 +- .../sis/storage/base/MetadataBuilderTest.java | 4 +- .../main/org/apache/sis/pending/jdk/JDK21.java | 24 +- .../org/apache/sis/util/privy/CollectionsExt.java | 8 +- netbeans-project/nbproject/project.xml | 2 + optional/src/org.apache.sis.gui/bundle/README | 2 +- settings.gradle.kts | 1 + 108 files changed, 3550 insertions(+), 1350 deletions(-) diff --cc endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/AbstractIdentifiedType.java index a4062967b0,b71469591d..95f2e249eb --- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/AbstractIdentifiedType.java +++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/AbstractIdentifiedType.java @@@ -204,11 -215,13 +217,13 @@@ public class AbstractIdentifiedType imp */ @SuppressWarnings("this-escape") protected AbstractIdentifiedType(final Map<String,?> identification) throws IllegalArgumentException { - // Implicit null value check. - Object value = identification.get(NAME_KEY); - final IdentifiedType inheritFrom = Containers.property(identification, INHERIT_FROM_KEY, IdentifiedType.class); ++ final AbstractIdentifiedType inheritFrom = Containers.property(identification, INHERIT_FROM_KEY, AbstractIdentifiedType.class); + Object value = identification.get(NAME_KEY); // Implicit null value check. if (value == null) { - throw new IllegalArgumentException(Errors.forProperties(identification) - .getString(Errors.Keys.MissingValueForProperty_1, NAME_KEY)); + if (inheritFrom == null || (name = inheritFrom.getName()) == null) { + throw new IllegalArgumentException(Errors.forProperties(identification) + .getString(Errors.Keys.MissingValueForProperty_1, NAME_KEY)); + } } else if (value instanceof String) { name = createName(DefaultNameFactory.provider(), (String) value); } else if (value instanceof GenericName) { @@@ -216,12 -229,12 +231,12 @@@ } else { throw illegalPropertyType(identification, NAME_KEY, value); } - definition = Types.toInternationalString(identification, DEFINITION_KEY); - designation = Types.toInternationalString(identification, DESIGNATION_KEY); - description = Types.toInternationalString(identification, DESCRIPTION_KEY); + definition = toInternationalString(identification, DEFINITION_KEY, inheritFrom); + designation = toInternationalString(identification, DESIGNATION_KEY, inheritFrom); + description = toInternationalString(identification, DESCRIPTION_KEY, inheritFrom); value = identification.get(DEPRECATED_KEY); if (value == null) { - deprecated = false; - deprecated = (inheritFrom instanceof Deprecable) ? ((Deprecable) inheritFrom).isDeprecated() : false; ++ deprecated = (inheritFrom != null) && inheritFrom.isDeprecated(); } else if (value instanceof Boolean) { deprecated = (Boolean) value; } else { @@@ -229,6 -242,29 +244,29 @@@ } } + /** + * Returns an international string for the values in the given properties map, or {@code null} if none. + * + * @param identification the map from which to get the string values for an international string. + * @param prefix the prefix of keys to use for creating the international string. + * @param inheritFrom the type from which to inherit a value if none is specified in the map, or {@code null}. + * @return the international string, or {@code null} if the given map is null or does not contain values + * associated to keys starting with the given prefix. + */ + private static InternationalString toInternationalString( - final Map<String,?> identification, final String prefix, final IdentifiedType inheritFrom) ++ final Map<String,?> identification, final String prefix, final AbstractIdentifiedType inheritFrom) + { + InternationalString i18n = Types.toInternationalString(identification, prefix); + if (i18n == null && inheritFrom != null) { + switch (prefix) { + case DEFINITION_KEY: i18n = inheritFrom.getDefinition(); break; + case DESIGNATION_KEY: i18n = inheritFrom.getDesignation().orElse(null); break; + case DESCRIPTION_KEY: i18n = inheritFrom.getDescription().orElse(null); break; + } + } + return i18n; + } + /** * Returns the exception to be thrown when a property is of illegal type. */ diff --cc endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/AbstractOperation.java index 8f1c56dd77,6c76e4c245..088a19ecee --- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/AbstractOperation.java +++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/AbstractOperation.java @@@ -47,14 -57,11 +46,11 @@@ import org.apache.sis.parameter.Default * <div class="note"><b>Example:</b> a mutator operation may raise the height of a dam. This changes * may affect other properties like the watercourse and the reservoir associated with the dam.</div> * - * The value is computed, or the operation is executed, by {@link #apply(Feature, ParameterValueGroup)}. - * If the value is modifiable, new value can be set by call to {@link Attribute#setValue(Object)}. + * The value is computed, or the operation is executed, by {@code apply(Feature, ParameterValueGroup)}. + * If the value is modifiable, new value can be set by call to {@code Attribute.setValue(Object)}. * - * <div class="warning"><b>Warning:</b> this class is experimental and may change after we gained more - * experience on this aspect of ISO 19109.</div> - * * @author Martin Desruisseaux (Geomatys) - * @version 0.8 + * @version 1.5 * * @see DefaultFeatureType * @@@ -212,7 -210,36 +203,39 @@@ public abstract class AbstractOperatio * @return the names of feature properties needed by this operation for performing its task. */ public Set<String> getDependencies() { - return Collections.emptySet(); + return Set.of(); + } + + /** + * Returns the same operation but using different properties as inputs. + * The keys in the given map should be values returned by {@link #getDependencies()}, + * and the associated values shall be the properties to use instead of the current dependencies. + * If any key in the given map is not a member of the {@linkplain #getDependencies() dependency set}, + * then the entry is ignored. Conversely, if any member of the dependency set is not contained in the + * given map, then the associated dependency is unchanged. + * ++ * <div class="warning"><b>Warning:</b> In a future SIS version, the return type may be changed ++ * to {@code org.opengis.feature.Operation}. This change is pending GeoAPI revision.</div> ++ * + * <h4>Purpose</h4> + * This method is needed by {@link org.apache.sis.feature.builder.FeatureTypeBuilder} when some properties + * are operations inherited from another feature type. Even if the dependencies are properties of the same + * name, some {@link DefaultAttributeType#characteristics() characteristics} may be different. + * For example, the <abbr>CRS</abbr> may change as a result of a change of <abbr>CRS</abbr>. + * + * <h4>Default implementation</h4> + * The default implementation returns {@code this}. + * This is consistent with the default implementation of {@link #getDependencies()} returning an empty set. + * + * @param dependencies the new properties to use as operation inputs. + * @return the new operation, or {@code this} if unchanged. + * + * @see #INHERIT_FROM_KEY + * + * @since 1.5 + */ - public Operation updateDependencies(final Map<String, PropertyType> dependencies) { ++ public AbstractOperation updateDependencies(final Map<String, AbstractIdentifiedType> dependencies) { + return this; } /** diff --cc endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/EnvelopeOperation.java index ee076b85f4,fc464e7c87..8addd7e879 --- 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 @@@ -37,9 -35,20 +35,10 @@@ import org.apache.sis.geometry.GeneralE import org.apache.sis.geometry.wrapper.Geometries; import org.apache.sis.geometry.wrapper.GeometryWrapper; import org.apache.sis.util.privy.CollectionsExt; - import org.apache.sis.referencing.CRS; import org.apache.sis.util.resources.Errors; + import org.apache.sis.referencing.CRS; + import org.apache.sis.pending.jdk.JDK21; -// Specific to the geoapi-3.1 and geoapi-4.0 branches: -import org.opengis.feature.Attribute; -import org.opengis.feature.AttributeType; -import org.opengis.feature.Feature; -import org.opengis.feature.FeatureInstantiationException; -import org.opengis.feature.IdentifiedType; -import org.opengis.feature.Operation; -import org.opengis.feature.Property; -import org.opengis.feature.PropertyType; - /** * An operation computing the envelope that encompass all geometries found in a list of attributes. @@@ -123,48 -157,78 +146,78 @@@ final class EnvelopeOperation extends A * @param identification the name and other information to be given to this operation. * @param targetCRS the coordinate reference system of envelopes to computes, or {@code null}. * @param geometryAttributes the operation or attribute type from which to get geometry values. + * @param inheritFrom the existing operation from which to inherit attributes, or {@code null}. */ - EnvelopeOperation(final Map<String,?> identification, CoordinateReferenceSystem targetCRS, - final AbstractIdentifiedType[] geometryAttributes) throws FactoryException + EnvelopeOperation(final Map<String,?> identification, + CoordinateReferenceSystem targetCRS, - final PropertyType[] geometryAttributes, ++ final AbstractIdentifiedType[] geometryAttributes, + final EnvelopeOperation inheritFrom) + throws FactoryException { super(identification); - String defaultGeometry = null; + explicitCRS = (targetCRS != null); // Whether the CRS was specified by the user or inferred automatically. + boolean characterizedByCRS = false; // Whether "sis:crs" characteristics exist, possibly with null values. + String defaultGeometry = null; // Attribute name of the target of the "sis:geometry" property. + boolean defaultIsFirst = true; // Whether the default geometry is the first entry in the `names` map. /* - * Get all property names without duplicated values. If a property is a link to an attribute, - * then the key will be the name of the referenced attribute instead of the operation name. - * The intent is to avoid querying the same geometry twice if the attribute is also specified - * explicitly in the array of properties. - * - * The map values will be the default Coordinate Reference System, or null if none. + * Get all property names without duplicated values, including the targets of links. + * The map values will be the default Coordinate Reference Systems, or null if none. */ - boolean characterizedByCRS = false; - final Map<String,CoordinateReferenceSystem> names = new LinkedHashMap<>(4); - for (final AbstractIdentifiedType property : geometryAttributes) { - final Optional<DefaultAttributeType<?>> at = Features.toAttribute(property); - if (at.isPresent() && Geometries.isKnownType(at.get().getValueClass())) { - final GenericName name = property.getName(); - final String attributeName = (property instanceof LinkOperation) - ? ((LinkOperation) property).referentName : name.toString(); - final boolean isDefault = AttributeConvention.GEOMETRY_PROPERTY.equals(name); - if (isDefault) { - defaultGeometry = attributeName; + final var names = new LinkedHashMap<String, CoordinateReferenceSystem>(4); + for (int i=0; i < geometryAttributes.length; i++) { + final String propertyName; // Name of `geometryAttributes[i]`, possibly inherited. + final String attributeName; // Name of the property after following the link. + CoordinateReferenceSystem attributeCRS = null; - final PropertyType property = geometryAttributes[i]; ++ final AbstractIdentifiedType property = geometryAttributes[i]; + if (property == null && inheritFrom != null) { + /* + * When this constructor is invoked by `updateDependencies(Map)`, a null property means to inherit + * the property at the same index from the previous operation. The caller is responsible to ensure + * that the indexes match. + */ + propertyName = attributeName = inheritFrom.attributeNames[i]; + if (inheritFrom.attributeToCRS != null) { + final CoordinateOperation op = inheritFrom.attributeToCRS[i]; + if (op != null) { + attributeCRS = op.getSourceCRS(); + characterizedByCRS = true; + } } - CoordinateReferenceSystem attributeCRS = null; + } else { - final AttributeType<?> at = Features.toAttribute(property).orElse(null); ++ final DefaultAttributeType<?> at = Features.toAttribute(property).orElse(null); + if (at == null || !Geometries.isKnownType(at.getValueClass())) { + continue; // Not a geometry property. Ignore as per method contract. + } + /* + * If a property is a link to an attribute, then the key will be the name of the referenced + * attribute instead of the operation name. This is for avoiding to query the same geometry + * twice when the attribute is also specified explicitly in the array of properties. + */ + propertyName = property.getName().toString(); + attributeName = Features.getLinkTarget(property).orElse(propertyName); /* - * Set `characterizedByCRS` to true if we find at least one attribute which may have the - * "CRS" characteristic. Note that we cannot rely on `attributeCRS` being non-null + * Set `characterizedByCRS` to `true` if we find at least one attribute which have the + * "sis:crs" characteristic. Note that we cannot rely on `attributeCRS` being non-null * because an attribute may be characterized by a CRS without providing default CRS. */ - final DefaultAttributeType<?> ct = at.get().characteristics().get(AttributeConvention.CRS); - final AttributeType<?> ct = at.characteristics().get(AttributeConvention.CRS); ++ final DefaultAttributeType<?> ct = at.characteristics().get(AttributeConvention.CRS); if (ct != null && CoordinateReferenceSystem.class.isAssignableFrom(ct.getValueClass())) { - attributeCRS = (CoordinateReferenceSystem) ct.getDefaultValue(); // May still null. - if (targetCRS == null && isDefault) { - targetCRS = attributeCRS; - } + attributeCRS = (CoordinateReferenceSystem) ct.getDefaultValue(); // May still be null. characterizedByCRS = true; } - names.putIfAbsent(attributeName, attributeCRS); } + /* + * If the user did not specify a CRS explicitly, take the CRS of the default geometry. + * If there is no default geometry, the CRS of the first geometry will be taken in next loop. + */ + if (AttributeConvention.GEOMETRY.equals(propertyName)) { + defaultGeometry = attributeName; + defaultIsFirst = names.isEmpty(); + if (targetCRS == null) { + targetCRS = attributeCRS; + } + } + names.putIfAbsent(attributeName, attributeCRS); } /* * Copy the names in an array with the default geometry first. If possible, find the coordinate operations @@@ -233,11 -293,37 +282,37 @@@ */ @Override @SuppressWarnings("ReturnOfCollectionOrArrayField") - public synchronized Set<String> getDependencies() { - if (dependencies == null) { - dependencies = CollectionsExt.immutableSet(true, attributeNames); + public Set<String> getDependencies() { + Set<String> cached = dependencies; + if (cached == null) { + // Not really a problem if computed twice concurrently. + dependencies = cached = CollectionsExt.immutableSet(true, attributeNames); + } + return cached; + } + + /** + * Returns the same operation but using different properties as inputs. + * + * @param dependencies the new properties to use as operation inputs. + * @return the new operation, or {@code this} if unchanged. + */ + @Override - public Operation updateDependencies(final Map<String, PropertyType> dependencies) { ++ public AbstractOperation updateDependencies(final Map<String, AbstractIdentifiedType> dependencies) { + boolean foundAny = false; - final var geometryAttributes = new PropertyType[attributeNames.length]; ++ final var geometryAttributes = new AbstractIdentifiedType[attributeNames.length]; + for (int i=0; i < geometryAttributes.length; i++) { + foundAny |= (geometryAttributes[i] = dependencies.get(attributeNames[i])) != null; + } + if (foundAny) try { + var op = new EnvelopeOperation(inherit(), explicitCRS ? targetCRS : null, geometryAttributes, this); + if (!equals(op)) { + return FeatureOperations.POOL.unique(op); + } + } catch (FactoryException e) { - throw new FeatureInstantiationException(e.getMessage(), e); ++ throw new IllegalStateException(e.getMessage(), e); } - return dependencies; + return this; } /** @@@ -326,7 -412,7 +401,7 @@@ * a CRS characteristic is associated to a particular feature, setting `op` to null * will cause a new coordinate operation to be searched. */ - final AbstractAttribute<?> at = ((AbstractAttribute<?>) feature.getProperty(attributeNames[i])) - final var at = ((Attribute<?>) feature.getProperty(attributeNames[i])) ++ final var at = ((AbstractAttribute<?>) feature.getProperty(attributeNames[i])) .characteristics().get(AttributeConvention.CRS); final Object geomCRS; if (at != null && (geomCRS = at.getValue()) != null) { diff --cc endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/FeatureOperations.java index 4df8b93098,1b9bde711c..07d883ef4b --- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/FeatureOperations.java +++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/FeatureOperations.java @@@ -257,11 -250,11 +257,11 @@@ public final class FeatureOperations ex * @return an operation which will compute the envelope encompassing all geometries in the given attributes. * @throws FactoryException if a coordinate operation to the target CRS cannot be created. */ - public static Operation envelope(final Map<String,?> identification, final CoordinateReferenceSystem crs, - final PropertyType... geometryAttributes) throws FactoryException + public static AbstractOperation envelope(final Map<String,?> identification, final CoordinateReferenceSystem crs, + final AbstractIdentifiedType... geometryAttributes) throws FactoryException { ArgumentChecks.ensureNonNull("geometryAttributes", geometryAttributes); - return POOL.unique(new EnvelopeOperation(identification, crs, geometryAttributes)); + return POOL.unique(new EnvelopeOperation(identification, crs, geometryAttributes, null)); } /** diff --cc endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/GroupAsPolylineOperation.java index 69481d4ba1,7f603f4ca3..64d4fbe988 --- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/GroupAsPolylineOperation.java +++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/GroupAsPolylineOperation.java @@@ -28,7 -28,16 +28,8 @@@ import org.apache.sis.geometry.wrapper. import org.apache.sis.geometry.wrapper.GeometryType; import org.apache.sis.geometry.wrapper.GeometryWrapper; import org.apache.sis.setup.GeometryLibrary; + import org.apache.sis.util.privy.CollectionsExt; -// Specific to the geoapi-3.1 and geoapi-4.0 branches: -import org.opengis.feature.Feature; -import org.opengis.feature.Property; -import org.opengis.feature.PropertyType; -import org.opengis.feature.AttributeType; -import org.opengis.feature.FeatureAssociationRole; -import org.opengis.feature.Operation; - /** * Creates a single (Multi){@code Polyline} instance from a sequence of points or polylines stored in another property. @@@ -89,8 -98,7 +90,7 @@@ final class GroupAsPolylineOperation ex } isFeatureAssociation = false; } else { - isFeatureAssociation = (components instanceof DefaultAssociationRole) - && ((DefaultAssociationRole) components).getMaximumOccurs() == 1; - isFeatureAssociation = (components instanceof FeatureAssociationRole); ++ isFeatureAssociation = (components instanceof DefaultAssociationRole); if (!isFeatureAssociation) { throw new IllegalArgumentException(Resources.format(Resources.Keys.IllegalPropertyType_2, components.getName(), components.getClass())); @@@ -121,6 -129,32 +121,32 @@@ return EMPTY_PARAMS; } + /** + * Returns the names of feature properties that this operation needs for performing its task. + */ + @Override + public Set<String> getDependencies() { + return Set.of(propertyName); + } + + /** + * Returns the same operation but using different properties as inputs. + * + * @param dependencies the new properties to use as operation inputs. + * @return the new operation, or {@code this} if unchanged. + */ + @Override - public Operation updateDependencies(final Map<String, PropertyType> dependencies) { - final PropertyType target = dependencies.get(propertyName); ++ public AbstractOperation updateDependencies(final Map<String, AbstractIdentifiedType> dependencies) { ++ final AbstractIdentifiedType target = dependencies.get(propertyName); + if (target != null) { + final AbstractOperation op = create(inherit(), geometries.library, target); + if (!equals(op)) { + return FeatureOperations.POOL.unique(op); + } + } + return this; + } + /** * Returns the expected result type. */ diff --cc endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/LinkOperation.java index 02a3a25491,63da5f3233..7a092d80e0 --- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/LinkOperation.java +++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/LinkOperation.java @@@ -94,6 -104,21 +94,21 @@@ final class LinkOperation extends Abstr return Set.of(referentName); } + /** + * Returns the same operation but using different properties as inputs. + * + * @param dependencies the new properties to use as operation inputs. + * @return the new operation, or {@code this} if unchanged. + */ + @Override - public Operation updateDependencies(final Map<String, PropertyType> dependencies) { - final PropertyType target = dependencies.get(referentName); ++ public AbstractOperation updateDependencies(final Map<String, AbstractIdentifiedType> dependencies) { ++ final AbstractIdentifiedType target = dependencies.get(referentName); + if (target == null || target.equals(result)) { + return this; + } + return FeatureOperations.POOL.unique(new LinkOperation(inherit(), target)); + } + /** * Returns the property from the referenced attribute of feature association. * diff --cc endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/StringJoinOperation.java index bb3d2431a0,c1c035a7dd..3c62aa7964 --- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/StringJoinOperation.java +++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/StringJoinOperation.java @@@ -174,7 -196,8 +183,8 @@@ final class StringJoinOperation extend */ @SuppressWarnings({"rawtypes", "unchecked"}) // Generic array creation. StringJoinOperation(final Map<String,?> identification, final String delimiter, - final String prefix, final String suffix, final AbstractIdentifiedType[] singleAttributes) - final String prefix, final String suffix, final PropertyType[] singleAttributes, ++ final String prefix, final String suffix, final AbstractIdentifiedType[] singleAttributes, + final StringJoinOperation inheritFrom) { super(identification); attributeNames = new String[singleAttributes.length]; @@@ -192,15 -215,21 +202,21 @@@ * which may in turn produce an AttributeType. We do not accept more complex * combinations (e.g. operation producing an association). */ - IdentifiedType propertyType = singleAttributes[i]; + AbstractIdentifiedType propertyType = singleAttributes[i]; - ArgumentChecks.ensureNonNullElement("singleAttributes", i, propertyType); + if (inheritFrom == null) { + ArgumentChecks.ensureNonNullElement("singleAttributes", i, propertyType); + } else if (propertyType == null) { + attributeNames[i] = inheritFrom.attributeNames[i]; + converters[i] = inheritFrom.converters[i]; + continue; + } final GenericName name = propertyType.getName(); int maximumOccurs = 0; // May be a bitwise combination; need only to know if > 1. - PropertyNotFoundException cause = null; // In case of failure to find "sis:identifier" property. - final boolean isAssociation = (propertyType instanceof FeatureAssociationRole); + IllegalArgumentException cause = null; // In case of failure to find "sis:identifier" property. + final boolean isAssociation = (propertyType instanceof DefaultAssociationRole); if (isAssociation) { - final DefaultAssociationRole role = (DefaultAssociationRole) propertyType; - final var role = (FeatureAssociationRole) propertyType; - final FeatureType ft = role.getValueType(); ++ final var role = (DefaultAssociationRole) propertyType; + final DefaultFeatureType ft = role.getValueType(); maximumOccurs = role.getMaximumOccurs(); try { propertyType = ft.getProperty(AttributeConvention.IDENTIFIER); @@@ -282,12 -326,25 +313,25 @@@ } /** - * Returns the name of the properties from which to get the values to concatenate. - * This is the same information as {@link #getDependencies()}, only in a different - * kind of collection. + * Returns the same operation but using different properties as inputs. + * + * @param dependencies the new properties to use as operation inputs. + * @return the new operation, or {@code this} if unchanged. */ - final List<String> getAttributeNames() { - return UnmodifiableArrayList.wrap(attributeNames); + @Override - public Operation updateDependencies(final Map<String, PropertyType> dependencies) { ++ public AbstractOperation updateDependencies(final Map<String, AbstractIdentifiedType> dependencies) { + boolean hasNonNull = false; - final var singleAttributes = new PropertyType[attributeNames.length]; ++ final var singleAttributes = new AbstractIdentifiedType[attributeNames.length]; + for (int i=0; i < singleAttributes.length; i++) { + hasNonNull |= (singleAttributes[i] = dependencies.get(attributeNames[i])) != null; + } + if (hasNonNull) { + final var op = new StringJoinOperation(inherit(), delimiter, prefix, suffix, singleAttributes, this); + if (!(Arrays.equals(op.attributeNames, attributeNames) && Arrays.equals(op.converters, converters))) { + return FeatureOperations.POOL.unique(op); + } + } + return this; } /** diff --cc endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/builder/FeatureTypeBuilder.java index 467fc3877f,8a22418d3f..9e687ebe24 --- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/builder/FeatureTypeBuilder.java +++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/builder/FeatureTypeBuilder.java @@@ -946,7 -923,7 +946,7 @@@ public class FeatureTypeBuilder extend int identifierCursor = 0; for (int i=0; i<numSpecified; i++) { final PropertyTypeBuilder builder = properties.get(i); - final AbstractIdentifiedType instance = builder.build(); - final PropertyType instance = builder.buildForFeature(); ++ final AbstractIdentifiedType instance = builder.buildForFeature(); propertyTypes[propertyCursor] = instance; /* * Collect the attributes to use as identifier components while we loop over all properties. diff --cc endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/builder/OperationWrapper.java index 0b69adb84f,7a6a656f7e..8f533bf737 --- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/builder/OperationWrapper.java +++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/builder/OperationWrapper.java @@@ -16,12 -16,14 +16,14 @@@ */ package org.apache.sis.feature.builder; + import java.util.HashMap; import java.util.Objects; import org.opengis.util.GenericName; + import org.apache.sis.feature.AbstractOperation; import org.apache.sis.util.resources.Errors; -// Specific to the geoapi-3.1 and geoapi-4.0 branches: -import org.opengis.feature.PropertyType; +// Specific to the main branch: +import org.apache.sis.feature.AbstractIdentifiedType; /** @@@ -56,6 -58,41 +58,41 @@@ final class OperationWrapper extends Pr return operation; } + /** + * Returns the operation or an updated version of the operation. + * Updated versions are created for some kinds of operation, described below. + * Otherwise, this method returns the same value as {@link #build()}. + * + * <h4>Updated operations</h4> + * If the operation is a link to another property of the feature to build, the result type + * of the original operation is replaced by the target of the link in the feature to build. + * Even if the attribute name is the same, sometime the value class or some characteristics + * are different. Similar updates may also be applied to other kinds of operation. + * + * @throws IllegalStateException if the builder contains inconsistent information. + */ + @Override - final PropertyType buildForFeature() { ++ final AbstractIdentifiedType buildForFeature() { + final FeatureTypeBuilder owner = owner(); + if (operation instanceof AbstractOperation) { + final var op = (AbstractOperation) operation; - final var dependencies = new HashMap<String, PropertyType>(); ++ final var dependencies = new HashMap<String, AbstractIdentifiedType>(); + for (final String name : op.getDependencies()) { + final PropertyTypeBuilder target; + try { + target = owner.getProperty(name); + } catch (IllegalArgumentException e) { + throw new IllegalStateException(e.getMessage(), e); + } + if (target != null) { + dependencies.put(name, target.build()); + } + } + return op.updateDependencies(dependencies); + } + return operation; + } + /** * Do not allow a change of multiplicity. */ diff --cc endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/builder/PropertyTypeBuilder.java index d062c56da1,214fd41d2a..84753e8ddc --- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/builder/PropertyTypeBuilder.java +++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/builder/PropertyTypeBuilder.java @@@ -294,8 -294,21 +294,21 @@@ public abstract class PropertyTypeBuild * @throws IllegalStateException if the builder contains inconsistent information. */ @Override - public abstract PropertyType build() throws IllegalStateException; + public abstract AbstractIdentifiedType build() throws IllegalStateException; + /** + * Builds the final property type to use in {@code FeatureType}. + * This method is invoked by {@link FeatureTypeBuilder#build()}. + * Subclasses can assume that the {@linkplain FeatureTypeBuilder#properties property} list is complete + * and use that information for refreshing some information such as the targets of the links. + * + * @return the property type. + * @throws IllegalStateException if the builder contains inconsistent information. + */ - PropertyType buildForFeature() throws IllegalStateException { ++ AbstractIdentifiedType buildForFeature() throws IllegalStateException { + return build(); + } + /** * Flags this builder as a disposed one. The builder should not be used anymore after this method call. */ diff --cc endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/privy/FeatureProjectionBuilder.java index d3d6af82db,51ab93d85f..8951b1e8bb --- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/privy/FeatureProjectionBuilder.java +++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/privy/FeatureProjectionBuilder.java @@@ -201,8 -205,8 +203,8 @@@ public final class FeatureProjectionBui * @param deferred where to add operation's dependencies, or {@code null} for not collecting dependencies. * @return builder for the projected property, or {@code null} if it cannot be resolved. */ - private PropertyTypeBuilder addPropertyResult(AbstractIdentifiedType property, final List<String> deferred) { - private PropertyTypeBuilder addPropertyResult(PropertyType property, final Collection<String> deferred) { - if (property instanceof Operation) { ++ private PropertyTypeBuilder addPropertyResult(AbstractIdentifiedType property, final Collection<String> deferred) { + if (property instanceof AbstractOperation) { final GenericName name = property.getName(); do { if (deferred != null) { @@@ -446,12 -448,15 +446,15 @@@ * We cannot change the type of an operation (unless we replace the operation * by a stored attribute). Therefore, we only check type compatibility. */ - final var result = ((Operation) property).getResult(); - if (result instanceof AttributeType<?>) { - final Class<?> c = ((AttributeType<?>) result).getValueClass(); + final var result = ((AbstractOperation) property).getResult(); + if (result instanceof DefaultAttributeType<?>) { + final Class<?> c = ((DefaultAttributeType<?>) result).getValueClass(); final Class<?> r = type.apply(c); if (r != null) { - // We can be lenient for link operation, but must be strict for other operations. + /* + * We can be lenient for link operation, but must be strict for other operations. + * Example: a link to a geometry, but relaxing the `Polygon` type to `Geometry`. + */ if (Features.getLinkTarget(property).isPresent() ? r.isAssignableFrom(c) : r.equals(c)) { return true; } @@@ -628,8 -633,9 +631,9 @@@ * The elements added into {@code deferred} are {@linkplain #source} properties. * * @param deferred where to add missing transitive dependencies (source properties). + * @throws UnsupportedOperationException if there is an attempt to rename a property which is used by an operation. */ - private void resolveDependencies(final List<PropertyType> deferred) { + private void resolveDependencies(final List<AbstractIdentifiedType> deferred) { final var it = dependencies.entrySet().iterator(); while (it.hasNext()) { final Map.Entry<String, List<Item>> entry = it.next(); diff --cc endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultDatumEnsemble.java index 7297a5f299,ebffe6fdfe..10c416cf6e --- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultDatumEnsemble.java +++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultDatumEnsemble.java @@@ -87,7 -91,7 +87,7 @@@ public class DefaultDatumEnsemble<D ext * <td>{@link Identifier} (optionally as array)</td> * <td>{@link #getIdentifiers()}</td> * </tr><tr> -- * <td>{@value org.opengis.referencing.IdentifiedObject#DOMAINS_KEY}</td> ++ * <td>{@code "domains"}</td> * <td>{@link org.opengis.referencing.ObjectDomain} (optionally as array)</td> * <td>{@link #getDomains()}</td> * </tr><tr> diff --cc endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultEllipsoid.java index 0549fa5a02,4f8a8ae51f..38b503c9ef --- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultEllipsoid.java +++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultEllipsoid.java @@@ -551,6 -556,41 +556,41 @@@ public class DefaultEllipsoid extends A return flattening(other).subtract(flattening(this)).doubleValue(); } + /** + * Returns the properties to use for the ellipsoid created by {@link #convertTo(Unit)}. + * + * @param target the desired unit of measurement. + * @return properties of the derived ellipsoid to create. + */ + final Map<String,?> properties(final Unit<Length> target) { + return Map.of(NAME_KEY, '“' + getName().getCode() + "” converted to " + target, - DOMAINS_KEY, getDomains()); ++ "domains", getDomains()); + } + + /** + * Returns an ellipsoid of the same shape as this ellipsoid but using the specified unit of measurement. + * If the given unit of measurement is equivalent to the unit used by this ellipsoid, then this method + * returns {@code this}. Otherwise, a new ellipsoid with an arbitrary name is returned. + * + * @param target the desired unit of measurement. + * @return ellipsoid of the same shape using the given unit of measurement. + * + * @see #getAxisUnit() + * @since 1.5 + */ + public DefaultEllipsoid convertTo(final Unit<Length> target) { + final UnitConverter c = unit.getConverterTo(target); + if (c.isIdentity()) { + return this; + } + return new DefaultEllipsoid(properties(target), + c.convert(semiMajorAxis), + c.convert(semiMinorAxis), + inverseFlattening, + ivfDefinitive, + target); + } + /** * Compares this ellipsoid with the specified object for equality. * diff --cc endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/CoordinateOperationFinder.java index 50910e58a2,a8f1ed8f1b..fe690092d8 --- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/CoordinateOperationFinder.java +++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/CoordinateOperationFinder.java @@@ -509,73 -506,46 +502,46 @@@ public class CoordinateOperationFinder final CoordinateSystem targetCS = targetCRS.getCoordinateSystem(); final GeodeticDatum sourceDatum = PseudoDatum.of(sourceCRS); final GeodeticDatum targetDatum = PseudoDatum.of(targetCRS); - Matrix datumShift = null; - /* - * If the prime meridian is not the same, we will concatenate a longitude rotation before or after datum shift - * (that concatenation will be performed by the `MathTransformContext` builder created below). - * Actually we do not know if the longitude rotation should be before or after datum shift. But this ambiguity - * can usually be ignored because Bursa-Wolf parameters are always used with source and target prime meridians - * set to Greenwich in EPSG dataset 8.9. For safety, the SIS's DefaultGeodeticDatum class ensures that if the - * prime meridians are not the same, then the target meridian must be Greenwich. - */ - final MathTransformFactory mtFactory = factorySIS.getMathTransformFactory(); - final var context = new MathTransformContext(mtFactory, sourceDatum, targetDatum); - context.setSourceAxes(sourceCS, sourceDatum.getEllipsoid()); - context.setTargetAxes(targetCS, targetDatum.getEllipsoid()); /* - * If both CRS use the same datum and the same prime meridian, then the coordinate operation is only axis - * swapping, unit conversion or change of coordinate system type (Ellipsoidal ↔ Cartesian ↔ Spherical). - * Otherwise (if the datum are not the same), we will need to perform a scale, translation and rotation - * in Cartesian space using the Bursa-Wolf parameters. If the user does not require the best accuracy, - * then the Molodensky approximation may be used for avoiding the conversion step to geocentric CRS. + * Find the type of operation depending on whether there is a change of geodetic reference frame (datum). + * The `DATUM_SHIFT` and `ELLIPSOID_CHANGE` identifiers mean that there is a datum change, and all other + * identifiers mean that the coordinate operation is only a change of coordinate system type (Ellipsoidal + * ↔ Cartesian ↔ Spherical), axis swapping and unit conversions. */ - Identifier identifier; - boolean isGeographicToGeocentric = false; + final Matrix datumShift; + final Identifier identifier; + final MathTransform transform; + ParameterValueGroup parameters; + final Optional<OperationMethod> method; final Optional<GeodeticDatum> commonDatum = PseudoDatum.ofOperation(sourceCRS, targetCRS); if (commonDatum.isPresent()) { - final boolean isGeocentricToGeographic; - isGeographicToGeocentric = (sourceCS instanceof EllipsoidalCS && targetCS instanceof CartesianCS); - isGeocentricToGeographic = (sourceCS instanceof CartesianCS && targetCS instanceof EllipsoidalCS); /* - * Above booleans should never be true at the same time. If it nevertheless happen (we are paranoiac; - * maybe a lazy user implemented all interfaces in a single class), do not apply any geographic ↔ - * geocentric conversion. Instead, do as if the coordinate system types were the same. + * Coordinate system change (including change in the number of dimensions) without datum shift. + * May contain the addition of ellipsoidal height or spherical radius, which need an ellipsoid. */ - if (isGeocentricToGeographic ^ isGeographicToGeocentric) { - identifier = GEOCENTRIC_CONVERSION; - } else { - identifier = AXIS_CHANGES; - } + final boolean isGeographic = (sourceCS instanceof EllipsoidalCS); + identifier = isGeographic != (targetCS instanceof EllipsoidalCS) ? GEOCENTRIC_CONVERSION : AXIS_CHANGES; - final var builder = factorySIS.getMathTransformFactory().builder(Constants.COORDINATE_SYSTEM_CONVERSION); ++ final var builder = CoordinateOperations.builder(factorySIS.getMathTransformFactory(), Constants.COORDINATE_SYSTEM_CONVERSION); + final var ellipsoid = (isGeographic ? sourceDatum : targetDatum).getEllipsoid(); + builder.setSourceAxes(sourceCS, ellipsoid); + builder.setTargetAxes(targetCS, ellipsoid); + transform = builder.create(); + method = builder.getMethod(); + parameters = builder.parameters(); + datumShift = null; } else { - identifier = ELLIPSOID_CHANGE; - if (sourceDatum instanceof DefaultGeodeticDatum) { - datumShift = ((DefaultGeodeticDatum) sourceDatum).getPositionVectorTransformation(targetDatum, areaOfInterest); - if (datumShift != null) { - identifier = DATUM_SHIFT; - } - } - } - /* - * Conceptually, all transformations below could be done by first converting from source coordinate - * system to geocentric Cartesian coordinates (X,Y,Z), apply an affine transform represented by the - * datum shift matrix, then convert from the (X′,Y′,Z′) coordinates to the target coordinate system. - * However, there are two exceptions to this path: - * - * 1) In the particular where both the source and target CS are ellipsoidal, we may use the - * Molodensky approximation as a shortcut (if the desired accuracy allows). - * - * 2) Even if we really go through the XYZ coordinates without Molodensky approximation, there is - * at least 9 different ways to name this operation depending on whether the source and target - * CRS are geocentric or geographic, 2- or 3-dimensional, whether there is a translation or not, - * the rotation sign, etc. We try to use the most specific name if we can find one, and fallback - * on an arbitrary name only in last resort. - */ - MathTransform before = null, after = null; - ParameterValueGroup parameters; - OperationMethod method = null; - if (identifier == DATUM_SHIFT || identifier == ELLIPSOID_CHANGE) { /* - * If the transform can be represented by a single coordinate operation, returns that operation. + * Conceptually, all transformations below could be done by first converting from source coordinate + * system to geocentric Cartesian coordinates (X,Y,Z), apply an affine transform represented by the + * datum shift matrix, then convert from the (X′,Y′,Z′) coordinates to the target coordinate system. + * However, there are exceptions to this path: + * + * 1) Conversion from ellipsoidal to spherical CS can skip the Cartesian step for performance. + * 2) Transformation between ellipsoidal CS may use the Molodensky approximation as a shortcut. + * 3) Even when really going through the XYZ coordinates, the name of that operation depends on + * whether the source and target CRS are geocentric or geographic, 2- or 3-dimensional, + * whether there is a translation, the rotation sign, etc. + * * Possible operations are: * * - Position Vector transformation (in geocentric, geographic-2D or geographic-3D domains) diff --cc endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/MathTransformContext.java index 977d7c478c,1e8f15335d..c610643d16 --- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/MathTransformContext.java +++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/MathTransformContext.java @@@ -92,12 -110,12 +110,12 @@@ final class MathTransformContext extend * @return a conversion from the given source to the given target coordinate system. * @throws FactoryException if the conversion cannot be created. */ - final MathTransform createCoordinateSystemChange(final CoordinateSystem source, - final CoordinateSystem target, - final Ellipsoid ellipsoid) + private MathTransform createCoordinateSystemChange(final CoordinateSystem source, + final CoordinateSystem target, + final Ellipsoid ellipsoid) throws FactoryException { - final var builder = CoordinateOperations.builder(getFactory(), Constants.COORDINATE_SYSTEM_CONVERSION); - final var builder = factory.builder(Constants.COORDINATE_SYSTEM_CONVERSION); ++ final var builder = CoordinateOperations.builder(factory, Constants.COORDINATE_SYSTEM_CONVERSION); builder.setSourceAxes(source, ellipsoid); builder.setTargetAxes(target, ellipsoid); return builder.create(); diff --cc endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/CoordinateSystemTransformBuilder.java index e01b832660,82a6d02100..225b068543 --- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/CoordinateSystemTransformBuilder.java +++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/CoordinateSystemTransformBuilder.java @@@ -201,12 -250,52 +253,52 @@@ final class CoordinateSystemTransformBu /** * Implementation of {@code create(…)} for a single component. - * This implementation can handle changes of coordinate system type between + * This implementation can handle changes of coordinate system type between {@link EllipsoidalCS}, * {@link CartesianCS}, {@link SphericalCS}, {@link CylindricalCS} and {@link PolarCS}. + * + * @param stepSource source coordinate system of the step to build. + * @param stepTarget target coordinate system of the step to build. + * @return transform between the given coordinate systems (never null in current implementation). + * @throws IllegalArgumentException if the <abbr>CS</abbr> are not compatible, or axes do not match. + * @throws IncommensurableException if the units are not compatible, or the conversion is non-linear. + * @throws FactoryException if a factory method failed. */ private MathTransform single(final CoordinateSystem stepSource, - final CoordinateSystem stepTarget) throws FactoryException + final CoordinateSystem stepTarget) + throws FactoryException, IncommensurableException { + /* + * Cases that require an ellipsoid. All those cases are delegated to another operation method + * in the transform factory. The check for axis order and unit of measurement will be done by + * public methods of the factory, which may invoke this `CoordinateSystemTransformBuilder` + * recursively but with a different pair of coordinate systems. + */ + if (ellipsoid != null) { + if (stepSource instanceof EllipsoidalCS) { + if (stepTarget instanceof EllipsoidalCS) { + return addOrRemoveVertical(stepSource, stepTarget, Geographic2Dto3D.NAME, Geographic3Dto2D.NAME); + } + if ((stepTarget instanceof CartesianCS || stepTarget instanceof SphericalCS)) { - final var context = factory.builder(GeographicToGeocentric.NAME); ++ final var context = CoordinateOperations.builder(factory, GeographicToGeocentric.NAME); + context.setSourceAxes(stepSource, ellipsoid); + context.setTargetAxes(stepTarget, null); + return delegate(context); + } + } else if (stepTarget instanceof EllipsoidalCS) { + if ((stepSource instanceof CartesianCS || stepSource instanceof SphericalCS)) { - final var context = factory.builder(GeocentricToGeographic.NAME); ++ final var context = CoordinateOperations.builder(factory, GeocentricToGeographic.NAME); + context.setSourceAxes(stepSource, null); + context.setTargetAxes(stepTarget, ellipsoid); + return delegate(context); + } + } else if (stepSource instanceof SphericalCS && stepTarget instanceof SphericalCS) { + return addOrRemoveVertical(stepSource, stepTarget, Spherical2Dto3D.NAME, Spherical3Dto2D.NAME); + } + } + /* + * Cases that can be done without ellipsoid. Change of axis order and unit of measurement + * needs to be done here. There is no `CoordinateSystemTransformBuilder` recursive calls. + */ int passthrough = 0; CoordinateSystemTransform kernel = null; if (stepSource instanceof CartesianCS) { @@@ -228,32 -317,122 +320,122 @@@ passthrough = 1; } } - Exception cause = null; - try { - if (kernel == null) { - return factory.createAffineTransform(CoordinateSystems.swapAndScaleAxes(stepSource, stepTarget)); - } else if (stepSource.getDimension() == kernel.getSourceDimensions() + passthrough && - stepTarget.getDimension() == kernel.getTargetDimensions() + passthrough) + final MathTransform normalized, result; + final OperationMethod method; + if (kernel == null) { + method = Affine.provider(); + result = factory.createAffineTransform(CoordinateSystems.swapAndScaleAxes(stepSource, stepTarget)); + normalized = result; + } else { + if (stepSource.getDimension() != kernel.getSourceDimensions() + passthrough || + stepTarget.getDimension() != kernel.getTargetDimensions() + passthrough) { - final MathTransform tr = (passthrough == 0) - ? kernel.completeTransform(factory) - : kernel.passthrough(factory); - final MathTransform before = factory.createAffineTransform( - CoordinateSystems.swapAndScaleAxes(stepSource, - CoordinateSystems.replaceAxes(stepSource, AxesConvention.NORMALIZED))); - final MathTransform after = factory.createAffineTransform( - CoordinateSystems.swapAndScaleAxes( - CoordinateSystems.replaceAxes(stepTarget, AxesConvention.NORMALIZED), stepTarget)); - final MathTransform result = factory.createConcatenatedTransform(before, - factory.createConcatenatedTransform(tr, after)); - provider = (passthrough == 0 ? kernel.method : kernel.method3D); - return result; + throw new OperationNotFoundException(operationNotFound(stepSource, stepTarget)); + } + final MathTransform before, after; + if (passthrough == 0) { + method = kernel.method; + normalized = kernel.completeTransform(factory); + } else { + method = kernel.method3D; + normalized = kernel.passthrough(factory); + } + /* + * Adjust for axis order an units of measurement. + */ + before = factory.createAffineTransform( + CoordinateSystems.swapAndScaleAxes(stepSource, + CoordinateSystems.replaceAxes(stepSource, AxesConvention.NORMALIZED))); + after = factory.createAffineTransform( + CoordinateSystems.swapAndScaleAxes( + CoordinateSystems.replaceAxes(stepTarget, AxesConvention.NORMALIZED), stepTarget)); + result = factory.createConcatenatedTransform(before, + factory.createConcatenatedTransform(normalized, after)); + } + setParameters(normalized, method, null); + return result; + } + + /** + * Adds or removes the ellipsoidal height or spherical radius dimension. + * + * @param stepSource source coordinate system of the step to build. + * @param stepTarget target coordinate system of the step to build. + * @param add the operation method for adding the vertical dimension. + * @param remove the operation method for removing the vertical dimension. + * @return transform adding or removing a vertical coordinate. + * @throws IllegalArgumentException if the <abbr>CS</abbr> are not compatible, or axes do not match. + * @throws IncommensurableException if the units are not compatible, or the conversion is non-linear. + * @throws FactoryException if a factory method failed. + * + * @see org.apache.sis.referencing.internal.ParameterizedTransformBuilder#addOrRemoveVertical + */ + private MathTransform addOrRemoveVertical(final CoordinateSystem stepSource, + final CoordinateSystem stepTarget, + final String add, final String remove) + throws FactoryException, IncommensurableException + { + final int change = stepTarget.getDimension() - stepSource.getDimension(); + if (change != 0) { + final String method = change < 0 ? remove : add; - final var context = factory.builder(method); ++ final var context = CoordinateOperations.builder(factory, method); + context.setSourceAxes(stepSource, ellipsoid); + context.setTargetAxes(stepTarget, ellipsoid); + return delegate(context); + } + // No change in the number of dimensions. Maybe there is axis swapping and unit conversions. + MathTransform step = factory.createAffineTransform(CoordinateSystems.swapAndScaleAxes(stepSource, stepTarget)); + setParameters(step, Affine.provider(), null); + return step; + } + + /** + * Delegates the transform creation to another builder, then remember the operation method which was used. + * + * @param context an initialized context on which to invoke the {@code create()} method. + * @return result of {@code context.create()}. + * @throws FactoryException if the given context cannot create the transform. + */ - private MathTransform delegate(final MathTransform.Builder context) throws FactoryException { ++ private MathTransform delegate(final MathTransformBuilder context) throws FactoryException { + final MathTransform step = context.create(); + setParameters(step, context.getMethod().orElse(null), context.parameters()); + return step; + } + + /** + * Remembers the operation method and parameters for the given transform. + * + * @param result the transform that has been created. + * @param method the method, or {@code null} if unspecified. + * @param values the parameter values, or {@code null} for inferring from the method. + */ + private void setParameters(final MathTransform result, final OperationMethod method, final ParameterValueGroup values) { + final byte type; + if (result.isIdentity()) { + type = IDENTITY; + } else if (MathTransforms.isLinear(result)) { + type = LINEAR; + } else { + type = CONVERSION; + } + if (parametersType < type) { + parametersType = type; + provider = method; + parameters= values; + if (result instanceof Parameterized) { + parameterized = (Parameterized) result; } - } catch (IllegalArgumentException | IncommensurableException e) { - cause = e; } - throw new OperationNotFoundException(Resources.format(Resources.Keys.CoordinateOperationNotFound_2, + } + + /** + * Returns the error message for an operation not found between the coordinate systems. + */ + private static String operationNotFound(final CoordinateSystem stepSource, + final CoordinateSystem stepTarget) + { + return Resources.format(Resources.Keys.CoordinateOperationNotFound_2, WKTUtilities.toType(CoordinateSystem.class, stepSource.getClass()), - WKTUtilities.toType(CoordinateSystem.class, stepTarget.getClass())), cause); + WKTUtilities.toType(CoordinateSystem.class, stepTarget.getClass())); } } diff --cc endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/EllipsoidToCentricTransform.java index 1b72fb547c,7c6fd5656a..49cc90c86c --- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/EllipsoidToCentricTransform.java +++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/EllipsoidToCentricTransform.java @@@ -500,10 -631,10 +631,10 @@@ public class EllipsoidToCentricTransfor final double h; switch (dim) { default: throw mismatchedDimension("point", getSourceDimensions(), dim); - case 3: wh = true; h = point.getOrdinate(2); break; - case 3: wh = true; h = point.getCoordinate(VERTICAL_DIM); break; ++ case 3: wh = true; h = point.getOrdinate(VERTICAL_DIM); break; case 2: wh = false; h = 0; break; } - return transform(point.getCoordinate(0), point.getCoordinate(1), h, null, 0, true, wh); + return transform(point.getOrdinate(0), point.getOrdinate(1), h, null, 0, true, wh); } /** @@@ -848,9 -1027,9 +1027,9 @@@ */ @Override public Matrix derivative(final DirectPosition point) throws TransformException { - final double[] coordinate = point.getCoordinate(); - ArgumentChecks.ensureDimensionMatches("point", 3, coordinate); - return this.transform(coordinate, 0, coordinate, 0, true); - final double[] coordinates = point.getCoordinates(); ++ final double[] coordinates = point.getCoordinate(); + ArgumentChecks.ensureDimensionMatches("point", NUM_CENTRIC_DIM, coordinates); + return this.transform(coordinates, 0, coordinates, 0, true); } /** diff --cc endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/OnewayLinearTransform.java index 0000000000,c5f4f31a45..08e43d6e1d mode 000000,100644..100644 --- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/OnewayLinearTransform.java +++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/OnewayLinearTransform.java @@@ -1,0 -1,212 +1,190 @@@ + /* + * 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.referencing.operation.transform; + + import java.io.Serializable; -import java.nio.DoubleBuffer; -import java.nio.FloatBuffer; + import org.opengis.geometry.DirectPosition; + import org.opengis.parameter.ParameterValueGroup; + import org.opengis.parameter.ParameterDescriptorGroup; + import org.opengis.referencing.operation.Matrix; + import org.opengis.referencing.operation.MathTransform; + import org.opengis.referencing.operation.TransformException; + + + /** + * A transform which is linear in the forward direction, but non-linear in the inverse direction. + * This case happens when the original transform is non-linear, but the inverse of that transform + * just drops the non-linear dimension. We want the inverse of the inverse to return the original + * transform. + * + * <p>Subclasses must implement {@link #inverse()}. That information is not stored as a field in this + * {@code OnewayLinearTransform} class because subclasses typically need a specific inverse subclass. + * Implementations should also override {@link #getContextualParameters()} and related methods.</p> + * + * @author Martin Desruisseaux (Geomatys) + */ + abstract class OnewayLinearTransform extends AbstractMathTransform.Inverse implements Serializable { + /** + * Serial number for inter-operability with different versions. + */ + private static final long serialVersionUID = -3677320306734738831L; + + /** + * The transform on which to delegate all operations except inverse. + */ + @SuppressWarnings("serial") // Most SIS implementations are serializable. + protected final LinearTransform delegate; + + /** + * Creates a new instance which will delegate most operations to the given transform. + * + * @param delegate the transform on which to delegate all operations except inverse. + */ + protected OnewayLinearTransform(final LinearTransform delegate) { + this.delegate = delegate; + } + + /** + * Case where {@code delegate} is the result of a concatenation of a kernel with normalization + * and denormalization matrices. Because of optimization, the result of the concatenation is a + * single {@link LinearTransform} with no information about the steps that produced the result. + * This class keeps (indirectly) a reference to the contextual parameters. + */ + static final class Concatenated extends OnewayLinearTransform { + /** Serial number for inter-operability with different versions. */ + private static final long serialVersionUID = -4439900049126605063L; + + /** + * The kernel of {@link #delegate}, without the normalization and denormalization. + * Used mostly for Well Known Text formatting. May be {@code null} if none. + */ + @SuppressWarnings("serial") // Most SIS implementations are serializable. + private final AbstractMathTransform kernel; + + /** + * The original transform for which this inverse is created. + */ + @SuppressWarnings("serial") // Most SIS implementations are serializable. + private final MathTransform inverse; + + /** + * Creates a new one-way linear transform. + * + * @param delegate the transform on which to delegate all operations except inverse. + * @param kernel the kernel of {@code delegate}, without the normalization and denormalization. + * @param inverse the original transform for which this inverse is created. + */ + Concatenated(final LinearTransform delegate, final AbstractMathTransform kernel, final MathTransform inverse) { + super(delegate); + this.kernel = kernel; + this.inverse = inverse; + } + + /** + * Returns the descriptor of the parameters returned by {@link #getParameterValues()}. + * See that latter method for more information. + */ + @Override + public ParameterDescriptorGroup getParameterDescriptors() { + final ParameterValueGroup parameters = getParameterValues(); + return (parameters != null) ? parameters.getDescriptor() : null; + } + + /** + * Returns the contextual parameters of the {@linkplain #kernel} as the parameters of this + * concatenated transform. The contextual parameters describes a kernel operation together + * with its normalization and denormalization matrices. Since those 3 transforms have been + * combined into a single transform (which is {@link #delegate}), the contextual parameters + * of the {@linkplain #kernel} applies to the parameters of this concatenated transform. + */ + @Override + public ParameterValueGroup getParameterValues() { + return (kernel != null) ? kernel.getContextualParameters() : null; + } + + /** + * Returns the original transform for which this transform is the inverse. + */ + @Override + public MathTransform inverse() { + return inverse; + } + } + + /** + * Returns whether the {@code tr} transform is null or is the actual implementation of {@code wrapper}. + * This method is used for assertions. + * + * @param tr the transform which is expected to be null or wrapped. + * @param wrapper the transform which is potentially a wrapper for {@code delegate}. + * @return whether {@code tr} is null or the implementation of {@code wrapper}. + */ + static boolean isNullOrDelegate(final MathTransform tr, final MathTransform wrapper) { + return (tr == null) || (wrapper instanceof OnewayLinearTransform && ((OnewayLinearTransform) wrapper).delegate == tr); + } + + /** + * Computes the derivative at the given location. + */ + @Override + public Matrix derivative(final DirectPosition point) throws TransformException { + return delegate.derivative(point); + } + + /** + * Transforms the given array of point and optionally computes the derivative. + * The implementation delegates to the {@link #delegate} linear transform. + */ + @Override + public Matrix transform(final double[] srcPts, final int srcOff, + final double[] dstPts, final int dstOff, + final boolean derivate) throws TransformException + { + if (delegate instanceof AbstractMathTransform) { + return ((AbstractMathTransform) delegate).transform(srcPts, srcOff, dstPts, dstOff, derivate); + } + if (dstPts != null) { + delegate.transform(srcPts, srcOff, dstPts, dstOff, 1); + } + return derivate ? delegate.derivative(null) : null; // Position of a linear transform can be null. + } + + @Override + public DirectPosition transform(DirectPosition source, DirectPosition target) throws TransformException { + return delegate.transform(source, target); + } + + @Override + public void transform(double[] srcPts, int srcOff, double[] dstPts, int dstOff, int numPts) throws TransformException { + delegate.transform(srcPts, srcOff, dstPts, dstOff, numPts); + } + + @Override + public void transform(float[] srcPts, int srcOff, float[] dstPts, int dstOff, int numPts) throws TransformException { + delegate.transform(srcPts, srcOff, dstPts, dstOff, numPts); + } + + @Override + public void transform(double[] srcPts, int srcOff, float[] dstPts, int dstOff, int numPts) throws TransformException { + delegate.transform(srcPts, srcOff, dstPts, dstOff, numPts); + } + + @Override + public void transform(float[] srcPts, int srcOff, double[] dstPts, int dstOff, int numPts) throws TransformException { + delegate.transform(srcPts, srcOff, dstPts, dstOff, numPts); + } - - @Override - public int transform(DoubleBuffer source, DoubleBuffer target) throws TransformException { - return delegate.transform(source, target); - } - - @Override - public int transform(FloatBuffer source, FloatBuffer target) throws TransformException { - return delegate.transform(source, target); - } - - @Override - public int transform(FloatBuffer source, DoubleBuffer target) throws TransformException { - return delegate.transform(source, target); - } - - @Override - public int transform(DoubleBuffer source, FloatBuffer target) throws TransformException { - return delegate.transform(source, target); - } + } diff --cc endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/ReferencingUtilities.java index 384290d9da,a2afff8b30..d14bc2d6f5 --- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/ReferencingUtilities.java +++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/ReferencingUtilities.java @@@ -52,15 -53,10 +53,16 @@@ import org.apache.sis.referencing.cs.Ax import org.apache.sis.referencing.cs.DefaultEllipsoidalCS; import org.apache.sis.referencing.internal.VerticalDatumTypes; import org.apache.sis.referencing.operation.transform.DefaultMathTransformFactory; + import org.apache.sis.parameter.DefaultParameterDescriptorGroup; -// Specific to the geoapi-3.1 and geoapi-4.0 branches: -import org.opengis.referencing.datum.DatumEnsemble; +// Specific to the main branch: +import java.util.Collection; +import java.util.NoSuchElementException; +import org.opengis.referencing.ReferenceIdentifier; +import org.apache.sis.referencing.datum.DefaultDatumEnsemble; +import org.apache.sis.pending.geoapi.referencing.MissingMethods; +import org.apache.sis.metadata.privy.Identifiers; +import org.apache.sis.xml.NilObject; /** @@@ -608,6 -560,31 +610,31 @@@ single: if (crs instanceof SingleCRS) return mapping; } + /** + * Returns a parameter descriptor group with the same parameters as the given group, but a different name. + * If the given code is equal to the current group code, then the {@code parameters} instance is returned. + * Otherwise, a new group is created with the same name {@linkplain Identifier#getAuthority() authority} + * and {@linkplain Identifier#getCodeSpace() code space} as the given parameter group, but with the + * {@linkplain Identifier#getCode() code} replaced by the given value. + * + * <p><b>Examples:</b> this method can be used for creating the parameters of an inverse operation + * in the common case where the inverse has the same parameters than the forward operation.</p> + * + * @param parameters the parameter group to rename, or {@code null}. + * @param code the new name of the group, in the same code space as the given parameters. + * @return a group with the same parameters but with a different name, or {@code null} if the given group is null. + */ + public static ParameterDescriptorGroup rename(final ParameterDescriptorGroup parameters, final String code) { + if (parameters != null) { - Identifier name = parameters.getName(); ++ ReferenceIdentifier name = parameters.getName(); + if (!code.equals(name.getCode())) { + name = new ImmutableIdentifier(name.getAuthority(), name.getCodeSpace(), code); + return new DefaultParameterDescriptorGroup(Map.of(ParameterDescriptorGroup.NAME_KEY, name), parameters); + } + } + return parameters; + } + /** * Returns short names for all axes of the given CRS. This method uses short names like "Latitude" or "Height", * even if the full ISO 19111 names are "Geodetic latitude" or "Ellipsoidal height". This is suitable as header diff --cc endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/EllipsoidToCentricTransformTest.java index 15f34db204,17f1206d96..e3487b5486 --- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/EllipsoidToCentricTransformTest.java +++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/EllipsoidToCentricTransformTest.java @@@ -39,9 -38,14 +38,11 @@@ import org.apache.sis.referencing.datum import static org.apache.sis.test.Assertions.assertSerializedEquals; import org.apache.sis.referencing.operation.provider.GeocentricTranslationTest; -// Specific to the geoapi-3.1 and geoapi-4.0 branches: -import org.opengis.test.ToleranceModifier; - /** - * Tests {@link EllipsoidToCentricTransform}. + * Tests {@link EllipsoidToCentricTransform} from geographic to geocentric coordinates. + * When a test provides hard-coded expected results, those results are in Cartesian coordinates. + * See {@link #targetType} for more information. * * @author Martin Desruisseaux (IRD, Geomatys) */ @@@ -121,10 -144,8 +141,9 @@@ public class EllipsoidToCentricTransfor tolerance = GeocentricTranslationTest.precision(1); // Required precision for (λ,φ) zTolerance = Formulas.LINEAR_TOLERANCE / 2; // Required precision for h zDimension = new int[] {2}; // Dimension of h where to apply zTolerance + tolerance = 1E-4; // Other SIS branches use a stricter threshold. verifyTransform(GeocentricTranslationTest.samplePoint(2), // X = 3771793.968, Y = 140253.342, Z = 5124304.349 metres GeocentricTranslationTest.samplePoint(1)); // 53°48'33.820"N, 02°07'46.380"E, 73.00 metres - loggings.assertNoUnexpectedLog(); } /** @@@ -138,10 -159,9 +157,9 @@@ final double delta = toRadians(100.0 / 60) / 1852; // Approximately 100 metres derivativeDeltas = new double[] {delta, delta, 100}; // (Δλ, Δφ, Δh) tolerance = Formulas.LINEAR_TOLERANCE; - toleranceModifier = ToleranceModifier.PROJECTION; +// toleranceModifier = ToleranceModifier.PROJECTION; - createGeodeticConversion(CommonCRS.WGS84.ellipsoid(), true); + createGeodeticConversion(HardCodedDatum.WGS84.getEllipsoid(), true); verifyInDomain(CoordinateDomain.GEOGRAPHIC, 306954540); - loggings.assertNoUnexpectedLog(); } /** @@@ -160,9 -181,8 +179,8 @@@ final double delta = toRadians(100.0 / 60) / 1852; derivativeDeltas = new double[] {delta, delta, 100}; tolerance = Formulas.LINEAR_TOLERANCE; - toleranceModifier = ToleranceModifier.PROJECTION; - verifyInverse(40, 30, 10000); +// toleranceModifier = ToleranceModifier.PROJECTION; + verifyInverse(new double[] {40, 30, 10000}); - loggings.assertNoUnexpectedLog(); } /** @@@ -181,17 -200,20 +198,20 @@@ * Derivative of the direct transform. */ tolerance = 1E-2; - derivativeDeltas = new double[] {toRadians(1.0 / 60) / 1852}; // Approximately one metre. + derivativeDeltas = new double[] { + toRadians(1.0 / 60) / 1852, // Approximately one metre. + toRadians(1.0 / 60) / 1852, + 1 + }; - verifyDerivative(point.getCoordinates()); + verifyDerivative(point.getCoordinate()); /* * Derivative of the inverse transform. */ point = transform.transform(point, null); transform = transform.inverse(); tolerance = 1E-8; - derivativeDeltas = new double[] {1}; // Approximately one metre. + derivativeDeltas = new double[] {1,1,1}; // Approximately one metre. - verifyDerivative(point.getCoordinates()); + verifyDerivative(point.getCoordinate()); - loggings.assertNoUnexpectedLog(); } /** diff --cc endorsed/src/org.apache.sis.storage.xml/main/org/apache/sis/storage/gpx/Types.java index 99f32801a2,99cfd33640..6c279b1227 --- a/endorsed/src/org.apache.sis.storage.xml/main/org/apache/sis/storage/gpx/Types.java +++ b/endorsed/src/org.apache.sis.storage.xml/main/org/apache/sis/storage/gpx/Types.java @@@ -41,12 -40,12 +40,12 @@@ import org.apache.sis.feature.builder.P import org.apache.sis.feature.builder.AttributeRole; import org.apache.sis.feature.privy.AttributeConvention; import org.apache.sis.geometry.wrapper.Geometries; - import org.apache.sis.storage.base.FeatureCatalogBuilder; + import org.apache.sis.storage.base.MetadataBuilder; import org.apache.sis.util.iso.DefaultNameFactory; -// Specific to the geoapi-3.1 and geoapi-4.0 branches: -import org.opengis.feature.FeatureType; -import org.opengis.feature.Operation; +// Specific to the main branch: +import org.apache.sis.feature.AbstractOperation; +import org.apache.sis.feature.DefaultFeatureType; /** diff --cc endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/FeatureQuery.java index 7add475ead,4412e94419..a0a15a293b --- a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/FeatureQuery.java +++ b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/FeatureQuery.java @@@ -43,18 -43,24 +43,19 @@@ import org.apache.sis.pending.jdk.JDK19 import org.apache.sis.util.ArgumentChecks; import org.apache.sis.util.CharSequences; import org.apache.sis.util.Emptiable; + import org.apache.sis.util.UnconvertibleObjectException; import org.apache.sis.util.iso.Names; -// Specific to the geoapi-3.1 and geoapi-4.0 branches: -import org.opengis.feature.Feature; -import org.opengis.feature.FeatureType; -import org.opengis.feature.Attribute; -import org.opengis.feature.AttributeType; -import org.opengis.feature.Operation; -import org.opengis.feature.PropertyNotFoundException; -import org.opengis.filter.FilterFactory; -import org.opengis.filter.Filter; -import org.opengis.filter.Expression; -import org.opengis.filter.InvalidFilterValueException; -import org.opengis.filter.Literal; -import org.opengis.filter.ValueReference; -import org.opengis.filter.SortBy; -import org.opengis.filter.SortProperty; +// Specific to the main branch: +import org.apache.sis.feature.AbstractFeature; +import org.apache.sis.feature.AbstractAttribute; +import org.apache.sis.feature.DefaultFeatureType; +import org.apache.sis.filter.Filter; +import org.apache.sis.filter.Expression; +import org.apache.sis.pending.geoapi.filter.Literal; +import org.apache.sis.pending.geoapi.filter.ValueReference; +import org.apache.sis.pending.geoapi.filter.SortBy; +import org.apache.sis.pending.geoapi.filter.SortProperty; /** @@@ -621,9 -621,12 +622,12 @@@ public class FeatureQuery extends Quer * * @param builder the builder where to add the property. * @return whether the property has been successfully added. + * @throws InvalidFilterValueException if {@linkplain #expression} is invalid. + * @throws PropertyNotFoundException if the property was not found in {@code builder.source()}. + * @throws UnconvertibleObjectException if the property default value cannot be converted to the expected type. */ final boolean addTo(final FeatureProjectionBuilder builder) { - final FeatureExpression<? super Feature, ?> fex = FeatureExpression.castOrCopy(expression); + final FeatureExpression<? super AbstractFeature, ?> fex = FeatureExpression.castOrCopy(expression); if (fex != null) { final FeatureProjectionBuilder.Item item = fex.expectedType(builder); if (item != null) { @@@ -745,10 -748,14 +749,14 @@@ * <li>Otherwise the localized string "Unnamed #1" with increasing numbers.</li> * </ul> * - * @param sourceType the feature type to project. - * @param locale locale for error messages, or {@code null} for the default locale. + * @param sourceType the feature type to project. + * @param locale locale for error messages, or {@code null} for the default locale. + * @throws InvalidFilterValueException if an {@linkplain NamedExpression#expression expression} is invalid. + * @throws PropertyNotFoundException if a property referenced by an expression was not found in {@code sourceType}. + * @throws UnconvertibleObjectException if a property default value cannot be converted to the expected type. + * @throws UnsupportedOperationException if there is an attempt to rename a property which is used by an operation. */ - final Optional<FeatureProjection> project(final FeatureType sourceType, final Locale locale) { + final Optional<FeatureProjection> project(final DefaultFeatureType sourceType, final Locale locale) { if (projection == null) { return Optional.empty(); } diff --cc endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/MetadataBuilder.java index b62caeb9ec,476d084f46..a307bc79a2 --- a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/MetadataBuilder.java +++ b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/MetadataBuilder.java @@@ -1881,10 -1875,8 +1880,8 @@@ public class MetadataBuilder * @param occurrences number of instances of the given feature type, or a negative value if unknown. * Note that ISO-19115 considers 0 as an invalid value. Consequently, if 0, the feature is not added. * @return the name of the added feature (even if not added to the metadata), or {@code null} if none. - * - * @see FeatureCatalogBuilder#define(DefaultFeatureType) */ - public final GenericName addFeatureType(final FeatureType type, final long occurrences) { + public final GenericName addFeatureType(final DefaultFeatureType type, final long occurrences) { if (type == null) { return null; } diff --cc endorsed/src/org.apache.sis.storage/test/org/apache/sis/storage/base/MetadataBuilderTest.java index b353796add,9ef0405554..dbbd6193b1 --- a/endorsed/src/org.apache.sis.storage/test/org/apache/sis/storage/base/MetadataBuilderTest.java +++ b/endorsed/src/org.apache.sis.storage/test/org/apache/sis/storage/base/MetadataBuilderTest.java @@@ -158,8 -154,8 +158,8 @@@ public final class MetadataBuilderTest assertTrue(metadata.getContentInfo().isEmpty()); } else { final ContentInformation content = getSingleton(metadata.getContentInfo()); - assertInstanceOf(DefaultFeatureCatalogueDescription.class, content); - final DefaultFeatureTypeInfo info = getSingleton(((DefaultFeatureCatalogueDescription) content).getFeatureTypeInfo()); - final var catalog = assertInstanceOf(FeatureCatalogueDescription.class, content); - final FeatureTypeInfo info = getSingleton(catalog.getFeatureTypeInfo()); ++ final var catalog = assertInstanceOf(DefaultFeatureCatalogueDescription.class, content); ++ final DefaultFeatureTypeInfo info = getSingleton(catalog.getFeatureTypeInfo()); assertEquals(expected, info.getFeatureInstanceCount(), errorMessage); } }
