This is an automated email from the ASF dual-hosted git repository. asf-gitbox-commits pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/sis.git
commit 8eb0911e58c0cbb7cc65d9819cf7b58c8ec5d424 Merge: eb71d2424c 60f07fc246 Author: Martin Desruisseaux <[email protected]> AuthorDate: Wed May 6 17:11:33 2026 +0200 Merge branch 'geoapi-3.1'. This is mostly consolidation with bug fixes, in particular in the GUI application. .../resources/IndexedResourceCompiler.java | 44 +- .../org/apache/sis/cloud/aws/s3/Resources.java | 14 + .../sis/coverage/grid/BufferedGridCoverage.java | 5 +- .../apache/sis/coverage/grid/DefaultEvaluator.java | 3 +- .../apache/sis/coverage/grid/GridCRSBuilder.java | 540 +++++++++++++++ .../org/apache/sis/coverage/grid/GridExtent.java | 64 +- .../apache/sis/coverage/grid/GridExtentCRS.java | 410 ------------ .../org/apache/sis/coverage/grid/GridGeometry.java | 59 +- .../sis/coverage/grid/ResampledGridCoverage.java | 4 +- .../apache/sis/coverage/grid/SliceGeometry.java | 3 +- .../main/org/apache/sis/feature/Validator.java | 3 +- .../org/apache/sis/feature/internal/Resources.java | 14 + .../geometry/wrapper/SpatialOperationContext.java | 5 +- .../apache/sis/image/BandedSampleConverter.java | 6 +- .../main/org/apache/sis/image/DataType.java | 2 +- .../org/apache/sis/image/SourceAlignedImage.java | 2 +- .../image/internal/shared/BatchComputedImage.java | 7 +- .../image/internal/shared/ColorModelFactory.java | 121 +++- .../apache/sis/coverage/grid/GridGeometryTest.java | 78 ++- .../builder/AssociationRoleBuilderTest.java | 7 +- .../apache/sis/metadata/internal/Resources.java | 14 + .../metadata/internal/shared/NameToIdentifier.java | 10 +- .../org/apache/sis/metadata/sql/Dispatcher.java | 33 +- .../org/apache/sis/metadata/sql/MetadataProxy.java | 13 +- .../apache/sis/metadata/sql/MetadataSource.java | 22 +- .../org/apache/sis/metadata/sql/package-info.java | 2 +- .../org/apache/sis/map/internal/Resources.java | 14 + .../gazetteer/MilitaryGridReferenceSystem.java | 4 +- .../referencing/gazetteer/internal/Resources.java | 14 + .../main/module-info.java | 3 + .../sis/geometry/AbstractDirectPosition.java | 2 + .../main/org/apache/sis/geometry/Envelopes.java | 2 +- .../main/org/apache/sis/io/wkt/Formatter.java | 5 +- .../main/org/apache/sis/io/wkt/WKTFormat.java | 6 +- .../main/org/apache/sis/io/wkt/Warnings.java | 145 ++-- .../main/org/apache/sis/io/wkt/package-info.java | 2 +- .../sis/referencing/AbstractIdentifiedObject.java | 16 +- .../main/org/apache/sis/referencing/Builder.java | 14 +- .../main/org/apache/sis/referencing/CRS.java | 8 +- .../main/org/apache/sis/referencing/CommonCRS.java | 27 +- .../referencing/EllipsoidalHeightSeparator.java | 8 +- .../apache/sis/referencing/IdentifiedObjects.java | 10 +- .../apache/sis/referencing/NamedIdentifier.java | 44 +- .../sis/referencing/StandardDefinitions.java | 4 +- .../sis/referencing/crs/DefaultDerivedCRS.java | 3 + .../org/apache/sis/referencing/cs/AbstractCS.java | 2 +- .../apache/sis/referencing/cs/DefaultAffineCS.java | 2 +- .../sis/referencing/cs/DefaultCylindricalCS.java | 2 +- .../apache/sis/referencing/cs/DefaultLinearCS.java | 2 +- .../apache/sis/referencing/cs/DefaultPolarCS.java | 2 +- .../sis/referencing/cs/DefaultSphericalCS.java | 2 +- .../apache/sis/referencing/cs/DefaultTimeCS.java | 10 +- .../sis/referencing/cs/DefaultVerticalCS.java | 2 +- .../apache/sis/referencing/cs/package-info.java | 2 +- .../sis/referencing/datum/AbstractDatum.java | 14 +- .../referencing/datum/DefaultDatumEnsemble.java | 4 +- .../referencing/datum/DefaultGeodeticDatum.java | 10 +- .../referencing/factory/GeodeticObjectFactory.java | 35 + .../referencing/factory/IdentifiedObjectSet.java | 2 +- .../apache/sis/referencing/internal/Resources.java | 14 + .../internal/shared/ReferencingUtilities.java | 8 +- .../operation/AbstractCoordinateOperation.java | 60 +- .../operation/AbstractSingleOperation.java | 3 +- .../operation/CoordinateOperationFinder.java | 2 +- .../referencing/operation/DefaultConversion.java | 123 ++-- .../DefaultCoordinateOperationFactory.java | 2 +- .../operation/DefaultPassThroughOperation.java | 2 - .../referencing/operation/DefaultProjection.java | 3 +- .../referencing/operation/DefiningConversion.java | 163 +++++ .../apache/sis/parameter/ParameterFormatTest.java | 3 +- .../test/org/apache/sis/referencing/CRSTest.java | 32 + .../internal/shared/DefinitionVerifierTest.java | 3 +- .../operation/DefaultConversionTest.java | 2 +- .../operation/HardCodedConversions.java | 2 +- .../transform/DefaultMathTransformFactoryTest.java | 4 +- .../apache/sis/storage/geotiff/base/Resources.java | 14 + .../sis/storage/geotiff/GeoTiffStoreTest.java | 4 +- .../test/org/apache/sis/storage/geotiff/tiled.tiff | Bin 3882 -> 2334 bytes .../org/apache/sis/storage/geotiff/untiled.tiff | Bin 2602 -> 1054 bytes .../org/apache/sis/storage/netcdf/base/Axis.java | 7 +- .../sis/storage/netcdf/internal/Resources.java | 14 + .../apache/sis/storage/sql/feature/Resources.java | 14 + .../main/org/apache/sis/storage/DataStore.java | 10 +- .../apache/sis/storage/base/MetadataBuilder.java | 6 +- .../org/apache/sis/storage/internal/Resources.java | 14 + .../main/org/apache/sis/util/resources/Errors.java | 14 + .../apache/sis/util/resources/KeyConstants.java | 38 +- .../org/apache/sis/util/resources/Messages.java | 14 + .../resources/ResourceInternationalString.java | 10 +- .../org/apache/sis/util/resources/Vocabulary.java | 24 + .../sis/util/resources/Vocabulary.properties | 6 +- .../sis/util/resources/Vocabulary_fr.properties | 6 +- .../sis/storage/shapefile/ShapefileStore.java | 64 +- .../apache/sis/gui/coverage/CoverageCanvas.java | 85 ++- .../apache/sis/gui/coverage/CoverageControls.java | 7 +- .../apache/sis/gui/coverage/CoverageExplorer.java | 27 +- .../org/apache/sis/gui/coverage/GridControls.java | 6 +- .../apache/sis/gui/coverage/GridSliceSelector.java | 2 +- .../main/org/apache/sis/gui/coverage/GridView.java | 27 +- .../org/apache/sis/gui/coverage/ImageRequest.java | 47 +- .../sis/gui/coverage/StyledRenderingData.java | 10 + .../apache/sis/gui/coverage/ViewAndControls.java | 3 +- .../org/apache/sis/gui/internal/Resources.java | 16 +- .../apache/sis/gui/internal/Resources.properties | 2 +- .../sis/gui/internal/Resources_fr.properties | 2 +- .../main/org/apache/sis/gui/internal/Styles.java | 2 +- .../main/org/apache/sis/gui/map/MapCanvas.java | 47 +- .../main/org/apache/sis/gui/map/MapMenu.java | 1 + .../main/org/apache/sis/gui/map/MultiCanvas.java | 45 +- .../sis/gui/map/RenderingCompletedEvent.java | 86 +++ .../main/org/apache/sis/gui/map/RenderingTask.java | 16 +- .../main/org/apache/sis/gui/map/StatusBar.java | 159 +++-- .../org/apache/sis/gui/map/ValuesUnderCursor.java | 1 + .../apache/sis/gui/referencing/AuthorityCodes.java | 32 +- .../org/apache/sis/gui/referencing/CRSChooser.java | 40 +- .../apache/sis/gui/referencing/FilterByDatum.java | 185 +++++ .../org/apache/sis/gui/referencing/MenuSync.java | 47 +- .../gui/referencing/RecentReferenceSystems.java | 743 ++++++++++----------- .../org/apache/sis/gui/referencing/Unverified.java | 60 ++ .../org/apache/sis/storage/panama/Resources.java | 14 + 120 files changed, 2896 insertions(+), 1408 deletions(-) diff --cc endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridCRSBuilder.java index 0000000000,1f96d230e7..7a1df0e19d mode 000000,100644..100644 --- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridCRSBuilder.java +++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridCRSBuilder.java @@@ -1,0 -1,540 +1,540 @@@ + /* + * 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.coverage.grid; + + import java.util.Arrays; + import java.util.Collections; + import java.util.Map; + import java.util.HashMap; + import java.util.Locale; + import java.util.Optional; + import org.opengis.util.FactoryException; + import org.opengis.util.InternationalString; + import org.opengis.metadata.Identifier; + import org.opengis.metadata.spatial.DimensionNameType; + import org.opengis.parameter.ParameterValueGroup; + import org.opengis.parameter.ParameterDescriptor; + import org.opengis.parameter.ParameterDescriptorGroup; + import org.opengis.referencing.IdentifiedObject; + import org.opengis.referencing.cs.CSFactory; + import org.opengis.referencing.cs.AxisDirection; + import org.opengis.referencing.cs.CoordinateSystem; + import org.opengis.referencing.cs.CoordinateSystemAxis; + import org.opengis.referencing.crs.CoordinateReferenceSystem; + import org.opengis.referencing.crs.CompoundCRS; + import org.opengis.referencing.crs.DerivedCRS; + import org.opengis.referencing.crs.EngineeringCRS; + import org.opengis.referencing.datum.EngineeringDatum; + import org.opengis.referencing.operation.Matrix; + import org.opengis.referencing.operation.OperationMethod; + import org.opengis.referencing.operation.MathTransform; + import org.opengis.referencing.operation.TransformException; + import org.opengis.referencing.operation.NoninvertibleTransformException; + import org.apache.sis.metadata.iso.extent.DefaultExtent; + import org.apache.sis.parameter.ParameterBuilder; + import org.apache.sis.referencing.CommonCRS; + import org.apache.sis.referencing.IdentifiedObjects; + import org.apache.sis.referencing.cs.AbstractCS; + import org.apache.sis.referencing.cs.CoordinateSystems; + import org.apache.sis.referencing.operation.DefiningConversion; + import org.apache.sis.referencing.operation.DefaultOperationMethod; + import org.apache.sis.referencing.operation.transform.TransformSeparator; + import org.apache.sis.referencing.factory.InvalidGeodeticParameterException; + import org.apache.sis.referencing.internal.shared.AxisDirections; + import org.apache.sis.referencing.internal.shared.DirectPositionView; + import org.apache.sis.referencing.internal.shared.ReferencingFactoryContainer; + import org.apache.sis.feature.internal.Resources; + import org.apache.sis.util.ArraysExt; + import org.apache.sis.util.Characters; + import org.apache.sis.util.logging.Logging; + import org.apache.sis.util.resources.Vocabulary; + import org.apache.sis.util.iso.Types; + import org.apache.sis.measure.Units; + -// Specific to the geoapi-3.1 and geoapi-4.0 branches: -import org.opengis.referencing.ObjectDomain; ++// Specific to the main branch: ++import org.opengis.referencing.datum.Datum; + + + /** + * Builder for coordinate reference system which is derived from the coverage <abbr>CRS</abbr> + * by the inverse of the "grid to <abbr>CRS</abbr>" transform. Those <abbr>CRS</abbr> describe + * coordinates associated to the grid extent. This class provides two factory methods: + * + * <ul> + * <li>{@link #forCoverage()}</li> + * <li>{@link #forExtentAlone(Matrix, DimensionNameType[])}</li> + * </ul> + * + * @author Martin Desruisseaux (IRD, Geomatys) + */ + final class GridCRSBuilder extends ReferencingFactoryContainer { + /** + * Name of the parameter specifying which part (center or corner) + * of the call is associated with the coverage data attributes. + */ + private static final String ANCHOR_PARAM = "Pixel in cell"; + + /** + * Description of the "<abbr>CRS</abbr> to grid indices" operation method. + */ + private static final OperationMethod METHOD; + static { + final ParameterBuilder b = new ParameterBuilder().setRequired(true); + final ParameterDescriptor<?> anchor = b.addName(ANCHOR_PARAM).create(PixelInCell.class, PixelInCell.CELL_CENTER); + final ParameterDescriptorGroup params = b.addName("CRS to grid indices").createGroup(anchor); + METHOD = new DefaultOperationMethod(Map.of(IdentifiedObject.NAME_KEY, params.getName()), params); + } + + /** + * Scope of usage of the derived or engineering <abbr>CRS</abbr> created by this class. + * This is a localized text saying "Conversions from coverage <abbr>CRS</abbr> to grid cell indices." + */ + private static final InternationalString SCOPE = Resources.formatInternational(Resources.Keys.CrsToGridConversion); + + /** + * The extent of the grid geometry, or {@code null} if none. + */ + private GridExtent extent; + + /** + * The cell part (center or corner) to map. + */ + private final PixelInCell anchor; + + /** + * A helper tool for separating the "<abbr>CRS</abbr> to grid" transform for each component. + * This is {@code null} if the grid geometry has no <abbr>CRS</abbr> or no transform. + */ + private TransformSeparator separator; + + /** + * Properties to pass to the constructors of <abbr>CRS</abbr> components. Populated with metadata + * of potential interest except {@value IdentifiedObject#NAME_KEY}, which must be added before usage. + * + * @see #properties(Object) + */ + private final Map<String, Object> properties; + + /** + * Locale to use for axis names and error messages, or {@code null} for default. + * This is static and final for now because we do not yet provide a public API + * for this option, but this policy may change in the future. + */ + private static final Locale LOCALE = null; + + /** + * Creates a new helper class for building a grid coordinate reference system. + * + * @param anchor the cell part to map (center or corner). + */ + GridCRSBuilder(final PixelInCell anchor) { + this.anchor = anchor; + properties = new HashMap<>(8); + if (LOCALE != null) { + properties.put(DefiningConversion.LOCALE_KEY, LOCALE); + } + } + + /** + * Returns an error message for an illegal "grid to <abbr>CRS</abbr>" transform. + */ + private static String illegalGridToCRS() { + return Resources.forLocale(LOCALE).getString(Resources.Keys.IllegalGridGeometryComponent_1, "gridToCRS"); + } + + /** + * Creates a derived or engineering <abbr>CRS</abbr> for the grid extent of a grid coverage. + * Derived <abbr>CRS</abbr> are preferred as they allow conversions to geospatial <abbr>CRS</abbr>. + * May return a compound <abbr>CRS</abbr> if the grid geometry has, for example, a temporal component. + * + * @param grid grid geometry of the coverage. + * @param derived whether to force {@link DerivedCRS} instances. + * @param name name of the derived or engineering <abbr>CRS</abbr> to create. + * @return a derived, engineering or compound <abbr>CRS</abbr> for cell indices associated to the grid extent. + * @throws InvalidGeodeticParameterException if characteristics of the grid geometry disallow this operation. + * @throws FactoryException if another error occurred during the use of a referencing factory. + */ + final CoordinateReferenceSystem forCoverage(final GridGeometry grid, final boolean derived, final Identifier name) + throws FactoryException + { + properties.put(DefiningConversion.NORMALIZED_KEY, Boolean.FALSE); - properties.put(ObjectDomain.SCOPE_KEY, SCOPE); ++ properties.put(Datum.SCOPE_KEY, SCOPE); + grid.getGeographicExtent().ifPresent((domain) -> { - properties.put(ObjectDomain.DOMAIN_OF_VALIDITY_KEY, new DefaultExtent(null, domain, null, null)); ++ properties.put(Datum.DOMAIN_OF_VALIDITY_KEY, new DefaultExtent(null, domain, null, null)); + }); + if (grid.isDefined(GridGeometry.EXTENT)) { + extent = grid.getExtent(); + } + if (derived || grid.isDefined(GridGeometry.CRS | GridGeometry.GRID_TO_CRS)) try { + separator = new TransformSeparator(grid.getGridToCRS(anchor).inverse()); + return forComponent(name, grid.getCoordinateReferenceSystem(), 0, 0); + } catch (NoninvertibleTransformException e) { + throw new InvalidGeodeticParameterException(illegalGridToCRS(), e); + } + /* + * Case where the grid geometry has no CRS or no "grid to CRS" transform. + * We cannot create a derived CRS. Fallback on an engineering CRS with no + * relationship to any other CRS. + */ + final int dimension = grid.getDimension(); + final DimensionNameType[] dimensionNames; + if (extent != null) { + dimensionNames = Arrays.copyOf(extent.getAxisTypes(), dimension); + } else { + dimensionNames = new DimensionNameType[dimension]; + } + final CoordinateSystem cs = createCS(dimension, dimensionNames, directions(dimensionNames), 1, true); + final EngineeringDatum datum = getDatumFactory().createEngineeringDatum(properties(name)); + return getCRSFactory().createEngineeringCRS(properties(datum.getName()), datum, cs); + } + + /** + * Creates a derived <abbr>CRS</abbr> with a conversion from the real world <abbr>CRS</abbr> to grid indices. + * This method may invoke itself recursively for separating a compound <abbr>CRS</abbr> into its components. + * + * <p>After return, {@link #separator} contains information about the transform for this component. + * Caller can get the dimensions that have been used. Caller shall invoke {@code transform.clear()} + * before to invoke this method again.</p> + * + * @param name name of the <abbr>CRS</abbr> to create. + * @param baseCRS real world <abbr>CRS</abbr> or component of that <abbr>CRS</abbr>. + * @param srcDim dimension of the first axis of {@code baseCRS} relatively to the full real world <abbr>CRS</abbr>. + * @param tgtDim dimension of the first axis of the return value relatively to the full derived <abbr>CRS</abbr>. + * @return grid extent <abbr>CRS</abbr> derived from the given {@code baseCRS}. + * @throws FactoryException if an error occurred during the use of a referencing factory. + */ + private CoordinateReferenceSystem forComponent(final Object name, final CoordinateReferenceSystem baseCRS, int srcDim, int tgtDim) + throws FactoryException + { + final int dimension = baseCRS.getCoordinateSystem().getDimension(); + /* + * If the given CRS is a compound CRS (e.g. horizontal + vertical + temporal), + * invoke this method recursively for each component and assemble the result. + * We must keep in mind that the resulting CRS components are not necessarily + * in same order as the components of the real world CRS. + */ + if (baseCRS instanceof CompoundCRS) { + // At first, elements are duplicated in the `components` array for each axis. + final var components = new CoordinateReferenceSystem[dimension]; + for (final CoordinateReferenceSystem crs : ((CompoundCRS) baseCRS).getComponents()) { + final CoordinateReferenceSystem derived = forComponent(name(crs), crs, srcDim, tgtDim); + for (int i : separator.getTargetDimensions()) { + components[i] = derived; + } + separator.clear(); + srcDim += crs.getCoordinateSystem().getDimension(); + tgtDim += derived.getCoordinateSystem().getDimension(); + } + // Deduplicate components with the restriction that same components must be consecutive. + int count = 1; + for (int i=1; i<dimension; i++) { + final CoordinateReferenceSystem crs = components[i]; + if (crs != components[i-1]) { + for (int j = count; --j >= 0;) { + if (components[j] == crs) { + throw new InvalidGeodeticParameterException(illegalGridToCRS()); + } + } + components[count++] = crs; + } + } + return getCRSFactory().createCompoundCRS(properties(name), ArraysExt.resize(components, count)); + } + /* + * Case of a single (non-compound) CRS. The separator contains the "CRS to grid" transform. + * Therefore, the source dimensions are in the base CRS (the real world CRS) and the target + * dimensions are those of the derived CRS to create. + */ + separator.addSourceDimensionRange(srcDim, srcDim + dimension); + final MathTransform crsToGrid = separator.separate(); + final int[] dispatch = separator.getTargetDimensions(); + final var dimensionNames = new DimensionNameType[dispatch.length]; + if (extent != null) { + Arrays.setAll(dimensionNames, (i) -> extent.getAxisType(dispatch[i]).orElse(null)); + } + /* + * Get the directions of the axes of the coverage coordinate system, but in the order of grid dimensions. + * The direction array may contain null elements if directions could not be inferred for some dimensions. + * Design note: we perform this check for each single component instead of computing the derivative once + * in the `forCoverage(…)` method because we need an affine transform. When the transform for the whole + * grid is not affine, very often the transform for some single components is still affine. + */ + AxisDirection[] directions; + toGrid: try { + final Matrix derivative; + if (extent != null) { + derivative = crsToGrid.derivative(new DirectPositionView.Double(extent.getPointOfInterest(anchor), srcDim, dimension)); + } else try { + derivative = crsToGrid.derivative(null); + } catch (NullPointerException e) { + Logging.ignorableException(GridExtent.LOGGER, GridGeometry.class, "createGridCRS", e); + directions = directions(dimensionNames); + break toGrid; + } + // Last arguments are `null` because `dimensionNames` is already in the desired order. + directions = CoordinateSystems.getSimpleAxisDirections(baseCRS.getCoordinateSystem()); + directions = reorder(directions, derivative, null, null); + for (int i=0; i < dimensionNames.length; i++) { + if (dimensionNames[i] == null && directions[i] != null) { + final DimensionNameType type = GridExtent.DIMENSION_NAMES.get(directions[i]); + if (type != null && !ArraysExt.contains(dimensionNames, type)) { + dimensionNames[i] = type; + } + } + } + } catch (TransformException e) { + // `GridGeometry.createGridCRS(…)` is the public API that invoked this method. + Logging.recoverableException(GridExtent.LOGGER, GridGeometry.class, "createGridCRS", e); + directions = directions(dimensionNames); + } + /* + * Creates the coordinate system, then the conversion, and finally the derived CRS. + */ + final CoordinateSystem cs = createCS(dispatch.length, dimensionNames, directions, tgtDim + 1, true); + final ParameterValueGroup params = METHOD.getParameters().createValue(); + params.parameter(ANCHOR_PARAM).setValue(anchor); + final var conversion = new DefiningConversion(properties(METHOD.getName()), METHOD, crsToGrid, params); + return getCRSFactory().createDerivedCRS(properties(name), baseCRS, conversion, cs); + } + + /** + * Returns the properties map for the construction of a <abbr>CRS</abbr> or operation of the given name. + */ + private Map<String,?> properties(final Object name) { + properties.put(IdentifiedObject.NAME_KEY, name); + return Collections.unmodifiableMap(properties); + } + + /** + * Returns a default name for the component of a grid <abbr>CRS</abbr>. + * + * @param baseCRS the real world <abbr>CRS</abbr> from which to derive a grid <abbr>CRS</abbr>. + * @return default name for a grid <abbr>CRS</abbr> derived from the given real world <abbr>CRS</abbr>. + */ + private static String name(final CoordinateReferenceSystem baseCRS) { + String name = IdentifiedObjects.getSimpleNameOrIdentifier(baseCRS); + if (name == null) { + name = IdentifiedObjects.getDisplayName(baseCRS, LOCALE); + } + return "Grid based on " + name; + } + + /** + * Returns the coordinate reference system that we use as a template for object names. + * This template uses generic terms such as "Cell indices" for the <abbr>CRS</abbr> name + * and "Unknown grid" for the datum. + */ + private static EngineeringCRS template() { + return CommonCRS.Engineering.GRID.crs(); + } + + /** + * Creates the coordinate system for the derived or engineering <abbr>CRS</abbr> of a grid. + * + * @param dimension number of dimensions of the coordinate system to create. + * @param dimensionNames names of grid dimension. Shall not be null but may contain null elements. + * @param directions directions of the axes of the coordinate system to create. May contain null elements. + * @param labelOffset offset to add to the dimension for producing a default axis name or abbreviation. + * @return coordinate system for the grid extent, or {@code null} if it cannot be inferred. + * @throws FactoryException if an error occurred during the use of {@link CSFactory}. + */ + private CoordinateSystem createCS(final int dimension, final DimensionNameType[] dimensionNames, + final AxisDirection[] directions, final int labelOffset, final boolean cartesian) + throws FactoryException + { + final CSFactory csFactory = getCSFactory(); + boolean hasVertical = false; + boolean hasTime = false; + boolean hasOther = false; + final var axes = new CoordinateSystemAxis[dimension]; + for (int j=0; j<dimension; j++) { + String abbreviation = null; + final DimensionNameType type = dimensionNames[j]; + if (type != null) { + if (type == DimensionNameType.COLUMN || type == DimensionNameType.SAMPLE) { + abbreviation = "x"; + } else if (type == DimensionNameType.ROW || type == DimensionNameType.LINE) { + abbreviation = "y"; + } else if (type == DimensionNameType.VERTICAL) { + abbreviation = "z"; hasVertical = true; + } else if (type == DimensionNameType.TIME) { + abbreviation = "t"; hasTime = true; + } else { + hasOther = true; + } + } + if (abbreviation != null) { + for (int i = j; --i >= 0;) { + final CoordinateSystemAxis previous = axes[i]; + if (abbreviation.equals(previous.getAbbreviation())) { + abbreviation = null; + break; + } + } + } + if (abbreviation == null) { + final var b = new StringBuilder(4).append('x').append(labelOffset + j); + for (int i = b.length(); --i >= 1;) { + b.setCharAt(i, Characters.toSubScript(b.charAt(i))); + } + abbreviation = b.toString(); + } + /* + * Try to infer the axis name from the grid dimension name type, otherwise create + * a default name in a way similar to the abbreviation (with indices in subscripts). + */ + String name = Types.toString(Types.getCodeTitle(type), LOCALE); + if (name == null) { + name = Vocabulary.forLocale(LOCALE).getString(Vocabulary.Keys.Dimension_1, labelOffset + j); + } + AxisDirection direction = directions[j]; + if (direction == null) { - direction = AxisDirection.UNSPECIFIED; ++ direction = AxisDirections.UNSPECIFIED; + } + axes[j] = csFactory.createCoordinateSystemAxis(properties(name), abbreviation, direction, Units.UNITY); + } + /* + * Create a coordinate system of affine type if all axes seem spatial. + * If no specialized type seems to fit, use an unspecified ("abstract") + * coordinate system type in last resort. + */ + @SuppressWarnings("LocalVariableHidesMemberVariable") + final Map<String,?> properties = properties(template().getCoordinateSystem().getName()); + final CoordinateSystemAxis axis = axes[0]; + switch (dimension) { + case 1: { + if (hasVertical) { + return csFactory.createVerticalCS(properties, axis); + } else if (hasTime) { + return csFactory.createTimeCS(properties, axis); + } else if (hasOther) { + break; + } else { + return csFactory.createLinearCS(properties, axis); + } + } + case 2: { + if (hasVertical | hasTime | hasOther) break; + return cartesian + ? csFactory.createCartesianCS(properties, axis, axes[1]) + : csFactory.createAffineCS (properties, axis, axes[1]); + } + case 3: { + if (hasVertical | hasTime | hasOther) break; + return cartesian + ? csFactory.createCartesianCS(properties, axis, axes[1], axes[2]) + : csFactory.createAffineCS (properties, axis, axes[1], axes[2]); + } + } + return new AbstractCS(properties, axes); + } + + /** + * Returns the default axis directions for grid dimensions of the given name. + * + * @param types grid dimension names. + * @return default axis directions. May contain null elements. + */ + private static AxisDirection[] directions(final DimensionNameType[] types) { + final var directions = new AxisDirection[types.length]; + for (int i=0; i<types.length; i++) { + final DimensionNameType type = types[i]; + if (type != null) { + final AxisDirection direction = GridExtent.AXIS_DIRECTIONS.get(type); + if (!ArraysExt.contains(directions, direction)) { + directions[i] = direction; + } + } + } + return directions; + } + + /** + * Adjusts the order of code list values for any change of order applied by the given transform. + * For example, if {@code directions} contains the axis directions in the coverage <abbr>CRS</abbr> + * and if {@code derivative} is the derivative of the <abbr>CRS</abbr> to grid transform, then this + * method returns the axis directions of the grid. Values that cannot be mapped are set to null. + * + * @param directions the directions to reorder. Shall not be null but may contain null elements. + * @param derivative derivative of the transform from source to target <abbr>CRS</abbr>. + * @param source an optional array to reorder together with {@code directions}. + * @param target where to store the result of {@code source} reordering, or {@code null}. + * @return the reordered axis directions. May contain {@code null} elements. + */ + private static AxisDirection[] reorder(final AxisDirection[] directions, final Matrix derivative, + final DimensionNameType[] source, final DimensionNameType[] target) + { + final var ordered = new AxisDirection[derivative.getNumRow()]; + for (int j=0; j<ordered.length; j++) { + boolean found = false; + for (int i=0; i<directions.length; i++) { + final double m = derivative.getElement(j, i); + if (m != 0) { + if (found) { + ordered[j] = null; + if (target != null) { + target[j] = null; + } + break; + } + found = true; + AxisDirection selected = directions[i]; + if (selected != null && m < 0) { + selected = AxisDirections.opposite(selected); + } + ordered[j] = selected; + if (target != null && i < source.length) { + target[j] = source[i]; + } + } + } + } + return ordered; + } + + /** + * Builds the coordinate reference system of the result of transforming a {@link GridExtent}. + * This is used only in the rare cases where we need to represent an extent as an envelope. + * This class converts {@link DimensionNameType} codes into axis names, abbreviations and directions. + * It is the converse of {@link GridExtent#typeFromAxes(CoordinateReferenceSystem, int)}. + * + * <p>The <abbr>CRS</abbr> type is always engineering. In particular, the <abbr>CRS</abbr> cannot be temporal + * because we do not know the temporal datum origin and because index unit is not a temporal unit.</p> + * + * @param derivative derivative of the transform converting grid cell indices to envelope coordinates. + * @param types the value of {@link GridExtent#types} or a default value (shall not be {@code null}). + * @return <abbr>CRS</abbr> for the grid, or empty if it cannot be built. + * @throws FactoryException if an error occurred during the use of a referencing factory. + * + * @see GridExtent#toEnvelope(MathTransform) + * @see GridExtent#typeFromAxes(CoordinateReferenceSystem, int) + */ + final Optional<EngineeringCRS> forExtentAlone(final Matrix derivative, final DimensionNameType[] types) + throws FactoryException + { + final int dimension = derivative.getNumRow(); + final var dimensionNames = new DimensionNameType[dimension]; + AxisDirection[] directions = directions(ArraysExt.resize(types, dimension)); + directions = reorder(directions, derivative, types, dimensionNames); + final CoordinateSystem cs = createCS(dimension, dimensionNames, directions, 1, false); + if (cs == null) { + return Optional.empty(); + } + final EngineeringCRS template = template(); + return Optional.of(getCRSFactory().createEngineeringCRS(properties(template.getName()), template.getDatum(), cs)); + } + } diff --cc endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridGeometry.java index 62112ecfca,80d8eaca8b..0730dd5952 --- 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 @@@ -37,9 -37,8 +37,9 @@@ import org.opengis.referencing.operatio import org.opengis.referencing.operation.MathTransform; import org.opengis.referencing.operation.TransformException; import org.opengis.referencing.operation.CoordinateOperation; - import org.opengis.referencing.operation.NoninvertibleTransformException; import org.opengis.referencing.crs.CoordinateReferenceSystem; import org.opengis.referencing.crs.DerivedCRS; ++import org.opengis.referencing.crs.EngineeringCRS; import org.opengis.referencing.cs.CoordinateSystem; import org.opengis.referencing.cs.CoordinateSystemAxis; import org.apache.sis.math.MathFunctions; diff --cc endorsed/src/org.apache.sis.feature/test/org/apache/sis/coverage/grid/GridGeometryTest.java index 433e41bdfa,4c03cc09fe..bccc0a700e --- 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 @@@ -17,11 -17,18 +17,17 @@@ package org.apache.sis.coverage.grid; import java.util.Set; + import java.util.List; import org.opengis.util.FactoryException; import org.opengis.geometry.Envelope; + import org.opengis.metadata.Identifier; import org.opengis.metadata.spatial.DimensionNameType; + import org.opengis.metadata.extent.GeographicBoundingBox; -import org.opengis.referencing.ObjectDomain; import org.opengis.referencing.cs.AxisDirection; + import org.opengis.referencing.cs.CoordinateSystem; + import org.opengis.referencing.crs.SingleCRS; import org.opengis.referencing.crs.DerivedCRS; + import org.opengis.referencing.crs.CoordinateReferenceSystem; import org.opengis.referencing.operation.Matrix; import org.opengis.referencing.operation.MathTransform; import org.opengis.referencing.operation.TransformException; @@@ -50,6 -61,6 +59,9 @@@ import static org.apache.sis.referencin import static org.apache.sis.feature.Assertions.assertGridToCenterEquals; import static org.apache.sis.feature.Assertions.assertGridToCornerEquals; ++// Specific to the main branch: ++import static org.apache.sis.test.GeoapiAssert.assertAxisDirectionsEqual; ++ /** * Tests the {@link GridGeometry} implementation. @@@ -808,29 -819,68 +820,67 @@@ public final class GridGeometryTest ext } /** - * Tests {@link GridGeometry#createImageCRS(String, PixelInCell)}. + * Tests {@link GridGeometry#createGridCRS(Identifier, PixelInCell)}. + * + * @throws FactoryException if the <abbr>CRS</abbr> cannot be built. */ @Test - public void testCreateImageCRS() { + public void testCreateGridCRS() throws FactoryException { final var gg = new GridGeometry( new GridExtent(null, null, new long[] {17, 10, 4}, true), - PixelInCell.CELL_CENTER, + PixelInCell.CELL_CORNER, MathTransforms.linear(new Matrix4( - 1, 0, 0, -7, - 0, -1, 0, 50, - 0, 0, 8, 20, + 0, -1, 0, 50, // Longitude + 1, 0, 0, -7, // Latitude + 0, 0, 8, 20, // Time 0, 0, 0, 1)), HardCodedCRS.WGS84_WITH_TIME); - - final DerivedCRS crs = gg.createImageCRS("Horizontal part", PixelInCell.CELL_CENTER); - assertEquals("Horizontal part", crs.getName().getCode()); - assertSame(HardCodedCRS.WGS84, crs.getBaseCRS()); + /* + * Metadata about the CRS as a whole (a CompoundCRS). + * Axes are like "Dimension 1 (x₁)" because we did not + * specified some `DimentionNameType` parameter values. + */ + final Identifier name = new ImmutableIdentifier(null, null, "Tested grid CRS"); + final CoordinateReferenceSystem crs = gg.createGridCRS(name, PixelInCell.CELL_CORNER); - final ObjectDomain domain = assertSingleton(crs.getDomains()); + final GeographicBoundingBox bbox = assertInstanceOf(GeographicBoundingBox.class, - assertSingleton(domain.getDomainOfValidity().getGeographicElements())); ++ assertSingleton(crs.getDomainOfValidity().getGeographicElements())); + assertEquals(39, bbox.getWestBoundLongitude()); + assertEquals(50, bbox.getEastBoundLongitude()); + assertEquals(-7, bbox.getSouthBoundLatitude()); + assertEquals(11, bbox.getNorthBoundLatitude()); - assertNotNull(domain.getScope()); // Text depends on the locale. ++ assertNotNull(crs.getScope()); // Text depends on the locale. + assertSame(name, crs.getName()); + /* + * Check the horizontal and temporal components. + */ + final List<SingleCRS> components = CRS.getSingleComponents(crs); + assertEquals(2, components.size()); + final var horizontal = assertInstanceOf(DerivedCRS.class, components.get(0)); + final var temporal = assertInstanceOf(DerivedCRS.class, components.get(1)); + assertSame(HardCodedCRS.WGS84, horizontal.getBaseCRS()); + assertSame(HardCodedCRS.TIME, temporal .getBaseCRS()); + assertAxisDirectionsEqual(horizontal.getCoordinateSystem(), AxisDirection.NORTH, AxisDirection.WEST); + assertAxisDirectionsEqual(temporal .getCoordinateSystem(), AxisDirection.FUTURE); assertMatrixEquals( - new Matrix3(1, 0, 7, // Opposite sign because this is the inverse transform. - 0, -1, 50, // Opposite sign cancelled by -1 scale factor. - 0, 0, 1), - crs.getConversionFromBase().getMathTransform(), + new Matrix3(0, 1, 7, // Opposite sign of translation term because this is the inverse transform. + -1, 0, 50, // Reminder: sign of translation term is inversed by the scale factor -1. + 0, 0, 1), + horizontal.getConversionFromBase().getMathTransform(), "CRS to grid"); + assertMatrixEquals( + new Matrix2(0.125, -2.5, 0, 1), + temporal.getConversionFromBase().getMathTransform(), + "CRS to grid"); + /* + * Check axis names. The grid dimension which is mapped to longitudes has its direction + * reversed because of the -1 sign in the scale factor. + */ + final CoordinateSystem cs = crs.getCoordinateSystem(); + assertEquals(3, cs.getDimension()); + assertEquals("x₁", cs.getAxis(0).getAbbreviation()); + assertEquals("x₂", cs.getAxis(1).getAbbreviation()); + assertEquals("t", cs.getAxis(2).getAbbreviation()); + assertAxisDirectionsEqual(cs, AxisDirection.NORTH, AxisDirection.WEST, AxisDirection.FUTURE); } /** diff --cc endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/internal/shared/NameToIdentifier.java index 43d3262342,b7624b10ff..876bbd9d08 --- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/internal/shared/NameToIdentifier.java +++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/internal/shared/NameToIdentifier.java @@@ -121,16 -125,10 +125,18 @@@ public final class NameToIdentifier imp return name.tip().toString(); } + /** + * Returns {@code null} since names are not versioned. + */ + @Override + public String getVersion() { + return null; + } + /** * Returns a hash code value for this object. + * + * @return hash code for this object. */ @Override public int hashCode() { diff --cc endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/Dispatcher.java index 0b11ac9e8a,c9bb1dd381..30fef315df --- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/Dispatcher.java +++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/Dispatcher.java @@@ -336,6 -337,27 +342,27 @@@ final class Dispatcher implements Invoc return value; } + /** + * Copies the content to an ordinary implementation class (plain old object). + * The returned object should be serializable if allowed by the implementation. + * + * @param proxy the object on which the method is invoked. + * @return a copy of this metadata object. + */ + private Object writeReplace(final Object proxy) throws NotSerializableException { + ReflectiveOperationException cause = null; + final Class<?> type = proxy.getClass().getInterfaces()[0]; + final Class<?> impl = source.standard.getImplementation(type); + if (impl != null) try { - return impl.getDeclaredConstructor(argument(type)).newInstance(proxy); ++ return impl.getDeclaredConstructor(type).newInstance(proxy); + } catch (ReflectiveOperationException e) { + cause = e; + } + final var e = new NotSerializableException(type.getCanonicalName()); + e.initCause(cause); + throw e; + } + /** * Returns the error message for a failure to query the database for the property identified by the given method. */ diff --cc endorsed/src/org.apache.sis.referencing.gazetteer/main/org/apache/sis/referencing/gazetteer/MilitaryGridReferenceSystem.java index 8f7355e473,6917ee93f9..9e82cf5dd7 --- a/endorsed/src/org.apache.sis.referencing.gazetteer/main/org/apache/sis/referencing/gazetteer/MilitaryGridReferenceSystem.java +++ b/endorsed/src/org.apache.sis.referencing.gazetteer/main/org/apache/sis/referencing/gazetteer/MilitaryGridReferenceSystem.java @@@ -289,8 -294,15 +289,8 @@@ public class MilitaryGridReferenceSyste */ @Workaround(library="JDK", version="8", fixed="25") private static Map<String,?> properties() { - AbstractParty party = new AbstractParty("North Atlantic Treaty Organization", null); - Party party; - try { - party = MetadataSource.getProvided().lookup(Party.class, "{org}NATO"); - } catch (MetadataStoreException e) { - party = null; - Logging.unexpectedException(LOGGER, MilitaryGridReferenceSystem.class, "<init>", e); - } - var name = new NamedIdentifier(null, "NATO", Resources.formatInternational(Resources.Keys.MGRS), null, null); - return properties(name, IDENTIFIER, party); ++ final var party = new AbstractParty("North Atlantic Treaty Organization", null); + return properties(new NamedIdentifier(null, "NATO", Resources.formatInternational(Resources.Keys.MGRS), null, null), IDENTIFIER, party); } /** diff --cc endorsed/src/org.apache.sis.referencing/main/module-info.java index b5620ddf6d,bacffd1670..6b1c1f3ee8 --- a/endorsed/src/org.apache.sis.referencing/main/module-info.java +++ b/endorsed/src/org.apache.sis.referencing/main/module-info.java @@@ -200,10 -204,6 +200,13 @@@ module org.apache.sis.referencing org.glassfish.jaxb.core, // For access to various classes. jakarta.xml.bind; // Seems ignored. + exports org.apache.sis.referencing.internal to // On main branch only, for transition from GeoAPI 3.0. + org.apache.sis.console, + org.apache.sis.openoffice; + ++ exports org.apache.sis.pending.geoapi.referencing to ++ org.apache.sis.gui; // In the "optional" sub-project. ++ /* * Allow JAXB to use reflection for marshalling and * unmarshalling Apache SIS objects in XML documents. diff --cc endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/CRS.java index c3e7f38698,48f2dee3bc..65524500e0 --- 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 @@@ -84,9 -84,10 +84,9 @@@ import org.apache.sis.referencing.datum import org.apache.sis.referencing.operation.AbstractCoordinateOperation; import org.apache.sis.referencing.operation.CoordinateOperationContext; import org.apache.sis.referencing.operation.DefaultCoordinateOperationFactory; - import org.apache.sis.referencing.operation.DefaultConversion; + import org.apache.sis.referencing.operation.DefiningConversion; import org.apache.sis.referencing.factory.GeodeticObjectFactory; import org.apache.sis.referencing.factory.UnavailableFactoryException; -import org.apache.sis.coordinate.DefaultCoordinateMetadata; import org.apache.sis.metadata.iso.extent.DefaultGeographicBoundingBox; import org.apache.sis.metadata.iso.extent.Extents; import org.apache.sis.util.ArgumentChecks; diff --cc endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/NamedIdentifier.java index 964472c9fc,de463c2b1f..8b4878a52c --- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/NamedIdentifier.java +++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/NamedIdentifier.java @@@ -122,14 -122,18 +122,18 @@@ public class NamedIdentifier extends Im * * @param identifier the identifier to copy. * - * @see #castOrCopy(Identifier) + * @see #castOrCopy(ReferenceIdentifier) */ - public NamedIdentifier(final Identifier identifier) { + public NamedIdentifier(final ReferenceIdentifier identifier) { super(identifier); - if (identifier instanceof GenericName) { + if (identifier instanceof NameToIdentifier) { + name = ((NameToIdentifier) identifier).name; + } else if (identifier instanceof GenericName) { name = (GenericName) identifier; - isNameSupplied = true; + } else { + return; } + isNameSupplied = true; } /** @@@ -342,9 -346,11 +346,11 @@@ * @return a SIS implementation containing the values of the given object (may be the * given object itself), or {@code null} if the argument was null. * + * @see #toGenericName(Identifier) + * * @since 1.0 */ - public static NamedIdentifier castOrCopy(final Identifier object) { + public static NamedIdentifier castOrCopy(final ReferenceIdentifier object) { if (object == null || object instanceof NamedIdentifier) { return (NamedIdentifier) object; } @@@ -378,6 -386,36 +386,36 @@@ return new NamedIdentifier(object); } + /** + * Returns the given identifier as a name. This method is similar to {@link #castOrCopy(Identifier)} + * except that it does not require the given {@code object} to be a {@code NamedIdentifier} instance. + * + * @param object the object to get as a name, or {@code nulk}. + * @return the given object as a name, or {@code null} if the argument was null. + * @since 1.7 + */ - public static GenericName toGenericName(final Identifier object) { ++ public static GenericName toGenericName(final ReferenceIdentifier object) { + if (object == null || object instanceof GenericName) { + return (GenericName) object; + } + return new NamedIdentifier(object); + } + + /** + * Returns the given name as an identifier. This method is similar to {@link #castOrCopy(GenericName)} + * except that it does not require the given {@code object} to be a {@code NamedIdentifier} instance. + * + * @param object the object to get as an identifier, or {@code nulk}. + * @return the given object as an identifier, or {@code null} if the argument was null. + * @since 1.7 + */ + public static ReferenceIdentifier toIdentifier(final GenericName object) { + if (object == null || object instanceof ReferenceIdentifier) { + return (ReferenceIdentifier) object; + } + return new NamedIdentifier(object); + } + /** * The last element in the sequence of {@linkplain #getParsedNames() parsed names}. * By default, this is the same value as the {@linkplain #getCode() code} provided as a local name. diff --cc endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/GeodeticObjectFactory.java index 4bcc651c5b,697396e01c..f3277fb785 --- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/GeodeticObjectFactory.java +++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/GeodeticObjectFactory.java @@@ -355,9 -348,10 +355,10 @@@ public class GeodeticObjectFactory exte * @param ensemble collection of reference frames which for low accuracy requirements may be considered to be * insignificantly different from each other, or {@code null} if there is no such ensemble. * @param cs the three-dimensional Cartesian coordinate system for the created CRS. + * @return the coordinate reference system for the given properties. * @throws FactoryException if the object creation failed. * - * @see DefaultGeocentricCRS#DefaultGeocentricCRS(Map, GeodeticDatum, DatumEnsemble, CartesianCS) + * @see DefaultGeocentricCRS#DefaultGeocentricCRS(Map, GeodeticDatum, DefaultDatumEnsemble, CartesianCS) * @see GeodeticAuthorityFactory#createGeodeticCRS(String) * * @since 1.5 @@@ -470,9 -463,10 +472,10 @@@ * @param ensemble collection of reference frames which for low accuracy requirements may be considered to be * insignificantly different from each other, or {@code null} if there is no such ensemble. * @param cs the spherical coordinate system for the created CRS. + * @return the coordinate reference system for the given properties. * @throws FactoryException if the object creation failed. * - * @see DefaultGeocentricCRS#DefaultGeocentricCRS(Map, GeodeticDatum, DatumEnsemble, SphericalCS) + * @see DefaultGeocentricCRS#DefaultGeocentricCRS(Map, GeodeticDatum, DefaultDatumEnsemble, SphericalCS) * @see GeodeticAuthorityFactory#createGeodeticCRS(String) * * @since 1.5 @@@ -618,9 -616,10 +623,10 @@@ * @param ensemble collection of reference frames which for low accuracy requirements may be considered to be * insignificantly different from each other, or {@code null} if there is no such ensemble. * @param cs the two- or three-dimensional ellipsoidal coordinate system for the created <abbr>CRS</abbr>. + * @return the coordinate reference system for the given properties. * @throws FactoryException if the object creation failed. * - * @see DefaultGeographicCRS#DefaultGeographicCRS(Map, GeodeticDatum, DatumEnsemble, EllipsoidalCS) + * @see DefaultGeographicCRS#DefaultGeographicCRS(Map, GeodeticDatum, DefaultDatumEnsemble, EllipsoidalCS) * @see GeodeticAuthorityFactory#createGeographicCRS(String) * * @since 1.5 @@@ -1086,9 -1095,10 +1102,10 @@@ * @param ensemble collection of reference frames which for low accuracy requirements may be considered to be * insignificantly different from each other, or {@code null} if there is no such ensemble. * @param cs the vertical coordinate system for the created <abbr>CRS</abbr>. + * @return the coordinate reference system for the given properties. * @throws FactoryException if the object creation failed. * - * @see DefaultVerticalCRS#DefaultVerticalCRS(Map, VerticalDatum, DatumEnsemble, VerticalCS) + * @see DefaultVerticalCRS#DefaultVerticalCRS(Map, VerticalDatum, DefaultDatumEnsemble, VerticalCS) * @see GeodeticAuthorityFactory#createVerticalCRS(String) * * @since 1.5 @@@ -1235,9 -1313,10 +1253,10 @@@ * @param ensemble collection of datum which for low accuracy requirements may be considered to be * insignificantly different from each other, or {@code null} if there is no such ensemble. * @param cs the temporal coordinate system for the created <abbr>CRS</abbr>. + * @return the coordinate reference system for the given properties. * @throws FactoryException if the object creation failed. * - * @see DefaultTemporalCRS#DefaultTemporalCRS(Map, TemporalDatum, DatumEnsemble, TimeCS) + * @see DefaultTemporalCRS#DefaultTemporalCRS(Map, TemporalDatum, DefaultDatumEnsemble, TimeCS) * @see GeodeticAuthorityFactory#createTemporalCRS(String) * * @since 1.5 @@@ -1362,9 -1439,10 +1383,10 @@@ * @param ensemble collection of datum which for low accuracy requirements may be considered to be * insignificantly different from each other, or {@code null} if there is no such ensemble. * @param cs the parametric coordinate system for the created <abbr>CRS</abbr>. + * @return the coordinate reference system for the given properties. * @throws FactoryException if the object creation failed. * - * @see DefaultParametricCRS#DefaultParametricCRS(Map, ParametricDatum, DatumEnsemble, ParametricCS) + * @see DefaultParametricCRS#DefaultParametricCRS(Map, DefaultParametricDatum, DefaultDatumEnsemble, ParametricCS) * @see GeodeticAuthorityFactory#createParametricCRS(String) * * @since 1.5 @@@ -1407,10 -1488,8 +1429,11 @@@ * Creates a parametric datum. * The default implementation creates a {@link DefaultParametricDatum} instance. * + * <div class="warning"><b>Warning:</b> in a future SIS version, the return type may be changed + * to {@code org.opengis.referencing.datum.ParametricDatum}. This change is pending GeoAPI revision.</div> + * * @param properties name and other properties to give to the new object. + * @return the datum for the given properties. * @throws FactoryException if the object creation failed. * * @see DefaultParametricDatum#DefaultParametricDatum(Map) @@@ -1440,11 -1520,9 +1463,12 @@@ * * The default implementation creates a {@link DefaultParametricCS} instance. * + * <div class="warning"><b>Warning:</b> in a future SIS version, the return type may be changed + * to {@code org.opengis.referencing.cs.ParametricCS}. This change is pending GeoAPI revision.</div> + * * @param properties name and other properties to give to the new object. * @param axis the single axis. + * @return the coordinate system for the given properties. * @throws FactoryException if the object creation failed. * * @see DefaultParametricCS#DefaultParametricCS(Map, CoordinateSystemAxis) diff --cc endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/DefaultConversion.java index 9e7c3209de,500fe6329d..425109e109 --- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/DefaultConversion.java +++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/DefaultConversion.java @@@ -50,13 -50,10 +50,13 @@@ import org.opengis.referencing.crs.Gene import org.opengis.referencing.crs.GeographicCRS; import org.opengis.referencing.crs.ProjectedCRS; +// Specific to the main branch: +import static org.apache.sis.pending.geoapi.referencing.MissingMethods.getDatumEnsemble; + /** - * A parameterized mathematical operation that converts coordinates to another CRS without any change of - * {@linkplain org.apache.sis.referencing.datum.AbstractDatum datum}. + * A parameterized mathematical operation that converts coordinates + * to another <abbr>CRS</abbr> without any change of datum. * The best-known example of a coordinate conversion is a map projection. * The parameters describing coordinate conversions are defined rather than empirically derived. * @@@ -220,16 -181,40 +184,39 @@@ public class DefaultConversion extends } /** - * Constructs a new conversion with the same values as the specified one, together with the - * specified source and target CRS. While the source conversion can be an arbitrary one, - * it is typically a defining conversion. + * Creates a new coordinate operation initialized from the given properties. + * It is caller's responsibility to set the following fields: + * + * <ul> + * <li>{@link #sourceCRS}</li> + * <li>{@link #targetCRS}</li> + * <li>{@link #transform}</li> + * <li>{@link #parameters}</li> + * </ul> + */ + DefaultConversion(final Map<String,?> properties, final OperationMethod method) { + super(properties, method); + } + + /** + * Constructs a new conversion with the same values as the specified one, + * together with the specified source and target <abbr>CRS</abbr>. + * While the source conversion can be an arbitrary one, it is typically a defining conversion. + * + * <p>The {@code normalized} argument is {@code true} if the defining conversion provides a normalized transform. + * In such case, an adjustment for axis directions and units of measurement will be added for matching the given + * source and target <abbr>CRS</abbr>s. If {@code normalized} is {@code false}, then the defining conversion shall + * provide the complete transform and no adjustments is added. This argument is ignored if the defining conversion + * already provides source and target <abbr>CRS</abbr>s.</p> * * @param definition the defining conversion. - * @param source the new source CRS. - * @param target the new target CRS. + * @param normalized whether the transform provided by the defining conversion is normalized. + * @param source the new source <abbr>CRS</abbr>. + * @param target the new target <abbr>CRS</abbr>. * @param factory the factory to use for creating a transform from the parameters or for performing axis changes. */ - @SuppressWarnings("deprecation") DefaultConversion(final Conversion definition, + final boolean normalized, final CoordinateReferenceSystem source, final CoordinateReferenceSystem target, final MathTransformFactory factory) throws FactoryException diff --cc endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/CRSTest.java index a81d3e9ab3,ac6cda0794..56c8eda0be --- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/CRSTest.java +++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/CRSTest.java @@@ -47,7 -53,11 +53,8 @@@ import org.apache.sis.referencing.crs.H import org.apache.sis.referencing.operation.HardCodedConversions; import static org.apache.sis.test.Assertions.assertEqualsIgnoreMetadata; import static org.apache.sis.test.Assertions.assertMessageContains; + import static org.apache.sis.test.Assertions.assertMultilinesEquals; -// Specific to the geoapi-3.1 and geoapi-4.0 branches: -import org.opengis.referencing.ObjectDomain; - /** * Tests the {@link CRS} class. diff --cc optional/src/org.apache.sis.gui/main/org/apache/sis/gui/referencing/CRSChooser.java index 66562ab1c5,415fd9f432..fc2218c89a --- a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/referencing/CRSChooser.java +++ b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/referencing/CRSChooser.java @@@ -71,9 -71,12 +71,9 @@@ import org.apache.sis.util.resources.Vo // Specific to the main and geoapi-3.1 branches: import org.opengis.referencing.crs.GeneralDerivedCRS; -// Specific to the geoapi-3.1 and geoapi-4.0 branches: -import org.opengis.referencing.ObjectDomain; - /** - * A list of Coordinate Reference Systems (CRS) from which the user can select. + * A list of Coordinate Reference Systems (<abbr>CRS</abbr>) from which the user can select. * The CRS choices is built in a background thread from a specified {@link CRSAuthorityFactory}. * * @author Johann Sorel (Geomatys) diff --cc optional/src/org.apache.sis.gui/main/org/apache/sis/gui/referencing/FilterByDatum.java index 0000000000,dfec44608c..66377022fe mode 000000,100644..100644 --- a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/referencing/FilterByDatum.java +++ b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/referencing/FilterByDatum.java @@@ -1,0 -1,182 +1,185 @@@ + /* + * 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.gui.referencing; + + import java.util.Set; + import java.util.List; + import java.util.HashSet; + import java.util.ArrayList; + import java.util.function.Predicate; + import org.opengis.referencing.ReferenceSystem; + import org.opengis.referencing.datum.Datum; + import org.opengis.referencing.datum.GeodeticDatum; + import org.opengis.referencing.datum.TemporalDatum; + import org.opengis.referencing.datum.VerticalDatum; + import org.opengis.referencing.datum.EngineeringDatum; + import org.opengis.referencing.crs.CoordinateReferenceSystem; + import org.opengis.referencing.crs.SingleCRS; + import org.opengis.referencing.crs.TemporalCRS; + import org.opengis.referencing.crs.VerticalCRS; + import org.opengis.referencing.crs.EngineeringCRS; + import org.apache.sis.util.Classes; + import org.apache.sis.referencing.CRS; + import org.apache.sis.referencing.internal.shared.ReferencingUtilities; + import org.apache.sis.util.Utilities; + ++// Specific to the main branch: ++import static org.apache.sis.pending.geoapi.referencing.MissingMethods.getDatumEnsemble; ++ + + /** + * Filter of reference systems that are compatible with the data to render. + * Instances of this class are immutable and thread-safe. + * + * @author Martin Desruisseaux (Geomatys) + */ + final class FilterByDatum implements Predicate<ReferenceSystem> { + /** + * The types of datum allowed by the coordinate reference systems shown in the menus of combo boxes. + * If a <abbr>CRS</abbr> has a datum of a type which is not in this array, then it will not be shown. + * A {@code null} array means that no filtering is applied. + * + * <p>The current version of this class filters only by datum type because this is quick. + * But a more advanced version should check if a path exists between <abbr>CRS</abbr>s.</p> + */ + private final Class<? extends Datum>[] allowedDatumTypes; + + /** + * Datum instances for which an exact match is required. + * We handle engineering datum in a special way because they are the kind of datum created + * when we cannot map the <abbr>CRS</abbr> of a grid coverage to a geodetic <abbr>CRS</abbr>. + * We no such pivot, we cannot transform from one engineering <abbr>CRS</abbr> to another. + */ + private final EngineeringDatum[] instances; + + /** + * Creates a new filter for coordinate reference systems to show in the menu or combo box. + * If the <abbr>CRS</abbr> of a menu item has a datum of a type which is different than the + * datum types of all elements in the {@code refsys} array, then that menu item is hidden. + * + * @param refsys <abbr>CRS</abbr>s from which to get the datum types. Null elements are ignored. + * @return filter, or {@code null} if none. + */ + static FilterByDatum create(final CoordinateReferenceSystem[] refsys) { + final var types = new HashSet<Class<? extends Datum>>(); + final var instances = new ArrayList<EngineeringDatum>(); + for (final CoordinateReferenceSystem crs : refsys) { + for (final SingleCRS component : CRS.getSingleComponents(crs)) { + add(component.getDatum(), types, instances); - final var ensemble = component.getDatumEnsemble(); ++ final var ensemble = getDatumEnsemble(component); + if (ensemble != null) { + for (final Datum datum : ensemble.getMembers()) { + add(datum, types, instances); + } + } + } + } + types.remove(null); + if (instances.isEmpty() && types.equals(Set.of(Datum.class))) { + return null; + } + return new FilterByDatum(types.toArray(Class[]::new), instances.toArray(EngineeringDatum[]::new)); + } + + /** + * Adds the given datum in the given collections. + * + * @param datum the datum to add. + * @param types where to add the datum type. + * @param instances where to add the datum instance if not a duplicate. + */ + private static void add(final Datum datum, final Set<Class<? extends Datum>> types, final List<EngineeringDatum> instances) { + types.add(ReferencingUtilities.getInterface(Datum.class, datum)); + if (datum instanceof EngineeringDatum) { + for (int i = instances.size(); --i >= 0;) { + if (Utilities.equalsIgnoreMetadata(datum, instances.get(i))) { + return; + } + } + instances.add((EngineeringDatum) datum); + } + } + + /** + * Creates a new filter for the given datum types. + */ + @SuppressWarnings({"rawtypes", "unchecked"}) + private FilterByDatum(final Class[] types, final EngineeringDatum[] instances) { + allowedDatumTypes = types; + this.instances = instances; + } + + /** + * Returns whether the given reference system has valid datum types. + * + * @param system the reference system to test. + * @return whether the given reference system can be accepted. + */ + @Override + public boolean test(final ReferenceSystem system) { + if (system instanceof CoordinateReferenceSystem) { + next: for (final SingleCRS component : CRS.getSingleComponents((CoordinateReferenceSystem) system)) { + if (!accept(component.getDatum())) { - final var ensemble = component.getDatumEnsemble(); ++ final var ensemble = getDatumEnsemble(component); + if (ensemble != null) { + for (final Datum datum : ensemble.getMembers()) { + if (accept(datum)) { + continue next; + } + } + } + return false; + } + } + return true; + } else { + // Assume that referencing by identifiers depend on geodetic datum. + return Classes.isAssignableToAny(GeodeticDatum.class, allowedDatumTypes); + } + } + + /** + * Tests whether the given datum is accepted. + */ + private boolean accept(final Datum datum) { + if (datum != null && Classes.isAssignableToAny(datum.getClass(), allowedDatumTypes)) { + if (!(datum instanceof EngineeringDatum)) { + return true; + } + for (final EngineeringDatum instance : instances) { + if (Utilities.equalsIgnoreMetadata(datum, instance)) { + return true; + } + } + } + return false; + } + + /** + * Returns the base type of coordinate reference system for the datum accepted by this filter. + */ + final Class<? extends CoordinateReferenceSystem> baseType() { + if (allowedDatumTypes.length == 1) { + final Class<? extends Datum> type = allowedDatumTypes[0]; + if (VerticalDatum.class.isAssignableFrom(type)) return VerticalCRS.class; + if (TemporalDatum.class.isAssignableFrom(type)) return TemporalCRS.class; + if (EngineeringDatum.class.isAssignableFrom(type)) return EngineeringCRS.class; + return SingleCRS.class; + } + return CoordinateReferenceSystem.class; + } + }
