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

Reply via email to