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 e336ffb266cdc37cdfabc11de7d43b81a4a2f21f Merge: 52d55b27ea e0d696a63a Author: Martin Desruisseaux <[email protected]> AuthorDate: Thu Jan 22 19:21:48 2026 +0100 Merge branch 'geoapi-3.1' .../org.apache.sis.console/main/module-info.java | 14 +- .../apache/sis/console/ResourcesDownloader.java | 6 +- .../main/org/apache/sis/coverage/CategoryList.java | 2 +- .../org/apache/sis/coverage/CoverageCombiner.java | 2 +- .../org/apache/sis/coverage/SampleDimension.java | 28 +- .../coverage/grid/CoordinateOperationFinder.java | 136 ++-- .../apache/sis/coverage/grid/DefaultEvaluator.java | 21 +- .../apache/sis/coverage/grid/DimensionReducer.java | 105 ++- .../sis/coverage/grid/DimensionalityReduction.java | 2 +- .../apache/sis/coverage/grid/GridDerivation.java | 135 +++- .../org/apache/sis/coverage/grid/GridExtent.java | 6 +- .../org/apache/sis/coverage/grid/GridGeometry.java | 157 +++- .../sis/coverage/grid/TranslatedTransform.java | 281 ++++++++ .../apache/sis/feature/StringJoinOperation.java | 2 +- .../sis/feature/builder/FeatureTypeBuilder.java | 4 +- .../org/apache/sis/feature/internal/Resources.java | 2 +- .../sis/feature/internal/Resources.properties | 2 +- .../sis/feature/internal/Resources_fr.properties | 2 +- .../org/apache/sis/filter/ComparisonFilter.java | 6 +- .../apache/sis/filter/DefaultFilterFactory.java | 3 +- .../org/apache/sis/filter/IdentifierFilter.java | 2 +- .../main/org/apache/sis/filter/PropertyValue.java | 10 +- .../org/apache/sis/filter/base/BinaryFunction.java | 146 +--- .../sis/filter/base/BinaryFunctionWidening.java | 421 +++++++++++ .../apache/sis/filter/base/ConvertFunction.java | 9 +- .../main/org/apache/sis/filter/base/Node.java | 12 + .../sis/filter/{ => math}/ArithmeticFunction.java | 224 ++++-- .../org/apache/sis/filter/math/BinaryOperator.java | 10 +- .../sis/filter/sqlmm/GeometryConstructor.java | 2 +- .../apache/sis/filter/sqlmm/GeometryParser.java | 2 +- .../org/apache/sis/filter/sqlmm/OneGeometry.java | 6 +- .../main/org/apache/sis/filter/sqlmm/ST_Point.java | 2 +- .../org/apache/sis/filter/sqlmm/ST_Transform.java | 4 +- .../org/apache/sis/filter/sqlmm/TwoGeometries.java | 4 +- .../main/org/apache/sis/image/AnnotatedImage.java | 4 +- .../apache/sis/image/BandedSampleConverter.java | 4 +- .../main/org/apache/sis/image/DataType.java | 28 +- .../sis/image/internal/shared/ImageUtilities.java | 19 +- .../sis/image/internal/shared/TileOpExecutor.java | 2 +- .../sis/coverage/grid/GridCoverage2DTest.java | 24 +- .../sis/coverage/grid/GridDerivationTest.java | 25 + .../apache/sis/coverage/grid/GridGeometryTest.java | 37 +- .../org/apache/sis/filter/LogicalFilterTest.java | 12 +- .../image/internal/shared/ImageUtilitiesTest.java | 14 +- .../org/apache/sis/metadata/PropertyAccessor.java | 8 +- .../apache/sis/metadata/PropertyInformation.java | 4 +- .../main/org/apache/sis/metadata/StateChanger.java | 2 +- .../org/apache/sis/util/iso/RecordDefinition.java | 4 +- .../main/org/apache/sis/util/iso/TypeNames.java | 6 +- .../main/org/apache/sis/util/iso/Types.java | 2 +- .../main/org/apache/sis/xml/Pooled.java | 6 +- .../apache/sis/xml/bind/FinalClassExtensions.java | 7 +- .../sis/metadata/PropertyConsistencyCheck.java | 22 +- .../org/apache/sis/metadata/iso/APIVerifier.java | 2 +- .../apache/sis/metadata/iso/AllMetadataTest.java | 1 + .../main/org/apache/sis/portrayal/Canvas.java | 2 +- .../org/apache/sis/portrayal/CanvasContext.java | 6 +- .../sis/geometry/AbstractDirectPosition.java | 36 +- .../main/org/apache/sis/geometry/Envelopes.java | 55 +- .../apache/sis/geometry/GeneralDirectPosition.java | 39 +- .../sis/geometry/ImmutableDirectPosition.java | 161 +++++ .../org/apache/sis/geometry/ImmutableEnvelope.java | 15 +- .../main/org/apache/sis/io/wkt/ElementKind.java | 4 +- .../main/org/apache/sis/io/wkt/Formatter.java | 4 +- .../org/apache/sis/io/wkt/MathTransformParser.java | 6 +- .../main/org/apache/sis/io/wkt/StoredTree.java | 8 +- .../main/org/apache/sis/io/wkt/WKTFormat.java | 4 +- .../sis/parameter/DefaultParameterDescriptor.java | 3 +- .../sis/parameter/DefaultParameterValue.java | 37 +- .../org/apache/sis/parameter/MatrixParameters.java | 14 +- .../main/org/apache/sis/parameter/Verifier.java | 15 +- .../main/org/apache/sis/referencing/CRS.java | 679 ++++++++++++++---- .../sis/referencing/MultiRegisterOperations.java | 1 + .../sis/referencing/cs/CoordinateSystems.java | 4 +- .../apache/sis/referencing/internal/Resources.java | 2 +- .../sis/referencing/internal/Resources.properties | 2 +- .../referencing/internal/Resources_fr.properties | 2 +- .../internal/shared/CoordinateOperations.java | 8 - .../internal/shared/DirectPositionView.java | 3 +- .../internal/shared/ExtendedPrecisionMatrix.java | 2 +- .../sis/referencing/internal/shared/Formulas.java | 9 - .../operation/CoordinateOperationContext.java | 257 +++++-- .../operation/CoordinateOperationFinder.java | 66 +- .../operation/CoordinateOperationRegistry.java | 92 +-- .../operation/InverseOperationMethod.java | 6 +- .../operation/MismatchedDatumException.java | 8 +- .../MissingSourceDimensionsException.java | 121 ++++ .../referencing/operation/SubOperationInfo.java | 321 +++++---- .../operation/TransformedCoordinateSet.java | 5 +- .../sis/referencing/operation/matrix/Matrices.java | 46 +- .../referencing/operation/matrix/MatrixSIS.java | 7 +- .../matrix/NoninvertibleMatrixException.java | 8 +- .../matrix/UnderdeterminedMatrixException.java | 81 +++ .../referencing/operation/matrix/package-info.java | 2 +- .../operation/projection/AuthalicConversion.java | 14 +- .../operation/projection/ConformalProjection.java | 8 +- .../operation/projection/MeridianArcBased.java | 33 +- .../transform/AbstractLinearTransform.java | 69 +- .../operation/transform/AbstractMathTransform.java | 5 + .../operation/transform/ConcatenatedTransform.java | 1 + .../operation/transform/CopyTransform.java | 16 +- .../operation/transform/IdentityTransform.java | 1 - .../operation/transform/LinearTransform1D.java | 49 +- .../operation/transform/MathTransforms.java | 4 +- .../operation/transform/ProjectiveTransform.java | 37 +- .../operation/transform/ScaleTransform.java | 3 +- .../operation/transform/TransformJoiner.java | 4 +- .../operation/transform/TranslationTransform.java | 3 +- .../sis/parameter/DefaultParameterValueTest.java | 2 +- .../test/org/apache/sis/referencing/CRSTest.java | 11 + .../referencing/operation/matrix/MatricesTest.java | 5 +- .../transform/ProjectiveTransformTest.java | 13 +- .../sis/storage/geotiff/MultiResolutionImage.java | 30 +- .../apache/sis/storage/geotiff/NativeMetadata.java | 4 +- .../apache/sis/storage/geotiff/reader/Type.java | 2 +- .../apache/sis/storage/netcdf/base/Convention.java | 25 +- .../apache/sis/storage/netcdf/base/DataType.java | 50 +- .../sis/storage/netcdf/base/GridMapping.java | 6 +- .../sis/storage/netcdf/base/RasterResource.java | 6 +- .../apache/sis/storage/netcdf/base/Variable.java | 8 +- .../sis/storage/netcdf/base/DataTypeTest.java | 25 +- .../sis/storage/sql/feature/FeatureAnalyzer.java | 4 +- .../sis/storage/sql/feature/ValueGetter.java | 8 +- .../apache/sis/io/stream/HyperRectangleReader.java | 20 +- .../org/apache/sis/storage/aggregate/Group.java | 2 +- .../apache/sis/storage/base/MetadataBuilder.java | 2 +- .../apache/sis/storage/base/TiledGridCoverage.java | 2 + .../sis/io/stream/HyperRectangleReaderTest.java | 4 +- .../org/apache/sis/converter/ArrayConverter.java | 6 +- .../apache/sis/converter/ConverterRegistry.java | 6 +- .../org/apache/sis/converter/NumberConverter.java | 10 +- .../org/apache/sis/converter/StringConverter.java | 6 +- .../org/apache/sis/converter/SystemRegistry.java | 5 +- .../main/org/apache/sis/io/CompoundFormat.java | 4 +- .../main/org/apache/sis/io/DefaultFormat.java | 29 +- .../main/org/apache/sis/math/ArrayVector.java | 46 +- .../org/apache/sis/math/ConcatenatedVector.java | 7 +- .../main/org/apache/sis/math/Fraction.java | 6 +- .../org/apache/sis/math/LinearlyDerivedVector.java | 5 +- .../main/org/apache/sis/math/NumberType.java | 791 +++++++++++++++++++++ .../main/org/apache/sis/math/PackedVector.java | 3 +- .../main/org/apache/sis/math/Statistics.java | 2 +- .../main/org/apache/sis/math/Vector.java | 75 +- .../org/apache/sis/measure/MeasurementRange.java | 18 +- .../main/org/apache/sis/measure/NumberRange.java | 60 +- .../main/org/apache/sis/measure/Range.java | 4 +- .../main/org/apache/sis/measure/RangeFormat.java | 19 +- .../main/org/apache/sis/measure/UnitRegistry.java | 4 +- .../package-info.java => pending/jdk/JDK12.java} | 31 +- .../apache/sis/setup/InstallationResources.java | 7 +- .../apache/sis/setup/OptionalInstallations.java | 60 +- .../main/org/apache/sis/setup/package-info.java | 2 +- .../main/org/apache/sis/util/Classes.java | 3 +- .../main/org/apache/sis/util/Locales.java | 2 +- .../main/org/apache/sis/util/Localized.java | 2 +- .../main/org/apache/sis/util/Numbers.java | 427 ++++------- .../main/org/apache/sis/util/collection/Cache.java | 4 +- .../org/apache/sis/util/collection/Containers.java | 2 +- .../org/apache/sis/util/collection/DerivedMap.java | 2 +- .../org/apache/sis/util/collection/RangeSet.java | 16 +- .../sis/util/collection/TreeTableFormat.java | 4 +- .../sis/util/collection/WeakValueHashMap.java | 4 +- .../sis/util/internal/shared/AbstractMap.java | 2 +- .../apache/sis/util/internal/shared/Cloner.java | 2 +- .../apache/sis/util/internal/shared/Strings.java | 18 + .../main/org/apache/sis/util/logging/Logging.java | 2 +- .../main/org/apache/sis/util/resources/Errors.java | 4 +- .../apache/sis/util/resources/Errors.properties | 2 +- .../apache/sis/util/resources/Errors_fr.properties | 2 +- .../test/org/apache/sis/math/NumberTypeTest.java | 201 ++++++ .../test/org/apache/sis/test/Assertions.java | 2 +- .../test/org/apache/sis/util/NumbersTest.java | 121 +--- .../org/apache/sis/storage/isobmff/Reader.java | 4 +- .../apache/sis/storage/isobmff/gimi/ModelCRS.java | 2 +- netbeans-project/nbproject/project.xml | 1 + optional/src/org.apache.sis.gui/bundle/bin/sis | 2 +- .../src/org.apache.sis.gui/bundle/bin/sis_shell | 2 +- optional/src/org.apache.sis.gui/bundle/bin/sisfx | 2 +- .../src/org.apache.sis.gui/bundle/conf/imports.jsh | 2 +- .../org/apache/sis/gui/coverage/CellFormat.java | 4 +- .../main/module-info.java | 11 +- .../apache/sis/storage/gdal/GDALStoreProvider.java | 2 + .../org/apache/sis/storage/gdal/package-info.java | 4 +- 183 files changed, 4749 insertions(+), 1935 deletions(-) diff --cc endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/DefaultEvaluator.java index 664bb59a45,3662d73a6e..65f3acb06d --- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/DefaultEvaluator.java +++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/DefaultEvaluator.java @@@ -490,10 -492,10 +490,10 @@@ abstract class DefaultEvaluator impleme */ gridCoordinates = getInputToGrid(point.getCoordinateReferenceSystem()).transform(point, gridCoordinates); final int dimension = inputToGrid.getTargetDimensions(); - final double[] coordinates = point.getCoordinates(); + final double[] coordinates = point.getCoordinate(); final double[] gridCoords = (dimension <= coordinates.length) ? coordinates : new double[dimension]; inputToGrid.transform(coordinates, 0, gridCoords, 0, 1); - wraparound(gridCoords, 0, 1); + postTransform(gridCoords, 0, 1); return gridCoords; } diff --cc endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/DimensionReducer.java index a11e1f4ec8,daa2eebcc5..8e2362c5fa --- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/DimensionReducer.java +++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/DimensionReducer.java @@@ -78,12 -105,12 +105,12 @@@ final class DimensionReducer * If the position cannot be reduced, then it is returned as-is. */ final DirectPosition apply(final DirectPosition target) { - if (dimensions == null) { + if (target == null || dimensions == null) { return target; } - final GeneralDirectPosition position = new GeneralDirectPosition(reducedCRS); + final var position = new GeneralDirectPosition(reducedCRS); for (int i=0; i < dimensions.length; i++) { - position.coordinates[i] = target.getCoordinate(dimensions[i]); + position.coordinates[i] = target.getOrdinate(dimensions[i]); } return position; } @@@ -98,10 -125,10 +125,10 @@@ } final DirectPosition lowerCorner = target.getLowerCorner(); final DirectPosition upperCorner = target.getUpperCorner(); - final GeneralEnvelope envelope = new GeneralEnvelope(reducedCRS); + final var envelope = new GeneralEnvelope(reducedCRS); for (int i=0; i < dimensions.length; i++) { final int s = dimensions[i]; - envelope.setRange(i, lowerCorner.getCoordinate(s), upperCorner.getCoordinate(s)); + envelope.setRange(i, lowerCorner.getOrdinate(s), upperCorner.getOrdinate(s)); } return envelope; } diff --cc endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridGeometry.java index a716c79441,6a44b3c327..2ed70819fb --- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridGeometry.java +++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridGeometry.java @@@ -30,6 -32,8 +32,7 @@@ import org.opengis.util.FactoryExceptio import org.opengis.metadata.Identifier; import org.opengis.metadata.extent.GeographicBoundingBox; import org.opengis.geometry.Envelope; + import org.opengis.geometry.DirectPosition; -import org.opengis.coordinate.CoordinateMetadata; import org.opengis.referencing.operation.Matrix; import org.opengis.referencing.operation.MathTransform; import org.opengis.referencing.operation.TransformException; @@@ -83,9 -90,10 +89,10 @@@ import org.apache.sis.io.TableAppender import org.apache.sis.xml.NilObject; import org.apache.sis.xml.NilReason; import static org.apache.sis.referencing.CRS.findOperation; + import static org.apache.sis.referencing.CRS.SeparationMode; -// Specific to the geoapi-3.1 and geoapi-4.0 branches: -import org.opengis.coordinate.MismatchedDimensionException; +// Specific to the main branch: +import org.opengis.geometry.MismatchedDimensionException; /** @@@ -980,6 -1000,20 +999,20 @@@ public class GridGeometry implements Le * force developers to think about whether they want a gridToCRS transform locating pixel corner or center. */ + /** + * Returns the "real world" coordinate reference system together with the data epoch if any. + * This method is preferable to {@link #getCoordinateReferenceSystem()} when the <abbr>CRS</abbr> may be dynamic. + * + * @return the coordinate reference system (never {@code null}) together with the data epoch if any. + * @throws IncompleteGridGeometryException if this grid geometry has no <abbr>CRS</abbr> — + * i.e. <code>{@linkplain #isDefined isDefined}({@linkplain #CRS})</code> returned {@code false}. + * + * @since 1.6 + */ - public CoordinateMetadata getCoordinateMetadata() { ++ public DefaultCoordinateMetadata getCoordinateMetadata() { + return new DefaultCoordinateMetadata(getCoordinateReferenceSystem(), null); + } + /** * Returns the coordinate reference system of the given envelope if defined, or {@code null} if none. * Contrarily to {@link #getCoordinateReferenceSystem()}, this method does not throw exception. diff --cc endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/ComparisonFilter.java index 5d26cdae0b,1f07dff723..b79c097a88 --- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/ComparisonFilter.java +++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/ComparisonFilter.java @@@ -37,13 -37,15 +37,13 @@@ import java.time.temporal.ChronoField import java.time.temporal.Temporal; import org.apache.sis.math.Fraction; import org.apache.sis.filter.base.Node; - import org.apache.sis.filter.base.BinaryFunction; + import org.apache.sis.filter.base.BinaryFunctionWidening; -// Specific to the geoapi-3.1 and geoapi-4.0 branches: -import org.opengis.filter.Filter; -import org.opengis.filter.Expression; -import org.opengis.filter.MatchAction; -import org.opengis.filter.ComparisonOperatorName; -import org.opengis.filter.BinaryComparisonOperator; -import org.opengis.filter.BetweenComparisonOperator; +// Specific to the main branch: +import org.apache.sis.pending.geoapi.filter.MatchAction; +import org.apache.sis.pending.geoapi.filter.ComparisonOperatorName; +import org.apache.sis.pending.geoapi.filter.BinaryComparisonOperator; +import org.apache.sis.pending.geoapi.filter.BetweenComparisonOperator; /** @@@ -70,9 -72,9 +70,9 @@@ * @author Johann Sorel (Geomatys) * @author Martin Desruisseaux (Geomatys) * - * @param <R> the type of resources (e.g. {@link org.opengis.feature.Feature}) used as inputs. + * @param <R> the type of resources (e.g. {@code Feature}) used as inputs. */ - abstract class ComparisonFilter<R> extends BinaryFunction<R,Object,Object> + abstract class ComparisonFilter<R> extends BinaryFunctionWidening<R, Object, Object> implements BinaryComparisonOperator<R>, Optimization.OnFilter<R> { /** diff --cc endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/DefaultFilterFactory.java index 35cce7249c,095fe5cd6e..4e6f958d0f --- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/DefaultFilterFactory.java +++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/DefaultFilterFactory.java @@@ -180,10 -187,10 +181,10 @@@ public abstract class DefaultFilterFact * * @see #forFeatures() */ - static final Features<Object,Object> DEFAULT = new Features<>(Object.class, Object.class, WraparoundMethod.SPLIT); + static final Features<Object, Object> DEFAULT = new Features<>(Object.class, Object.class, WraparoundMethod.SPLIT); /** - * Creates a new factory operating on {@link Feature} instances. + * Creates a new factory operating on {@link AbstractFeature} instances. * See the {@linkplain DefaultFilterFactory#DefaultFilterFactory(Class, Class, WraparoundMethod)} * super-class constructor} for a list of valid class arguments. * diff --cc endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/IdentifierFilter.java index 7be5f05ce3,817d61d711..a3fd58122e --- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/IdentifierFilter.java +++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/IdentifierFilter.java @@@ -142,8 -149,8 +142,8 @@@ final class IdentifierFilter extends No if (object != null) try { Object id = object.getPropertyValue(property); if (id != null) return identifier.equals(id.toString()); - } catch (PropertyNotFoundException e) { + } catch (IllegalArgumentException e) { - warning(e, false); + warning(e); } return false; } diff --cc endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/PropertyValue.java index df231e855b,c6d049952e..50fcc9e12b --- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/PropertyValue.java +++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/PropertyValue.java @@@ -243,11 -245,11 +243,11 @@@ abstract class PropertyValue<V> extend * If no value is found for the given feature, then this method returns {@code null}. */ @Override - public Object apply(final Feature instance) { + public Object apply(final AbstractFeature instance) { if (instance != null) try { return instance.getPropertyValue(name); - } catch (PropertyNotFoundException e) { + } catch (IllegalArgumentException e) { - warning(e, false); + warning(e); } return null; } @@@ -320,11 -322,11 +320,11 @@@ * If no value is found for the given feature, then this method returns {@code null}. */ @Override - public V apply(final Feature instance) { + public V apply(final AbstractFeature instance) { if (instance != null) try { return ObjectConverters.convert(instance.getPropertyValue(name), type); - } catch (PropertyNotFoundException | UnconvertibleObjectException e) { + } catch (IllegalArgumentException e) { - warning(e, false); + warning(e); } return null; } @@@ -484,11 -486,11 +484,11 @@@ * If no value is found for the given feature, then this method returns {@code null}. */ @Override - public V apply(final Feature instance) { + public V apply(final AbstractFeature instance) { if (instance != null) try { return converter.apply(source.cast(instance.getPropertyValue(name))); - } catch (PropertyNotFoundException | ClassCastException | UnconvertibleObjectException e) { + } catch (IllegalArgumentException | ClassCastException e) { - warning(e, false); + warning(e); } return null; } @@@ -536,11 -538,11 +536,11 @@@ */ @Override @SuppressWarnings("unchecked") - public V apply(final Feature instance) { + public V apply(final AbstractFeature instance) { if (instance != null) try { return (V) instance.getPropertyValue(name); - } catch (PropertyNotFoundException e) { + } catch (IllegalArgumentException e) { - warning(e, false); + warning(e); } return null; } diff --cc endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/base/BinaryFunction.java index 29dbcf14bc,0501c7c14b..da8077f372 --- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/base/BinaryFunction.java +++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/base/BinaryFunction.java @@@ -19,15 -19,10 +19,10 @@@ package org.apache.sis.filter.base import java.util.List; import java.util.Collection; import java.util.Objects; - import java.math.BigInteger; - import java.math.BigDecimal; - import org.apache.sis.util.Numbers; - import org.apache.sis.math.Fraction; - import org.apache.sis.math.DecimalFunctions; -// Specific to the geoapi-3.1 and geoapi-4.0 branches: -import org.opengis.filter.Filter; -import org.opengis.filter.Expression; +// Specific to the main branch: +import org.apache.sis.filter.Filter; +import org.apache.sis.filter.Expression; /** @@@ -39,11 -38,11 +38,11 @@@ * @author Johann Sorel (Geomatys) * @author Martin Desruisseaux (Geomatys) * - * @param <R> the type of resources (e.g. {@link org.opengis.feature.Feature}) used as inputs. + * @param <R> the type of resources (e.g. {@code Feature}) used as inputs. - * @param <V1> the type of value computed by the first expression. - * @param <V2> the type of value computed by the second expression. + * @param <A1> the type of value computed by the first expression (left operand). + * @param <A2> the type of value computed by the second expression (right operand). */ - public abstract class BinaryFunction<R,V1,V2> extends Node { + public abstract class BinaryFunction<R, A1, A2> extends Node { /** * For cross-version compatibility. */ diff --cc endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/base/BinaryFunctionWidening.java index 0000000000,e3e69504f3..531ef9df4e mode 000000,100644..100644 --- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/base/BinaryFunctionWidening.java +++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/base/BinaryFunctionWidening.java @@@ -1,0 -1,419 +1,421 @@@ + /* + * 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.base; + + import java.util.List; + import java.util.Collection; + import java.math.BigInteger; + import java.math.BigDecimal; + import org.apache.sis.util.ConditionallySafe; + import org.apache.sis.math.Fraction; + import org.apache.sis.math.NumberType; + import org.apache.sis.math.DecimalFunctions; + import org.apache.sis.feature.internal.shared.FeatureExpression; + import org.apache.sis.feature.internal.shared.FeatureProjectionBuilder; + import org.apache.sis.filter.Optimization; + + // Specific to the geoapi-3.1 and geoapi-4.0 branches: -import org.opengis.filter.Expression; + import org.opengis.util.ScopedName; + ++// Specific to the main branch: ++import org.apache.sis.filter.Expression; ++ + + /** + * Expression performing an operation on two expressions with values convertible to {@code Number}. + * Each {@link Number} instance will be converted to {@code long}, {@code double}, {@link Fraction}, + * {@link BigInteger} or {@link BigDecimal} before the operation is applied. This class is used for + * operations where specialized methods exist for each of above-cited types. + * + * <p>The inputs are not necessarily of the same class, but typically need to be promoted to the widest type + * before the operation is executed. The result may be of a type different to all input types. For example, + * a division of two {@link Integer} values may produce a {@link Fraction}, and a multiplication of the same + * {@link Integer} values may produce a {@link Long}.</p> + * + * <p>The current version does not provide optimization for every cases. It is not clear that it is worth + * to optimize the {@link Fraction}, {@link BigInteger} and {@link BigDecimal} cases.</p> + * + * <h2>Requirement</h2> + * If a subclass implements {@link Expression}, then it shall also implement {@link FeatureExpression} + * and the type parameters <strong>must</strong> be {@code Expression<R, Number>}. That subclass shall + * also implement {@link Optimization.OnExpression}. + * + * @author Johann Sorel (Geomatys) + * @author Martin Desruisseaux (Geomatys) + * + * @param <R> the type of resources (e.g. {@code Feature}) used as inputs. + * @param <A1> the type of value computed by the first expression (left operand). + * @param <A2> the type of value computed by the second expression (right operand). + */ + public abstract class BinaryFunctionWidening<R, A1, A2> extends BinaryFunction<R, A1, A2> { + /** + * For cross-version compatibility. + */ + private static final long serialVersionUID = -2515131813531876123L; + + /** + * Creates a new binary function. + * + * @param expression1 the first of the two expressions to be used by this function. + * @param expression2 the second of the two expressions to be used by this function. + */ + protected BinaryFunctionWidening(final Expression<R, ? extends A1> expression1, + final Expression<R, ? extends A2> expression2) + { + super(expression1, expression2); + } + + /** + * Tries to return an expression which will invoke more directly an {@code applyAsXXX(…)} method, + * without the need to inspect the argument type. The returned expression, if non-null, should be + * more efficient than {@link #apply(Number, Number)}. + * + * @return the simplified or optimized function, or {@code null} if no optimization has been applied. + */ + @SuppressWarnings("unchecked") + protected final Expression<R, ? extends Number> specialize() { + switch (effective(widestOperandType())) { + case LONG: return new Longs<> ((BinaryFunctionWidening<R, ? extends Number, ? extends Number>) this); + case DOUBLE: return new Doubles<>((BinaryFunctionWidening<R, ? extends Number, ? extends Number>) this); + default: return null; + } + } + + /** + * Returns the type of values computed by this expression. + * In case of doubt, this method returns the {@link Number} base class. + * + * @return the type of values computed by this expression. + * + * @see #widestOperandType() + * @see #effective(NumberType) + */ + protected Class<? extends Number> getResultClass() { + return Number.class; + } + + /** + * Returns an enumeration value identifying the type of return value of the given expression. + * If the expression result cannot be mapped to a number type, returns {@link NumberType#NULL}. + * + * @param expression the expression for which to identifying the type of return value. + * @return type of numbers computed by the given expression. + * + * @see FeatureExpression#getResultClass() + */ + private static NumberType getNumberType(final Expression<?,?> expression) { + return (expression instanceof FeatureExpression<?,?>) + ? NumberType.forClass(((FeatureExpression<?,?>) expression).getResultClass()).orElse(NumberType.NULL) : NumberType.NULL; + } + + /** + * Returns the widest type of the given arguments, or {@link NumberType#NULL} if none. + * Note that conversions to the returned type are not guaranteed to be lossless. + * For example, conversions from {@code long} to {@code double} may loss accuracy. + * + * <p>Conversion from {@code float} to {@code double} is disallowed because the + * {@link #apply(Number, Number)} method handles the decimal representation.</p> + */ + private static NumberType widest(final NumberType t1, final NumberType t2) { + if (t1 == t2) return t1; + if (t1.isWiderThan(t2)) { + if (t2 != NumberType.FLOAT || t1 == NumberType.BIG_DECIMAL) return t1; + } else if (t2.isWiderThan(t1)) { + if (t1 != NumberType.FLOAT || t2 == NumberType.BIG_DECIMAL) return t2; + } + return NumberType.NULL; + } + + /** + * Returns the widest operand type, or {@link NumberType#NULL} if it cannot be determined. + * Note that conversions to the returned type are not guaranteed to be lossless. + * For example, conversions from {@code long} to {@code double} may loss accuracy. + * + * @return the widest operand type, or {@link NumberType#NULL}. + * + * @see FeatureExpression#getResultClass() + */ + protected final NumberType widestOperandType() { + return widest(getNumberType(expression1), getNumberType(expression2)); + } + + /** + * Simplifies the given type to one of the types handled as a special case in this class. + * The {@code switch} statement in this method's body shall be consistent with the switch + * statement in {@link #apply(Number, Number)}, with the addition of the {@code NULL} and + * {@code NUMBER} cases. + * + * @param type a number type. + * @return one of {@code LONG}, {@code DOUBLE}, {@code FRACTION}, {@code BIG_INTEGER}, + * {@code BIG_DECIMAL}, {@code NUMBER} or {@code NULL}. + */ + protected static NumberType effective(final NumberType type) { + switch (type) { + case NULL: // Case of expressions without `FeatureExpression.getResultType()`. + case NUMBER: // Case of expressions that declare only the generic `Number` class. + case FRACTION: + case BIG_INTEGER: + case BIG_DECIMAL: return type; + case BYTE: + case SHORT: + case INTEGER: + case LONG: return NumberType.LONG; + default: return NumberType.DOUBLE; // The fallback used for unrecognized types. + } + } + + /** + * Evaluates the expression for producing a result of numeric type. + * This method delegates to one of the {@code applyAs(…)} methods. + * If no {@code applyAs(…)} implementations can return null values, + * this this method never return {@code null}. + * + * @param left the left operand. Cannot be null. + * @param right the right operand. Cannot be null. + * @return result of this function applied on the two given operands. + * May be {@code null} only if an {@code applyAs(…)} implementation returned a null value. + */ + protected final Number apply(final Number left, final Number right) { + final NumberType type = widest( + NumberType.forNumberClass(left.getClass()), + NumberType.forNumberClass(right.getClass())); + try { + switch (type) { + case FRACTION: { + return applyAsFraction((Fraction) type.cast(left), + (Fraction) type.cast(right)); + } + case BIG_INTEGER: { + return applyAsInteger((BigInteger) type.cast(left), + (BigInteger) type.cast(right)); + } + case BIG_DECIMAL: { + return applyAsDecimal((BigDecimal) type.cast(left), + (BigDecimal) type.cast(right)); + } + case BYTE: + case SHORT: + case INTEGER: + case LONG: { + return applyAsLong(left.longValue(), right.longValue()); + } + } + } catch (IllegalArgumentException | ArithmeticException e) { + /* + * Integer overflow, or division by zero, or attempt to convert NaN or infinity + * to `BigDecimal`, or division does not have a terminating decimal expansion. + * This is recoverable because we can fallback on floating point arithmetic. + */ + warning(e, true); + } + return applyAsDouble((left instanceof Float) ? DecimalFunctions.floatToDouble((Float) left) : left.doubleValue(), + (right instanceof Float) ? DecimalFunctions.floatToDouble((Float) right) : right.doubleValue()); + } + + /** + * Calculates this function using given operands of {@code long} primitive type. If this function is a filter, + * then this method should returns an {@link Integer} value 0 or 1 for false or true respectively. + * Otherwise the result is usually a {@link Long}, except for division which may produce other types. + * This method may return {@code null} if the operation cannot apply on numbers. + * + * @param left the first operand. + * @param right the second operand. + * @return the result of applying the function on the given operands. + * @throws ArithmeticException if the operation overflows or if there is a division by zero. + */ + protected abstract Number applyAsLong(long left, long right); + + /** + * Calculates this function using given operands of {@code double} primitive type. If this function is a filter, + * then this method should returns an {@link Integer} value 0 or 1 for false or true respectively. + * Otherwise the result is usually a {@link Double}. + * This method may return {@code null} if the operation cannot apply on numbers. + * + * @param left the first operand. + * @param right the second operand. + * @return the result of applying the function on the given operands. + */ + protected abstract Number applyAsDouble(double left, double right); + + /** + * Calculates this function using given operands of {@code Fraction} type. If this function is a filter, + * then this method should returns an {@link Integer} value 0 or 1 for false or true respectively. + * Otherwise the result is usually a {@link Fraction}. + * This method may return {@code null} if the operation cannot apply on numbers. + * + * @param left the first operand. + * @param right the second operand. + * @return the result of applying the function on the given operands. + * @throws ArithmeticException if the operation overflows or if there is a division by zero. + */ + protected abstract Number applyAsFraction(Fraction left, Fraction right); + + /** + * Calculates this function using given operands of {@code BigInteger} type. If this function is a filter, + * then this method should returns an {@link Integer} value 0 or 1 for false or true respectively. + * Otherwise the result is usually a {@link BigInteger}, except for division which may produce other types. + * This method may return {@code null} if the operation cannot apply on numbers. + * + * @param left the first operand. + * @param right the second operand. + * @return the result of applying the function on the given operands. + * @throws ArithmeticException if there is a division by zero. + */ + protected abstract Number applyAsInteger(BigInteger left, BigInteger right); + + /** + * Calculates this function using given operands of {@code BigDecimal} type. If this function is a filter, + * then this method should returns an {@link Integer} value 0 or 1 for false or true respectively. + * Otherwise the result is usually a {@link BigDecimal}. + * This method may return {@code null} if the operation cannot apply on numbers. + * + * @param left the first operand. + * @param right the second operand. + * @return the result of applying the function on the given operands. + * @throws ArithmeticException if a division does not have a terminating decimal expansion. + */ + protected abstract Number applyAsDecimal(BigDecimal left, BigDecimal right); + + + + + /** + * An expression which will invoke more directly an {@code applyAsXXX(…)} method, + * without the need to inspect the argument type. + * + * @param <R> the type of resources (typically {@code Feature}) used as inputs. + * @param <A> the type of value computed by the two expressions used as inputs. + */ + private static abstract class Specialization<R, A extends Number> extends Node + implements FeatureExpression<R, Number>, Optimization.OnExpression<R, Number> + { + /** For cross-version compatibility during (de)serialization. */ + private static final long serialVersionUID = -6902891170861955149L; + + /** The implementation of the function. */ + protected final BinaryFunctionWidening<R, ? extends A, ? extends A> delegate; + + /** Creates a new specialization which will delegate the work to the given implementation. */ + protected Specialization(final BinaryFunctionWidening<R, ? extends A, ? extends A> delegate) { + this.delegate = delegate; + } + + /** Delegates to the function. */ + @Override public final ScopedName getFunctionName() {return ((Expression<?,?>) delegate).getFunctionName();} + @Override public final Class<? super R> getResourceClass() {return delegate.getResourceClass();} + @Override public final List<Expression<R,?>> getParameters() {return delegate.getParameters();} + @Override protected final Collection<?> getChildren() {return delegate.getChildren();} + + /** Returns the type of values computed by this expression. */ + @Override public final Class<? extends Number> getResultClass() { + return delegate.getResultClass(); + } + + /** + * Provides the type of results computed by the implementation of the function. + * The value type is declared as the generic {@link Number} type rather than {@code <V>}, + * but this is desired as the result of division is not always of type {@code <V>}. + */ + @Override public final FeatureProjectionBuilder.Item expectedType(FeatureProjectionBuilder addTo) { + return ((FeatureExpression<?,?>) delegate).expectedType(addTo); + } + + /** + * Delegates the optimization to the implementation and checks if the result is the same. + * This method performs a cast which is safe only if the requirement documented in the + * {@link BinaryFunctionWidening} javadoc is true. + */ + @Override public final Expression<R, ? extends Number> optimize(final Optimization optimization) { + @ConditionallySafe + @SuppressWarnings("unchecked") // See Javadoc + final Expression<R, ? extends Number> result = ((Optimization.OnExpression<R, Number>) delegate).optimize(optimization); + if (result.getClass() == getClass() && ((Specialization<?,?>) result).delegate == delegate) { + return this; + } + return result; + } + } + + + + + /** + * An expression which will invoke more directly the {@code applyAsLong(…)} method. + * This implementation can be used with operands of type {@link Byte}, {@link Short}, + * {@link Integer} and {@link Long}. + * + * @param <R> the type of resources (typically {@code Feature}) used as inputs. + */ + private static final class Longs<R> extends Specialization<R, Number> { + /** For cross-version compatibility during (de)serialization. */ + private static final long serialVersionUID = 8799719407972742175L; + + /** Creates a new specialization for integers. */ + Longs(BinaryFunctionWidening<R, ? extends Number, ? extends Number> delegate) { + super(delegate); + } + + /** Executes the operation with the assumption that values are convertible to {@code long}. */ + @Override public Number apply(final R feature) { + final Number left = delegate.expression1.apply(feature); + if (left != null) { + final Number right = delegate.expression2.apply(feature); + if (right != null) try { + return delegate.applyAsLong(left.longValue(), right.longValue()); + } catch (IllegalArgumentException | ArithmeticException e) { + warning(e, true); + return delegate.applyAsDouble(left.doubleValue(), right.doubleValue()); + } + } + return null; + } + } + + + + + /** + * An expression which will invoke more directly the {@code applyAsDouble(…)} method. + * This implementation can be used with operands of type {@link Byte}, {@link Short}, + * {@link Integer}, {@link Long}, {@link Fraction} and {@link Double}. Note that the + * {@link Float} type is excluded because we use a conversion that tries to preserve + * the decimal representation. + * + * @param <R> the type of resources (typically {@code Feature}) used as inputs. + */ + private static final class Doubles<R> extends Specialization<R, Number> { + /** For cross-version compatibility during (de)serialization. */ + private static final long serialVersionUID = -1962350161229383018L; + + /** Creates a new specialization for integers. */ + Doubles(BinaryFunctionWidening<R, ? extends Number, ? extends Number> delegate) { + super(delegate); + } + + /** Executes the operation with the assumption that values are convertible to {@code long}. */ + @Override public Number apply(final R feature) { + final Number left = delegate.expression1.apply(feature); + if (left != null) { + final Number right = delegate.expression2.apply(feature); + if (right != null) { + return delegate.applyAsDouble(left.doubleValue(), right.doubleValue()); + } + } + return null; + } + } + } diff --cc endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/math/ArithmeticFunction.java index df9dc46f2c,aa94231833..3e355f571d --- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/math/ArithmeticFunction.java +++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/math/ArithmeticFunction.java @@@ -21,12 -21,15 +21,15 @@@ import java.math.BigInteger import org.opengis.util.ScopedName; import org.apache.sis.feature.internal.shared.FeatureExpression; import org.apache.sis.feature.internal.shared.FeatureProjectionBuilder; - import org.apache.sis.filter.base.BinaryFunction; + import org.apache.sis.filter.Optimization; import org.apache.sis.filter.visitor.FunctionNames; + import org.apache.sis.filter.base.BinaryFunctionWidening; import org.apache.sis.math.Fraction; + import org.apache.sis.math.NumberType; -// Specific to the geoapi-3.1 and geoapi-4.0 branches: -import org.opengis.feature.AttributeType; -import org.opengis.filter.Expression; +// Specific to the main branch: +import org.apache.sis.feature.DefaultAttributeType; ++import org.apache.sis.filter.Expression; /** @@@ -76,8 -100,20 +100,20 @@@ public abstract class ArithmeticFunctio /** * Returns the type of results computed by this arithmetic function. + * It should be a constant of the following form: + * + * {@snippet lang="java" : - * private static final AttributeType<Number> TYPE = createNumericType("Add"); ++ * private static final DefaultAttributeType<Number> TYPE = createNumericType("Add"); + * + * @Override - * protected AttributeType<Number> expectedType() { ++ * protected DefaultAttributeType<Number> expectedType() { + * return TYPE; + * } + * } + * + * @return the type of result computed by this arithmetic function. */ - protected abstract AttributeType<Number> expectedType(); + protected abstract DefaultAttributeType<Number> expectedType(); /** * Provides the type of results computed by this expression. That type depends only @@@ -132,10 -199,14 +199,14 @@@ effective[1].toValueType(Number.class)); } - /** Identification of the {@code "Add"} operation. */ - private static final ScopedName NAME = createName(FunctionNames.Add); - @Override public ScopedName getFunctionName() {return NAME;} + /** Description of results of the {@code "Add"} expression. */ - @Override protected AttributeType<Number> expectedType() {return TYPE;} - private static final AttributeType<Number> TYPE = createNumericType(FunctionNames.Add); ++ @Override protected DefaultAttributeType<Number> expectedType() {return TYPE;} ++ private static final DefaultAttributeType<Number> TYPE = createNumericType(FunctionNames.Add); + + /** Representation of the {@code "Add"} operation. */ @Override protected char symbol() {return '+';} + @Override public ScopedName getFunctionName() {return NAME;} + private static final ScopedName NAME = createName(FunctionNames.Add); /** Applies this expression to the given operands. */ @Override protected Number applyAsDouble (double left, double right) {return left + right;} @@@ -172,10 -245,14 +245,14 @@@ effective[1].toValueType(Number.class)); } - /** Identification of the {@code "Subtract"} operation. */ - private static final ScopedName NAME = createName(FunctionNames.Subtract); - @Override public ScopedName getFunctionName() {return NAME;} + /** Description of results of the {@code "Subtract"} expression. */ - @Override protected AttributeType<Number> expectedType() {return TYPE;} - private static final AttributeType<Number> TYPE = createNumericType(FunctionNames.Subtract); ++ @Override protected DefaultAttributeType<Number> expectedType() {return TYPE;} ++ private static final DefaultAttributeType<Number> TYPE = createNumericType(FunctionNames.Subtract); + + /** Representation of the {@code "Subtract"} operation. */ @Override protected char symbol() {return '−';} + @Override public ScopedName getFunctionName() {return NAME;} + private static final ScopedName NAME = createName(FunctionNames.Subtract); /** Applies this expression to the given operands. */ @Override protected Number applyAsDouble (double left, double right) {return left - right;} @@@ -212,10 -291,14 +291,14 @@@ effective[1].toValueType(Number.class)); } - /** Identification of the {@code "Multiply"} operation. */ - private static final ScopedName NAME = createName(FunctionNames.Multiply); - @Override public ScopedName getFunctionName() {return NAME;} + /** Description of results of the {@code "Multiply"} expression. */ - @Override protected AttributeType<Number> expectedType() {return TYPE;} - private static final AttributeType<Number> TYPE = createNumericType(FunctionNames.Multiply); ++ @Override protected DefaultAttributeType<Number> expectedType() {return TYPE;} ++ private static final DefaultAttributeType<Number> TYPE = createNumericType(FunctionNames.Multiply); + + /** Representation of the {@code "Multiply"} operation. */ @Override protected char symbol() {return '×';} + @Override public ScopedName getFunctionName() {return NAME;} + private static final ScopedName NAME = createName(FunctionNames.Multiply); /** Applies this expression to the given operands. */ @Override protected Number applyAsDouble (double left, double right) {return left * right;} @@@ -252,10 -337,14 +337,14 @@@ effective[1].toValueType(Number.class)); } - /** Identification of the {@code "Divide"} operation. */ - private static final ScopedName NAME = createName(FunctionNames.Divide); - @Override public ScopedName getFunctionName() {return NAME;} + /** Description of results of the {@code "Divide"} expression. */ - @Override protected AttributeType<Number> expectedType() {return TYPE;} - private static final AttributeType<Number> TYPE = createNumericType(FunctionNames.Divide); ++ @Override protected DefaultAttributeType<Number> expectedType() {return TYPE;} ++ private static final DefaultAttributeType<Number> TYPE = createNumericType(FunctionNames.Divide); + + /** Representation of the {@code "Divide"} operation. */ @Override protected char symbol() {return '÷';} + @Override public ScopedName getFunctionName() {return NAME;} + private static final ScopedName NAME = createName(FunctionNames.Divide); /** Divides the given integers, changing the type if the result is not an integer. */ @Override protected Number applyAsDouble (double left, double right) {return left / right;} diff --cc endorsed/src/org.apache.sis.feature/test/org/apache/sis/coverage/grid/GridGeometryTest.java index b5958df5c7,0f7c2a0c69..433e41bdfa --- a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/coverage/grid/GridGeometryTest.java +++ b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/coverage/grid/GridGeometryTest.java @@@ -567,6 -572,30 +572,30 @@@ public final class GridGeometryTest ext new double[] { 1, -3.0, 0}), envelope); } + /** + * Tests {@link GridGeometry#getConstantCoordinates()}. + */ + @Test + public void testGetConstantCoordinates() { + for (double sy = 0; sy <= 1; sy++) { + final var grid = new GridGeometry( + new GridExtent(12, 1), + PixelInCell.CELL_CORNER, + MathTransforms.linear(new Matrix3( + 0.25, 0, -2, + 0, sy, -3, + 0, 0, 1)), + HardCodedCRS.WGS84); + + final var constant = grid.getConstantCoordinates(); + assertEquals(constant, grid.getConstantCoordinates()); // Verify the cache. + assertEquals(sy == 0, constant.isPresent()); + if (sy == 0) { - assertArrayEquals(new double[] {Double.NaN, -3}, constant.orElseThrow().getCoordinates()); ++ assertArrayEquals(new double[] {Double.NaN, -3}, constant.orElseThrow().getCoordinate()); + } + } + } + /** * Tests {@link GridGeometry#upsample(long...)}. */ diff --cc endorsed/src/org.apache.sis.metadata/main/org/apache/sis/util/iso/RecordDefinition.java index 8248b4e703,b9cb36b691..97f56d7ab9 --- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/util/iso/RecordDefinition.java +++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/util/iso/RecordDefinition.java @@@ -26,13 -26,13 +26,13 @@@ import org.opengis.util.Type import org.opengis.util.RecordType; import org.opengis.util.MemberName; import org.apache.sis.util.Classes; - import org.apache.sis.util.Numbers; import org.apache.sis.util.CharSequences; import org.apache.sis.util.collection.Containers; + import org.apache.sis.math.NumberType; import org.apache.sis.pending.jdk.JDK19; -// Specific to the geoapi-3.1 and geoapi-4.0 branches: -import org.opengis.feature.AttributeType; +// Specific to the main branch: +import org.apache.sis.metadata.simple.SimpleAttributeType; /** diff --cc endorsed/src/org.apache.sis.referencing/main/org/apache/sis/geometry/AbstractDirectPosition.java index edd229b52a,433ac32bc2..9eb6d84d89 --- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/geometry/AbstractDirectPosition.java +++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/geometry/AbstractDirectPosition.java @@@ -195,8 -100,12 +195,11 @@@ public abstract class AbstractDirectPos * * @since 1.5 */ - @Override public void setCoordinate(int dimension, double value) { - throw new UnsupportedOperationException(Errors.format(Errors.Keys.UnmodifiableObject_1, getClass())); + // Be tolerant if the coordinate is the same for allowing `normalize()` to be a no-operation. + if (!Numerics.equals(getCoordinate(dimension), value)) { + throw new UnsupportedOperationException(Errors.format(Errors.Keys.UnmodifiableObject_1, getClass())); + } } /** @@@ -210,10 -119,11 +213,11 @@@ * * @param position the new position, or {@code null}. * @throws MismatchedDimensionException if the given position doesn't have the expected dimension. - * @throws MismatchedCoordinateMetadataException if the given position doesn't use the expected CRS. + * @throws MismatchedReferenceSystemException if the given position doesn't use the expected CRS. + * @throws UnsupportedOperationException if this direct position is immutable. */ public void setLocation(final DirectPosition position) - throws MismatchedDimensionException, MismatchedCoordinateMetadataException + throws MismatchedDimensionException, MismatchedReferenceSystemException { final int dimension = getDimension(); if (position != null) { diff --cc endorsed/src/org.apache.sis.referencing/main/org/apache/sis/geometry/GeneralDirectPosition.java index 1a7327a955,4957278c50..d50c088224 --- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/geometry/GeneralDirectPosition.java +++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/geometry/GeneralDirectPosition.java @@@ -27,13 -27,12 +27,12 @@@ import java.util.Objects import java.io.Serializable; import java.lang.reflect.Field; import org.opengis.geometry.DirectPosition; -import org.opengis.coordinate.MismatchedDimensionException; +import org.opengis.geometry.MismatchedDimensionException; import org.opengis.referencing.crs.CoordinateReferenceSystem; import org.apache.sis.util.resources.Errors; + import org.apache.sis.util.ArgumentChecks; import org.apache.sis.util.ArraysExt; - import static org.apache.sis.util.ArgumentChecks.ensureDimensionMatches; - /** * A mutable {@code DirectPosition} (the coordinates of a position) of arbitrary dimension. @@@ -135,9 -134,9 +134,9 @@@ public class GeneralDirectPosition exte * @param point the position to copy. */ public GeneralDirectPosition(final DirectPosition point) { - coordinates = point.getCoordinates(); // Should already be cloned. + coordinates = point.getCoordinate(); // Should already be cloned. crs = point.getCoordinateReferenceSystem(); - ensureDimensionMatches("crs", coordinates.length, crs); + ArgumentChecks.ensureDimensionMatches("crs", coordinates.length, crs); } /** @@@ -279,10 -278,10 +278,10 @@@ if (position == null) { Arrays.fill(coordinates, Double.NaN); } else { - ensureDimensionMatches("position", coordinates.length, position); + ArgumentChecks.ensureDimensionMatches("position", coordinates.length, position); setCoordinateReferenceSystem(position.getCoordinateReferenceSystem()); for (int i=0; i<coordinates.length; i++) { - coordinates[i] = position.getCoordinate(i); + coordinates[i] = position.getOrdinate(i); } } } diff --cc endorsed/src/org.apache.sis.referencing/main/org/apache/sis/geometry/ImmutableDirectPosition.java index 0000000000,5df93bb69f..fe5c1f9292 mode 000000,100644..100644 --- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/geometry/ImmutableDirectPosition.java +++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/geometry/ImmutableDirectPosition.java @@@ -1,0 -1,161 +1,161 @@@ + /* + * 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; + + import java.util.Arrays; + import java.util.Objects; + import java.io.Serializable; + import org.opengis.geometry.DirectPosition; -import org.opengis.coordinate.MismatchedDimensionException; ++import org.opengis.geometry.MismatchedDimensionException; + import org.opengis.referencing.crs.CoordinateReferenceSystem; + import org.apache.sis.util.ArgumentChecks; + import org.apache.sis.util.ArraysExt; + + + /** + * An immutable {@code DirectPosition} (the coordinates of a position) of arbitrary dimension. + * This final class is immutable and thus inherently thread-safe if the {@link CoordinateReferenceSystem} + * instance given to the constructor is immutable. This is usually the case in Apache <abbr>SIS</abbr>. + * + * @author Martin Desruisseaux (Geomatys) + * @version 1.6 + * @since 1.6 + */ + public final class ImmutableDirectPosition extends AbstractDirectPosition implements Serializable { + /** + * For cross-version compatibility. + */ + private static final long serialVersionUID = -4275832076346637274L; + + /** + * The coordinate reference system, or {@code null}. + */ + @SuppressWarnings("serial") // Most SIS implementations are serializable. + private final CoordinateReferenceSystem crs; + + /** + * The coordinates of the direct position. The length of this array is + * the {@linkplain #getDimension() dimension} of this direct position. + */ + private final double[] coordinates; + + /** + * Constructs a position defined by a sequence of coordinate values. + * + * @param crs the <abbr>CRS</abbr> to assign to this direct position, or {@code null}. + * @param coordinates the coordinate values for each dimension. + * @throws MismatchedDimensionException if the CRS dimension is not equal to the number of coordinates. + */ + public ImmutableDirectPosition(final CoordinateReferenceSystem crs, final double... coordinates) + throws MismatchedDimensionException + { + this.crs = crs; + this.coordinates = coordinates.clone(); + ArgumentChecks.ensureDimensionMatches("crs", coordinates.length, crs); + } + + /** + * Returns the given position as an {@code ImmutableDirectPosition} instance. + * If the given position is already an instance of {@code ImmutableDirectPosition}, + * then it is returned unchanged. Otherwise, the coordinate values and the <abbr>CRS</abbr> + * of the given position are copied in a new position. + * + * @param position the position to cast or copy, or {@code null}. + * @return the values of the given position as an {@code ImmutableDirectPosition} instance. + */ + public static ImmutableDirectPosition castOrCopy(final DirectPosition position) { + if (position == null || position instanceof ImmutableDirectPosition) { + return (ImmutableDirectPosition) position; + } - return new ImmutableDirectPosition(position.getCoordinateReferenceSystem(), position.getCoordinates()); ++ return new ImmutableDirectPosition(position.getCoordinateReferenceSystem(), position.getCoordinate()); + } + + /** + * The length of coordinate sequence (the number of entries). + * + * @return the dimensionality of this position. + */ + @Override + public int getDimension() { + return coordinates.length; + } + + /** + * Returns the coordinate reference system in which the coordinates are given. + * May be {@code null} if this particular {@code DirectPosition} is included + * in a larger object with such a reference to a <abbr>CRS</abbr>. + * + * @return the coordinate reference system, or {@code null}. + */ + @Override + public CoordinateReferenceSystem getCoordinateReferenceSystem() { + return crs; + } + + /** + * Returns a sequence of numbers that hold the coordinates of this position in its reference system. + * + * @return a copy of the coordinates array. + */ + @Override + public double[] getCoordinates() { + return coordinates.clone(); + } + + /** + * Returns the coordinate at the specified dimension. + * + * @param dimension the dimension in the range 0 to {@linkplain #getDimension() dimension}-1. + * @return the coordinate at the specified dimension. + * @throws IndexOutOfBoundsException if the specified dimension is out of bounds. + */ + @Override + public double getCoordinate(final int dimension) throws IndexOutOfBoundsException { + return coordinates[dimension]; + } + + /** + * @hidden because nothing new to said. + */ + @Override + public String toString() { + return toString(this, ArraysExt.isSinglePrecision(coordinates)); + } + + /** + * @hidden because nothing new to said. + */ + @Override + public int hashCode() { + return Arrays.hashCode(coordinates) + Objects.hashCode(crs); + } + + /** + * @hidden because nothing new to said. + */ + @Override + public boolean equals(final Object object) { + if (object == this) { + return true; + } + if (object instanceof ImmutableDirectPosition) { + final var that = (ImmutableDirectPosition) object; + return Arrays.equals(coordinates, that.coordinates) && Objects.equals(crs, that.crs); + } + return super.equals(object); // Comparison of other implementation classes. + } + } diff --cc endorsed/src/org.apache.sis.referencing/main/org/apache/sis/geometry/ImmutableEnvelope.java index 64fdfee76a,33e8e65e75..197d363195 --- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/geometry/ImmutableEnvelope.java +++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/geometry/ImmutableEnvelope.java @@@ -25,11 -25,11 +25,10 @@@ package org.apache.sis.geometry import java.io.Serializable; import org.opengis.geometry.Envelope; import org.opengis.geometry.DirectPosition; -import org.opengis.coordinate.MismatchedDimensionException; -import org.opengis.coordinate.MismatchedCoordinateMetadataException; +import org.opengis.geometry.MismatchedDimensionException; import org.opengis.referencing.crs.CoordinateReferenceSystem; import org.opengis.metadata.extent.GeographicBoundingBox; - - import static org.apache.sis.util.ArgumentChecks.ensureDimensionMatches; + import org.apache.sis.util.ArgumentChecks; /** diff --cc endorsed/src/org.apache.sis.referencing/main/org/apache/sis/io/wkt/ElementKind.java index 6c5649aaf1,a126c18307..46b75dd435 --- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/io/wkt/ElementKind.java +++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/io/wkt/ElementKind.java @@@ -25,10 -25,10 +25,10 @@@ import org.opengis.referencing.datum.Da import org.opengis.referencing.cs.CoordinateSystemAxis; import org.opengis.referencing.operation.OperationMethod; import org.opengis.parameter.GeneralParameterValue; - import org.apache.sis.util.Numbers; + import org.apache.sis.math.NumberType; -// Specific to the geoapi-3.1 and geoapi-4.0 branches: -import org.opengis.referencing.datum.DatumEnsemble; +// Specific to the main branch: +import org.apache.sis.referencing.datum.DefaultDatumEnsemble; /** diff --cc endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/CRS.java index 020580f9f7,3c9eac8e41..eb28070e2d --- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/CRS.java +++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/CRS.java @@@ -678,9 -683,13 +684,13 @@@ public final class CRS * @throws FactoryException if the operation cannot be created for another reason. * * @since 1.5 + * - * @deprecated Replaced by {@link #findOperation(CoordinateMetadata, CoordinateMetadata, CoordinateOperationContext)}. ++ * @deprecated Replaced by {@link #findOperation(DefaultCoordinateMetadata, DefaultCoordinateMetadata, CoordinateOperationContext)}. + * This method will be removed for avoiding ambiguity when the last argument is null. */ + @Deprecated(since = "1.6", forRemoval = true) - public static CoordinateOperation findOperation(final CoordinateMetadata source, - final CoordinateMetadata target, + public static CoordinateOperation findOperation(final DefaultCoordinateMetadata source, + final DefaultCoordinateMetadata target, final GeographicBoundingBox areaOfInterest) throws FactoryException { @@@ -699,14 -704,14 +705,14 @@@ * {@linkplain DefaultObjectDomain#getDomainOfValidity() domain of validity}. * A future Apache SIS version may also take the country of current locale in account. * - * <div class="note"><b>Note:</b> - * the area of interest is just one aspect that may affect the coordinate operation. - * Other aspects are the time of interest (because some coordinate operations take in account the - * plate tectonics movement) or the desired accuracy. For more control on the coordinate operation - * to create, see {@link CoordinateOperationContext}.</div> + * <p>Note that the area of interest is only one aspect that may affect the coordinate operation. + * Other aspects are the data epochs (because some coordinate operations take in account the plate tectonics movement) + * or the desired accuracy. For more control on the coordinate operation to create, see the - * {@linkplain #findOperation(CoordinateMetadata, CoordinateMetadata, CoordinateOperationContext) method below}. ++ * {@linkplain #findOperation(DefaultCoordinateMetadata, DefaultCoordinateMetadata, CoordinateOperationContext) method below}. + * Alternatively, the area of interest can also be {@linkplain Envelopes#findOperation(Envelope, Envelope) specified as envelopes}.</p> * - * After the caller received a {@code CoordinateOperation} instance, the following methods can be invoked - * for checking if the operation suits the caller's needs: + * <p>After the caller received a {@code CoordinateOperation} instance, + * the following methods can be invoked for checking if the operation suits the caller's needs:</p> * * <ul> * <li>{@link #getGeographicBoundingBox(CoordinateOperation)} @@@ -745,7 -761,66 +762,66 @@@ { ArgumentChecks.ensureNonNull("sourceCRS", sourceCRS); ArgumentChecks.ensureNonNull("targetCRS", targetCRS); - final CoordinateOperationContext context = CoordinateOperationContext.fromBoundingBox(areaOfInterest); + return findOperation( + new DefaultCoordinateMetadata(sourceCRS, null), + new DefaultCoordinateMetadata(targetCRS, null), + CoordinateOperationContext.fromBoundingBox(areaOfInterest)); + } + + /** + * Finds a mathematical operation that transforms coordinates between the given <abbr>CRS</abbr>s and epochs. + * If many operations exist between the specified pair of <abbr>CRS</abbr>s, an operation is selected using the + * area of interest (<abbr>AOI</abbr>) and desired accuracy specified in the optional {@code context} argument: + * the operation having a {@linkplain DefaultObjectDomain#getDomainOfValidity() domain of validity} resulting in + * the widest intersection with the <abbr>AOI</abbr> is preferred. If many operations result in the same intersection, + * then the operation having an accuracy just sufficient for the desired accuracy is preferred. + * + * <p>After the caller received a {@code CoordinateOperation} instance, + * the following methods can be invoked for checking if the operation suits the caller's needs:</p> + * + * <ul> + * <li>{@link #getGeographicBoundingBox(CoordinateOperation)} + * for checking if the operation is valid in the caller's area of interest.</li> + * <li>{@link #getLinearAccuracy(CoordinateOperation)} + * for checking if the operation has sufficient accuracy for caller's purpose.</li> + * </ul> + * + * <p>If the source and target <abbr>CRS</abbr> are equivalent, then this method returns an operation + * backed by an {@linkplain org.opengis.referencing.operation.MathTransform#isIdentity() identity} transform. + * If there is no known operation between the given pair of <abbr>CRS</abbr>s, + * then this method throws an {@link OperationNotFoundException}.</p> + * + * <h4>Inverse operation</h4> + * Note that <code>CRS.findOperation(<var>B</var>, <var>A</var>, <var>context</var>)</code> is not necessarily + * the exact converse of <code>CRS.findOperation(<var>A</var>, <var>B</var>, <var>context</var>)</code>. + * Some deviations may exist, for example because of different paths explored in the geodetic database. + * For the mathematical inverse of an existing {@link CoordinateOperation}, using + * {@link org.opengis.referencing.operation.MathTransform#inverse()} is preferable. + * + * @param source the <abbr>CRS</abbr> and epoch of source coordinates. + * @param target the <abbr>CRS</abbr> and epoch of target coordinates. + * @param context area of interest, desired accuracy and other options, or {@code null} if none. + * @return the mathematical operation from {@code source} to {@code target}. + * @throws OperationNotFoundException if no operation was found between the given pair of <abbr>CRS</abbr>s and epochs. + * @throws FactoryException if the operation cannot be created for another reason. + * + * @see DefaultCoordinateOperationFactory#createOperation(CoordinateReferenceSystem, CoordinateReferenceSystem, CoordinateOperationContext) + * + * @since 1.6 + */ - public static CoordinateOperation findOperation(final CoordinateMetadata source, - final CoordinateMetadata target, ++ public static CoordinateOperation findOperation(final DefaultCoordinateMetadata source, ++ final DefaultCoordinateMetadata target, + final CoordinateOperationContext context) + throws FactoryException + { + ArgumentChecks.ensureNonNull("source", source); + ArgumentChecks.ensureNonNull("target", target); + if (source.getCoordinateEpoch().isPresent() || target.getCoordinateEpoch().isPresent()) { + throw new FactoryException("This version of Apache SIS does not yet support coordinate epoch."); + } + // TODO: take epoch in account. + final CoordinateReferenceSystem sourceCRS = source.getCoordinateReferenceSystem(); + final CoordinateReferenceSystem targetCRS = target.getCoordinateReferenceSystem(); /* * In principle following code should just delegate to factory.createOperation(…). However, that operation * may fail if a connection to the EPSG database has been found, but the EPSG tables do not yet exist in @@@ -768,21 -843,22 +844,22 @@@ } /** - * Finds mathematical operations that transform or convert coordinates from the given source to the - * given target coordinate reference system. If at least one operation exists, they are returned in - * preference order: the operation having the widest intersection between its - * {@linkplain DefaultObjectDomain#getDomainOfValidity() domain of validity} - * and the given area of interest are returned first. - * - * @param sourceCRS the CRS of source coordinates. - * @param targetCRS the CRS of target coordinates. + * Finds mathematical operations that transform coordinates from the given source to the given target <abbr>CRS</abbr> + * in a given area of interest. If many operations exist, they are returned in preference order: the operation having + * the widest intersection between its {@linkplain DefaultObjectDomain#getDomainOfValidity() domain of validity} and + * the given area of interest are returned first. + * + * <p>This is a convenience method for static <abbr>CRS</abbr>s and a context defined only by the area of interest. + * For an alternative allowing to specify data epochs (for dynamic <abbr>CRS</abbr>s) and desired accuracy, see the - * {@linkplain #findOperations(CoordinateMetadata, CoordinateMetadata, CoordinateOperationContext) method below}.</p> ++ * {@linkplain #findOperations(DefaultCoordinateMetadata, DefaultCoordinateMetadata, CoordinateOperationContext) method below}.</p> + * + * @param sourceCRS the <abbr>CRS</abbr> of source coordinates. + * @param targetCRS the <abbr>CRS</abbr> of target coordinates. * @param areaOfInterest the area of interest, or {@code null} if none. * @return mathematical operations from {@code sourceCRS} to {@code targetCRS}. - * @throws OperationNotFoundException if no operation was found between the given pair of CRS. + * @throws OperationNotFoundException if no operation was found between the given pair of <abbr>CRS</abbr>s. * @throws FactoryException if the operation cannot be created for another reason. * - * @see DefaultCoordinateOperationFactory#createOperations(CoordinateReferenceSystem, CoordinateReferenceSystem, CoordinateOperationContext) - * * @since 1.0 */ public static List<CoordinateOperation> findOperations(final CoordinateReferenceSystem sourceCRS, @@@ -792,7 -868,42 +869,42 @@@ { ArgumentChecks.ensureNonNull("sourceCRS", sourceCRS); ArgumentChecks.ensureNonNull("targetCRS", targetCRS); - final CoordinateOperationContext context = CoordinateOperationContext.fromBoundingBox(areaOfInterest); + return findOperations( + new DefaultCoordinateMetadata(sourceCRS, null), + new DefaultCoordinateMetadata(targetCRS, null), + CoordinateOperationContext.fromBoundingBox(areaOfInterest)); + } + + /** + * Finds mathematical operations that transforms coordinates between the given <abbr>CRS</abbr>s and epochs. + * If many operations exist, they are sorted in preference order according criteria specified by {@code context}: + * best matches with the area of interest (<abbr>AOI</abbr>) are first, then operations matching <abbr>AOI</abbr> + * equally well are sorted by best matches with the desired accuracy. + * + * @param source the <abbr>CRS</abbr> and epoch of source coordinates. + * @param target the <abbr>CRS</abbr> and epoch of target coordinates. + * @param context area of interest, desired accuracy and other options, or {@code null} if none. + * @return mathematical operations from {@code source} to {@code target} <abbr>CRS</abbr> and epoch. + * @throws OperationNotFoundException if no operation was found between the given pair of <abbr>CRS</abbr>s. + * @throws FactoryException if the operation cannot be created for another reason. + * + * @see DefaultCoordinateOperationFactory#createOperations(CoordinateReferenceSystem, CoordinateReferenceSystem, CoordinateOperationContext) + * + * @since 1.6 + */ - public static List<CoordinateOperation> findOperations(final CoordinateMetadata source, - final CoordinateMetadata target, ++ public static List<CoordinateOperation> findOperations(final DefaultCoordinateMetadata source, ++ final DefaultCoordinateMetadata target, + final CoordinateOperationContext context) + throws FactoryException + { + ArgumentChecks.ensureNonNull("source", source); + ArgumentChecks.ensureNonNull("target", target); + if (source.getCoordinateEpoch().isPresent() || target.getCoordinateEpoch().isPresent()) { + throw new FactoryException("This version of Apache SIS does not yet support coordinate epoch."); + } + // TODO: take epoch in account. + final CoordinateReferenceSystem sourceCRS = source.getCoordinateReferenceSystem(); + final CoordinateReferenceSystem targetCRS = target.getCoordinateReferenceSystem(); final DefaultCoordinateOperationFactory factory = DefaultCoordinateOperationFactory.provider(); try { return factory.createOperations(sourceCRS, targetCRS, context); @@@ -1560,22 -1960,14 +1964,21 @@@ * @see DefaultCompoundCRS#getSingleComponents() */ public static List<SingleCRS> getSingleComponents(final CoordinateReferenceSystem crs) { - final List<SingleCRS> singles; if (crs == null) { - singles = List.of(); + return List.of(); } else if (crs instanceof CompoundCRS) { - return ((CompoundCRS) crs).getSingleComponents(); + if (crs instanceof DefaultCompoundCRS) { - singles = ((DefaultCompoundCRS) crs).getSingleComponents(); ++ return ((DefaultCompoundCRS) crs).getSingleComponents(); + } else { + final List<CoordinateReferenceSystem> elements = ((CompoundCRS) crs).getComponents(); - singles = new ArrayList<>(elements.size()); ++ final var singles = new ArrayList<SingleCRS>(elements.size()); + ReferencingUtilities.getSingleComponents(elements, singles); ++ return singles; + } } else { // Intentional CassCastException here if the crs is not a SingleCRS. - singles = List.of((SingleCRS) crs); + return List.of((SingleCRS) crs); } - return singles; } /** diff --cc endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/CoordinateOperationFinder.java index 03b1ece95c,8e0e4c8bca..0f4cf59499 --- 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 @@@ -1308,8 -1322,12 +1317,11 @@@ public class CoordinateOperationFinder * @param crs the CRS having a conversion that cannot be inverted. * @return a default error message. */ - @SuppressWarnings("deprecation") private String canNotInvert(final GeneralDerivedCRS crs) { - return resources().getString(Resources.Keys.NonInvertibleOperation_1, label(crs.getConversionFromBase())); + final Locale locale = getLocale(); + return Resources.forLocale(locale).getString( + Resources.Keys.NonInvertibleOperation_1, + CRSPair.label(crs.getConversionFromBase(), locale)); } /** diff --cc endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/SubOperationInfo.java index 9f1776e14a,6ad57255b4..1d3222e4fd --- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/SubOperationInfo.java +++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/SubOperationInfo.java @@@ -265,6 -254,46 +255,46 @@@ searchSrc: while (sourceComponentInde return infos; } + /** + * Initializes the {@link #constantCoordinates} field to the constant coordinate values for the operation step + * managed by this {@code SubOperationInfo}. If no constants have been specified, {@link #constantCoordinates} + * field stay null and the reason for the failure is returned. That reason is an axis missing in the source + * <abbr>CRS</abbr>, or {@code null} if that axis is unknown. + * + * @param context options supplied by the user, or {@code null}. + * @return the coordinate axis that could not be resolved, or {@code null} if none or unknown. + */ + private CoordinateSystemAxis fetchConstantsForMissingSourceDimensions(final CoordinateOperationContext context) { + if (context != null) { + final DirectPosition coordinates = context.getConstantCoordinates(); + if (coordinates != null) { + /* + * Finds the index of the first coordinate to use among the constant coordinates. + * The default CRS of `coordinates` is the full target CRS (with all dimensions). + * If a different CRS is specified, search the index of this target CRS component. + * No coordinate transformation is perfomed in this method, only selection. + */ + int indexOfConstant = targetLowerDimension; // Value for the default CRS. + final CoordinateReferenceSystem crs = coordinates.getCoordinateReferenceSystem(); + if (crs != null) { + indexOfConstant = CRS.locateDimensions(crs, targetComponent).nextSetBit(0); + if (indexOfConstant < 0) { + return null; + } + } + final int d = coordinates.getDimension(); + final var c = new double[targetUpperDimension - targetLowerDimension]; + for (int i=0; i<c.length; i++) { - if (indexOfConstant >= d || Double.isNaN(c[i] = coordinates.getCoordinate(indexOfConstant++))) { ++ if (indexOfConstant >= d || Double.isNaN(c[i] = coordinates.getOrdinate(indexOfConstant++))) { + return targetComponent.getCoordinateSystem().getAxis(i); + } + } + constantCoordinates = c; + } + } + return null; + } + /** * Returns the source CRS of given operations. This method modifies the given array in-place by moving all * sourceless operations last. Then an array is returned with the source CRS of only ordinary operations. diff --cc endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/TransformedCoordinateSet.java index 68ff144099,eca20504d4..4088f0ef00 --- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/TransformedCoordinateSet.java +++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/TransformedCoordinateSet.java @@@ -80,11 -80,11 +79,11 @@@ final class TransformedCoordinateSet ex if (transform == null) { throw new TransformException(Resources.format(Resources.Keys.OperationHasNoTransform_2, op.getClass(), op.getName())); } - final CoordinateMetadata metadata = data.getCoordinateMetadata(); + final DefaultCoordinateMetadata metadata = data.getCoordinateMetadata(); if (metadata != null) try { - GeographicBoundingBox aoi = CRS.getGeographicBoundingBox(op); + final var context = CoordinateOperationContext.fromBoundingBox(CRS.getGeographicBoundingBox(op)); final var step = new DefaultCoordinateMetadata(op.getSourceCRS(), op.getSourceEpoch().orElse(null)); - transform = MathTransforms.concatenate(CRS.findOperation(metadata, step, aoi).getMathTransform(), transform); + transform = MathTransforms.concatenate(CRS.findOperation(metadata, step, context).getMathTransform(), transform); } catch (FactoryException | MismatchedDimensionException e) { throw new TransformException(e.getMessage(), e); } diff --cc endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/matrix/MatricesTest.java index 6286420243,b67b620626..99cb67acff --- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/matrix/MatricesTest.java +++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/matrix/MatricesTest.java @@@ -33,10 -34,11 +34,11 @@@ import org.apache.sis.util.iso.Types import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.*; import org.apache.sis.test.TestCase; + import static org.apache.sis.test.Assertions.assertSetEquals; import static org.apache.sis.test.Assertions.assertMultilinesEquals; -// Specific to the geoapi-3.1 and geoapi-4.0 branches: -import static org.opengis.test.Assertions.assertMatrixEquals; +// Specific to the main branch: +import static org.apache.sis.test.GeoapiAssert.assertMatrixEquals; /** diff --cc endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/base/GridMapping.java index 5f93d2e3f2,5b45db26ad..24ec1325fd --- a/endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/base/GridMapping.java +++ b/endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/base/GridMapping.java @@@ -85,10 -85,11 +84,11 @@@ import org.apache.sis.util.resources.Er import org.apache.sis.util.resources.IndexedResourceBundle; import org.apache.sis.io.wkt.WKTFormat; import org.apache.sis.io.wkt.Warnings; + import org.apache.sis.math.NumberType; import org.apache.sis.measure.Units; -// Specific to the geoapi-3.1 and geoapi-4.0 branches: -import org.opengis.referencing.datum.DatumEnsemble; +// Specific to the main branch: +import org.apache.sis.referencing.datum.DatumOrEnsemble; /** diff --cc endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/FeatureAnalyzer.java index 2f61ea29ac,db117660cb..a09eeb32ff --- a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/FeatureAnalyzer.java +++ b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/FeatureAnalyzer.java @@@ -32,12 -32,12 +32,12 @@@ import org.apache.sis.feature.builder.A import org.apache.sis.feature.builder.AttributeTypeBuilder; import org.apache.sis.feature.builder.AttributeRole; import org.apache.sis.geometry.wrapper.Geometries; + import org.apache.sis.math.NumberType; import org.apache.sis.util.CharSequences; import org.apache.sis.util.Classes; - import org.apache.sis.util.Numbers; -// Specific to the geoapi-3.1 and geoapi-4.0 branches: -import org.opengis.feature.FeatureType; +// Specific to the main branch: +import org.apache.sis.feature.DefaultFeatureType; /**
