This is an automated email from the ASF dual-hosted git repository. asf-gitbox-commits pushed a commit to branch geoapi-3.1 in repository https://gitbox.apache.org/repos/asf/sis.git
commit e3537fc7c1862eba0b639476eb0e40aba58c6c91 Merge: 9c02e400ee e96a107208 Author: Martin Desruisseaux <[email protected]> AuthorDate: Fri Jul 17 16:51:55 2026 +0200 Merge branch 'geoapi-4.0' into geoapi-3.1. Contains a fix in netCDF parsing of GDAL's "GeoTransform" attribute. endorsed/build.gradle.kts | 1 + .../sis/coverage/grid/GridDerivationTest.java | 2 +- .../apache/sis/coverage/grid/GridGeometryTest.java | 9 +- .../test/org/apache/sis/feature/Assertions.java | 13 + .../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 | 603 ++++++++++++--------- .../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 + 41 files changed, 784 insertions(+), 414 deletions(-) diff --cc endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/CRS.java index 48f2dee3bc,320141476b..b1cbb21a4a --- 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 @@@ -1127,10 -1127,14 +1130,14 @@@ public final class CRS * * @since 1.6 */ - public static Optional<GeodeticDatum> getGeodeticReferenceFrame(final CoordinateReferenceSystem crs) { + public static Optional<GeodeticDatum> getGeodeticReferenceFrame(CoordinateReferenceSystem crs) { - if (crs instanceof DerivedCRS) { - crs = ((DerivedCRS) crs).getBaseCRS(); ++ if (crs instanceof GeneralDerivedCRS) { ++ crs = ((GeneralDerivedCRS) crs).getBaseCRS(); + } if (crs instanceof GeodeticCRS) { return Optional.ofNullable(DatumOrEnsemble.asDatum((GeodeticCRS) crs)); - } else if (crs instanceof CompoundCRS) { + } + if (crs instanceof CompoundCRS) { for (CoordinateReferenceSystem component : ((CompoundCRS) crs).getComponents()) { final Optional<GeodeticDatum> datum = getGeodeticReferenceFrame(component); if (datum.isPresent()) { diff --cc endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/base/GridMapping.java index 253642a975,22a3b3a02e..958a9270a4 --- 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,7 -89,8 +89,10 @@@ 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.crs.DerivedCRS; import org.opengis.referencing.datum.DatumEnsemble; @@@ -486,19 -485,83 +487,83 @@@ final class GridMapping } } /* - * 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. */ - 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, - ensemble, - defaultDefinitions.geographic().getCoordinateSystem()); + 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 DerivedCRS) { - base = ((DerivedCRS) fromWKT).getBaseCRS(); ++ } else if (base instanceof GeneralDerivedCRS) { ++ base = ((GeneralDerivedCRS) fromWKT).getBaseCRS(); + } + return base.getName(); + }); + baseCRS = crsFactory.createGeographicCRS(properties, datum, ensemble, baseCRS.getCoordinateSystem()); + } + // Only swap axis order from (latitude, longitude) to (longitude, latitude). + MathTransform baseToCRS = MathTransforms.swapTwoFirstAxes(2); + if (wantGeographicCRS) { + crs = baseCRS; } else { - return defaultDefinitions.geographic(); + /* + * 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. + */ + 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 { + gridToCRS = MathTransforms.concatenate(gridToCRS, baseToCRS); + } + return true; } /**
