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 2b9172f57c Provides metadata information about the software used by `NetcdfStore` for reading a netCDF file. This commit completes the previous one, which was providing this information for all other stores. The netCDF case required additional pre-defined metadata and helper methods for fetching version. This commit also simplifies the way to build that "formatSpecificationCitation" metadata node. 2b9172f57c is described below commit 2b9172f57cd57b920ca923d33b9074fa275bc426 Author: Martin Desruisseaux <martin.desruisse...@geomatys.com> AuthorDate: Sun Sep 29 15:15:29 2024 +0200 Provides metadata information about the software used by `NetcdfStore` for reading a netCDF file. This commit completes the previous one, which was providing this information for all other stores. The netCDF case required additional pre-defined metadata and helper methods for fetching version. This commit also simplifies the way to build that "formatSpecificationCitation" metadata node. --- .../sis/metadata/iso/citation/Citations.java | 2 +- .../main/org/apache/sis/metadata/sql/Citations.sql | 34 +-- .../apache/sis/metadata/sql/MetadataFallback.java | 24 +- .../sis/metadata/iso/citation/CitationsTest.java | 2 +- .../sis/metadata/sql/MetadataFallbackVerifier.java | 3 +- .../operation/provider/NorthPoleRotation.java | 3 +- .../operation/provider/SouthPoleRotation.java | 3 +- .../apache/sis/storage/landsat/LandsatStore.java | 2 +- .../apache/sis/storage/landsat/MetadataReader.java | 14 +- .../sis/storage/landsat/MetadataReaderTest.java | 282 +++++++++------------ .../apache/sis/storage/geotiff/GeoTiffStore.java | 11 +- .../apache/sis/storage/netcdf/MetadataReader.java | 41 +-- .../org/apache/sis/storage/netcdf/NetcdfStore.java | 7 +- .../sis/storage/netcdf/NetcdfStoreProvider.java | 12 +- .../apache/sis/storage/netcdf/base/Decoder.java | 15 +- .../sis/storage/netcdf/classic/ChannelDecoder.java | 16 +- .../sis/storage/netcdf/ucar/DecoderWrapper.java | 65 ++++- .../sis/storage/netcdf/MetadataReaderTest.java | 104 ++++---- .../apache/sis/storage/base/MetadataBuilder.java | 80 +++--- .../main/org/apache/sis/storage/csv/Store.java | 32 +-- .../org/apache/sis/storage/esri/RasterStore.java | 11 +- .../apache/sis/storage/image/WorldFileStore.java | 9 +- .../main/org/apache/sis/util/Version.java | 53 +++- .../main/org/apache/sis/util/privy/Constants.java | 6 + .../test/org/apache/sis/util/VersionTest.java | 9 + geoapi/snapshot | 2 +- 26 files changed, 454 insertions(+), 388 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 bdd25d3564..5c3c60ee61 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 @@ -336,7 +336,7 @@ public final class Citations extends Static { * * @since 0.4 */ - public static final IdentifierSpace<String> NETCDF = new CitationConstant.Authority<>("NetCDF"); + public static final IdentifierSpace<String> NETCDF = new CitationConstant.Authority<>(Constants.NETCDF); /** * The authority for identifiers of objects defined by the the GeoTIFF specification. 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 34af3febff..7dd4abb1c4 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 @@ -36,6 +36,7 @@ CREATE TABLE metadata."OnlineResource" ( INSERT INTO metadata."OnlineResource" ("ID", "linkage") VALUES ('EPSG', 'https://epsg.org/'), ('ESRI', 'https://www.esri.com/'), + ('GDAL', 'https://gdal.org/'), ('GeoTIFF', 'https://trac.osgeo.org/geotiff/'), ('IHO', 'https://www.iho.int/'), ('IOGP', 'https://www.iogp.org/'), @@ -50,7 +51,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/'), + ('Unidata', 'https://www.unidata.ucar.edu/'), ('WMO', 'https://www.wmo.int/'), ('WMS', 'https://www.ogc.org/standards/wms'); @@ -108,6 +109,7 @@ INSERT INTO metadata."Organisation" ("ID", "name") VALUES ('{org}NATO', 'North Atlantic Treaty Organization'), ('{org}OGC', 'Open Geospatial Consortium'), ('{org}OSGeo', 'The Open Source Geospatial Foundation'), + ('{org}UCAR', 'University Corporation for Atmospheric Research'), ('{org}WMO', 'World Meteorological Organization'); INSERT INTO metadata."Responsibility" ("ID", "party", "role") VALUES @@ -122,6 +124,7 @@ INSERT INTO metadata."Responsibility" ("ID", "party", "role") VALUES ('NATO', '{org}NATO', 'principalInvestigator'), ('OGC', '{org}OGC', 'principalInvestigator'), ('OSGeo', '{org}OSGeo', 'resourceProvider'), + ('UCAR', '{org}UCAR', 'resourceProvider'), ('WMO', '{org}WMO', 'principalInvestigator'); @@ -198,19 +201,20 @@ INSERT INTO metadata."Identifier" ("ID", "code", "codeSpace", "version") VALUES ('SIS', 'SIS', 'Apache', NULL); INSERT INTO metadata."Citation" ("ID", "onlineResource", "edition", "citedResponsibleParty", "presentationForm", "alternateTitle" , "title") VALUES - ('ISBN', 'ISBN', NULL, 'ISBN', NULL, 'ISBN', 'International Standard Book Number'), - ('ISSN', 'ISSN', NULL, 'ISSN', NULL, 'ISSN', 'International Standard Serial Number'), - ('ISO 19115-1', NULL, 'ISO 19115-1:2014', 'ISO', 'documentDigital', 'ISO 19115-1', 'Geographic Information — Metadata Part 1: Fundamentals'), - ('ISO 19115-2', NULL, 'ISO 19115-2:2019', 'ISO', 'documentDigital', 'ISO 19115-2', 'Geographic Information — Metadata Part 2: Extensions for imagery and gridded data'), - ('IHO S-57', NULL, '3.1', 'IHO', 'documentDigital', 'S-57', 'IHO transfer standard for digital hydrographic data'), - ('MGRS', NULL, NULL, 'NATO', 'documentDigital', NULL, 'Military Grid Reference System'), - ('WMS', 'WMS', '1.3', 'OGC', 'documentDigital', 'WMS', 'Web Map Server'), - ('EPSG', 'EPSG', NULL, 'IOGP', 'tableDigital', 'EPSG Dataset', 'EPSG Geodetic Parameter Dataset'), - ('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'); + ('ISBN', 'ISBN', NULL, 'ISBN', NULL, 'ISBN', 'International Standard Book Number'), + ('ISSN', 'ISSN', NULL, 'ISSN', NULL, 'ISSN', 'International Standard Serial Number'), + ('ISO 19115-1', NULL, 'ISO 19115-1:2014', 'ISO', 'documentDigital', 'ISO 19115-1', 'Geographic Information — Metadata Part 1: Fundamentals'), + ('ISO 19115-2', NULL, 'ISO 19115-2:2019', 'ISO', 'documentDigital', 'ISO 19115-2', 'Geographic Information — Metadata Part 2: Extensions for imagery and gridded data'), + ('IHO S-57', NULL, '3.1', 'IHO', 'documentDigital', 'S-57', 'IHO transfer standard for digital hydrographic data'), + ('MGRS', NULL, NULL, 'NATO', 'documentDigital', NULL, 'Military Grid Reference System'), + ('WMS', 'WMS', '1.3', 'OGC', 'documentDigital', 'WMS', 'Web Map Server'), + ('EPSG', 'EPSG', NULL, 'IOGP', 'tableDigital', 'EPSG Dataset', 'EPSG Geodetic Parameter Dataset'), + ('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, 'GDAL', 'Geospatial Data Abstraction Library'), + ('Unidata', 'Unidata', NULL, 'UCAR', NULL, NULL, 'Unidata netCDF library'), + ('SIS', 'SIS', NULL, 'Apache', NULL, 'Apache SIS', 'Apache Spatial Information System'); @@ -233,4 +237,4 @@ INSERT INTO metadata."Citation" ("ID", "onlineResource", "citedResponsibleParty" ('WMO', 'WMO', 'WMO', 'documentDigital', 'WMO Information System (WIS)'), ('IOGP', 'IOGP', 'IOGP', 'documentDigital', 'IOGP Surveying and Positioning Guidance Note 7'); -UPDATE metadata."Citation" SET "identifier" = "ID" WHERE "ID"<>'ISBN' AND "ID"<>'ISSN' AND "ID"<>'MGRS'; +UPDATE metadata."Citation" SET "identifier" = "ID" WHERE "ID"<>'ISBN' AND "ID"<>'ISSN' AND "ID"<>'MGRS' AND "ID"<>'Unidata'; 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 c275652657..edee1c42fe 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 @@ -110,7 +110,7 @@ final class MetadataFallback extends MetadataSource { CharSequence title; CharSequence alternateTitle = null; CharSequence edition = null; - String code = null; + String code = key; String codeSpace = null; String version = null; CharSequence citedResponsibleParty = null; @@ -141,29 +141,25 @@ final class MetadataFallback extends MetadataSource { title = "Web Map Server"; alternateTitle = "WMS"; edition = version = "1.3"; - code = "WMS"; // Note: OGC internal code is 06-042. codeSpace = "OGC"; copyFrom = "OGC"; presentationForm = PresentationForm.DOCUMENT_DIGITAL; break; } - case "OGC": { + case Constants.OGC: { title = "OGC Naming Authority"; - code = Constants.OGC; citedResponsibleParty = "Open Geospatial Consortium"; presentationForm = PresentationForm.DOCUMENT_DIGITAL; break; } case "WMO": { title = "WMO Information System (WIS)"; - code = key; citedResponsibleParty = "World Meteorological Organization"; presentationForm = PresentationForm.DOCUMENT_DIGITAL; break; } - case "IOGP": { // Not in public API (see Citations.IOGP javadoc) + case Constants.IOGP: { // Not in public API (see Citations.IOGP javadoc) title = "IOGP Surveying and Positioning Guidance Note 7"; - code = Constants.IOGP; copyFrom = Constants.EPSG; presentationForm = PresentationForm.DOCUMENT_DIGITAL; break; @@ -171,7 +167,6 @@ final class MetadataFallback extends MetadataSource { case Constants.EPSG: { title = "EPSG Geodetic Parameter Dataset"; alternateTitle = "EPSG Dataset"; - code = Constants.EPSG; codeSpace = Constants.IOGP; citedResponsibleParty = "International Association of Oil & Gas producers"; presentationForm = PresentationForm.TABLE_DIGITAL; @@ -179,32 +174,37 @@ final class MetadataFallback extends MetadataSource { } case Constants.SIS: { title = "Apache Spatial Information System"; - code = key; codeSpace = "Apache"; break; } case "ISBN": { title = "International Standard Book Number"; alternateTitle = key; + code = null; break; } case "ISSN": { title = "International Standard Serial Number"; alternateTitle = key; + code = null; break; } case "PROJ": { title = "PROJ coordinate transformation software library"; - code = "PROJ"; codeSpace = "OSGeo"; break; } case Constants.GDAL: { - title = "Geospatial Data Abstraction Library"; - code = key; + title = "Geospatial Data Abstraction Library"; + alternateTitle = "GDAL"; codeSpace = "OSGeo"; break; } + case "Unidata": { + title = "Unidata netCDF library"; + code = null; + break; + } case "IHO S-57": { title = code = "S-57"; codeSpace = "IHO"; diff --git a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/iso/citation/CitationsTest.java b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/iso/citation/CitationsTest.java index 8be419e1a4..1e15401bc4 100644 --- a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/iso/citation/CitationsTest.java +++ b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/iso/citation/CitationsTest.java @@ -91,7 +91,7 @@ public final class CitationsTest extends TestCase { assertSame(IOGP, fromName(Constants.IOGP)); assertSame(IOGP, fromName("OGP")); assertSame(ESRI, fromName("ESRI")); // Handled in a way very similar to "OGC". - assertSame(NETCDF, fromName("NetCDF")); + assertSame(NETCDF, fromName(Constants.NETCDF)); assertSame(GEOTIFF, fromName(Constants.GEOTIFF)); assertSame(PROJ4, fromName("Proj.4")); assertSame(PROJ4, fromName("Proj4")); diff --git a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/sql/MetadataFallbackVerifier.java b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/sql/MetadataFallbackVerifier.java index a1d4ad0cd1..6ff30f8380 100644 --- a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/sql/MetadataFallbackVerifier.java +++ b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/sql/MetadataFallbackVerifier.java @@ -24,6 +24,7 @@ import org.apache.sis.metadata.MetadataStandard; import org.apache.sis.metadata.internal.CitationConstant; import org.apache.sis.metadata.iso.citation.Citations; import static org.apache.sis.util.privy.CollectionsExt.first; +import org.apache.sis.util.privy.Constants; // Test dependencies import org.junit.jupiter.api.Test; @@ -45,7 +46,7 @@ public final class MetadataFallbackVerifier { /** * Identifier for which {@link MetadataFallback} does not provide hard-coded values. */ - private static final Set<String> EXCLUDES = Set.of("NetCDF", "GeoTIFF", "ArcGIS", "MapInfo"); + private static final Set<String> EXCLUDES = Set.of(Constants.NETCDF, Constants.GEOTIFF, "ArcGIS", "MapInfo"); /** * Creates a new test case. diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/provider/NorthPoleRotation.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/provider/NorthPoleRotation.java index 84ab4fecf0..d796a8d838 100644 --- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/provider/NorthPoleRotation.java +++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/provider/NorthPoleRotation.java @@ -30,6 +30,7 @@ import org.apache.sis.parameter.Parameters; import org.apache.sis.measure.Longitude; import org.apache.sis.measure.Latitude; import org.apache.sis.measure.Units; +import org.apache.sis.util.privy.Constants; /** @@ -104,7 +105,7 @@ public final class NorthPoleRotation extends AbstractProvider { */ public static final ParameterDescriptorGroup PARAMETERS; static { - final ParameterBuilder builder = new ParameterBuilder().setCodeSpace(Citations.NETCDF, "NetCDF").setRequired(true); + final ParameterBuilder builder = new ParameterBuilder().setCodeSpace(Citations.NETCDF, Constants.NETCDF).setRequired(true); POLE_LATITUDE = builder .addNameAndIdentifier(Citations.SIS, SouthPoleRotation.POLE_LATITUDE) diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/provider/SouthPoleRotation.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/provider/SouthPoleRotation.java index 4720654bef..c945ca7936 100644 --- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/provider/SouthPoleRotation.java +++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/provider/SouthPoleRotation.java @@ -30,6 +30,7 @@ import org.apache.sis.parameter.Parameters; import org.apache.sis.measure.Longitude; import org.apache.sis.measure.Latitude; import org.apache.sis.measure.Units; +import org.apache.sis.util.privy.Constants; /** @@ -114,7 +115,7 @@ public final class SouthPoleRotation extends AbstractProvider { */ public static final ParameterDescriptorGroup PARAMETERS; static { - final ParameterBuilder builder = new ParameterBuilder().setCodeSpace(Citations.NETCDF, "NetCDF").setRequired(true); + final ParameterBuilder builder = new ParameterBuilder().setCodeSpace(Citations.NETCDF, Constants.NETCDF).setRequired(true); POLE_LATITUDE = builder .addName(Citations.SIS, "Latitude of rotated pole") 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 6f5907a965..6dab2cb236 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 @@ -223,7 +223,7 @@ public class LandsatStore extends DataStore implements Aggregate { source = null; // Will be closed at the end of this try-finally block. final var parser = new MetadataReader(this, getDisplayName(), listeners); parser.read(reader); - parser.addFormatReader(getProvider()); + parser.addFormatReaderSIS(LandsatStoreProvider.NAME); metadata = parser.getMetadata(); /* * Create the array of components. The resource identifier is the band name. diff --git a/endorsed/src/org.apache.sis.storage.earthobservation/main/org/apache/sis/storage/landsat/MetadataReader.java b/endorsed/src/org.apache.sis.storage.earthobservation/main/org/apache/sis/storage/landsat/MetadataReader.java index ba59a0aa86..a4cf6a0906 100644 --- a/endorsed/src/org.apache.sis.storage.earthobservation/main/org/apache/sis/storage/landsat/MetadataReader.java +++ b/endorsed/src/org.apache.sis.storage.earthobservation/main/org/apache/sis/storage/landsat/MetadataReader.java @@ -48,7 +48,6 @@ import org.apache.sis.metadata.iso.DefaultMetadata; import org.apache.sis.metadata.iso.content.DefaultAttributeGroup; import org.apache.sis.metadata.iso.content.DefaultSampleDimension; import org.apache.sis.metadata.iso.content.DefaultCoverageDescription; -import org.apache.sis.metadata.sql.MetadataStoreException; import org.apache.sis.referencing.CRS; import org.apache.sis.referencing.CommonCRS; import org.apache.sis.storage.DataStoreException; @@ -469,15 +468,12 @@ final class MetadataReader extends MetadataBuilder { * Value is "GEOTIFF". */ case "OUTPUT_FORMAT": { - String name = value; - if (Constants.GEOTIFF.equalsIgnoreCase(name)) try { - name = Constants.GEOTIFF; // Because `metadata.setPredefinedFormat(…)` is case-sensitive. - setPredefinedFormat(name); - break; - } catch (MetadataStoreException e) { - warning(key, null, e); + if (Constants.GEOTIFF.equalsIgnoreCase(value)) { + setPredefinedFormat(Constants.GEOTIFF, listeners, true); + } else { + addFormatName(value); } - addFormatName(name); + // Do not invoke `addFormatReaderSIS(name)`, it will be done by the caller. break; } /* diff --git a/endorsed/src/org.apache.sis.storage.earthobservation/test/org/apache/sis/storage/landsat/MetadataReaderTest.java b/endorsed/src/org.apache.sis.storage.earthobservation/test/org/apache/sis/storage/landsat/MetadataReaderTest.java index 954c104830..31099f6d44 100644 --- a/endorsed/src/org.apache.sis.storage.earthobservation/test/org/apache/sis/storage/landsat/MetadataReaderTest.java +++ b/endorsed/src/org.apache.sis.storage.earthobservation/test/org/apache/sis/storage/landsat/MetadataReaderTest.java @@ -24,6 +24,7 @@ import static org.junit.jupiter.api.Assertions.*; import org.apache.sis.test.TestCase; // Specific to the geoapi-3.1 and geoapi-4.0 branches: +import static java.util.Map.entry; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; @@ -55,6 +56,16 @@ import org.opengis.test.dataset.ContentVerifier; * @author Martin Desruisseaux (Geomatys) */ public final class MetadataReaderTest extends TestCase { + /** + * Helper class for verifying metadata content. + */ + private ContentVerifier verifier; + + /** + * A buffer for building paths to expected properties. + */ + private StringBuilder buffer; + /** * Creates a new test case. */ @@ -92,180 +103,139 @@ public final class MetadataReaderTest extends TestCase { reader.read(in); actual = reader.getMetadata(); } - final ContentVerifier verifier = new ContentVerifier(); + verifier = new ContentVerifier(); verifier.addPropertyToIgnore(Metadata.class, "metadataStandard"); // Because hard-coded in SIS. verifier.addPropertyToIgnore(Metadata.class, "referenceSystemInfo"); // Very verbose and depends on EPSG connection. verifier.addPropertyToIgnore(TemporalExtent.class, "extent"); // Because currently time-zone sensitive. verifier.addMetadataToVerify(actual); verifier.addExpectedValues( - "defaultLocale+otherLocale[0]", "en", - "metadataIdentifier.code", "LandsatTest", - "metadataScope[0].resourceScope", ScopeCode.COVERAGE, - "dateInfo[0].date", OffsetDateTime.of(2016, 6, 27, 16, 48, 12, 0, ZoneOffset.UTC), - "dateInfo[0].dateType", DateType.CREATION, - "identificationInfo[0].topicCategory[0]", TopicCategory.GEOSCIENTIFIC_INFORMATION, - "identificationInfo[0].citation.date[0].date", OffsetDateTime.of(2016, 6, 27, 16, 48, 12, 0, ZoneOffset.UTC), - "identificationInfo[0].citation.date[0].dateType", DateType.CREATION, - "identificationInfo[0].citation.title", "LandsatTest", - "identificationInfo[0].credit[0]", "Derived from U.S. Geological Survey data", - "identificationInfo[0].resourceFormat[0].formatSpecificationCitation.title", "GeoTIFF Coverage Encoding Profile", - "identificationInfo[0].resourceFormat[0].formatSpecificationCitation.alternateTitle[0]", "GeoTIFF", - "identificationInfo[0].resourceFormat[0].formatSpecificationCitation.citedResponsibleParty[0].party[0].name", "Open Geospatial Consortium", - "identificationInfo[0].resourceFormat[0].formatSpecificationCitation.citedResponsibleParty[0].role", Role.PRINCIPAL_INVESTIGATOR, - "identificationInfo[0].extent[0].geographicElement[0].extentTypeCode", true, - "identificationInfo[0].extent[0].geographicElement[0].westBoundLongitude", 108.34, - "identificationInfo[0].extent[0].geographicElement[0].eastBoundLongitude", 110.44, - "identificationInfo[0].extent[0].geographicElement[0].southBoundLatitude", 10.50, - "identificationInfo[0].extent[0].geographicElement[0].northBoundLatitude", 12.62, - "identificationInfo[0].spatialResolution[0].distance", 15.0, - "identificationInfo[0].spatialResolution[1].distance", 30.0, - - "acquisitionInformation[0].platform[0].identifier.code", "Pseudo LANDSAT", - "acquisitionInformation[0].platform[0].instrument[0].identifier.code", "Pseudo TIRS", - "acquisitionInformation[0].acquisitionRequirement[0].identifier.code", "Software unit tests", - "acquisitionInformation[0].operation[0].significantEvent[0].context", Context.ACQUISITION, - "acquisitionInformation[0].operation[0].significantEvent[0].time", OffsetDateTime.of(2016, 6, 26, 3, 2, 1, 90_000_000, ZoneOffset.UTC), - "acquisitionInformation[0].operation[0].status", Progress.COMPLETED, - "acquisitionInformation[0].operation[0].type", OperationType.REAL, - - "contentInfo[0].processingLevelCode.authority.title", "Landsat", - "contentInfo[0].processingLevelCode.codeSpace", "Landsat", - "contentInfo[0].processingLevelCode.code", "Pseudo LT1", - - "contentInfo[0].attributeGroup[0].attribute[0].description", "Coastal Aerosol", - "contentInfo[0].attributeGroup[0].attribute[1].description", "Blue", - "contentInfo[0].attributeGroup[0].attribute[2].description", "Green", - "contentInfo[0].attributeGroup[0].attribute[3].description", "Red", - "contentInfo[0].attributeGroup[0].attribute[4].description", "Near-Infrared", - "contentInfo[0].attributeGroup[0].attribute[5].description", "Short Wavelength Infrared (SWIR) 1", - "contentInfo[0].attributeGroup[0].attribute[6].description", "Short Wavelength Infrared (SWIR) 2", - "contentInfo[0].attributeGroup[0].attribute[7].description", "Cirrus", - "contentInfo[0].attributeGroup[1].attribute[0].description", "Panchromatic", - "contentInfo[0].attributeGroup[2].attribute[0].description", "Thermal Infrared Sensor (TIRS) 1", - "contentInfo[0].attributeGroup[2].attribute[1].description", "Thermal Infrared Sensor (TIRS) 2", + entry("defaultLocale+otherLocale[0]", "en"), + entry("metadataIdentifier.code", "LandsatTest"), + entry("metadataScope[0].resourceScope", ScopeCode.COVERAGE), + entry("dateInfo[0].date", OffsetDateTime.of(2016, 6, 27, 16, 48, 12, 0, ZoneOffset.UTC)), + entry("dateInfo[0].dateType", DateType.CREATION), - "contentInfo[0].attributeGroup[0].attribute[0].minValue", 1.0, - "contentInfo[0].attributeGroup[0].attribute[1].minValue", 1.0, - "contentInfo[0].attributeGroup[0].attribute[2].minValue", 1.0, - "contentInfo[0].attributeGroup[0].attribute[3].minValue", 1.0, - "contentInfo[0].attributeGroup[0].attribute[4].minValue", 1.0, - "contentInfo[0].attributeGroup[0].attribute[5].minValue", 1.0, - "contentInfo[0].attributeGroup[0].attribute[6].minValue", 1.0, - "contentInfo[0].attributeGroup[0].attribute[7].minValue", 1.0, - "contentInfo[0].attributeGroup[1].attribute[0].minValue", 1.0, - "contentInfo[0].attributeGroup[2].attribute[0].minValue", 1.0, - "contentInfo[0].attributeGroup[2].attribute[1].minValue", 1.0, + entry("identificationInfo[0].topicCategory[0]", TopicCategory.GEOSCIENTIFIC_INFORMATION), + entry("identificationInfo[0].citation.date[0].date", OffsetDateTime.of(2016, 6, 27, 16, 48, 12, 0, ZoneOffset.UTC)), + entry("identificationInfo[0].citation.date[0].dateType", DateType.CREATION), + entry("identificationInfo[0].citation.title", "LandsatTest"), + entry("identificationInfo[0].credit[0]", "Derived from U.S. Geological Survey data"), - "contentInfo[0].attributeGroup[0].attribute[0].maxValue", 65535.0, - "contentInfo[0].attributeGroup[0].attribute[1].maxValue", 65535.0, - "contentInfo[0].attributeGroup[0].attribute[2].maxValue", 65535.0, - "contentInfo[0].attributeGroup[0].attribute[3].maxValue", 65535.0, - "contentInfo[0].attributeGroup[0].attribute[4].maxValue", 65535.0, - "contentInfo[0].attributeGroup[0].attribute[5].maxValue", 65535.0, - "contentInfo[0].attributeGroup[0].attribute[6].maxValue", 65535.0, - "contentInfo[0].attributeGroup[0].attribute[7].maxValue", 65535.0, - "contentInfo[0].attributeGroup[1].attribute[0].maxValue", 65535.0, - "contentInfo[0].attributeGroup[2].attribute[0].maxValue", 65535.0, - "contentInfo[0].attributeGroup[2].attribute[1].maxValue", 65535.0, + entry("identificationInfo[0].resourceFormat[0].formatSpecificationCitation.title", "GeoTIFF Coverage Encoding Profile"), + entry("identificationInfo[0].resourceFormat[0].formatSpecificationCitation.alternateTitle[0]", "GeoTIFF"), + entry("identificationInfo[0].resourceFormat[0].formatSpecificationCitation.citedResponsibleParty[0].party[0].name", "Open Geospatial Consortium"), + entry("identificationInfo[0].resourceFormat[0].formatSpecificationCitation.citedResponsibleParty[0].role", Role.PRINCIPAL_INVESTIGATOR), - "contentInfo[0].attributeGroup[0].attribute[0].peakResponse", 433.0, - "contentInfo[0].attributeGroup[0].attribute[1].peakResponse", 482.0, - "contentInfo[0].attributeGroup[0].attribute[2].peakResponse", 562.0, - "contentInfo[0].attributeGroup[0].attribute[3].peakResponse", 655.0, - "contentInfo[0].attributeGroup[0].attribute[4].peakResponse", 865.0, - "contentInfo[0].attributeGroup[0].attribute[5].peakResponse", 1610.0, - "contentInfo[0].attributeGroup[0].attribute[6].peakResponse", 2200.0, - "contentInfo[0].attributeGroup[0].attribute[7].peakResponse", 1375.0, - "contentInfo[0].attributeGroup[1].attribute[0].peakResponse", 590.0, - "contentInfo[0].attributeGroup[2].attribute[0].peakResponse", 10800.0, - "contentInfo[0].attributeGroup[2].attribute[1].peakResponse", 12000.0, + entry("identificationInfo[0].extent[0].geographicElement[0].extentTypeCode", true), + entry("identificationInfo[0].extent[0].geographicElement[0].westBoundLongitude", 108.34), + entry("identificationInfo[0].extent[0].geographicElement[0].eastBoundLongitude", 110.44), + entry("identificationInfo[0].extent[0].geographicElement[0].southBoundLatitude", 10.50), + entry("identificationInfo[0].extent[0].geographicElement[0].northBoundLatitude", 12.62), + entry("identificationInfo[0].spatialResolution[0].distance", 15.0), + entry("identificationInfo[0].spatialResolution[1].distance", 30.0), - "contentInfo[0].attributeGroup[0].attribute[0].transferFunctionType", TransferFunctionType.LINEAR, - "contentInfo[0].attributeGroup[0].attribute[1].transferFunctionType", TransferFunctionType.LINEAR, - "contentInfo[0].attributeGroup[0].attribute[2].transferFunctionType", TransferFunctionType.LINEAR, - "contentInfo[0].attributeGroup[0].attribute[3].transferFunctionType", TransferFunctionType.LINEAR, - "contentInfo[0].attributeGroup[0].attribute[4].transferFunctionType", TransferFunctionType.LINEAR, - "contentInfo[0].attributeGroup[0].attribute[5].transferFunctionType", TransferFunctionType.LINEAR, - "contentInfo[0].attributeGroup[0].attribute[6].transferFunctionType", TransferFunctionType.LINEAR, - "contentInfo[0].attributeGroup[0].attribute[7].transferFunctionType", TransferFunctionType.LINEAR, - "contentInfo[0].attributeGroup[1].attribute[0].transferFunctionType", TransferFunctionType.LINEAR, - "contentInfo[0].attributeGroup[2].attribute[0].transferFunctionType", TransferFunctionType.LINEAR, - "contentInfo[0].attributeGroup[2].attribute[1].transferFunctionType", TransferFunctionType.LINEAR, + entry("acquisitionInformation[0].platform[0].identifier.code", "Pseudo LANDSAT"), + entry("acquisitionInformation[0].platform[0].instrument[0].identifier.code", "Pseudo TIRS"), + entry("acquisitionInformation[0].acquisitionRequirement[0].identifier.code", "Software unit tests"), + entry("acquisitionInformation[0].operation[0].significantEvent[0].context", Context.ACQUISITION), + entry("acquisitionInformation[0].operation[0].significantEvent[0].time", OffsetDateTime.of(2016, 6, 26, 3, 2, 1, 90_000_000, ZoneOffset.UTC)), + entry("acquisitionInformation[0].operation[0].status", Progress.COMPLETED), + entry("acquisitionInformation[0].operation[0].type", OperationType.REAL), - "contentInfo[0].attributeGroup[0].attribute[0].scaleFactor", 2.0E-5, - "contentInfo[0].attributeGroup[0].attribute[1].scaleFactor", 2.0E-5, - "contentInfo[0].attributeGroup[0].attribute[2].scaleFactor", 2.0E-5, - "contentInfo[0].attributeGroup[0].attribute[3].scaleFactor", 2.0E-5, - "contentInfo[0].attributeGroup[0].attribute[4].scaleFactor", 2.0E-5, - "contentInfo[0].attributeGroup[0].attribute[5].scaleFactor", 2.0E-5, - "contentInfo[0].attributeGroup[0].attribute[6].scaleFactor", 2.0E-5, - "contentInfo[0].attributeGroup[0].attribute[7].scaleFactor", 2.0E-5, - "contentInfo[0].attributeGroup[1].attribute[0].scaleFactor", 2.0E-5, - "contentInfo[0].attributeGroup[2].attribute[0].scaleFactor", 0.000334, - "contentInfo[0].attributeGroup[2].attribute[1].scaleFactor", 0.000334, + entry("contentInfo[0].processingLevelCode.authority.title", "Landsat"), + entry("contentInfo[0].processingLevelCode.codeSpace", "Landsat"), + entry("contentInfo[0].processingLevelCode.code", "Pseudo LT1"), - "contentInfo[0].attributeGroup[0].attribute[0].offset", -0.1, - "contentInfo[0].attributeGroup[0].attribute[1].offset", -0.1, - "contentInfo[0].attributeGroup[0].attribute[2].offset", -0.1, - "contentInfo[0].attributeGroup[0].attribute[3].offset", -0.1, - "contentInfo[0].attributeGroup[0].attribute[4].offset", -0.1, - "contentInfo[0].attributeGroup[0].attribute[5].offset", -0.1, - "contentInfo[0].attributeGroup[0].attribute[6].offset", -0.1, - "contentInfo[0].attributeGroup[0].attribute[7].offset", -0.1, - "contentInfo[0].attributeGroup[1].attribute[0].offset", -0.1, - "contentInfo[0].attributeGroup[2].attribute[0].offset", 0.1, - "contentInfo[0].attributeGroup[2].attribute[1].offset", 0.1, + entry("contentInfo[0].cloudCoverPercentage", 8.3), + entry("contentInfo[0].illuminationAzimuthAngle", 116.9), + entry("contentInfo[0].illuminationElevationAngle", 58.8), - "contentInfo[0].attributeGroup[0].attribute[0].units", "", - "contentInfo[0].attributeGroup[0].attribute[1].units", "", - "contentInfo[0].attributeGroup[0].attribute[2].units", "", - "contentInfo[0].attributeGroup[0].attribute[3].units", "", - "contentInfo[0].attributeGroup[0].attribute[4].units", "", - "contentInfo[0].attributeGroup[0].attribute[5].units", "", - "contentInfo[0].attributeGroup[0].attribute[6].units", "", - "contentInfo[0].attributeGroup[0].attribute[7].units", "", - "contentInfo[0].attributeGroup[1].attribute[0].units", "", + entry("spatialRepresentationInfo[0].numberOfDimensions", 2), + entry("spatialRepresentationInfo[1].numberOfDimensions", 2), + entry("spatialRepresentationInfo[0].axisDimensionProperties[0].dimensionName", DimensionNameType.SAMPLE), + entry("spatialRepresentationInfo[1].axisDimensionProperties[0].dimensionName", DimensionNameType.SAMPLE), + entry("spatialRepresentationInfo[0].axisDimensionProperties[1].dimensionName", DimensionNameType.LINE), + entry("spatialRepresentationInfo[1].axisDimensionProperties[1].dimensionName", DimensionNameType.LINE), + entry("spatialRepresentationInfo[0].axisDimensionProperties[0].dimensionSize", 7600), + entry("spatialRepresentationInfo[0].axisDimensionProperties[1].dimensionSize", 7800), + entry("spatialRepresentationInfo[1].axisDimensionProperties[0].dimensionSize", 15000), + entry("spatialRepresentationInfo[1].axisDimensionProperties[1].dimensionSize", 15500), + entry("spatialRepresentationInfo[0].transformationParameterAvailability", false), + entry("spatialRepresentationInfo[1].transformationParameterAvailability", false), + entry("spatialRepresentationInfo[0].checkPointAvailability", false), + entry("spatialRepresentationInfo[1].checkPointAvailability", false), - "contentInfo[0].attributeGroup[0].attribute[0].boundUnits", "nm", - "contentInfo[0].attributeGroup[0].attribute[1].boundUnits", "nm", - "contentInfo[0].attributeGroup[0].attribute[2].boundUnits", "nm", - "contentInfo[0].attributeGroup[0].attribute[3].boundUnits", "nm", - "contentInfo[0].attributeGroup[0].attribute[4].boundUnits", "nm", - "contentInfo[0].attributeGroup[0].attribute[5].boundUnits", "nm", - "contentInfo[0].attributeGroup[0].attribute[6].boundUnits", "nm", - "contentInfo[0].attributeGroup[0].attribute[7].boundUnits", "nm", - "contentInfo[0].attributeGroup[1].attribute[0].boundUnits", "nm", - "contentInfo[0].attributeGroup[2].attribute[0].boundUnits", "nm", - "contentInfo[0].attributeGroup[2].attribute[1].boundUnits", "nm", + entry("resourceLineage[0].source[0].description", "Pseudo GLS")); - "contentInfo[0].attributeGroup[0].contentType[0]", CoverageContentType.PHYSICAL_MEASUREMENT, - "contentInfo[0].attributeGroup[1].contentType[0]", CoverageContentType.PHYSICAL_MEASUREMENT, - "contentInfo[0].attributeGroup[2].contentType[0]", CoverageContentType.PHYSICAL_MEASUREMENT, - - "contentInfo[0].cloudCoverPercentage", 8.3, - "contentInfo[0].illuminationAzimuthAngle", 116.9, - "contentInfo[0].illuminationElevationAngle", 58.8, - - "spatialRepresentationInfo[0].numberOfDimensions", 2, - "spatialRepresentationInfo[1].numberOfDimensions", 2, - "spatialRepresentationInfo[0].axisDimensionProperties[0].dimensionName", DimensionNameType.SAMPLE, - "spatialRepresentationInfo[1].axisDimensionProperties[0].dimensionName", DimensionNameType.SAMPLE, - "spatialRepresentationInfo[0].axisDimensionProperties[1].dimensionName", DimensionNameType.LINE, - "spatialRepresentationInfo[1].axisDimensionProperties[1].dimensionName", DimensionNameType.LINE, - "spatialRepresentationInfo[0].axisDimensionProperties[0].dimensionSize", 7600, - "spatialRepresentationInfo[0].axisDimensionProperties[1].dimensionSize", 7800, - "spatialRepresentationInfo[1].axisDimensionProperties[0].dimensionSize", 15000, - "spatialRepresentationInfo[1].axisDimensionProperties[1].dimensionSize", 15500, - "spatialRepresentationInfo[0].transformationParameterAvailability", false, - "spatialRepresentationInfo[1].transformationParameterAvailability", false, - "spatialRepresentationInfo[0].checkPointAvailability", false, - "spatialRepresentationInfo[1].checkPointAvailability", false, - - "resourceLineage[0].source[0].description", "Pseudo GLS"); + /* + * The expected values in "contentInfo[0].attributeGroup[…].attribute[…].*" have a lot of redundancy. + * Therefore, we set those expected values by loop instead of repeating tens of long property paths. + */ + final String[] descriptions = { + "Coastal Aerosol", + "Blue", + "Green", + "Red", + "Near-Infrared", + "Short Wavelength Infrared (SWIR) 1", + "Short Wavelength Infrared (SWIR) 2", + "Cirrus", + "Panchromatic", + "Thermal Infrared Sensor (TIRS) 1", + "Thermal Infrared Sensor (TIRS) 2" + }; + final short[] peakResponses = {433, 482, 562, 655, 865, 1610, 2200, 1375, 590, 10800, 12000}; + int band = 0; + buffer = new StringBuilder(80).append("contentInfo[0].attributeGroup["); + final int groupBase = buffer.length(); + final int[] numAttributes = {8, 1, 2}; + for (int group = 0; group < numAttributes.length; group++) { + final boolean mainGroups = (group != 2); + /* + * contentInfo[0].attributeGroup[0…2].contentType[0] + */ + buffer.setLength(groupBase); + buffer.append(group).append("]."); + addExpectedValue("contentType[0]", CoverageContentType.PHYSICAL_MEASUREMENT); + /* + * contentInfo[0].attributeGroup[0…2].attribute[…].minValue + * contentInfo[0].attributeGroup[0…2].attribute[…].maxValue + * ... etc ... + */ + final int attributeBase = buffer.append("attribute[").length(); + for (int attribute = 0; attribute < numAttributes[group]; attribute++) { + buffer.setLength(attributeBase); + buffer.append(attribute).append("]."); + addExpectedValue("minValue", 1.0); + addExpectedValue("maxValue", 65535.0); + addExpectedValue("description", descriptions[band]); + addExpectedValue("peakResponse", (double) peakResponses[band++]); + addExpectedValue("boundUnits", "nm"); + addExpectedValue("transferFunctionType", TransferFunctionType.LINEAR); + addExpectedValue("scaleFactor", mainGroups ? 2.0E-5 : 0.000334); + addExpectedValue("offset", mainGroups ? -0.1 : 0.1); + if (mainGroups) { + addExpectedValue("units", ""); + } + } + } + assertEquals(descriptions.length, band); + assertEquals(peakResponses.length, band); verifier.assertMetadataEquals(); } + /** + * Adds an expected value for the given property. The path to that property is the + * current content of {@link #buffer}, including a trailing {@code '.'} separator. + * The buffer is reset to its original length after this method call. + */ + private void addExpectedValue(final String tip, Object value) { + final int length = buffer.length(); + verifier.addExpectedValue(buffer.append(tip).toString(), value); + buffer.setLength(length); + } + /** * Creates a dummy set of store listeners. * Used only for constructors that require a non-null {@link StoreListeners} instance. 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 d16c7bd3b5..9ddabf81b3 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,7 +21,6 @@ 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; @@ -62,7 +61,6 @@ import org.apache.sis.io.stream.ChannelDataInput; import org.apache.sis.io.stream.ChannelDataOutput; import org.apache.sis.io.stream.IOUtilities; import org.apache.sis.metadata.iso.DefaultMetadata; -import org.apache.sis.metadata.sql.MetadataStoreException; import org.apache.sis.coverage.SubspaceNotSpecifiedException; import org.apache.sis.coverage.grid.GridCoverage; import org.apache.sis.coverage.grid.GridGeometry; @@ -382,13 +380,8 @@ public class GeoTiffStore extends DataStore implements Aggregate { * Sets the {@code metadata/identificationInfo/resourceFormat} node to "GeoTIFF" format. */ final void setFormatInfo(final MetadataBuilder builder) { - try { - builder.setPredefinedFormat(Constants.GEOTIFF); - } catch (MetadataStoreException e) { - builder.addFormatName(Constants.GEOTIFF); - listeners.warning(Level.FINE, null, e); - } - builder.addFormatReader(getProvider()); + builder.setPredefinedFormat(Constants.GEOTIFF, listeners, true); + builder.addFormatReaderSIS(Constants.GEOTIFF); builder.addLanguage(Locale.ENGLISH, encoding, MetadataBuilder.Scope.METADATA); builder.addResourceScope(ScopeCode.COVERAGE, null); } 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 1d257bc899..890cf53904 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,7 +25,6 @@ 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. @@ -51,7 +50,6 @@ import org.opengis.referencing.crs.CoordinateReferenceSystem; import org.apache.sis.metadata.iso.DefaultMetadata; import org.apache.sis.metadata.iso.citation.*; import org.apache.sis.metadata.iso.identification.*; -import org.apache.sis.metadata.sql.MetadataStoreException; import org.apache.sis.storage.DataStoreException; import org.apache.sis.storage.base.MetadataBuilder; import org.apache.sis.storage.event.StoreListeners; @@ -69,6 +67,7 @@ import org.apache.sis.system.Configuration; import org.apache.sis.util.CharSequences; import org.apache.sis.util.iso.Types; import org.apache.sis.util.privy.CollectionsExt; +import org.apache.sis.util.privy.Constants; import org.apache.sis.util.privy.CodeLists; import org.apache.sis.util.privy.Strings; import org.apache.sis.util.resources.Errors; @@ -656,22 +655,30 @@ split: while ((start = CharSequences.skipLeadingWhitespaces(value, start, lengt addBoundingPolygon(new StoreFormat(null, null, decoder.geomlib, decoder.listeners).parseGeometry(wkt, stringValue(GEOSPATIAL_BOUNDS + "_crs"), stringValue(GEOSPATIAL_BOUNDS + "_vertical_crs"))); } - final String[] format = decoder.getFormatDescription(); - String id = format[0]; - if (NetcdfStoreProvider.NAME.equalsIgnoreCase(id)) try { - setPredefinedFormat(NetcdfStoreProvider.NAME); - id = null; - } catch (MetadataStoreException e) { - // Will add `id` at the end of this method. - decoder.listeners.warning(Level.FINE, null, e); - } - if (format.length >= 2) { - addFormatName(format[1]); - if (format.length >= 3) { - setFormatEdition(format[2]); - } + /* + * Add a description of the format. The description is determined by the decoder in use. + * That decoder may itself infer that description from another library such as UCAR. + */ + decoder.addFormatDescription(this); + } + + /** + * Adds the format description with a check about whether the given format identifier is recognized. + * This is a helper method for {@link Decoder#addFormatDescription(MetadataBuilder)} implementations. + * + * @param format format identifier. Recognized value is {@value NetcdfStoreProvider#NAME}. + * @param listeners ignored. Will be replaced by the listeners of the decoder. + * @param fallback whether to use a fallback if the description was not found. + * @return whether the format description has been added. + */ + @Override + public boolean setPredefinedFormat(String format, StoreListeners listeners, boolean fallback) { + if (Constants.NETCDF.equalsIgnoreCase(format)) { + return super.setPredefinedFormat(format, decoder.listeners, fallback); + } else if (fallback) { + addFormatName(format); } - addFormatName(id); // Do nothing is `id` is null. + return false; } /** diff --git a/endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/NetcdfStore.java b/endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/NetcdfStore.java index 21fb0746c5..39f7c2229b 100644 --- a/endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/NetcdfStore.java +++ b/endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/NetcdfStore.java @@ -48,6 +48,7 @@ import org.apache.sis.setup.OptionKey; import org.apache.sis.util.Version; import org.apache.sis.util.CharSequences; import org.apache.sis.util.privy.Strings; +import org.apache.sis.util.privy.Constants; import org.apache.sis.util.privy.UnmodifiableArrayList; import org.apache.sis.util.collection.DefaultTreeTable; import org.apache.sis.util.collection.TableColumn; @@ -117,7 +118,7 @@ public class NetcdfStore extends DataStore implements Aggregate { throw new DataStoreException(e); } if (decoder == null) { - throw new UnsupportedStorageException(super.getLocale(), NetcdfStoreProvider.NAME, + throw new UnsupportedStorageException(super.getLocale(), Constants.NETCDF, connector.getStorage(), connector.getOption(OptionKey.OPEN_OPTIONS)); } decoder.location = path; @@ -217,7 +218,7 @@ public class NetcdfStore extends DataStore implements Aggregate { public Optional<TreeTable> getNativeMetadata() throws DataStoreException { final DefaultTreeTable table = new DefaultTreeTable(TableColumn.NAME, TableColumn.VALUE); final TreeTable.Node root = table.getRoot(); - root.setValue(TableColumn.NAME, NetcdfStoreProvider.NAME); + root.setValue(TableColumn.NAME, Constants.NETCDF); decoder().addAttributesTo(root); return Optional.of(table); } @@ -294,7 +295,7 @@ public class NetcdfStore extends DataStore implements Aggregate { private Decoder decoder() throws DataStoreClosedException { final Decoder reader = decoder; if (reader == null) { - throw new DataStoreClosedException(getLocale(), NetcdfStoreProvider.NAME, StandardOpenOption.READ); + throw new DataStoreClosedException(getLocale(), Constants.NETCDF, StandardOpenOption.READ); } return reader; } diff --git a/endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/NetcdfStoreProvider.java b/endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/NetcdfStoreProvider.java index 8acabbcba4..2a37675b3b 100644 --- a/endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/NetcdfStoreProvider.java +++ b/endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/NetcdfStoreProvider.java @@ -52,6 +52,7 @@ import org.apache.sis.setup.OptionKey; import org.apache.sis.storage.event.StoreListeners; import org.apache.sis.util.Version; import org.apache.sis.util.logging.Logging; +import org.apache.sis.util.privy.Constants; /** @@ -72,7 +73,7 @@ import org.apache.sis.util.logging.Logging; * * @since 0.3 */ -@StoreMetadata(formatName = NetcdfStoreProvider.NAME, +@StoreMetadata(formatName = Constants.NETCDF, fileSuffixes = "nc", capabilities = Capability.READ, resourceTypes = {Aggregate.class, FeatureSet.class, GridCoverageResource.class}, @@ -85,11 +86,6 @@ import org.apache.sis.util.logging.Logging; * specialized readers to be tested before this generic netCDF reader. */ public class NetcdfStoreProvider extends DataStoreProvider { - /** - * The format name. - */ - static final String NAME = "NetCDF"; - /** * The MIME type for netCDF files. */ @@ -98,7 +94,7 @@ public class NetcdfStoreProvider extends DataStoreProvider { /** * The parameter descriptor to be returned by {@link #getOpenParameters()}. */ - private static final ParameterDescriptorGroup OPEN_DESCRIPTOR = URIDataStoreProvider.descriptor(NAME); + private static final ParameterDescriptorGroup OPEN_DESCRIPTOR = URIDataStoreProvider.descriptor(Constants.NETCDF); /** * The name of the {@link ucar.nc2.NetcdfFile} class, which is {@value}. @@ -161,7 +157,7 @@ public class NetcdfStoreProvider extends DataStoreProvider { */ @Override public String getShortName() { - return NAME; + return Constants.NETCDF; } /** diff --git a/endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/base/Decoder.java b/endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/base/Decoder.java index 545f8c968b..14bf4b1c2a 100644 --- a/endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/base/Decoder.java +++ b/endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/base/Decoder.java @@ -39,6 +39,7 @@ import org.apache.sis.system.Modules; import org.apache.sis.setup.GeometryLibrary; import org.apache.sis.storage.DataStore; import org.apache.sis.storage.DataStoreException; +import org.apache.sis.storage.base.MetadataBuilder; import org.apache.sis.storage.event.StoreListeners; import org.apache.sis.storage.netcdf.internal.Resources; import org.apache.sis.util.Utilities; @@ -233,19 +234,15 @@ public abstract class Decoder extends ReferencingFactoryContainer { public abstract String getFilename(); /** - * Returns an identification of the file format. This method should returns an array of length 1, 2 or 3 as below: + * Adds to the given metadata an identification of the file format. + * Subclasses should invoke the following methods: * * <ul> - * <li>One of the following identifier in the first element: {@code "NetCDF"}, {@code "NetCDF-4"} or other values - * defined by the UCAR library. If known, it will be used as an identifier for a more complete description to - * be provided by {@link org.apache.sis.metadata.sql.MetadataSource#lookup(Class, String)}.</li> - * <li>Optionally a human-readable description in the second array element.</li> - * <li>Optionally a version in the third array element.</li> + * <li>{@link MetadataBuilder#setPredefinedFormat(String, StoreListeners, boolean)}</li> + * <li>{@link MetadataBuilder#addFormatReaderSIS(String)} (if applicable)</li> * </ul> - * - * @return identification of the file format, human-readable description and version number. */ - public abstract String[] getFormatDescription(); + public abstract void addFormatDescription(MetadataBuilder builder); /** * Defines the groups where to search for named attributes, in preference order. diff --git a/endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/classic/ChannelDecoder.java b/endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/classic/ChannelDecoder.java index e920decb07..c8430ff09c 100644 --- a/endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/classic/ChannelDecoder.java +++ b/endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/classic/ChannelDecoder.java @@ -45,6 +45,7 @@ import org.opengis.parameter.InvalidParameterCardinalityException; import org.apache.sis.storage.DataStore; import org.apache.sis.storage.DataStoreException; import org.apache.sis.storage.DataStoreContentException; +import org.apache.sis.storage.base.MetadataBuilder; import org.apache.sis.storage.netcdf.base.DataType; import org.apache.sis.storage.netcdf.base.Decoder; import org.apache.sis.storage.netcdf.base.Node; @@ -262,8 +263,9 @@ public final class ChannelDecoder extends Decoder { * Read the dimension, attribute and variable declarations. We expect exactly 3 lists, * where any of them can be flagged as absent by a long (64 bits) 0. */ - DimensionInfo[] dimensions = null; + @SuppressWarnings("LocalVariableHidesMemberVariable") VariableInfo[] variables = null; + DimensionInfo[] dimensions = null; List<Map.Entry<String,Object>> attributes = List.of(); for (int i=0; i<3; i++) { final long tn = input.readLong(); // Combination of tag and nelems @@ -676,14 +678,13 @@ public final class ChannelDecoder extends Decoder { } /** - * Returns an identification of the file format. The returned value is a reference to a database entry + * Sets an identification of the file format. This method uses a reference to a database entry * known to {@link org.apache.sis.metadata.sql.MetadataSource#lookup(Class, String)}. - * - * @return an identification of the file format in an array of length 1. */ @Override - public String[] getFormatDescription() { - return new String[] {"NetCDF"}; + public void addFormatDescription(MetadataBuilder builder) { + builder.setPredefinedFormat(Constants.NETCDF, null, true); + builder.addFormatReaderSIS(Constants.NETCDF); } /** @@ -719,6 +720,7 @@ public final class ChannelDecoder extends Decoder { * @return dimension of the given name, or {@code null} if none. */ @Override + @SuppressWarnings("StringEquality") protected Dimension findDimension(final String dimName) { DimensionInfo dim = dimensionMap.get(dimName); // Give precedence to exact match before to ignore case. if (dim == null) { @@ -736,6 +738,7 @@ public final class ChannelDecoder extends Decoder { * @param name the name of the variable to search, or {@code null}. * @return the variable of the given name, or {@code null} if none. */ + @SuppressWarnings("StringEquality") private VariableInfo findVariableInfo(final String name) { VariableInfo v = variableMap.get(name); if (v == null && name != null) { @@ -782,6 +785,7 @@ public final class ChannelDecoder extends Decoder { * * @see #getAttributeNames() */ + @SuppressWarnings("StringEquality") private Object findAttribute(final String name) { if (name == null) { return null; diff --git a/endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/ucar/DecoderWrapper.java b/endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/ucar/DecoderWrapper.java index b62529d2d7..5f813e9e2f 100644 --- a/endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/ucar/DecoderWrapper.java +++ b/endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/ucar/DecoderWrapper.java @@ -42,11 +42,19 @@ import ucar.nc2.ft.FeatureDataset; import ucar.nc2.ft.FeatureDatasetPoint; import ucar.nc2.ft.FeatureDatasetFactoryManager; import ucar.nc2.ft.DsgFeatureCollection; +import org.opengis.metadata.citation.Citation; +import org.apache.sis.util.Version; import org.apache.sis.util.ArraysExt; +import org.apache.sis.util.privy.Constants; import org.apache.sis.util.collection.TreeTable; import org.apache.sis.util.collection.TableColumn; +import org.apache.sis.metadata.sql.MetadataSource; +import org.apache.sis.metadata.sql.MetadataStoreException; +import org.apache.sis.referencing.ImmutableIdentifier; import org.apache.sis.storage.DataStore; import org.apache.sis.storage.DataStoreException; +import org.apache.sis.storage.base.MetadataBuilder; +import org.apache.sis.storage.event.StoreListeners; import org.apache.sis.storage.netcdf.base.Decoder; import org.apache.sis.storage.netcdf.base.Variable; import org.apache.sis.storage.netcdf.base.Dimension; @@ -55,7 +63,6 @@ import org.apache.sis.storage.netcdf.base.Grid; import org.apache.sis.storage.netcdf.base.Convention; import org.apache.sis.storage.netcdf.base.DiscreteSampling; import org.apache.sis.setup.GeometryLibrary; -import org.apache.sis.storage.event.StoreListeners; /** @@ -64,6 +71,17 @@ import org.apache.sis.storage.event.StoreListeners; * @author Martin Desruisseaux (Geomatys) */ public final class DecoderWrapper extends Decoder implements CancelTask { + /** + * Version of the <abbr>UCAR</abbr> library, fetched when first requested. + * May be {@code null} if no version information was found. + */ + private static Version version; + + /** + * Whether {@link #version} has been initialized. The result may still be null. + */ + private static boolean versionInitialized; + /** * The netCDF file to read. * This file is set at construction time. @@ -171,21 +189,45 @@ public final class DecoderWrapper extends Decoder implements CancelTask { /** * Returns the file format information provided by the UCAR library. + * The information includes: + * + * <ol> + * <li>{@code "NetCDF"}, {@code "NetCDF-4"} or other values defined by the UCAR library. + * If known, it will be used as an identifier for a more complete description to be + * provided by {@link org.apache.sis.metadata.sql.MetadataSource#lookup(Class, String)}.</li> + * <li>Optionally a human-readable description.</li> + * <li>Optionally a file format version.</li> + * </ol> * * @return identification of the file format, human-readable description and version number. */ @Override - @SuppressWarnings("fallthrough") - public String[] getFormatDescription() { - final String version = Utils.nonEmpty(file.getFileTypeVersion()); - final String[] format = new String[version != null ? 3 : 2]; - switch (format.length) { - default: format[2] = version; // Fallthrough everywhere. - case 2: format[1] = file.getFileTypeDescription(); - case 1: format[0] = file.getFileTypeId(); - case 0: break; // As a matter of principle. + public void addFormatDescription(MetadataBuilder builder) { + String name = Utils.nonEmpty(file.getFileTypeId()); + if (builder.setPredefinedFormat(name, null, false)) { + name = null; } - return format; + builder.addFormatName(Utils.nonEmpty(file.getFileTypeDescription())); + builder.setFormatEdition(Utils.nonEmpty(file.getFileTypeVersion())); + builder.addFormatName(name); // Do nothing if `name` is null. + Citation provider; + try { + provider = MetadataSource.getProvided().lookup(Citation.class, "Unidata"); + } catch (MetadataStoreException e) { + provider = null; + } + builder.addFormatReader(new ImmutableIdentifier(provider, "UCAR", Constants.NETCDF), getVersion()); + } + + /** + * Returns the version number of the netCDF library, or {@code null} if not found. + */ + private static synchronized Version getVersion() { + if (!versionInitialized) { + versionInitialized = true; + version = Version.ofLibrary(NetcdfFile.class).orElse(null); + } + return version; } /** @@ -194,6 +236,7 @@ public final class DecoderWrapper extends Decoder implements CancelTask { */ @Override public void setSearchPath(final String... groupNames) { + @SuppressWarnings("LocalVariableHidesMemberVariable") final Group[] groups = new Group[groupNames.length]; int count = 0; for (final String name : groupNames) { diff --git a/endorsed/src/org.apache.sis.storage.netcdf/test/org/apache/sis/storage/netcdf/MetadataReaderTest.java b/endorsed/src/org.apache.sis.storage.netcdf/test/org/apache/sis/storage/netcdf/MetadataReaderTest.java index 3550322273..ceabb95e9e 100644 --- a/endorsed/src/org.apache.sis.storage.netcdf/test/org/apache/sis/storage/netcdf/MetadataReaderTest.java +++ b/endorsed/src/org.apache.sis.storage.netcdf/test/org/apache/sis/storage/netcdf/MetadataReaderTest.java @@ -22,6 +22,7 @@ import java.time.LocalDateTime; import java.time.temporal.Temporal; import org.opengis.metadata.Metadata; import org.opengis.metadata.citation.Role; +import org.opengis.metadata.citation.Citation; import org.opengis.metadata.citation.DateType; import org.opengis.metadata.extent.TemporalExtent; import org.opengis.metadata.identification.KeywordType; @@ -41,6 +42,7 @@ import org.apache.sis.storage.netcdf.base.TestCase; import org.apache.sis.storage.netcdf.classic.ChannelDecoderTest; // Specific to the geoapi-3.1 and geoapi-4.0 branches: +import static java.util.Map.entry; import org.opengis.test.dataset.ContentVerifier; import org.opengis.test.dataset.TestData; @@ -118,66 +120,70 @@ public final class MetadataReaderTest extends TestCase { * @param ucar whether the UCAR wrapper is used. */ static ContentVerifier compareToExpected(final Metadata actual, final boolean ucar) { - final ContentVerifier verifier = new ContentVerifier(); + final var verifier = new ContentVerifier(); verifier.addPropertyToIgnore(Metadata.class, "metadataStandard"); verifier.addPropertyToIgnore(Metadata.class, "referenceSystemInfo"); + verifier.addPropertyToIgnore(Citation.class, "otherCitationDetails"); // "Read by Foo version XYZ" in format citation. verifier.addPropertyToIgnore(TemporalExtent.class, "extent"); + verifier.addPropertyToIgnore((path) -> path.equals("identificationInfo[0].resourceFormat[0].formatSpecificationCitation.identifier[0].authority")); verifier.addMetadataToVerify(actual); verifier.addExpectedValues( // Hard-coded - "identificationInfo[0].resourceFormat[0].formatSpecificationCitation.alternateTitle[0]", "NetCDF", - "identificationInfo[0].resourceFormat[0].formatSpecificationCitation.title", "NetCDF Classic and 64-bit Offset Format", - "identificationInfo[0].resourceFormat[0].formatSpecificationCitation.citedResponsibleParty[0].party[0].name", "Open Geospatial Consortium", - "identificationInfo[0].resourceFormat[0].formatSpecificationCitation.citedResponsibleParty[0].role", Role.PRINCIPAL_INVESTIGATOR, + entry("identificationInfo[0].resourceFormat[0].formatSpecificationCitation.alternateTitle[0]", "NetCDF"), + entry("identificationInfo[0].resourceFormat[0].formatSpecificationCitation.title", "NetCDF Classic and 64-bit Offset Format"), + entry("identificationInfo[0].resourceFormat[0].formatSpecificationCitation.citedResponsibleParty[0].party[0].name", "Open Geospatial Consortium"), + entry("identificationInfo[0].resourceFormat[0].formatSpecificationCitation.citedResponsibleParty[0].role", Role.PRINCIPAL_INVESTIGATOR), + entry("identificationInfo[0].resourceFormat[0].formatSpecificationCitation.identifier[0].codeSpace", ucar ? "UCAR" : "SIS"), + entry("identificationInfo[0].resourceFormat[0].formatSpecificationCitation.identifier[0].code", "NetCDF"), // Read from the file - "dateInfo[0].date", actual(LocalDateTime.of(2018, 5, 15, 13, 1), ucar), - "dateInfo[0].dateType", DateType.REVISION, - "metadataScope[0].resourceScope", ScopeCode.DATASET, - "identificationInfo[0].abstract", "Global, two-dimensional model data", - "identificationInfo[0].purpose", "GeoAPI conformance tests", - "identificationInfo[0].supplementalInformation", "For testing purpose only.", - "identificationInfo[0].citation.title", "Test data from Sea Surface Temperature Analysis Model", - "identificationInfo[0].descriptiveKeywords[0].keyword[0]", "EARTH SCIENCE > Oceans > Ocean Temperature > Sea Surface Temperature", - "identificationInfo[0].descriptiveKeywords[0].thesaurusName.title", "GCMD Science Keywords", - "identificationInfo[0].descriptiveKeywords[0].type", KeywordType.THEME, - "identificationInfo[0].pointOfContact[0].role", Role.POINT_OF_CONTACT, - "identificationInfo[0].pointOfContact[0].party[0].name", "NOAA/NWS/NCEP", - "identificationInfo[0].citation.citedResponsibleParty[0].role", Role.ORIGINATOR, - "identificationInfo[0].citation.citedResponsibleParty[0].party[0].name", "NOAA/NWS/NCEP", - "identificationInfo[0].citation.date[0].date", actual(LocalDateTime.of(2005, 9, 22, 0, 0), ucar), - "identificationInfo[0].citation.date[1].date", actual(LocalDateTime.of(2018, 5, 15, 13, 0), ucar), - "identificationInfo[0].citation.date[0].dateType", DateType.CREATION, - "identificationInfo[0].citation.date[1].dateType", DateType.REVISION, - "identificationInfo[0].citation.identifier[0].code", "NCEP/SST/Global_5x2p5deg/SST_Global_5x2p5deg_20050922_0000.nc", - "identificationInfo[0].citation.identifier[0].authority.title", "edu.ucar.unidata", - "identificationInfo[0].resourceConstraints[0].useLimitation[0]", "Freely available", - "identificationInfo[0].extent[0].geographicElement[0].extentTypeCode", Boolean.TRUE, - "identificationInfo[0].extent[0].geographicElement[0].westBoundLongitude", -180.0, - "identificationInfo[0].extent[0].geographicElement[0].eastBoundLongitude", 180.0, - "identificationInfo[0].extent[0].geographicElement[0].southBoundLatitude", -90.0, - "identificationInfo[0].extent[0].geographicElement[0].northBoundLatitude", 90.0, - "identificationInfo[0].extent[0].verticalElement[0].maximumValue", 0.0, - "identificationInfo[0].extent[0].verticalElement[0].minimumValue", 0.0, - "identificationInfo[0].spatialRepresentationType[0]", SpatialRepresentationType.GRID, - "spatialRepresentationInfo[0].cellGeometry", CellGeometry.AREA, - "spatialRepresentationInfo[0].numberOfDimensions", 2, - "spatialRepresentationInfo[0].axisDimensionProperties[0].dimensionName", DimensionNameType.COLUMN, - "spatialRepresentationInfo[0].axisDimensionProperties[1].dimensionName", DimensionNameType.ROW, - "spatialRepresentationInfo[0].axisDimensionProperties[0].dimensionSize", 73, - "spatialRepresentationInfo[0].axisDimensionProperties[1].dimensionSize", 73, - "spatialRepresentationInfo[0].transformationParameterAvailability", false, + entry("dateInfo[0].date", actual(LocalDateTime.of(2018, 5, 15, 13, 1), ucar)), + entry("dateInfo[0].dateType", DateType.REVISION), + entry("metadataScope[0].resourceScope", ScopeCode.DATASET), + entry("identificationInfo[0].abstract", "Global, two-dimensional model data"), + entry("identificationInfo[0].purpose", "GeoAPI conformance tests"), + entry("identificationInfo[0].supplementalInformation", "For testing purpose only."), + entry("identificationInfo[0].citation.title", "Test data from Sea Surface Temperature Analysis Model"), + entry("identificationInfo[0].descriptiveKeywords[0].keyword[0]", "EARTH SCIENCE > Oceans > Ocean Temperature > Sea Surface Temperature"), + entry("identificationInfo[0].descriptiveKeywords[0].thesaurusName.title", "GCMD Science Keywords"), + entry("identificationInfo[0].descriptiveKeywords[0].type", KeywordType.THEME), + entry("identificationInfo[0].pointOfContact[0].role", Role.POINT_OF_CONTACT), + entry("identificationInfo[0].pointOfContact[0].party[0].name", "NOAA/NWS/NCEP"), + entry("identificationInfo[0].citation.citedResponsibleParty[0].role", Role.ORIGINATOR), + entry("identificationInfo[0].citation.citedResponsibleParty[0].party[0].name", "NOAA/NWS/NCEP"), + entry("identificationInfo[0].citation.date[0].date", actual(LocalDateTime.of(2005, 9, 22, 0, 0), ucar)), + entry("identificationInfo[0].citation.date[1].date", actual(LocalDateTime.of(2018, 5, 15, 13, 0), ucar)), + entry("identificationInfo[0].citation.date[0].dateType", DateType.CREATION), + entry("identificationInfo[0].citation.date[1].dateType", DateType.REVISION), + entry("identificationInfo[0].citation.identifier[0].code", "NCEP/SST/Global_5x2p5deg/SST_Global_5x2p5deg_20050922_0000.nc"), + entry("identificationInfo[0].citation.identifier[0].authority.title", "edu.ucar.unidata"), + entry("identificationInfo[0].resourceConstraints[0].useLimitation[0]", "Freely available"), + entry("identificationInfo[0].extent[0].geographicElement[0].extentTypeCode", Boolean.TRUE), + entry("identificationInfo[0].extent[0].geographicElement[0].westBoundLongitude", -180.0), + entry("identificationInfo[0].extent[0].geographicElement[0].eastBoundLongitude", 180.0), + entry("identificationInfo[0].extent[0].geographicElement[0].southBoundLatitude", -90.0), + entry("identificationInfo[0].extent[0].geographicElement[0].northBoundLatitude", 90.0), + entry("identificationInfo[0].extent[0].verticalElement[0].maximumValue", 0.0), + entry("identificationInfo[0].extent[0].verticalElement[0].minimumValue", 0.0), + entry("identificationInfo[0].spatialRepresentationType[0]", SpatialRepresentationType.GRID), + entry("spatialRepresentationInfo[0].cellGeometry", CellGeometry.AREA), + entry("spatialRepresentationInfo[0].numberOfDimensions", 2), + entry("spatialRepresentationInfo[0].axisDimensionProperties[0].dimensionName", DimensionNameType.COLUMN), + entry("spatialRepresentationInfo[0].axisDimensionProperties[1].dimensionName", DimensionNameType.ROW), + entry("spatialRepresentationInfo[0].axisDimensionProperties[0].dimensionSize", 73), + entry("spatialRepresentationInfo[0].axisDimensionProperties[1].dimensionSize", 73), + entry("spatialRepresentationInfo[0].transformationParameterAvailability", false), // Variable descriptions (only one in this test). - "contentInfo[0].attributeGroup[0].attribute[0].sequenceIdentifier", "SST", - "contentInfo[0].attributeGroup[0].attribute[0].description", "Sea temperature", - "contentInfo[0].attributeGroup[0].attribute[0].name[0].code", "sea_water_temperature", - "contentInfo[0].attributeGroup[0].attribute[0].transferFunctionType", TransferFunctionType.LINEAR, - "contentInfo[0].attributeGroup[0].attribute[0].scaleFactor", 0.0011, - "contentInfo[0].attributeGroup[0].attribute[0].offset", -1.85, - "contentInfo[0].attributeGroup[0].attribute[0].units", "°C", + entry("contentInfo[0].attributeGroup[0].attribute[0].sequenceIdentifier", "SST"), + entry("contentInfo[0].attributeGroup[0].attribute[0].description", "Sea temperature"), + entry("contentInfo[0].attributeGroup[0].attribute[0].name[0].code", "sea_water_temperature"), + entry("contentInfo[0].attributeGroup[0].attribute[0].transferFunctionType", TransferFunctionType.LINEAR), + entry("contentInfo[0].attributeGroup[0].attribute[0].scaleFactor", 0.0011), + entry("contentInfo[0].attributeGroup[0].attribute[0].offset", -1.85), + entry("contentInfo[0].attributeGroup[0].attribute[0].units", "°C"), - "resourceLineage[0].statement", "Decimated and modified by GeoAPI for inclusion in conformance test suite."); + entry("resourceLineage[0].statement", "Decimated and modified by GeoAPI for inclusion in conformance test suite.")); return verifier; } 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 a6acdad992..258a05277a 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 @@ -97,7 +97,6 @@ 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; @@ -1037,34 +1036,34 @@ public class MetadataBuilder { * </ul> * * This method should be invoked <strong>before</strong> any other method writing in the - * {@code identificationInfo/resourceFormat} node. If this exception throws an exception, - * than that exception should be reported as a warning. Example: - * - * {@snippet lang="java" : - * try { - * metadata.setPredefinedFormat("MyFormat"); - * } catch (MetadataStoreException e) { - * metadata.addFormatName("MyFormat"); - * listeners.warning(Level.FINE, null, e); - * } - * metadata.addCompression("decompression technique"); - * } + * {@code identificationInfo/resourceFormat} node. * * @param abbreviation the format short name or abbreviation, or {@code null} for no-operation. - * @throws MetadataStoreException if this method cannot connect to the {@code jdbc/SpatialMetadata} database. - * Callers should generally handle this exception as a recoverable one (i.e. log a warning and continue). + * @param listeners where to report a failure to connect to the {@code jdbc/SpatialMetadata} database. + * @param fallback whether to fallback on {@link #addFormatName(String)} if the description was not found. + * @return whether the format description has been added. * * @see #addCompression(CharSequence) * @see #addFormatName(CharSequence) */ - public final void setPredefinedFormat(final String abbreviation) throws MetadataStoreException { + public boolean setPredefinedFormat(final String abbreviation, final StoreListeners listeners, boolean fallback) { if (abbreviation != null && abbreviation.length() != 0) { - if (format == null) { + if (format == null) try { format = MetadataSource.getProvided().lookup(Format.class, abbreviation); - } else { + return true; + } catch (MetadataStoreException e) { + if (listeners != null) { + listeners.warning(Level.FINE, null, e); + } else { + Logging.recoverableException(StoreUtilities.LOGGER, null, null, e); + } + } + if (fallback) { addFormatName(abbreviation); + return true; } } + return false; } /** @@ -3028,12 +3027,11 @@ public class MetadataBuilder { * <li>{@code metadata/identificationInfo/resourceFormat/formatSpecificationCitation/alternateTitle}</li> * </ul> * - * If this method is used together with {@link #setPredefinedFormat(String)}, - * then {@code setPredefinedFormat(…)} should be invoked <strong>before</strong> this method. + * If this method is used together with {@link #setPredefinedFormat setPredefinedFormat(…)}, + * then the predefined format should be set <strong>before</strong> this method. * * @param value the format name, or {@code null} for no-operation. * - * @see #setPredefinedFormat(String) * @see #setFormatEdition(CharSequence) * @see #addCompression(CharSequence) */ @@ -3073,12 +3071,11 @@ public class MetadataBuilder { * <li>{@code metadata/identificationInfo/resourceFormat/formatSpecificationCitation/edition}</li> * </ul> * - * If this method is used together with {@link #setPredefinedFormat(String)}, - * then {@code setPredefinedFormat(…)} should be invoked <strong>before</strong> this method. + * If this method is used together with {@link #setPredefinedFormat setPredefinedFormat(…)}, + * then the predefined format should be set <strong>before</strong> this method. * * @param value the format edition, or {@code null} for no-operation. * - * @see #setPredefinedFormat(String) * @see #addFormatName(CharSequence) */ public final void setFormatEdition(final CharSequence value) { @@ -3097,19 +3094,29 @@ public class MetadataBuilder { * <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. + * If this method is used together with {@link #setPredefinedFormat setPredefinedFormat(…)}, + * then the predefined format should be set <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) { + CharSequence title = null; + Citation authority = driver.getAuthority(); + if (authority != null) { + title = authority.getTitle(); + if (title != null) { + for (CharSequence t : authority.getAlternateTitles()) { + if (t.length() < title.length()) { + title = t; // Alternate titles are often abbreviations. + } + } + } + } final DefaultCitation c = getFormatCitation(); addIfNotPresent(c.getIdentifiers(), driver); addIfNotPresent(c.getOtherCitationDetails(), - Resources.formatInternational( - Resources.Keys.ReadBy_2, - driver.getCodeSpace(), + Resources.formatInternational(Resources.Keys.ReadBy_2, (title != null) ? title : driver.getCodeSpace(), (version != null) ? version : Vocabulary.formatInternational(Vocabulary.Keys.Unspecified))); } @@ -3117,13 +3124,11 @@ public class MetadataBuilder { * 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. + * @param name the format name, 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); + public void addFormatReaderSIS(final String name) { + if (name != null) { + addFormatReader(new ImmutableIdentifier(Citations.SIS, Constants.SIS, name), Version.SIS); } } @@ -3135,12 +3140,11 @@ public class MetadataBuilder { * <li>{@code metadata/identificationInfo/resourceFormat/fileDecompressionTechnique}</li> * </ul> * - * If this method is used together with {@link #setPredefinedFormat(String)}, - * then {@code setPredefinedFormat(…)} should be invoked <strong>before</strong> this method. + * If this method is used together with {@link #setPredefinedFormat setPredefinedFormat(…)}, + * then the predefined format should be set <strong>before</strong> this method. * * @param value the compression name, or {@code null} for no-operation. * - * @see #setPredefinedFormat(String) * @see #addFormatName(CharSequence) */ public final void addCompression(final CharSequence value) { 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 119e835081..c45855411b 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 @@ -32,7 +32,6 @@ import java.io.BufferedReader; import java.io.LineNumberReader; import java.io.IOException; import java.net.URI; -import java.nio.charset.Charset; import javax.measure.Unit; import javax.measure.quantity.Time; import org.opengis.util.GenericName; @@ -71,7 +70,6 @@ import org.apache.sis.geometry.GeneralEnvelope; import org.apache.sis.geometry.ImmutableEnvelope; import org.apache.sis.geometry.wrapper.Geometries; import org.apache.sis.metadata.iso.DefaultMetadata; -import org.apache.sis.metadata.sql.MetadataStoreException; import org.apache.sis.setup.OptionKey; import org.apache.sis.measure.Units; @@ -140,13 +138,6 @@ final class Store extends URIDataStore implements FeatureSet { */ private BufferedReader source; - /** - * The character encoding, or {@code null} if unspecified (in which case the platform default is assumed). - * Note that the default value is different than the moving feature specification, which requires UTF-8. - * See "Departures from Moving Features specification" in package javadoc. - */ - private final Charset encoding; - /** * The metadata object, or {@code null} if not yet created. */ @@ -235,9 +226,9 @@ final class Store extends URIDataStore implements FeatureSet { source = (r instanceof BufferedReader) ? (BufferedReader) r : new LineNumberReader(r); geometries = Geometries.factory(connector.getOption(OptionKey.GEOMETRY_LIBRARY)); dissociate = connector.getOption(DataOptionKey.FOLIATION_REPRESENTATION) == FoliationRepresentation.FRAGMENTED; - GeneralEnvelope envelope = null; - FeatureType featureType = null; - Foliation foliation = null; + @SuppressWarnings("LocalVariableHidesMemberVariable") GeneralEnvelope envelope = null; + @SuppressWarnings("LocalVariableHidesMemberVariable") FeatureType featureType = null; + @SuppressWarnings("LocalVariableHidesMemberVariable") Foliation foliation = null; try { final List<String> elements = new ArrayList<>(); source.mark(StorageConnector.READ_AHEAD_LIMIT); @@ -296,7 +287,6 @@ final class Store extends URIDataStore implements FeatureSet { } catch (IllegalArgumentException | DateTimeException e) { throw new DataStoreContentException(getLocale(), StoreProvider.NAME, super.getDisplayName(), source).initCause(e); } - this.encoding = connector.getOption(OptionKey.ENCODING); this.envelope = ImmutableEnvelope.castOrCopy(envelope); this.featureType = featureType; this.foliation = foliation; @@ -349,8 +339,10 @@ final class Store extends URIDataStore implements FeatureSet { */ @SuppressWarnings("fallthrough") private GeneralEnvelope parseEnvelope(final List<String> elements) throws DataStoreException, FactoryException { + @SuppressWarnings("LocalVariableHidesMemberVariable") + int spatialDimensionCount = 2; // Another result of this method to be computed as a side-effect. + CoordinateReferenceSystem crs = null; - int spatialDimensionCount = 2; boolean isDimExplicit = false; double[] lowerCorner = ArraysExt.EMPTY_DOUBLE; double[] upperCorner = ArraysExt.EMPTY_DOUBLE; @@ -410,7 +402,8 @@ final class Store extends URIDataStore implements FeatureSet { * Assumed never part of the authority code. We need to build the temporal component ourselves * in order to set the origin to the start time. */ - final GeneralEnvelope envelope; + @SuppressWarnings("LocalVariableHidesMemberVariable") + final GeneralEnvelope envelope; // Value will be assigned to `this.envelope` by the caller. if (crs != null) { int count = 0; final CoordinateReferenceSystem[] components = new CoordinateReferenceSystem[3]; @@ -632,13 +625,8 @@ final class Store extends URIDataStore implements FeatureSet { if (metadata == null) { final MetadataBuilder builder = new MetadataBuilder(); final String format = (timeEncoding != null) && hasTrajectories ? StoreProvider.MOVING : StoreProvider.NAME; - try { - builder.setPredefinedFormat(format); - } catch (MetadataStoreException e) { - builder.addFormatName(format); - listeners.warning(Level.FINE, null, e); - } - builder.addFormatReader(getProvider()); + builder.setPredefinedFormat(format, listeners, true); + builder.addFormatReaderSIS(format); 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 e74e6765ad..75e4d8c67f 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,7 +21,6 @@ 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; @@ -33,7 +32,6 @@ import java.awt.image.SampleModel; import java.awt.image.WritableRaster; import org.opengis.metadata.Metadata; import org.opengis.metadata.maintenance.ScopeCode; -import org.apache.sis.metadata.sql.MetadataStoreException; import org.apache.sis.coverage.SampleDimension; import org.apache.sis.coverage.grid.GridGeometry; import org.apache.sis.coverage.grid.GridCoverage2D; @@ -152,13 +150,8 @@ abstract class RasterStore extends PRJDataStore implements GridCoverageResource final void createMetadata(final String formatName, final String formatKey) throws DataStoreException { final GridGeometry gridGeometry = getGridGeometry(); // May cause parsing of header. final MetadataBuilder builder = new MetadataBuilder(); - try { - builder.setPredefinedFormat(formatKey); - } catch (MetadataStoreException e) { - builder.addFormatName(formatName); - listeners.warning(Level.FINE, null, e); - } - builder.addFormatReader(getProvider()); + builder.setPredefinedFormat(formatKey, listeners, true); + builder.addFormatReaderSIS(provider != null ? provider.getShortName() : null); 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 b454c8d45a..692af3ddd9 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 @@ -21,7 +21,6 @@ import java.util.Collection; import java.util.Map; import java.util.HashMap; import java.util.Optional; -import java.util.logging.Level; import java.io.Closeable; import java.io.IOException; import java.io.EOFException; @@ -54,7 +53,6 @@ import org.apache.sis.storage.base.PRJDataStore; import org.apache.sis.storage.base.MetadataBuilder; import org.apache.sis.storage.base.AuxiliaryContent; import org.apache.sis.referencing.privy.AffineTransform2D; -import org.apache.sis.metadata.sql.MetadataStoreException; import org.apache.sis.util.CharSequences; import org.apache.sis.util.ArraysExt; import org.apache.sis.util.privy.ListOfUnknownSize; @@ -529,17 +527,14 @@ loop: for (int convention=0;; convention++) { String format = reader().getFormatName(); for (final String key : KNOWN_FORMATS) { if (key.equalsIgnoreCase(format)) { - try { - builder.setPredefinedFormat(key); + if (builder.setPredefinedFormat(key, listeners, false)) { format = null; - } catch (MetadataStoreException e) { - listeners.warning(Level.FINE, null, e); } break; } } builder.addFormatName(format); // Does nothing if `format` is null. - builder.addFormatReader(getProvider()); + builder.addFormatReaderSIS(WorldFileStoreProvider.NAME); 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.util/main/org/apache/sis/util/Version.java b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/Version.java index deb39d2408..0dfa8fca6c 100644 --- a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/Version.java +++ b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/Version.java @@ -16,8 +16,18 @@ */ package org.apache.sis.util; +import java.net.URI; +import java.net.URL; +import java.io.File; import java.io.Serializable; +import java.util.Optional; import java.util.StringTokenizer; +import java.util.jar.JarFile; +import java.util.jar.Manifest; +import java.util.jar.Attributes; +import java.util.logging.Logger; +import org.apache.sis.system.Modules; +import org.apache.sis.util.logging.Logging; import static org.apache.sis.system.Modules.MAJOR_VERSION; import static org.apache.sis.system.Modules.MINOR_VERSION; @@ -39,7 +49,7 @@ import static org.apache.sis.system.Modules.MINOR_VERSION; * encouraged to make sure that subclasses remain immutable for more predictable behavior. * * @author Martin Desruisseaux (IRD, Geomatys) - * @version 0.4 + * @version 1.5 * @since 0.3 */ public class Version implements CharSequence, Comparable<Version>, Serializable { @@ -74,6 +84,8 @@ public class Version implements CharSequence, Comparable<Version>, Serializable /** * The version in string form. + * + * @see #toString() */ private final String version; @@ -102,6 +114,45 @@ public class Version implements CharSequence, Comparable<Version>, Serializable this.version = version; } + /** + * Returns the version of the library that provides the given class. This method reads the + * {@code "Implementation-Version"} attribute of the {@code META-INF/MANIFEST.MF} file of + * the <abbr>JAR</abbr> file that contains the given class. + * + * @param member any public class of the library for which to get the version. + * @return version declared by the library that provides the given class. + * + * @see Attributes.Name#IMPLEMENTATION_VERSION + * @since 1.5 + */ + public static Optional<Version> ofLibrary(final Class<?> member) { + URL url = member.getResource(member.getSimpleName() + ".class"); + if ("jar".equalsIgnoreCase(url.getProtocol())) { + String path = url.getPath(); + if (path != null) { + int s = path.indexOf('!'); + if (s >= 0) { + path = path.substring(0, s); + } + try (JarFile jar = new JarFile(new File(new URI(path)))) { + final Manifest manifest = jar.getManifest(); + if (manifest != null) { + final Attributes attributes = manifest.getMainAttributes(); + if (attributes != null) { + String version = attributes.getValue(Attributes.Name.IMPLEMENTATION_VERSION); + if (version != null) { + return Optional.of(new Version(version)); + } + } + } + } catch (Exception e) { + Logging.recoverableException(Logger.getLogger(Modules.UTILITIES), Version.class, "ofLibrary", e); + } + } + } + return Optional.empty(); + } + /** * Returns an instance for the given integer values. * The {@code components} array must contain at least 1 element, where: 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 3afa5e81b8..21595c5d94 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 @@ -138,6 +138,12 @@ public final class Constants extends Static { */ public static final String GDAL = "GDAL"; + /** + * The {@value} code space. Uses upper case "N" in the assumption that this name is used + * in the beginning of sentences. Otherwise, the <abbr>UCAR</abbr> usage is "netCDF". + */ + public static final String NETCDF = "NetCDF"; + /** * 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/endorsed/src/org.apache.sis.util/test/org/apache/sis/util/VersionTest.java b/endorsed/src/org.apache.sis.util/test/org/apache/sis/util/VersionTest.java index c7ae3783bf..3932b77c10 100644 --- a/endorsed/src/org.apache.sis.util/test/org/apache/sis/util/VersionTest.java +++ b/endorsed/src/org.apache.sis.util/test/org/apache/sis/util/VersionTest.java @@ -140,4 +140,13 @@ public final class VersionTest extends TestCase { final Version version = new Version("1.6.b2"); assertNotSame(version, assertSerializedEquals(version)); } + + /** + * Tests {@link Version#ofLibrary(Class)}. + */ + @Test + public void testOfLibrary() { + Version version = Version.ofLibrary(javax.measure.Unit.class).orElseThrow(); + assertEquals(Version.valueOf(2, 1, 3), version); + } } diff --git a/geoapi/snapshot b/geoapi/snapshot index 367366e67f..8b993c74d8 160000 --- a/geoapi/snapshot +++ b/geoapi/snapshot @@ -1 +1 @@ -Subproject commit 367366e67f7f860e100e0e8dafa3a03f34162e71 +Subproject commit 8b993c74d8f49b215f6b4aa2747e0f362d97d3e3