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
The following commit(s) were added to refs/heads/geoapi-4.0 by this push: new b43d5d4d36 Reimplement `getComponentFiles()` by fetching this information from GDAL instead of guessing. b43d5d4d36 is described below commit b43d5d4d36f6995c0275994546b1da4cb5360871 Author: Martin Desruisseaux <martin.desruisse...@geomatys.com> AuthorDate: Mon Sep 16 17:55:15 2024 +0200 Reimplement `getComponentFiles()` by fetching this information from GDAL instead of guessing. --- .../main/org/apache/sis/storage/gdal/GDAL.java | 23 ++++- .../org/apache/sis/storage/gdal/GDALStore.java | 97 +++++++++------------- .../apache/sis/storage/gdal/SubdatasetList.java | 9 +- .../org/apache/sis/storage/gdal/TiledResource.java | 1 + .../org/apache/sis/storage/gdal/GDALStoreTest.java | 6 ++ 5 files changed, 73 insertions(+), 63 deletions(-) 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 1f7e6de681..1857780d47 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 @@ -114,6 +114,19 @@ final class GDAL extends NativeFunctions { */ final MethodHandle free; + /** + * <abbr>GDAL</abbr> {@code void CSLDestroy(char **papszStrList)}. + * Releases memory allocated by <abbr>GDAL</abbr> for a string list. + * It is safe to pass {@code NULL}. + */ + final MethodHandle destroy; + + /** + * <abbr>GDAL</abbr> {@code char **GDALGetFileList(GDALDatasetH)}. + * Fetch files forming dataset. + */ + final MethodHandle getFileList; + /** * <abbr>GDAL</abbr> {@code GDALDriverH GDALGetDatasetDriver(GDALDatasetH)}. * Fetches the driver to which a dataset relates. @@ -270,7 +283,12 @@ final class GDAL extends NativeFunctions { final var acceptTwoPtrsReturnPointer = FunctionDescriptor.of(ValueLayout.ADDRESS, ValueLayout.ADDRESS, ValueLayout.ADDRESS); final Linker linker = Linker.nativeLinker(); + // Memory management + free = lookup(linker, "VSIFree", FunctionDescriptor.ofVoid(ValueLayout.ADDRESS)); + destroy = lookup(linker, "CSLDestroy", FunctionDescriptor.ofVoid(ValueLayout.ADDRESS)); + // For Driver and/or all major objects + identifyDriver = lookup(linker, "GDALIdentifyDriver", acceptTwoPtrsReturnPointer); getName = lookup(linker, "GDALGetDriverLongName", acceptPointerReturnPointer); getIdentifier = lookup(linker, "GDALGetDriverShortName", acceptPointerReturnPointer); getMetadata = lookup(linker, "GDALGetMetadata", acceptTwoPtrsReturnPointer); @@ -281,8 +299,6 @@ final class GDAL extends NativeFunctions { ValueLayout.ADDRESS)); // const char* domain // For Opener - identifyDriver = lookup(linker, "GDALIdentifyDriver", acceptTwoPtrsReturnPointer); - 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 @@ -291,7 +307,8 @@ final class GDAL extends NativeFunctions { ValueLayout.ADDRESS, // const char *const *papszOpenOptions ValueLayout.ADDRESS)); // const char *const *papszSiblingFiles - // For all data set + // For all data sets + getFileList = lookup(linker, "GDALGetFileList", acceptPointerReturnPointer); getDatasetDriver = lookup(linker, "GDALGetDatasetDriver", acceptPointerReturnPointer); getSpatialRef = lookup(linker, "GDALGetSpatialRef", acceptPointerReturnPointer); getGCPSpatialRef = lookup(linker, "GDALGetGCPSpatialRef", acceptPointerReturnPointer); 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 3e9382ba92..f5272fc1a7 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 @@ -17,16 +17,12 @@ package org.apache.sis.storage.gdal; import java.util.List; -import java.util.ArrayList; import java.util.Collection; import java.util.Objects; import java.util.Optional; import java.util.logging.Level; import java.util.logging.LogRecord; import java.net.URI; -import java.io.IOException; -import java.nio.file.DirectoryStream; -import java.nio.file.Files; import java.nio.file.Path; import java.lang.ref.Cleaner; import java.lang.foreign.Arena; @@ -238,7 +234,7 @@ public class GDALStore extends DataStore implements Aggregate, ResourceOnFileSys * @return name of the <abbr>GDAL</abbr> driver used for opening the file. * @throws DataStoreException if the driver name cannot be fetched. */ - final String getDriverName(final GDAL gdal) throws DataStoreException { + private String getDriverName(final GDAL gdal) throws DataStoreException { try { var result = (MemorySegment) gdal.getDatasetDriver.invokeExact(handle()); if (!GDAL.isNull(result)) { // Paranoiac check. @@ -272,6 +268,44 @@ public class GDALStore extends DataStore implements Aggregate, ResourceOnFileSys return Optional.ofNullable(param); } + /** + * Returns an array of files believed to be part of this resource. + * + * @todo This method is often used for copying a resources from one location to another. + * <abbr>GDAL</abbr> provides a {@code GDALCopyDatasetFiles} function for this purpose. + * That function is not yet used by Apache <abbr>SIS</abbr>. + * + * @return files used by this resource, or an empty array if unknown. + * @throws DataStoreException if the list of files cannot be obtained. + */ + @Override + public synchronized Path[] getComponentFiles() throws DataStoreException { + final GDAL gdal = getProvider().GDAL(); + final List<String> files; + try { + final var list = (MemorySegment) gdal.getFileList.invokeExact(handle()); + try { + files = GDAL.fromNullTerminatedStrings(list); + } finally { + gdal.destroy.invokeExact(list); + } + } catch (Throwable e) { + throw GDAL.propagate(e); + } + if (files == null || files.isEmpty()) { + return (path != null) ? new Path[] {path} : new Path[0]; + } + final var paths = new Path[files.size()]; + for (int i=0; i < paths.length; i++) { + var item = Path.of(files.get(i)); + if (path != null) { + item = path.resolveSibling(item); + } + paths[i] = item; + } + return paths; + } + /** * Returns an identifier for the root resource of this data store, or an empty value if none. * @@ -348,63 +382,12 @@ public class GDALStore extends DataStore implements Aggregate, ResourceOnFileSys if (all != null) { final String driver = getDriverName(gdal); if (driver != null) { // Should never be null. - return new SubdatasetList(gdal, this, all); + return new SubdatasetList(gdal, this, driver, all); } } return null; } - /** - * Gets the paths to files potentially used by this resource. - * This method returns the path to the main file opened by the <abbr>GDAL</abbr> driver, - * and applies heuristic rules for guessing which other paths may be part of the format. - * Heuristic rules are used because <abbr>GDAL</abbr> provides no <abbr>API</abbr> for - * listing which files are used. - * - * @return files used by this resource, or an empty array if unknown. - * @throws DataStoreException if an error on the file system prevent the creation of the list. - */ - @Override - public Path[] getComponentFiles() throws DataStoreException { - @SuppressWarnings("LocalVariableHidesMemberVariable") - final Path path = this.path; - if (path == null) { - return new Path[0]; - } - final DirectoryStream.Filter<Path> filter; - switch (getDriver().getIdentifier()) { - default: { - return new Path[] {path}; - } - case "AIG": { // Arc/Info Binary Grid - // Special case: we must take "metadata.xml" and all "*.adf" in the folder. - filter = (Path entry) -> ("metadata.xml".equalsIgnoreCase(entry.getFileName().toString()) - || "adf".equalsIgnoreCase(IOUtilities.extension(entry))); - break; - } - // TODO: list more case where we want the same default as GeoTIFF. - case "GTiff": { - // List all existing paths with the same file name but possibly with different suffixes. - final String filename = path.getFileName().toString(); - final int s = filename.lastIndexOf('.'); - final String base = (s >= 0) ? filename.substring(0, s+1) : filename; - filter = (Path entry) -> entry.getFileName().toString().startsWith(base); - break; - } - } - final var paths = new ArrayList<Path>(); - paths.addFirst(path); - try (DirectoryStream<Path> stream = Files.newDirectoryStream(path.getParent(), - (entry) -> !entry.equals(path) && filter.accept(entry) && Files.isRegularFile(entry))) - { - stream.forEach(paths::add); - } catch (IOException ex) { - throw cannotExecute(ex); - } - // Ensure that the main path is first. - return paths.toArray(Path[]::new); - } - /** * 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}. diff --git a/incubator/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/gdal/SubdatasetList.java b/incubator/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/gdal/SubdatasetList.java index 20378a05e0..826aa56453 100644 --- a/incubator/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/gdal/SubdatasetList.java +++ b/incubator/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/gdal/SubdatasetList.java @@ -66,11 +66,14 @@ final class SubdatasetList extends AbstractList<Subdataset> { * * @param gdal set of handles for invoking <abbr>GDAL</abbr> functions. * @param parent the data store which owns this list. + * @param driver name of the <abbr>GDAL</abbr> driver to use for opening the sub-datasets. * @param metadata the metadata items for the {@code "SUBDATASETS"} domain. */ - SubdatasetList(final GDAL gdal, final GDALStore parent, final List<String> metadata) throws DataStoreException { - this.parent = parent; - this.driver = parent.getDriverName(gdal); + SubdatasetList(final GDAL gdal, final GDALStore parent, final String driver, final List<String> metadata) + throws DataStoreException + { + this.parent = parent; + this.driver = driver; /* * URLs of all sub-dataset, optionally associated to their descriptions. * Keys are metadata keys. Values at index 0 are the URLs. Values at index 1 are descriptions. 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 a5ba1c4d6b..45e42ee8f6 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 @@ -322,6 +322,7 @@ final class TiledResource extends TiledGridResource { m.getAtIndex(layout, 0), m.getAtIndex(layout, 3)); } + // TODO: if above is not available, we could fallback on `GDALGCPsToGeoTransform`. } /* * The axis order used by GDAL is not the axis order in the CRS definition. diff --git a/incubator/src/org.apache.sis.storage.gdal/test/org/apache/sis/storage/gdal/GDALStoreTest.java b/incubator/src/org.apache.sis.storage.gdal/test/org/apache/sis/storage/gdal/GDALStoreTest.java index 9353bdf4a5..d316abd1e4 100644 --- a/incubator/src/org.apache.sis.storage.gdal/test/org/apache/sis/storage/gdal/GDALStoreTest.java +++ b/incubator/src/org.apache.sis.storage.gdal/test/org/apache/sis/storage/gdal/GDALStoreTest.java @@ -16,6 +16,7 @@ */ package org.apache.sis.storage.gdal; +import java.nio.file.Path; import java.awt.image.DataBuffer; import java.awt.image.RenderedImage; import org.opengis.util.GenericName; @@ -156,6 +157,11 @@ public final class GDALStoreTest { assertEquals(5, data.getElem(2000)); // Check a random value. } } + + // Check the file components + final Path[] paths = store.getComponentFiles(); + assertEquals(1, paths.length); + assertEquals("test.tiff", paths[0].getFileName().toString()); } assertTrue(foundGrid); assertTrue(foundBand);