This is an automated email from the ASF dual-hosted git repository.

desruisseaux pushed a commit to branch geoapi-4.0
in repository https://gitbox.apache.org/repos/asf/sis.git

commit 7c4a4b5114ef1cbc0eea2e07798ef60d8f16df9d
Author: Martin Desruisseaux <martin.desruisse...@geomatys.com>
AuthorDate: Mon Sep 16 15:21:41 2024 +0200

    Parse GDAL CRS from WKT 2 instead of WKT 1 and takes in account GDAL change 
of axis order.
---
 .../main/module-info.java                          |   2 +-
 .../main/org/apache/sis/storage/gdal/Band.java     |  16 +-
 .../main/org/apache/sis/storage/gdal/GDAL.java     |  69 ++++++--
 .../org/apache/sis/storage/gdal/GDALStore.java     |  31 +---
 .../org/apache/sis/storage/gdal/SpatialRef.java    | 173 +++++++++++++++++++++
 .../org/apache/sis/storage/gdal/TiledResource.java |  49 ++++--
 .../org/apache/sis/storage/gdal/package-info.java  |   2 +-
 7 files changed, 279 insertions(+), 63 deletions(-)

diff --git a/incubator/src/org.apache.sis.storage.gdal/main/module-info.java 
b/incubator/src/org.apache.sis.storage.gdal/main/module-info.java
index df04713494..e4d87b9584 100644
--- a/incubator/src/org.apache.sis.storage.gdal/main/module-info.java
+++ b/incubator/src/org.apache.sis.storage.gdal/main/module-info.java
@@ -17,7 +17,7 @@
 
 /**
  * Bridge to the <abbr>GDAL</abbr> library for reading rasters.
- * This module assumes that <abbr>GDAL</abbr> is preinstalled.
+ * This module assumes that <abbr>GDAL</abbr> 3.0 or later is preinstalled.
  * The <abbr>GDAL</abbr> C/C++ functions are invoked by using the {@link 
java.lang.foreign} package.
  * <em>Accesses to native functions are restricted by default in Java and 
requires user's authorization.</em>
  * This authorization can be granted by the following option on the 
command-line:
diff --git 
a/incubator/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/gdal/Band.java
 
b/incubator/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/gdal/Band.java
index e6e252243f..c8f06f505c 100644
--- 
a/incubator/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/gdal/Band.java
+++ 
b/incubator/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/gdal/Band.java
@@ -126,7 +126,7 @@ final class Band {
          * even if the transfer function (scale and offset) was not specified. 
The GDAL
          * default when there is no scale/offset corresponds to the identity 
transform.
          */
