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;
      }
  
      /**

Reply via email to