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.