-        final List<String> categories = GDAL.toStringArray(names);
+        final List<String> categories = GDAL.fromNullTerminatedStrings(names);
         final String symbol = GDAL.toString(uom);
         Unit<?> units = null;
         if (symbol != null && !symbol.isBlank()) try {
@@ -210,17 +210,21 @@ final class Band {
      */
     final int[] getARGB(final GDAL gdal) {
         try (Arena arena = Arena.ofConfined()) {
-            final MemorySegment colorEntry = 
arena.allocate(ValueLayout.JAVA_SHORT, 4);
+            final var layout = ValueLayout.JAVA_SHORT;
+            final MemorySegment colorEntry = arena.allocate(layout, 4);
             final var colors  = (MemorySegment) 
gdal.getRasterColorTable.invokeExact(handle);
             if (!GDAL.isNull(colors)) {
                 final int count = (int) 
gdal.getColorEntryCount.invokeExact(colors);
                 final int[] ARGB = new int[count];
                 for (int i=0; i<count; i++) {
                     final int err = (int) 
gdal.getColorEntryAsRGB.invokeExact(colors, i, colorEntry);
-                    final short c1 = colorEntry.get(ValueLayout.JAVA_SHORT, 
Short.BYTES * 0);   // gray, red, cyan or hue
-                    final short c2 = colorEntry.get(ValueLayout.JAVA_SHORT, 
Short.BYTES * 1);   // green, magenta, or lightness
-                    final short c3 = colorEntry.get(ValueLayout.JAVA_SHORT, 
Short.BYTES * 2);   // blue, yellow, or saturation
-                    final short c4 = colorEntry.get(ValueLayout.JAVA_SHORT, 
Short.BYTES * 3);   // alpha or blackband
+                    if (!ErrorHandler.checkCPLErr(err)) {
+                        return null;
+                    }
+                    final short c1 = colorEntry.getAtIndex(layout, 0);   // 
gray, red, cyan or hue
+                    final short c2 = colorEntry.getAtIndex(layout, 1);   // 
green, magenta, or lightness
+                    final short c3 = colorEntry.getAtIndex(layout, 2);   // 
blue, yellow, or saturation
+                    final short c4 = colorEntry.getAtIndex(layout, 3);   // 
alpha or blackband
                     ARGB[i] = (Short.toUnsignedInt(c4) << 24)
                             | (Short.toUnsignedInt(c1) << 16)
                             | (Short.toUnsignedInt(c2) <<  8)
diff --git 
a/incubator/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/gdal/GDAL.java
 
b/incubator/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/gdal/GDAL.java
index 5ad892c414..aff1f1b0b3 100644
--- 
a/incubator/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/gdal/GDAL.java
+++ 
b/incubator/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/gdal/GDAL.java
@@ -21,6 +21,7 @@ import java.util.List;
 import java.util.ArrayList;
 import java.util.Optional;
 import java.util.NoSuchElementException;
+import java.lang.foreign.Arena;
 import java.util.logging.Level;
 import java.util.logging.LogRecord;
 import java.lang.foreign.Linker;
@@ -89,6 +90,7 @@ final class GDAL extends NativeFunctions {
     /**
      * <abbr>GDAL</abbr> {@code GDALDatasetH GDALOpenEx(const char 
*pszFilename, …)}.
      * Opens a raster or vector file by invoking the open method of each 
driver in turn.
+     * Requires <abbr>GDAL</abbr> 2.0.
      */
     final MethodHandle open;
 
@@ -100,6 +102,12 @@ final class GDAL extends NativeFunctions {
      */
     final MethodHandle close;
 
+    /**
+     * <abbr>GDAL</abbr> {@code void VSIFree(void*)}, alias {@code CPLFree}.
+     * Releases memory allocated by <abbr>GDAL</abbr>.
+     */
+    final MethodHandle free;
+
     /**
      * <abbr>GDAL</abbr> {@code GDALDriverH 
GDALGetDatasetDriver(GDALDatasetH)}.
      * Fetches the driver to which a dataset relates.
@@ -109,11 +117,24 @@ final class GDAL extends NativeFunctions {
     /**
      * <abbr>GDAL</abbr> {@code OGRSpatialReferenceH 
GDALGetSpatialRef(GDALDatasetH)}.
      * Fetches the spatial reference for this dataset in <abbr>WKT</abbr> 
format.
-     *
-     * @todo Replace {@code "GDALGetProjectionRef"} by {@code 
"GDALGetSpatialRef"},
-     *       which returns {@code OGRSpatialReference}.
+     * Requires <abbr>GDAL</abbr> 3.0.
      */
-    final MethodHandle getSpatialRef;
+    final MethodHandle getSpatialRef, getGCPSpatialRef;
+
+    /**
+     * <abbr>GDAL</abbr> {@code OGRErr OSRExportToWktEx(OGRSpatialReferenceH, 
char** ppszResult, char** papszOption}.
+     * Convert a <abbr>SRS</abbr> into <abbr>WKT</abbr> format.
+     * The returned WKT string shall be freed with {@link #free}.
+     * Requires <abbr>GDAL</abbr> 3.0.
+     */
+    final MethodHandle exportToWkt;
+
+    /**
+     * <abbr>GDAL</abbr> {@code nt 
*OSRGetDataAxisToSRSAxisMapping(OGRSpatialReferenceH hSRS, int *pnCount)}.
+     * Return the data axis to SRS axis mapping.
+     * Requires <abbr>GDAL</abbr> 3.0.
+     */
+    final MethodHandle getDataAxisToCRSAxis;
 
     /**
      * <abbr>GDAL</abbr> {@code CPLErr GDALGetGeoTransform(GDALDatasetH, 
double*)}.
@@ -204,7 +225,7 @@ final class GDAL extends NativeFunctions {
     final MethodHandle getColorEntryCount;
 
     /**
-     * <abbr>GDAL</abbr> {@code GDALGetColorEntryAsRGB(GDALColorTableH, int, 
GDALColorEntry*)}.
+     * <abbr>GDAL</abbr> {@code int GDALGetColorEntryAsRGB(GDALColorTableH, 
int, GDALColorEntry*)}.
      * Fetches a table entry in RGB format.
      */
     final MethodHandle getColorEntryAsRGB;
@@ -240,16 +261,13 @@ final class GDAL extends NativeFunctions {
         final var acceptPointerReturnPointer = 
FunctionDescriptor.of(ValueLayout.ADDRESS,     ValueLayout.ADDRESS);
         final var acceptPointerReturnInt     = 
FunctionDescriptor.of(ValueLayout.JAVA_INT,    ValueLayout.ADDRESS);
         final var acceptTwoPtrsReturnDouble  = 
FunctionDescriptor.of(ValueLayout.JAVA_DOUBLE, ValueLayout.ADDRESS, 
ValueLayout.ADDRESS);
+        final var acceptTwoPtrsReturnPointer = 
FunctionDescriptor.of(ValueLayout.ADDRESS,     ValueLayout.ADDRESS, 
ValueLayout.ADDRESS);
         final Linker linker = Linker.nativeLinker();
 
         // For Driver and/or all major objects
         getName         = lookup(linker, "GDALGetDriverLongName",  
acceptPointerReturnPointer);
         getIdentifier   = lookup(linker, "GDALGetDriverShortName", 
acceptPointerReturnPointer);
-        getMetadata     = lookup(linker, "GDALGetMetadata", 
FunctionDescriptor.of(
-                ValueLayout.ADDRESS,    // const char* (return type)
-                ValueLayout.ADDRESS,    // GDALMajorObject
-                ValueLayout.ADDRESS));  // const char* domain
-
+        getMetadata     = lookup(linker, "GDALGetMetadata",        
acceptTwoPtrsReturnPointer);
         getMetadataItem = lookup(linker, "GDALGetMetadataItem", 
FunctionDescriptor.of(
                 ValueLayout.ADDRESS,    // const char* (return type)
                 ValueLayout.ADDRESS,    // GDALMajorObject
@@ -257,6 +275,7 @@ final class GDAL extends NativeFunctions {
                 ValueLayout.ADDRESS));  // const char* domain
 
         // For Opener
+        free  = lookup(linker, "VSIFree",    
FunctionDescriptor.ofVoid(ValueLayout.ADDRESS));
         close = lookup(linker, "GDALClose",  acceptPointerReturnInt);
         open  = lookup(linker, "GDALOpenEx", 
FunctionDescriptor.of(ValueLayout.ADDRESS,
                 ValueLayout.ADDRESS,        // const char *pszFilename
@@ -267,7 +286,15 @@ final class GDAL extends NativeFunctions {
 
         // For all data set
         getDatasetDriver = lookup(linker, "GDALGetDatasetDriver", 
acceptPointerReturnPointer);
-        getSpatialRef    = lookup(linker, "GDALGetProjectionRef", 
acceptPointerReturnPointer);
+        getSpatialRef    = lookup(linker, "GDALGetSpatialRef",    
acceptPointerReturnPointer);
+        getGCPSpatialRef = lookup(linker, "GDALGetGCPSpatialRef", 
acceptPointerReturnPointer);
+        exportToWkt      = lookup(linker, "OSRExportToWktEx",     
FunctionDescriptor.of(
+                ValueLayout.JAVA_INT,       // CPLErr error code (return value)
+                ValueLayout.ADDRESS,        // OGRSpatialReferenceH
+                ValueLayout.ADDRESS,        // char **ppszWKT
+                ValueLayout.ADDRESS));      // const char *const *papszOptions
+
+        getDataAxisToCRSAxis = lookup(linker, 
"OSRGetDataAxisToSRSAxisMapping", acceptTwoPtrsReturnPointer);
 
         // For TiledResource (GDAL Raster)
         getGeoTransform = lookup(linker, "GDALGetGeoTransform", 
FunctionDescriptor.of(
@@ -482,7 +509,7 @@ final class GDAL extends NativeFunctions {
      * @return the results as strings, or {@code null} if the result was null.
      */
     @SuppressWarnings("restricted")
-    static List<String> toStringArray(MemorySegment result) {
+    static List<String> fromNullTerminatedStrings(MemorySegment result) {
         if (isNull(result)) {
             return null;
         }
@@ -496,6 +523,24 @@ final class GDAL extends NativeFunctions {
         return items;
     }
 
+    /**
+     * Returns a Java array of strings as a {@code NULL}-terminated array.
+     * This way to encode arrays of strings is specific to <abbr>GDAL</abbr>.
+     *
+     * @param  arena  the arena to use for memory allocation.
+     * @param  items  the Java strings to copy.
+     * @return the {@code NULL}-terminated array of string.
+     */
+    static MemorySegment toNullTerminatedStrings(final Arena arena, final 
String... items) {
+        final var layout = ValueLayout.ADDRESS;
+        final MemorySegment array = arena.allocate(layout, items.length + 1);
+        for (int i=0; i<items.length; i++) {
+            array.setAtIndex(layout, i, arena.allocateFrom(items[i]));
+        }
+        array.setAtIndex(layout, items.length, MemorySegment.NULL);
+        return array;
+    }
+
     /**
      * Unloads the <abbr>GDAL</abbr> library. If the arena is global,
      * then this method should not be invoked before <abbr>JVM</abbr> shutdown.
diff --git 
a/incubator/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/gdal/GDALStore.java
 
b/incubator/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/gdal/GDALStore.java
index 8122818c66..c51c689141 100644
--- 
a/incubator/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/gdal/GDALStore.java
+++ 
b/incubator/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/gdal/GDALStore.java
@@ -23,7 +23,6 @@ import java.util.Objects;
 import java.util.Optional;
 import java.util.logging.Level;
 import java.util.logging.LogRecord;
-import java.text.ParseException;
 import java.net.URI;
 import java.io.IOException;
 import java.nio.file.DirectoryStream;
@@ -37,7 +36,6 @@ import org.opengis.util.GenericName;
 import org.opengis.util.NameFactory;
 import org.opengis.metadata.Metadata;
 import org.opengis.parameter.ParameterValueGroup;
-import org.opengis.referencing.crs.CoordinateReferenceSystem;
 import org.apache.sis.parameter.Parameters;
 import org.apache.sis.io.wkt.WKTFormat;
 import org.apache.sis.io.wkt.Convention;
@@ -342,7 +340,7 @@ public class GDALStore extends DataStore implements 
Aggregate, ResourceOnFileSys
         } catch (Throwable e) {
             throw GDAL.propagate(e);
         }
-        final List<String> all = GDAL.toStringArray(result);
+        final List<String> all = GDAL.fromNullTerminatedStrings(result);
         if (all != null) {
             final String driver = getDriverName(gdal);
             if (driver != null) {   // Should never be null.
@@ -407,7 +405,7 @@ public class GDALStore extends DataStore implements 
Aggregate, ResourceOnFileSys
      * Returns the object to use for parsing and formatting <abbr>CRS</abbr> 
definitions from/to <abbr>GDAL</abbr>.
      * This object must be used in a block synchronized on {@code this}.
      */
-    private WKTFormat wktFormat() {
+    final WKTFormat wktFormat() {
         if (wktFormat == null) {
             wktFormat = new WKTFormat(null, null);
             wktFormat.setConvention(Convention.WKT1_COMMON_UNITS);
@@ -415,31 +413,6 @@ public class GDALStore extends DataStore implements 
Aggregate, ResourceOnFileSys
         return wktFormat;
     }
 
-    /**
-     * Gets the <abbr>CRS</abbr> of the data set by parsing its 
<abbr>WKT</abbr> representation.
-     * This method must be invoked from a method synchronized on {@code this}.
-     *
-     * @param  gdal    set of handles for invoking <abbr>GDAL</abbr> functions.
-     * @param  caller  name of the {@code GDALStore} method invoking this 
method.
-     * @return the parsed <abbr>CRS</abbr>, or {@code null} if none.
-     * @throws DataStoreException if a fatal error occurred according 
<abbr>GDAL</abbr>.
-     */
-    final CoordinateReferenceSystem parseCRS(final GDAL gdal, final String 
caller) throws DataStoreException {
-        MemorySegment result = null;
-        try {
-            result = (MemorySegment) gdal.getSpatialRef.invokeExact(handle());
-        } catch (Throwable e) {
-            throw GDAL.propagate(e);
-        }
-        final String wkt = GDAL.toString(result);
-        if (wkt != null && !wkt.isBlank()) try {
-            return (CoordinateReferenceSystem) wktFormat().parseObject(wkt);
-        } catch (ParseException | ClassCastException e) {
-            warning(caller, "Cannot parse the CRS of " + getDisplayName(), e);
-        }
-        return null;
-    }
-
     /**
      * Returns the exception to throw for the given cause.
      *
diff --git 
a/incubator/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/gdal/SpatialRef.java
 
b/incubator/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/gdal/SpatialRef.java
new file mode 100644
index 0000000000..33242b225d
--- /dev/null
+++ 
b/incubator/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/gdal/SpatialRef.java
@@ -0,0 +1,173 @@
+/*
+ * 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.storage.gdal;
+
+import java.lang.foreign.Arena;
+import java.lang.foreign.ValueLayout;
+import java.lang.foreign.MemorySegment;
+import java.text.ParseException;
+import org.opengis.referencing.operation.Matrix;
+import org.opengis.referencing.crs.CoordinateReferenceSystem;
+import org.apache.sis.referencing.operation.matrix.Matrices;
+import org.apache.sis.storage.DataStoreException;
+
+
+/**
+ * Information about a Coordinate Reference System object in <abbr>GDAL</abbr>.
+ * Instances of this class should be short-lived and used inside a 
synchronized block,
+ * because it is a wrapper around {@code OGRSpatialReference} which has the 
following note:
+ *
+ * <blockquote>A pointer to an internal object. It should not be altered or 
freed.
+ * Its lifetime will be the one of the dataset object, or until the next call 
to this method.
+ * </blockquote>
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ */
+final class SpatialRef {
+    /**
+     * A constant for identifying the codes which assume two-dimensional data.
+     */
+    static final int BIDIMENSIONAL = 2;
+
+    /**
+     * The data set which provided the <abbr>CRS</abbr> definition.
+     * A desired side-effect of this field is to prevent premature garbage 
collection of
+     * that {@code GDALStore}, because it would cause the {@link #handle} to 
become invalid.
+     */
+    private final GDALStore owner;
+
+    /**
+     * Sets of handles for invoking <abbr>GDAL</abbr> functions. Usually not 
stored in objects,
+     * but we make an exception for this {@code SpatialRef} class because 
instances should be short-lived.
+     */
+    private final GDAL gdal;
+
+    /**
+     * Pointer to the <abbr>GDAL</abbr> object in native memory.
+     * This is a {@code OGRSpatialReferenceH} in the C/C++ <abbr>API</abbr>.
+     */
+    private final MemorySegment handle;
+
+    /**
+     * Creates a new instance.
+     */
+    private SpatialRef(final GDALStore owner, final GDAL gdal, final 
MemorySegment handle) {
+        this.owner  = owner;
+        this.gdal   = gdal;
+        this.handle = handle;
+    }
+
+    /**
+     * Creates a new instance.
+     *
+     * @param  owner    the dataset which is providing the <abbr>CRS</abbr> 
definition.
+     * @param  gdal     sets of handles for invoking <abbr>GDAL</abbr> 
functions.
+     * @param  dataset  value of {@link GDALStore#handle()}.
+     * @return wrapper for the <abbr>CRS</abbr> definition provided by 
<abbr>GDAL</abbr>, or {@code null} if none.
+     * @throws DataStoreException if an error occurred while fetching 
information from <abbr>GDAL</abbr>.
+     */
+    static SpatialRef create(final GDALStore owner, final GDAL gdal, final 
MemorySegment dataset) throws DataStoreException {
+        MemorySegment handle;
+        try {
+            handle = (MemorySegment) gdal.getSpatialRef.invokeExact(dataset);
+            if (GDAL.isNull(handle)) {
+                handle = (MemorySegment) 
gdal.getGCPSpatialRef.invokeExact(owner.handle());
+                if (GDAL.isNull(handle)) {
+                    return null;
+                }
+            }
+        } catch (Throwable e) {
+            throw GDAL.propagate(e);
+        }
+        return new SpatialRef(owner, gdal, handle);
+    }
+
+    /**
+     * Parses the <abbr>CRS</abbr> of the data set by parsing its 
<abbr>WKT</abbr> representation.
+     * This method must be invoked from a method synchronized on {@link 
GDALStore}.
+     *
+     * @param  caller  name of the {@code GDALStore} method invoking this 
method.
+     * @return the parsed <abbr>CRS</abbr>, or {@code null} if none.
+     * @throws DataStoreException if a fatal error occurred according 
<abbr>GDAL</abbr>.
+     */
+    final CoordinateReferenceSystem parseCRS(final String caller) throws 
DataStoreException {
+        final int err;
+        final String wkt;
+        try (Arena arena = Arena.ofConfined()) {
+            final var layout = ValueLayout.ADDRESS;
+            final MemorySegment ptr = arena.allocate(layout).fill((byte) 0);
+            final MemorySegment opt = GDAL.toNullTerminatedStrings(arena, 
"FORMAT=WKT2_2015");
+            err = (int) gdal.exportToWkt.invokeExact(handle, ptr, opt);
+            final MemorySegment result = ptr.get(layout, 0);
+            if (GDAL.isNull(result)) {
+                wkt = null;
+            } else try {
+                wkt = GDAL.toString(result);
+            } finally {
+                gdal.free.invokeExact(result);
+            }
+        } catch (Throwable e) {
+            throw GDAL.propagate(e);
+        }
+        if (err == 0) {     // OGRErr, not CPLErr.
+            if (wkt != null && !wkt.isBlank()) try {
+                return (CoordinateReferenceSystem) 
owner.wktFormat().parseObject(wkt);
+            } catch (ParseException | ClassCastException e) {
+                owner.warning(caller, "Cannot parse the CRS of " + 
owner.getDisplayName(), e);
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Returns the transform from data axis order to CRS axis order, or {@code 
null} if unspecified.
+     * This method also takes care of adding a dimension to the "grid to CRS" 
transform if needed.
+     *
+     * @param  dimension  maximal number of dimensions of the <abbr>CRS</abbr>.
+     * @return axis swapping as an affine transform matrix.
+     */
+    @SuppressWarnings("restricted")
+    final Matrix getDataToCRS(final int dimension) {
+        final int length;
+        MemorySegment vector;
+        final var layout = ValueLayout.JAVA_INT;
+        try (Arena arena = Arena.ofConfined()) {
+            final MemorySegment ptr = arena.allocate(layout);
+            vector = (MemorySegment) 
gdal.getDataAxisToCRSAxis.invokeExact(handle, ptr);
+            if (GDAL.isNull(vector) || (length = Math.min(ptr.get(layout, 0), 
dimension)) < 0) {
+                return null;
+            }
+        } catch (Throwable e) {
+            throw GDAL.propagate(e);
+        }
+        vector = vector.reinterpret(layout.byteSize() * length);
+        /*
+         * From GDAL documentation: The number of elements of the vector will 
be the number of axis of the CRS.
+         * Values start at 1. A negative value can also be used to ask for a 
sign reversal during coordinate
+         * transformation (to deal with northing vs southing, easting vs 
westing, heights vs depths).
+         */
+        final Matrix swap = Matrices.createZero(length+1, BIDIMENSIONAL + 1);
+        swap.setElement(length, BIDIMENSIONAL, 1);
+        for (int i=0; i<length; i++) {
+            final int p = vector.getAtIndex(layout, i);
+            if (p != 0) {
+                swap.setElement(i, Math.abs(p) - 1, Integer.signum(p));
+            }
+        }
+        return swap;
+    }
+}
diff --git 
a/incubator/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/gdal/TiledResource.java
 
b/incubator/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/gdal/TiledResource.java
index 22935058ed..a5ba1c4d6b 100644
--- 
a/incubator/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/gdal/TiledResource.java
+++ 
b/incubator/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/gdal/TiledResource.java
@@ -30,9 +30,12 @@ import java.lang.foreign.Arena;
 import java.lang.foreign.ValueLayout;
 import java.lang.foreign.MemorySegment;
 import org.opengis.util.GenericName;
+import org.opengis.referencing.operation.Matrix;
+import org.opengis.referencing.operation.MathTransform;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
 import org.apache.sis.referencing.privy.AffineTransform2D;
 import org.apache.sis.referencing.privy.ExtendedPrecisionMatrix;
+import org.apache.sis.referencing.operation.transform.MathTransforms;
 import org.apache.sis.coverage.SampleDimension;
 import org.apache.sis.coverage.grid.GridExtent;
 import org.apache.sis.coverage.grid.GridGeometry;
@@ -209,8 +212,9 @@ final class TiledResource extends TiledGridResource {
         final var bands = new LinkedHashMap<SizeAndType, ArrayList<Band>>();
         final int mainWidth, mainHeight;
         try (Arena arena = Arena.ofConfined()) {
-            final MemorySegment pnXSize = arena.allocate(ValueLayout.JAVA_INT, 
2);
-            final MemorySegment pnYSize = pnXSize.asSlice(Integer.BYTES);
+            final var layout = ValueLayout.JAVA_INT;
+            final MemorySegment pnXSize = arena.allocate(layout, 2);
+            final MemorySegment pnYSize = pnXSize.asSlice(layout.byteSize());
             final int count = (int) gdal.getRasterCount.invokeExact(dataset);
             for (int i=0; i<count; i++) {
                 final var band = (MemorySegment) 
gdal.getRasterBand.invokeExact(dataset, i+1);
@@ -223,8 +227,8 @@ final class TiledResource extends TiledGridResource {
                 final int height = (int) 
gdal.getRasterBandYSize.invokeExact(band);
                 final int type   = (int) gdal.getRasterDataType 
.invokeExact(band);
                 gdal.getBlockSize.invokeExact(band, pnXSize, pnYSize);
-                int tileWidth  = pnXSize.get(ValueLayout.JAVA_INT, 0);
-                int tileHeight = pnYSize.get(ValueLayout.JAVA_INT, 0);
+                int tileWidth  = pnXSize.get(layout, 0);
+                int tileHeight = pnYSize.get(layout, 0);
                 var key = new SizeAndType(width, height, type, tileWidth, 
tileHeight);
                 bands.computeIfAbsent(key, (_) -> new 
ArrayList<Band>()).add(new Band(band));
             }
@@ -286,14 +290,20 @@ final class TiledResource extends TiledGridResource {
             if (geometry == null) try {
                 final MemorySegment handle = parent.handle();          // 
Handle to the GDAL dataset.
                 final GDAL gdal = parent.getProvider().GDAL();
-                final CoordinateReferenceSystem crs = parent.parseCRS(gdal, 
"getGridGeometry");
+                final var  srs  = SpatialRef.create(parent, gdal, handle);
+                final CoordinateReferenceSystem crs;
+                if (srs != null) {
+                    crs = srs.parseCRS("getGridGeometry");
+                } else {
+                    crs = null;
+                }
                 /*
                  * Note that the CRS may be null. Now get the "grid to CRS" 
transform,
                  * which may also be null if GDAL reported an error. We do not 
use the
                  * GDAL default, which is the identity transform. Instead, we 
keep the
                  * information that the transform is missing (null).
                  */
-                AffineTransform2D gridToCRS = null;
+                MathTransform gridToCRS = null;
                 try (final Arena arena = Arena.ofConfined()) {
                     final var layout = ValueLayout.JAVA_DOUBLE;
                     final MemorySegment m = arena.allocate(layout, 6);
@@ -305,20 +315,31 @@ final class TiledResource extends TiledGridResource {
                     }
                     if (ErrorHandler.checkCPLErr(err)) {
                         gridToCRS = new AffineTransform2D(
-                                m.get(layout, Double.BYTES * 1),
-                                m.get(layout, Double.BYTES * 4),
-                                m.get(layout, Double.BYTES * 2),
-                                m.get(layout, Double.BYTES * 5),
-                                m.get(layout, Double.BYTES * 0),
-                                m.get(layout, Double.BYTES * 3));
+                                m.getAtIndex(layout, 1),
+                                m.getAtIndex(layout, 4),
+                                m.getAtIndex(layout, 2),
+                                m.getAtIndex(layout, 5),
+                                m.getAtIndex(layout, 0),
+                                m.getAtIndex(layout, 3));
+                    }
+                }
+                /*
+                 * The axis order used by GDAL is not the axis order in the 
CRS definition.
+                 * GDAL provides a separated method for specifying the axis 
swapping.
+                 */
+                if (gridToCRS != null) {
+                    int dimension = (crs != null) ? 
crs.getCoordinateSystem().getDimension() : SpatialRef.BIDIMENSIONAL;
+                    final Matrix swap = srs.getDataToCRS(dimension);
+                    if (swap != null) {
+                        gridToCRS = MathTransforms.concatenate(gridToCRS, 
MathTransforms.linear(swap));
                     }
                 }
-                var extent = new GridExtent(Integer.toUnsignedLong(width),
-                                            Integer.toUnsignedLong(height));
                 /*
                  * According GDAL documentation, the upper left corner of the 
upper left pixel
                  * is at position (m[0], m[3]). Therefore, we have a "cell 
corner" convention.
                  */
+                var extent = new GridExtent(Integer.toUnsignedLong(width),
+                                            Integer.toUnsignedLong(height));
                 try {
                     geometry = new GridGeometry(extent, 
PixelInCell.CELL_CORNER, gridToCRS, crs);
                 } catch (NullPointerException | IllegalArgumentException e) {
diff --git 
a/incubator/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/gdal/package-info.java
 
b/incubator/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/gdal/package-info.java
index d756cee2e3..8ad298dee7 100644
--- 
a/incubator/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/gdal/package-info.java
+++ 
b/incubator/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/gdal/package-info.java
@@ -17,7 +17,7 @@
 
 /**
  * Bridge to the <abbr>GDAL</abbr> library for reading rasters.
- * This package assumes that <abbr>GDAL</abbr> is preinstalled.
+ * This package assumes that <abbr>GDAL</abbr> 3.0 or later is preinstalled.
  * The <abbr>GDAL</abbr> C/C++ functions are invoked by using the {@link 
java.lang.foreign} package.
  * Running this package requires user's authorization to perform native 
accesses.
  * See the module Javadoc for more information.

Reply via email to