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 57aa818b350d8f414e8aa94a5b4479276b9fe393 Author: Martin Desruisseaux <martin.desruisse...@geomatys.com> AuthorDate: Thu Sep 26 19:08:20 2024 +0200 Add metadata information about which library (including its version) was used for reading a raster or other data file. --- .../sis/metadata/iso/citation/Citations.java | 56 +++++++++----- .../main/org/apache/sis/metadata/sql/Citations.sql | 3 + .../main/org/apache/sis/metadata/sql/Metadata.sql | 4 +- .../apache/sis/metadata/sql/MetadataFallback.java | 6 ++ .../apache/sis/storage/landsat/LandsatStore.java | 3 +- .../apache/sis/storage/geotiff/GeoTiffStore.java | 6 +- .../sis/storage/geotiff/ImageFileDirectory.java | 9 ++- .../geotiff/reader/ImageMetadataBuilder.java | 2 - .../apache/sis/storage/netcdf/MetadataReader.java | 3 +- .../sis/storage/AbstractGridCoverageResource.java | 2 +- .../apache/sis/storage/base/MetadataBuilder.java | 90 ++++++++++++++++++---- .../main/org/apache/sis/storage/csv/Store.java | 3 +- .../org/apache/sis/storage/esri/RasterStore.java | 4 +- .../apache/sis/storage/image/WorldFileStore.java | 1 + .../org/apache/sis/storage/internal/Resources.java | 5 ++ .../sis/storage/internal/Resources.properties | 1 + .../sis/storage/internal/Resources_fr.properties | 1 + .../main/org/apache/sis/util/privy/Constants.java | 5 ++ .../main/org/apache/sis/storage/gdal/Driver.java | 3 +- .../main/org/apache/sis/storage/gdal/GDAL.java | 9 ++- .../org/apache/sis/storage/gdal/GDALStore.java | 25 +++++- .../apache/sis/storage/gdal/GDALStoreProvider.java | 16 ++-- .../org/apache/sis/storage/gdal/TiledResource.java | 45 +++++++---- .../org/apache/sis/storage/gdal/GDALStoreTest.java | 3 +- 24 files changed, 229 insertions(+), 76 deletions(-) diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/citation/Citations.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/citation/Citations.java index 46dfd1afd1..bdd25d3564 100644 --- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/citation/Citations.java +++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/citation/Citations.java @@ -115,13 +115,14 @@ public final class Citations extends Static { }); /** - * The <a href="https://www.iogp.org/">International Association of Oil & Gas producers</a> (IOGP) organization. + * The International Association of Oil & Gas producers (<abbr>IOGP</abbr>) organization. * This organization is responsible for maintainance of {@link #EPSG} database. * * <p>We do not expose this citation in public API because it is an organization rather than a reference - * to a document or a database (see SIS-200). For now we keep this citation mostly for resolving the legacy - * "OGP" identifier as "IOGP" (see the special case in fromName(String) method). This is also a way to share - * the same citation instance in GML like below:</p> + * to a document or a database (see <a href="http://issues.apache.org/jira/browse/SIS-200">SIS-200</a>). + * For now we keep this citation mostly for resolving the legacy "OGP" identifier as "IOGP" + * (see the special case in fromName(String) method). + * This is also a way to share the same citation instance in <abbr>GML</abbr> like below:</p> * * {@snippet lang="xml" : * <gml:identifier codeSpace="IOGP">urn:ogc:def:crs:EPSG::4326</gml:identifier> @@ -129,13 +130,12 @@ public final class Citations extends Static { * * @see #fromName(String) * @see org.apache.sis.xml.bind.referencing.Code#getIdentifier() - * @see <a href="http://issues.apache.org/jira/browse/SIS-200">SIS-200</a> + * @see <a href="https://www.iogp.org/">International Association of Oil & Gas producers</a> */ static final CitationConstant IOGP = new CitationConstant(Constants.IOGP); /** - * The authority for identifiers of objects defined by the - * <a href="https://epsg.org/">EPSG Geodetic Parameter Dataset</a>. + * The authority for identifiers of objects defined by the <abbr>EPSG</abbr> Geodetic Parameter Dataset. * EPSG is not an organization by itself, but is the <em>identifier space</em> managed by the * <a href="https://www.iogp.org/">International Association of Oil & Gas producers</a> (IOGP) organization * for {@linkplain org.apache.sis.referencing.crs.AbstractCRS Coordinate Reference System} identifiers. @@ -184,14 +184,15 @@ public final class Citations extends Static { * with the addition of version information.</li> * </ul> * + * @see <a href="https://epsg.org/">EPSG Geodetic Parameter Dataset</a> + * * @since 0.4 */ public static final IdentifierSpace<Integer> EPSG = new CitationConstant.Authority<>(Constants.EPSG); /** - * The authority for identifiers of objects defined by the - * <a href="https://www.ogc.org/standards/wms">Web Map Service</a> (WMS) specification. - * The WMS 1.3 specifications is also known as ISO 19128 + * The authority for identifiers of objects defined by the Web Map Service (<abbr>WMS</abbr>) specification. + * The <abbr>WMS</abbr> 1.3 specifications is also known as <abbr>ISO</abbr> 19128 * <cite>Geographic Information — Web map server interface</cite> standard. * * <p>The citation {@linkplain DefaultCitation#getCitedResponsibleParties() responsible parties} @@ -205,13 +206,14 @@ public final class Citations extends Static { * <li>{@link org.apache.sis.referencing.factory.CommonAuthorityFactory#getAuthority()}</li> * </ul> * + * @see <a href="https://www.ogc.org/standards/wms">Web Map Service specification</a> + * * @since 0.7 */ public static final IdentifierSpace<Integer> WMS = new CitationConstant.Authority<>("WMS", Constants.OGC); /** - * The authority for identifiers found in specifications from the - * <a href="https://www.ogc.org/">Open Geospatial Consortium</a>. + * The authority for identifiers found in specifications from the Open Geospatial Consortium (<abbr>OGC</abbr>). * The {@linkplain IdentifierSpace#getName() name} of this identifier space is fixed to {@code "OGC"}. * Apache SIS uses this authority mostly for map projection methods and parameters as they were defined in older * OGC specifications (in more recent specifications, {@linkplain #EPSG} identifiers tend to be more widely used). @@ -253,11 +255,12 @@ public final class Citations extends Static { * * @see #EPSG * @see #ESRI + * @see <a href="https://www.ogc.org/">Open Geospatial Consortium</a> */ public static final IdentifierSpace<String> OGC = new CitationConstant.Authority<>(Constants.OGC); /** - * The authority for identifiers of objects defined by <a href="https://www.esri.com">ESRI</a>. + * The authority for identifiers of objects defined by <abbr>ESRI</abbr>. * The {@linkplain IdentifierSpace#getName() name} of this identifier space is fixed to {@code "ESRI"}. * This citation is used as the authority for many map projection method and parameter names * other than the {@linkplain #EPSG} ones. @@ -286,18 +289,20 @@ public final class Citations extends Static { * * @see #OGC * @see #EPSG + * @see <a href="https://www.esri.com">ESRI</a> * * @since 0.4 */ public static final IdentifierSpace<String> ESRI = new CitationConstant.Authority<>("ArcGIS", "ESRI"); /** - * The authority for identifiers of objects defined by the - * <a href="https://www.wmo.int">World Meteorological Organization</a>. + * The authority for identifiers of objects defined by the World Meteorological Organization (<abbr>WMO</abbr>). * The {@linkplain IdentifierSpace#getName() name} of this identifier space is fixed to {@code "WMO"}. * This citation is used as the authority for some coordinate operations other than EPSG and ESRI ones, * for example "Rotated latitude/longitude". * + * @see <a href="https://www.wmo.int">World Meteorological Organization</a> + * * @since 1.2 */ public static final IdentifierSpace<String> WMO = new CitationConstant.Authority<>("WMO"); @@ -334,8 +339,7 @@ public final class Citations extends Static { public static final IdentifierSpace<String> NETCDF = new CitationConstant.Authority<>("NetCDF"); /** - * The authority for identifiers of objects defined by the - * the <a href="https://www.ogc.org/standards/geotiff">GeoTIFF</a> specification. + * The authority for identifiers of objects defined by the the GeoTIFF specification. * This specification identifies some map projections by their own numerical codes. * * <h4>Main usage</h4> @@ -344,12 +348,14 @@ public final class Citations extends Static { * <li>{@link org.apache.sis.referencing.ImmutableIdentifier#getAuthority()}</li> * </ul> * + * @see <a href="https://www.ogc.org/standards/geotiff">GeoTIFF specification</a> + * * @since 0.4 */ public static final IdentifierSpace<Integer> GEOTIFF = new CitationConstant.Authority<>(Constants.GEOTIFF); /** - * The authority for identifiers of objects defined by the <a href="https://proj.org/">PROJ</a> project. + * The authority for identifiers of objects defined by the PROJ project. * We use the {@code PROJ4} name for historical reasons, because those identifiers were defined mostly * when the project was known as "Proj.4". Starting at PROJ version 6, EPSG identifiers should be used * instead. @@ -360,6 +366,9 @@ public final class Citations extends Static { * <li>{@link org.apache.sis.referencing.ImmutableIdentifier#getAuthority()}</li> * </ul> * + * @see #GDAL + * @see <a href="https://proj.org/">PROJ</a> + * * @since 0.4 */ public static final IdentifierSpace<String> PROJ4 = new CitationConstant.Authority<>("PROJ", Constants.PROJ4); @@ -433,6 +442,16 @@ public final class Citations extends Static { */ public static final Citation SIS = new CitationConstant.Authority<String>(Constants.SIS); + /** + * The codespace for data formats accessed through the <abbr>GDAL</abbr> library. + * + * @see #PROJ4 + * @see <a href="https://gdal.org/">Geospatial Data Abstraction Library</a> + * + * @since 1.5 + */ + public static final Citation GDAL = new CitationConstant.Authority<String>(Constants.GDAL); + /** * List of <em>public</em> citations declared in this class. * Most frequently used citations (at least in SIS) should be first. @@ -452,6 +471,7 @@ public final class Citations extends Static { (CitationConstant) ISBN, (CitationConstant) ISSN, (CitationConstant) SIS, + (CitationConstant) GDAL, (CitationConstant) ISO_19115.get(0), (CitationConstant) ISO_19115.get(1) }; diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/Citations.sql b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/Citations.sql index 2d0626052e..34af3febff 100644 --- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/Citations.sql +++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/Citations.sql @@ -50,6 +50,7 @@ INSERT INTO metadata."OnlineResource" ("ID", "linkage") VALUES ('PostGIS', 'https://postgis.net/'), ('PROJ', 'https://proj.org/'), ('SIS', 'https://sis.apache.org/'), + ('GDAL', 'https://gdal.org/'), ('WMO', 'https://www.wmo.int/'), ('WMS', 'https://www.ogc.org/standards/wms'); @@ -193,6 +194,7 @@ INSERT INTO metadata."Identifier" ("ID", "code", "codeSpace", "version") VALUES ('ArcGIS', 'ArcGIS', 'ESRI', NULL), ('MapInfo', 'MapInfo', 'Precisely', NULL), ('PROJ', 'PROJ', 'OSGeo', NULL), + ('GDAL', 'GDAL', 'OSGeo', NULL), ('SIS', 'SIS', 'Apache', NULL); INSERT INTO metadata."Citation" ("ID", "onlineResource", "edition", "citedResponsibleParty", "presentationForm", "alternateTitle" , "title") VALUES @@ -207,6 +209,7 @@ INSERT INTO metadata."Citation" ("ID", "onlineResource", "edition", "citedRespon ('ArcGIS', 'ESRI', NULL, 'ESRI', NULL, NULL, 'ArcGIS'), ('MapInfo', NULL, NULL, 'MapInfo', NULL, 'MapInfo', 'MapInfo Pro'), ('PROJ', 'PROJ', NULL, 'OSGeo', NULL, 'Proj', 'PROJ coordinate transformation software library'), + ('GDAL', 'GDAL', NULL, 'OSGeo', NULL, NULL, 'Geospatial Data Abstraction Library'), ('SIS', 'SIS', NULL, 'Apache', NULL, 'Apache SIS', 'Apache Spatial Information System'); diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/Metadata.sql b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/Metadata.sql index 9494ce1660..5be61a9218 100644 --- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/Metadata.sql +++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/Metadata.sql @@ -10,6 +10,6 @@ -- CREATE TABLE metadata."Metadata" ( "ID" VARCHAR(15) NOT NULL PRIMARY KEY, - "metadataIdentifier" VARCHAR(15) REFERENCES metadata."Identifier" ("ID") ON UPDATE RESTRICT ON DELETE RESTRICT, - "parentMetadata" VARCHAR(15) REFERENCES metadata."Citation" ("ID") ON UPDATE RESTRICT ON DELETE RESTRICT + "metadataIdentifier" VARCHAR(15) REFERENCES metadata."Identifier" ("ID") ON UPDATE RESTRICT ON DELETE RESTRICT, + "parentMetadata" VARCHAR(15) REFERENCES metadata."Citation" ("ID") ON UPDATE RESTRICT ON DELETE RESTRICT ); diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/MetadataFallback.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/MetadataFallback.java index 5ce03ca4ea..c275652657 100644 --- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/MetadataFallback.java +++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/MetadataFallback.java @@ -199,6 +199,12 @@ final class MetadataFallback extends MetadataSource { codeSpace = "OSGeo"; break; } + case Constants.GDAL: { + title = "Geospatial Data Abstraction Library"; + code = key; + codeSpace = "OSGeo"; + break; + } case "IHO S-57": { title = code = "S-57"; codeSpace = "IHO"; diff --git a/endorsed/src/org.apache.sis.storage.earthobservation/main/org/apache/sis/storage/landsat/LandsatStore.java b/endorsed/src/org.apache.sis.storage.earthobservation/main/org/apache/sis/storage/landsat/LandsatStore.java index 4dec25b23d..6f5907a965 100644 --- a/endorsed/src/org.apache.sis.storage.earthobservation/main/org/apache/sis/storage/landsat/LandsatStore.java +++ b/endorsed/src/org.apache.sis.storage.earthobservation/main/org/apache/sis/storage/landsat/LandsatStore.java @@ -221,8 +221,9 @@ public class LandsatStore extends DataStore implements Aggregate { int count = 0; try (BufferedReader reader = (source instanceof BufferedReader) ? (BufferedReader) source : new LineNumberReader(source)) { source = null; // Will be closed at the end of this try-finally block. - final MetadataReader parser = new MetadataReader(this, getDisplayName(), listeners); + final var parser = new MetadataReader(this, getDisplayName(), listeners); parser.read(reader); + parser.addFormatReader(getProvider()); metadata = parser.getMetadata(); /* * Create the array of components. The resource identifier is the band name. diff --git a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/GeoTiffStore.java b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/GeoTiffStore.java index 69bc362295..d16c7bd3b5 100644 --- a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/GeoTiffStore.java +++ b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/GeoTiffStore.java @@ -21,6 +21,7 @@ import java.util.List; import java.util.Locale; import java.util.TimeZone; import java.util.Optional; +import java.util.logging.Level; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.net.URI; @@ -385,8 +386,9 @@ public class GeoTiffStore extends DataStore implements Aggregate { builder.setPredefinedFormat(Constants.GEOTIFF); } catch (MetadataStoreException e) { builder.addFormatName(Constants.GEOTIFF); - listeners.warning(e); + listeners.warning(Level.FINE, null, e); } + builder.addFormatReader(getProvider()); builder.addLanguage(Locale.ENGLISH, encoding, MetadataBuilder.Scope.METADATA); builder.addResourceScope(ScopeCode.COVERAGE, null); } @@ -404,7 +406,7 @@ public class GeoTiffStore extends DataStore implements Aggregate { if (metadata == null) { @SuppressWarnings("LocalVariableHidesMemberVariable") final Reader reader = reader(); - final MetadataBuilder builder = new MetadataBuilder(); + final var builder = new MetadataBuilder(); setFormatInfo(builder); int n = 0; try { diff --git a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/ImageFileDirectory.java b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/ImageFileDirectory.java index abf4f071ab..e115ebe374 100644 --- a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/ImageFileDirectory.java +++ b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/ImageFileDirectory.java @@ -1385,9 +1385,16 @@ final class ImageFileDirectory extends DataCube { source = new SchemaModifier.Source(reader.store, index, getDataType()); } getIdentifier().ifPresent((id) -> { + metadata.addIdentifier(id, ImageMetadataBuilder.Scope.RESOURCE); + final CharSequence title; if (!getImageIndex().equals(id.tip().toString())) { - metadata.addTitle(id.toString()); + title = id.toString(); + } else if (source != null && !metadata.hasTitle()) { + title = Vocabulary.formatInternational(Vocabulary.Keys.Image_1, index + 1); + } else { + return; // Return from lambda, not from `createMetadata()`. } + metadata.addTitle(title); }); /* * Add information about sample dimensions. diff --git a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/reader/ImageMetadataBuilder.java b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/reader/ImageMetadataBuilder.java index fd198315c2..bdc9dd1331 100644 --- a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/reader/ImageMetadataBuilder.java +++ b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/reader/ImageMetadataBuilder.java @@ -194,8 +194,6 @@ public final class ImageMetadataBuilder extends MetadataBuilder { * * Destination: metadata/resourceLineage/processStep/description */ - final int cellWidth = this.cellWidth; - final int cellHeight = this.cellHeight; switch (Math.min(cellWidth, cellHeight)) { case -1: { // Nothing to report. diff --git a/endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/MetadataReader.java b/endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/MetadataReader.java index 3940e00ac3..1d257bc899 100644 --- a/endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/MetadataReader.java +++ b/endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/MetadataReader.java @@ -25,6 +25,7 @@ import java.util.LinkedHashSet; import java.util.LinkedHashMap; import java.util.ArrayList; import java.util.Collection; +import java.util.logging.Level; import java.io.IOException; import java.time.temporal.Temporal; import ucar.nc2.constants.CF; // String constants are copied by the compiler with no UCAR reference left. @@ -662,7 +663,7 @@ split: while ((start = CharSequences.skipLeadingWhitespaces(value, start, lengt id = null; } catch (MetadataStoreException e) { // Will add `id` at the end of this method. - warning(e); + decoder.listeners.warning(Level.FINE, null, e); } if (format.length >= 2) { addFormatName(format[1]); diff --git a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/AbstractGridCoverageResource.java b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/AbstractGridCoverageResource.java index a0abd3b89a..19c1728154 100644 --- a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/AbstractGridCoverageResource.java +++ b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/AbstractGridCoverageResource.java @@ -126,7 +126,7 @@ public abstract class AbstractGridCoverageResource extends AbstractResource impl */ @Override protected Metadata createMetadata() throws DataStoreException { - final MetadataBuilder builder = new MetadataBuilder(); + final var builder = new MetadataBuilder(); builder.addDefaultMetadata(this, listeners); return builder.build(); } diff --git a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/MetadataBuilder.java b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/MetadataBuilder.java index dd81894447..a6acdad992 100644 --- a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/MetadataBuilder.java +++ b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/MetadataBuilder.java @@ -67,6 +67,7 @@ import org.opengis.referencing.operation.TransformException; import org.apache.sis.util.AbstractInternationalString; import org.apache.sis.util.CharSequences; import org.apache.sis.util.Characters; +import org.apache.sis.util.Version; import org.apache.sis.util.iso.Names; import org.apache.sis.util.iso.Types; import org.apache.sis.util.logging.Logging; @@ -90,11 +91,13 @@ import org.apache.sis.metadata.sql.MetadataStoreException; import org.apache.sis.metadata.sql.MetadataSource; import org.apache.sis.metadata.privy.Merger; import org.apache.sis.referencing.NamedIdentifier; +import org.apache.sis.referencing.ImmutableIdentifier; import org.apache.sis.referencing.privy.AxisDirections; import org.apache.sis.geometry.AbstractEnvelope; import org.apache.sis.storage.AbstractResource; import org.apache.sis.storage.AbstractFeatureSet; import org.apache.sis.storage.AbstractGridCoverageResource; +import org.apache.sis.storage.DataStoreProvider; import org.apache.sis.storage.DataStoreException; import org.apache.sis.storage.event.StoreListeners; import org.apache.sis.storage.internal.Resources; @@ -849,8 +852,10 @@ public class MetadataBuilder { * @throws DataStoreException if an error occurred while reading metadata from the data store. */ public final void addDefaultMetadata(final AbstractResource resource, final StoreListeners listeners) throws DataStoreException { - // Note: title is mandatory in ISO metadata, contrarily to the identifier. - resource.getIdentifier().ifPresent((name) -> addTitle(new Sentence(name))); + if (!hasTitle()) { + // Note: title is mandatory in ISO metadata, contrarily to the identifier. + resource.getIdentifier().ifPresent((name) -> addTitle(new Sentence(name))); + } resource.getEnvelope().ifPresent((envelope) -> addExtent(envelope, listeners)); } @@ -1040,7 +1045,7 @@ public class MetadataBuilder { * metadata.setPredefinedFormat("MyFormat"); * } catch (MetadataStoreException e) { * metadata.addFormatName("MyFormat"); - * listeners.warning(null, e); + * listeners.warning(Level.FINE, null, e); * } * metadata.addCompression("decompression technique"); * } @@ -1244,7 +1249,7 @@ public class MetadataBuilder { */ public final void addTitleOrIdentifier(final String code, Scope scope) { if (scope != Scope.METADATA) { - if (citation == null || citation.getTitle() == null) { + if (!hasTitle()) { addTitle(code); if (scope == Scope.RESOURCE) { return; @@ -1255,6 +1260,16 @@ public class MetadataBuilder { addIdentifier(null, code, scope); } + /** + * Returns whether the current citation has a title. This method is typically used for deciding + * whether to use some fallback as title, because titles are mandatory in ISO 19115. + * + * @return whether a title is defined in the current citation. + */ + public final boolean hasTitle() { + return (citation != null) && citation.getTitle() != null; + } + /** * Adds a version of the resource. * If a version already exists, the new one will be appended after a new line. @@ -3005,8 +3020,9 @@ public class MetadataBuilder { } /** - * Adds a name to the resource format. Note that this method does not add a new format, - * but only an alternative name to current format. Storage location is: + * Adds a name to the resource format. If no format citation has been created yet, + * then the given value is used as the format title. Otherwise, the given value is + * used as an alternative name of the current formaT. Storage location is: * * <ul> * <li>{@code metadata/identificationInfo/resourceFormat/formatSpecificationCitation/alternateTitle}</li> @@ -3036,6 +3052,20 @@ public class MetadataBuilder { } } + /** + * Returns the citation of the format as a modifiable object for allowing the caller to set properties. + */ + private DefaultCitation getFormatCitation() { + @SuppressWarnings("LocalVariableHidesMemberVariable") + final DefaultFormat format = format(); + DefaultCitation c = DefaultCitation.castOrCopy(format.getFormatSpecificationCitation()); + if (c == null) { + c = new DefaultCitation(); + } + format.setFormatSpecificationCitation(c); // Unconditional because may replace a proxy. + return c; + } + /** * Sets a version number for the resource format. Storage location is: * @@ -3054,14 +3084,46 @@ public class MetadataBuilder { public final void setFormatEdition(final CharSequence value) { final InternationalString i18n = trim(value); if (i18n != null) { - @SuppressWarnings("LocalVariableHidesMemberVariable") - final DefaultFormat format = format(); - DefaultCitation c = DefaultCitation.castOrCopy(format.getFormatSpecificationCitation()); - if (c == null) { - c = new DefaultCitation(); - format.setFormatSpecificationCitation(c); - } - c.setEdition(i18n); + getFormatCitation().setEdition(i18n); + } + } + + /** + * Adds a note about which reader is used. This method should not be invoked before + * the {@linkplain #addFormatName format name} has been set. Storage location is: + * + * <ul> + * <li>{@code metadata/identificationInfo/resourceFormat/formatSpecificationCitation/identifier}</li> + * <li>{@code metadata/identificationInfo/resourceFormat/formatSpecificationCitation/otherCitationDetails}</li> + * </ul> + * + * If this method is used together with {@link #setPredefinedFormat(String)}, + * then {@code setPredefinedFormat(…)} should be invoked <strong>before</strong> this method. + * + * @param driver library-specific way to identify the format (mandatory). + * @param version the library version, or {@code null} if unknown. + */ + public final void addFormatReader(final Identifier driver, final Version version) { + final DefaultCitation c = getFormatCitation(); + addIfNotPresent(c.getIdentifiers(), driver); + addIfNotPresent(c.getOtherCitationDetails(), + Resources.formatInternational( + Resources.Keys.ReadBy_2, + driver.getCodeSpace(), + (version != null) ? version : Vocabulary.formatInternational(Vocabulary.Keys.Unspecified))); + } + + /** + * Adds a note saying that Apache <abbr>SIS</abbr> has been used for decoding the format. + * This method should not be invoked before the {@linkplain #addFormatName format name} has been set. + * + * @param provider the data store provider, or {@code null} if unspecified. + */ + public final void addFormatReader(final DataStoreProvider provider) { + if (provider != null) { + String name = provider.getShortName(); + var driver = (name != null) ? new ImmutableIdentifier(Citations.SIS, Constants.SIS, name) : null; + addFormatReader(driver, Version.SIS); } } diff --git a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/csv/Store.java b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/csv/Store.java index 8ac1db8491..119e835081 100644 --- a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/csv/Store.java +++ b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/csv/Store.java @@ -636,8 +636,9 @@ final class Store extends URIDataStore implements FeatureSet { builder.setPredefinedFormat(format); } catch (MetadataStoreException e) { builder.addFormatName(format); - listeners.warning(e); + listeners.warning(Level.FINE, null, e); } + builder.addFormatReader(getProvider()); builder.addLanguage(Locale.ENGLISH, encoding, MetadataBuilder.Scope.ALL); builder.addResourceScope(ScopeCode.FEATURE, null); builder.addExtent(envelope, listeners); diff --git a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/esri/RasterStore.java b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/esri/RasterStore.java index 3b5f993518..e74e6765ad 100644 --- a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/esri/RasterStore.java +++ b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/esri/RasterStore.java @@ -21,6 +21,7 @@ import java.util.Arrays; import java.util.Hashtable; import java.util.Locale; import java.util.Optional; +import java.util.logging.Level; import java.io.IOException; import java.io.FileNotFoundException; import java.nio.file.NoSuchFileException; @@ -155,8 +156,9 @@ abstract class RasterStore extends PRJDataStore implements GridCoverageResource builder.setPredefinedFormat(formatKey); } catch (MetadataStoreException e) { builder.addFormatName(formatName); - listeners.warning(e); + listeners.warning(Level.FINE, null, e); } + builder.addFormatReader(getProvider()); builder.addResourceScope(ScopeCode.COVERAGE, null); builder.addLanguage(Locale.ENGLISH, encoding, MetadataBuilder.Scope.METADATA); builder.addSpatialRepresentation(null, gridGeometry, true); diff --git a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/image/WorldFileStore.java b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/image/WorldFileStore.java index 36b095adf6..b454c8d45a 100644 --- a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/image/WorldFileStore.java +++ b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/image/WorldFileStore.java @@ -539,6 +539,7 @@ loop: for (int convention=0;; convention++) { } } builder.addFormatName(format); // Does nothing if `format` is null. + builder.addFormatReader(getProvider()); builder.addResourceScope(ScopeCode.COVERAGE, null); builder.addSpatialRepresentation(null, getGridGeometry(MAIN_IMAGE), true); if (gridGeometry.isDefined(GridGeometry.ENVELOPE)) { diff --git a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/internal/Resources.java b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/internal/Resources.java index efa18420f6..0209faa010 100644 --- a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/internal/Resources.java +++ b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/internal/Resources.java @@ -358,6 +358,11 @@ public class Resources extends IndexedResourceBundle { */ public static final short ProcessingExecutedOn_1 = 12; + /** + * Read with {0} version {1}. + */ + public static final short ReadBy_2 = 83; + /** * The request [{3} … {4}] is outside the [{1} … {2}] domain for “{0}” axis. */ diff --git a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/internal/Resources.properties b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/internal/Resources.properties index b0eb6f1e38..7e876bb0e4 100644 --- a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/internal/Resources.properties +++ b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/internal/Resources.properties @@ -79,6 +79,7 @@ NoSuchResourceDirectory_1 = No directory of resources found at \u201c{0} NoSuchResourceInAggregate_2 = Resource \u201c{1}\u201d is not part of aggregate \u201c{0}\u201d. NotAWritableFeatureSet_1 = Resource \u201c{0}\u201d is not a writable feature set. ProcessingExecutedOn_1 = Processing executed on {0}. +ReadBy_2 = Read with {0} version {1}. ResourceAlreadyExists_1 = A resource already exists at \u201c{0}\u201d. ResourceIdentifierCollision_2 = More than one resource have the \u201c{1}\u201d identifier in the \u201c{0}\u201d data store. ResourceNotFound_2 = No resource found for the \u201c{1}\u201d identifier in the \u201c{0}\u201d data store. diff --git a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/internal/Resources_fr.properties b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/internal/Resources_fr.properties index 6b64a03e76..b628d0fc9d 100644 --- a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/internal/Resources_fr.properties +++ b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/internal/Resources_fr.properties @@ -84,6 +84,7 @@ NoSuchResourceDirectory_1 = Aucun r\u00e9pertoire de ressources n\u2019a NoSuchResourceInAggregate_2 = La ressource \u00ab\u202f{1}\u202f\u00bb n\u2019est pas une partie de l\u2019agr\u00e9gat \u00ab\u202f{0}\u202f\u00bb. NotAWritableFeatureSet_1 = La ressource \u00ab\u202f{0}\u202f\u00bb n\u2019est pas un ensemble d\u2019entit\u00e9s accessibles en \u00e9criture. ProcessingExecutedOn_1 = Traitement ex\u00e9cut\u00e9 sur {0}. +ReadBy_2 = Lecture faite avec {0} version {1}. ResourceAlreadyExists_1 = Une ressource existe d\u00e9j\u00e0 \u00e0 l\u2019emplacement \u00ab\u202f{0}\u202f\u00bb. ResourceIdentifierCollision_2 = Plusieurs ressources utilisent l\u2019identifiant \u00ab\u202f{1}\u202f\u00bb dans les donn\u00e9es de \u00ab\u202f{0}\u202f\u00bb. ResourceNotFound_2 = Aucune ressource n\u2019a \u00e9t\u00e9 trouv\u00e9e pour l\u2019identifiant \u00ab\u202f{1}\u202f\u00bb dans les donn\u00e9es de \u00ab\u202f{0}\u202f\u00bb. diff --git a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/privy/Constants.java b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/privy/Constants.java index de6ecf2cf6..3afa5e81b8 100644 --- a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/privy/Constants.java +++ b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/privy/Constants.java @@ -133,6 +133,11 @@ public final class Constants extends Static { */ public static final String SIS = "SIS"; + /** + * The {@value} code space. + */ + public static final String GDAL = "GDAL"; + /** * The {@value} code space. The project name is {@code "Proj.4"}, but this constant omits * the dot because this name is used as a code space and we want to avoid risk of confusion. diff --git a/incubator/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/gdal/Driver.java b/incubator/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/gdal/Driver.java index 085323c917..aa71bf3d03 100644 --- a/incubator/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/gdal/Driver.java +++ b/incubator/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/gdal/Driver.java @@ -35,6 +35,7 @@ import org.apache.sis.util.collection.TableColumn; import org.apache.sis.util.collection.TreeTable; import org.apache.sis.util.resources.Vocabulary; import org.apache.sis.util.logging.Logging; +import org.apache.sis.util.privy.Constants; import org.apache.sis.util.privy.Strings; import org.apache.sis.storage.Resource; import org.apache.sis.storage.DataStoreException; @@ -296,7 +297,7 @@ public final class Driver { final var table = new DefaultTreeTable(shortNameColumn, longNameColumn); final TreeTable.Node root = table.getRoot(); final String version = (gdal != null) ? gdal.version("--version").orElse(null) : null; - root.setValue(shortNameColumn, (gdal != null) ? gdal.libraryName : Resources.format(Resources.Keys.LibraryNotFound_1, GDALStoreProvider.NAME)); + root.setValue(shortNameColumn, (gdal != null) ? gdal.libraryName : Resources.format(Resources.Keys.LibraryNotFound_1, Constants.GDAL)); root.setValue(longNameColumn, (version != null) ? version : Vocabulary.format(Vocabulary.Keys.NotKnown)); DataStoreException error = null; 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 acedf5c8cd..c70763fc39 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 @@ -29,6 +29,7 @@ import java.lang.foreign.SymbolLookup; import java.lang.foreign.MemorySegment; import java.lang.foreign.FunctionDescriptor; import java.lang.invoke.MethodHandle; +import org.apache.sis.util.privy.Constants; import org.apache.sis.util.logging.Logging; import org.apache.sis.storage.DataStoreException; import org.apache.sis.storage.panama.LibraryLoader; @@ -399,7 +400,7 @@ final class GDAL extends NativeFunctions { // Initialize GDAL after we found all functions. if (!invoke("GDALAllRegister")) { log(GDAL.class, "<init>", Resources.forLocale(null) - .getLogRecord(Level.WARNING, Resources.Keys.CannotInitialize_1, GDALStoreProvider.NAME)); + .getLogRecord(Level.WARNING, Resources.Keys.CannotInitialize_1, Constants.GDAL)); } } @@ -460,9 +461,9 @@ final class GDAL extends NativeFunctions { */ static synchronized GDAL global() throws DataStoreException { if (globalStatus == null) { - load(true).validate(GDALStoreProvider.NAME); + load(true).validate(Constants.GDAL); } - globalStatus.report(GDALStoreProvider.NAME, null); + globalStatus.report(Constants.GDAL, null); return global; } @@ -475,7 +476,7 @@ final class GDAL extends NativeFunctions { */ static synchronized Optional<GDAL> tryGlobal(final Class<?> classe, final String method) { if (globalStatus == null) { - load(true).getError(GDALStoreProvider.NAME).ifPresent((record) -> log(classe, method, record)); + load(true).getError(Constants.GDAL).ifPresent((record) -> log(classe, method, record)); } return Optional.ofNullable(global); } 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 324153cbbb..be76b49eca 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 @@ -36,6 +36,8 @@ import org.apache.sis.parameter.Parameters; import org.apache.sis.io.wkt.WKTFormat; import org.apache.sis.io.wkt.Convention; import org.apache.sis.io.stream.IOUtilities; +import org.apache.sis.metadata.iso.citation.Citations; +import org.apache.sis.referencing.ImmutableIdentifier; import org.apache.sis.storage.Resource; import org.apache.sis.storage.Aggregate; import org.apache.sis.storage.DataStore; @@ -44,6 +46,7 @@ import org.apache.sis.storage.DataStoreClosedException; import org.apache.sis.storage.StorageConnector; import org.apache.sis.storage.base.MetadataBuilder; import org.apache.sis.storage.base.URIDataStore; +import org.apache.sis.util.privy.Constants; import org.apache.sis.util.privy.UnmodifiableArrayList; import org.apache.sis.util.iso.DefaultNameFactory; import org.apache.sis.system.Cleaners; @@ -150,7 +153,7 @@ public class GDALStore extends DataStore implements Aggregate { drivers = connector.getOption(GDALStoreProvider.DRIVERS_OPTION_KEY); location = connector.getStorageAs(URI.class); path = connector.getStorageAs(Path.class); - String url = connector.commit(String.class, GDALStoreProvider.NAME); + String url = connector.commit(String.class, Constants.GDAL); if (location != null) { url = Opener.toURL(location, path, true); } @@ -190,7 +193,7 @@ public class GDALStore extends DataStore implements Aggregate { final MemorySegment handle() throws DataStoreClosedException { assert Thread.holdsLock(this); if (handle != null) return handle; - throw new DataStoreClosedException(getLocale(), GDALStoreProvider.NAME); + throw new DataStoreClosedException(getLocale(), Constants.GDAL); } /** @@ -331,12 +334,30 @@ public class GDALStore extends DataStore implements Aggregate { final var builder = new MetadataBuilder(); builder.addTitle(description); builder.addIdentifier(getIdentifier().orElse(null), MetadataBuilder.Scope.RESOURCE); + addFormatInfo(builder); // TODO: add more information. return builder.buildAndFreeze(); } return metadata; } + /** + * Adds information about the data format. + * + * @param builder the builder where to add information. + */ + final void addFormatInfo(final MetadataBuilder builder) throws DataStoreException { + final Driver driver = getDriver(); + if (driver != null) { + builder.addFormatName(driver.getName()); + String id = driver.getIdentifier(); + if (id != null) { + builder.addFormatReader(new ImmutableIdentifier(Citations.GDAL, Constants.GDAL, id), + getProvider().getVersion().orElse(null)); + } + } + } + /** * Returns the children resources of this aggregate. * diff --git a/incubator/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/gdal/GDALStoreProvider.java b/incubator/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/gdal/GDALStoreProvider.java index 020e2f2dde..58a51dad00 100644 --- a/incubator/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/gdal/GDALStoreProvider.java +++ b/incubator/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/gdal/GDALStoreProvider.java @@ -43,6 +43,7 @@ import org.apache.sis.parameter.Parameters; import org.apache.sis.setup.OptionKey; import org.apache.sis.system.Cleaners; import org.apache.sis.util.Version; +import org.apache.sis.util.privy.Constants; import org.apache.sis.util.collection.TreeTable; @@ -61,16 +62,11 @@ import org.apache.sis.util.collection.TreeTable; * @version 1.5 * @since 1.5 */ -@StoreMetadata(formatName = GDALStoreProvider.NAME, +@StoreMetadata(formatName = Constants.GDAL, capabilities = {Capability.READ}, resourceTypes = {Aggregate.class, GridCoverageResource.class}, yieldPriority = true) // For trying Java implementations before GDAL. public class GDALStoreProvider extends DataStoreProvider { - /** - * The name of this data store provider. - */ - static final String NAME = "GDAL"; - /** * The logger used by <abbr>GDAL</abbr> stores. * @@ -103,9 +99,9 @@ public class GDALStoreProvider extends DataStoreProvider { static { final var builder = new ParameterBuilder(); DRIVERS_PARAM = builder.addName("drivers") - .setDescription(Resources.formatInternational(Resources.Keys.AllowedDrivers_1, NAME)) + .setDescription(Resources.formatInternational(Resources.Keys.AllowedDrivers_1, Constants.GDAL)) .create(String[].class, null); - OPEN_DESCRIPTOR = builder.addName(NAME).createGroup(URIDataStoreProvider.LOCATION_PARAM, DRIVERS_PARAM); + OPEN_DESCRIPTOR = builder.addName(Constants.GDAL).createGroup(URIDataStoreProvider.LOCATION_PARAM, DRIVERS_PARAM); } /** @@ -149,7 +145,7 @@ public class GDALStoreProvider extends DataStoreProvider { if (status == null) { return GDAL.global(); // Fetch each time (no cache) because may have changed outside this class. } - status.report(NAME, null); // Should never return if `nativeFunctions` is null. + status.report(Constants.GDAL, null); // Should never return if `nativeFunctions` is null. return nativeFunctions; } @@ -187,7 +183,7 @@ public class GDALStoreProvider extends DataStoreProvider { */ @Override public String getShortName() { - return NAME; + return Constants.GDAL; } /** 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 dbd7ea7034..1832044f7f 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,6 +30,7 @@ import java.lang.foreign.Arena; import java.lang.foreign.ValueLayout; import java.lang.foreign.MemorySegment; import org.opengis.util.GenericName; +import org.opengis.metadata.Metadata; import org.opengis.referencing.operation.Matrix; import org.opengis.referencing.operation.MathTransform; import org.opengis.referencing.crs.CoordinateReferenceSystem; @@ -45,6 +46,7 @@ import org.apache.sis.coverage.privy.ImageLayout; import org.apache.sis.coverage.privy.ColorModelFactory; import org.apache.sis.storage.DataStoreException; import org.apache.sis.storage.DataStoreReferencingException; +import org.apache.sis.storage.base.MetadataBuilder; import org.apache.sis.storage.base.TiledGridResource; import org.apache.sis.util.resources.Vocabulary; import org.apache.sis.util.ArraysExt; @@ -65,6 +67,11 @@ final class TiledResource extends TiledGridResource { */ final GDALStore parent; + /** + * Index of this image, with numbers starting at 1. This is the tip of the identifier. + */ + private final int imageIndex; + /** * The identifier of this resource, or {@code null} if none. * @@ -142,26 +149,22 @@ final class TiledResource extends TiledGridResource { /** * Creates a new instance as a child of the given data set. * - * @param parent the parent data set. - * @param size the raster width, height and data type. - * @param bands description of all bands as an array of {@link Band}. - * @param name an identifier for this band. + * @param parent the parent data set. + * @param imageIndex index of this image, with numbers starting at 1. + * @param size the raster width, height and data type. + * @param bands description of all bands as an array of {@link Band}. + * @param name an identifier for this band. */ - private TiledResource(final GDALStore parent, final SizeAndType size, final List<Band> bands, final CharSequence name) + private TiledResource(final GDALStore parent, final int imageIndex, final SizeAndType size, final List<Band> bands) throws DataStoreException { super(parent); - @SuppressWarnings("LocalVariableHidesMemberVariable") - GenericName identifier = parent.factory.createLocalName(parent.namespace, name); - final GenericName scope = parent.getIdentifier().orElse(null); - if (scope != null) { - identifier = identifier.push(scope); - } final Dimension t = size.tileSize(); this.tileWidth = t.width; this.tileHeight = t.height; this.parent = parent; - this.identifier = identifier; + this.imageIndex = imageIndex; + this.identifier = parent.factory.createLocalName(parent.namespace, String.valueOf(imageIndex)).toFullyQualifiedName(); this.width = size.width(); this.height = size.height(); this.dataType = DataType.valueOf(size.type()); @@ -244,8 +247,7 @@ final class TiledResource extends TiledGridResource { final var rasters = new TiledResource[bands.size()]; int count = 0; for (Map.Entry<SizeAndType, ArrayList<Band>> entry : bands.entrySet()) { - var name = Vocabulary.formatInternational(Vocabulary.Keys.Image_1, count + 1); - rasters[count++] = new TiledResource(parent, entry.getKey(), entry.getValue(), name); + rasters[count++] = new TiledResource(parent, count, entry.getKey(), entry.getValue()); } // Search for the main image and, if found, move it first. for (int i=0; i<count; i++) { @@ -279,6 +281,21 @@ final class TiledResource extends TiledGridResource { return Optional.ofNullable(identifier); } + /** + * Builds the metadata. + * This method is invoked only if the user requested the ISO 19115 metadata. + * + * @throws DataStoreException if an error occurred while reading metadata from the data store. + */ + @Override + protected Metadata createMetadata() throws DataStoreException { + final var builder = new MetadataBuilder(); + parent.addFormatInfo(builder); + builder.addTitle(Vocabulary.formatInternational(Vocabulary.Keys.Image_1, imageIndex)); + builder.addDefaultMetadata(this, listeners); + return builder.build(); + } + /** * Returns the extent of grid coordinates together with the conversion to real world coordinates. * 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 78755043bd..09f2b0e091 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 @@ -110,7 +110,8 @@ public final class GDALStoreTest { foundGrid = true; final GenericName name = r.getIdentifier().orElseThrow(); - assertEquals("test.tiff:Image #1", name.toString()); + assertEquals("test.tiff:1", name.toString()); + assertEquals("1", name.tip().toString()); assertSame(r, store.findResource(name.toString())); // Test reading fully the coverage.