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 b8ae418ba3a4cd2e7dc6c51037b31929cbccb1df Merge: 30c90dd3c6 e3537fc7c1 Author: Martin Desruisseaux <[email protected]> AuthorDate: Fri Jul 17 17:10:55 2026 +0200 Merge branch 'geoapi-3.1'. endorsed/build.gradle.kts | 1 + .../apache/sis/coverage/grid/GridGeometryTest.java | 2 +- .../main/org/apache/sis/referencing/CRS.java | 19 +- .../operation/transform/CopyTransform.java | 4 +- .../operation/transform/MathTransforms.java | 33 +- .../sis/storage/geoheif}/SelfConsistencyTest.java | 39 +- .../sis/storage/geotiff/GeoTiffStoreProvider.java | 2 +- .../org/apache/sis/storage/geotiff/IOBase.java | 10 + .../sis/storage/geotiff/ImageFileDirectory.java | 4 +- .../apache/sis/storage/geotiff/base/Resources.java | 2 +- .../sis/storage/geotiff/base/Resources.properties | 2 +- .../storage/geotiff/base/Resources_fr.properties | 2 +- .../sis/storage/geotiff/reader/CRSBuilder.java | 109 +++- .../geotiff/reader/GridGeometryBuilder.java | 7 +- .../sis/storage/geotiff/GeoTiffStoreTest.java | 18 +- .../sis/storage/geotiff/SelfConsistencyTest.java | 2 + .../apache/sis/storage/netcdf/base/CRSBuilder.java | 8 +- .../apache/sis/storage/netcdf/base/Convention.java | 16 +- .../org/apache/sis/storage/netcdf/base/Grid.java | 4 +- .../sis/storage/netcdf/base/GridMapping.java | 608 ++++++++++++--------- .../sis/storage/netcdf/classic/ChannelDecoder.java | 2 +- .../sis/storage/netcdf/internal/Resources.java | 5 + .../storage/netcdf/internal/Resources.properties | 1 + .../netcdf/internal/Resources_fr.properties | 1 + .../sis/storage/netcdf/MetadataReaderTest.java | 6 +- .../storage/netcdf/NetcdfStoreProviderTest.java | 13 +- .../apache/sis/storage/netcdf/NetcdfStoreTest.java | 82 ++- .../apache/sis/storage/netcdf/base/GridTest.java | 12 +- .../apache/sis/storage/netcdf/base/TestCase.java | 1 + .../transverse-mercator-wrong-geotransform.nc | Bin 0 -> 2676 bytes .../org/apache/sis/storage/DataStoreTestCase.java | 67 +++ .../org/apache/sis/storage/FeatureNamingTest.java | 4 +- .../sis/storage/event/StoreListenersTest.java | 1 + .../org/apache/sis/storage/folder/StoreTest.java | 8 +- .../apache/sis/storage/folder/test-data/README.txt | 12 +- .../apache/sis/storage/test/SubsampledImage.java | 21 +- .../test/org/apache/sis/test/LoggingWatcher.java | 43 +- .../test/org/apache/sis/test/OptionalTestData.java | 8 +- netbeans-project/nbproject/project.properties | 2 + 39 files changed, 775 insertions(+), 406 deletions(-) diff --cc endorsed/src/org.apache.sis.feature/test/org/apache/sis/coverage/grid/GridGeometryTest.java index 7f80048fe3,d2faf4e56a..365fed43d4 --- 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 @@@ -79,14 -81,6 +79,14 @@@ public final class GridGeometryTest ext public GridGeometryTest() { } + /** + * Verifies grid extent coordinates. + */ - static void assertExtentEquals(final long[] low, final long[] high, final GridExtent extent) { ++ public static void assertExtentEquals(final long[] low, final long[] high, final GridExtent extent) { + assertArrayEquals(low, extent.getLow() .getCoordinateValues(), "extent.low"); + assertArrayEquals(high, extent.getHigh().getCoordinateValues(), "extent.high"); + } + /** * Verifies the shift between the two {@code gridToCRS} transforms. * This method should be invoked when the transforms are linear. diff --cc endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/base/GridMapping.java index 93de6bf0a8,958a9270a4..8d4dea68d7 --- 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 @@@ -88,6 -89,12 +89,12 @@@ import org.apache.sis.util.Exceptions import org.apache.sis.math.NumberType; import org.apache.sis.measure.Units; + // 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.datum.DatumEnsemble; ++// Specific to the main branch: ++import org.apache.sis.referencing.datum.DefaultDatumEnsemble; + /** * Helper object for creating a {@link GridGeometry} instance defined by attributes on a variable. @@@ -463,34 -464,106 +464,103 @@@ final class GridMapping } /* * Geodetic reference frame built from "towgs84" and above properties. + * The class of the "towgs84" entry will be verified by the datum constuctor. */ - final Object bursaWolf = definition.remove(Convention.TOWGS84); final GeodeticDatum datum; - if (isSpecified || bursaWolf != null) { - Supplier<Object> nameFallback = () -> CRS.getGeodeticReferenceFrame(fromWKT).map(GeodeticDatum::getName).orElse(null); - Map<String,Object> properties = properties(definition, Convention.GEODETIC_DATUM_NAME, nameFallback, false); - if (bursaWolf instanceof BursaWolfParameters) { - DatumEnsemble<GeodeticDatum> ensemble = null; ++ DefaultDatumEnsemble<GeodeticDatum> ensemble = null; + final Object bursaWolf = definition.remove(Convention.TOWGS84); + if (hasBuiltSomeCustomObjects || bursaWolf != null) { + Map<String, Object> properties = properties(definition, false, Convention.GEODETIC_DATUM_NAME, () -> { + // Fallback if `definition` does not contain a name for the geodetic reference frame. + return CRS.getGeodeticReferenceFrame(fromWKT).map(GeodeticDatum::getName).orElse(null); + }); + if (bursaWolf != null) { properties = new HashMap<>(properties); properties.put(DefaultGeodeticDatum.BURSA_WOLF_KEY, bursaWolf); - isSpecified = true; + hasBuiltSomeCustomObjects = true; } datum = datumFactory.createGeodeticDatum(properties, ellipsoid, meridian); } else { - datum = defaultDefinitions.datum(false); - if (datum == null) { - ensemble = defaultDefinitions.datumEnsemble(); - } + datum = DatumOrEnsemble.asDatum(defaultDefinitions.geographic()); } /* - * Geographic CRS from all above properties. + * Geographic or projected CRS built from above properties. + * The geographic CRS will always have (latitude, longitude) axes in that order and in degrees. + * The swapping to (longitude, latitude) axis order will be done by the `baseToCRS` transform. + */ + final boolean wantGeographicCRS = (method instanceof PseudoPlateCarree); + GeographicCRS baseCRS = defaultDefinitions.geographic(); + final CRSFactory crsFactory = decoder.getCRSFactory(); + if (hasBuiltSomeCustomObjects) { + final Map<String,?> properties = properties(definition, wantGeographicCRS, Convention.GEOGRAPHIC_CRS_NAME, () -> { + // Fallback if `definition` does not contain a name for the geodetic CRS. + IdentifiedObject base = CRS.getHorizontalComponent(fromWKT); + if (base == null) { + base = datum; + } else if (base instanceof GeneralDerivedCRS) { + base = ((GeneralDerivedCRS) fromWKT).getBaseCRS(); + } + return base.getName(); + }); - baseCRS = crsFactory.createGeographicCRS(properties, datum, ensemble, baseCRS.getCoordinateSystem()); ++ baseCRS = crsFactory.createGeographicCRS(properties, (datum != null) ? datum : (GeodeticDatum) ensemble, baseCRS.getCoordinateSystem()); + } + // Only swap axis order from (latitude, longitude) to (longitude, latitude). + MathTransform baseToCRS = MathTransforms.swapTwoFirstAxes(2); + if (wantGeographicCRS) { + crs = baseCRS; + } else { + /* + * For any "projection" other than Pseudo Plate Carrée, we will create a projected CRS, + * which requires a `Conversion` object built from the values in the `parameters` group. + * Reminder: this parameter group has been created from a subset of `definition` at the + * beginning of this method. + */ + Map<String,?> properties = properties(definition, false, Convention.CONVERSION_NAME, () -> { + if (fromWKT instanceof ProjectedCRS) { + return ((ProjectedCRS) fromWKT).getConversionFromBase().getName(); + } + return mapping.getName(); // Variable on which projection parameters are defined as attributes. + }); + final Conversion conversion = decoder.getCoordinateOperationFactory() + .createDefiningConversion(properties, method, parameters); + /* + * Projected CRS. The "base to CRS" transform is the conversion from base directly. + */ + properties = properties(definition, true, Convention.PROJECTED_CRS_NAME, () -> { + return (fromWKT != null) ? fromWKT.getName() : conversion.getName(); + }); + final ProjectedCRS projected = crsFactory.createProjectedCRS(properties, baseCRS, conversion, decoder.getStandardProjectedCS()); + baseToCRS = MathTransforms.concatenate(baseToCRS, projected.getConversionFromBase().getMathTransform()); + crs = projected; + } + /* + * The CF-Convention said that even if a WKT definition is provided, other attributes shall be present + * and have precedence over the WKT definition. Consequently, the purpose of WKT in netCDF files is not + * obvious (except for CompoundCRS). + */ + if (fromWKT != null) verifyCRS(fromWKT); + setOrVerifyWKT(alreadyParsedWKT, definition, CRS_WKT); + setOrVerifyWKT(alreadyParsedWKT, definition, SPATIAL_REF); + /* + * Report all projection parameters that have not been used. If the map is not rendered + * at expected location, it may be because we have ignored some important parameters. */ - if (isSpecified) { - Supplier<Object> nameFallback = () -> (fromWKT != null ? fromWKT : datum).getName(); - Map<String,?> properties = properties(definition, Convention.GEOGRAPHIC_CRS_NAME, nameFallback, isMainCRS); - return decoder.getCRSFactory().createGeographicCRS( - properties, - datum, - defaultDefinitions.geographic().getCoordinateSystem()); + definition.remove(CF.LONG_NAME); + if (!definition.isEmpty()) { + warningInMapping(mapping, null, Resources.Keys.UnknownProjectionParameters_3, + String.join(", ", definition.keySet())); + } + /* + * Build the "grid to CRS" if present. This is not defined by CF-convention, + * but may be present in some non-CF conventions. + */ + if (gridToCRS == null) { + gridToCRS = decoder.convention().gridToCRS(mapping, baseToCRS); + // Map pixel corners by `convention().gridToCRS(…)` contract. } else { - return defaultDefinitions.geographic(); + gridToCRS = MathTransforms.concatenate(gridToCRS, baseToCRS); } + return true; } /** diff --cc endorsed/src/org.apache.sis.storage.netcdf/test/org/apache/sis/storage/netcdf/NetcdfStoreTest.java index b1d35ca424,823b849ebc..48e71b5cb5 --- a/endorsed/src/org.apache.sis.storage.netcdf/test/org/apache/sis/storage/netcdf/NetcdfStoreTest.java +++ b/endorsed/src/org.apache.sis.storage.netcdf/test/org/apache/sis/storage/netcdf/NetcdfStoreTest.java @@@ -25,10 -34,11 +34,11 @@@ import org.apache.sis.util.Version // Test dependencies import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.*; - import org.apache.sis.test.TestCaseWithLogs; -import static org.apache.sis.feature.Assertions.assertExtentEquals; + import org.apache.sis.storage.DataStoreTestCase; -// Specific to the geoapi-3.1 and geoapi-4.0 branches: -import org.opengis.test.dataset.TestData; +// Specific to the main branch: +import org.apache.sis.storage.netcdf.base.TestData; ++import static org.apache.sis.coverage.grid.GridGeometryTest.assertExtentEquals; /**
