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

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

commit 589b2aa29aa3fa6a54142cb0d518992ec9cebcba
Merge: 4c46d7bb98 2563f4abe5
Author: Martin Desruisseaux <martin.desruisse...@geomatys.com>
AuthorDate: Fri Aug 30 15:49:57 2024 +0200

    Merge branch 'geoapi-3.1'.

 buildSrc/build.gradle.kts                          |    4 +-
 .../apache/sis/buildtools/gradle/Conventions.java  |    3 +-
 .../apache/sis/buildtools/gradle/Dependency.java   |    5 +
 .../sis/buildtools/gradle/ModularCompilation.java  |    2 +-
 .../org/apache/sis/buildtools/maven/Generator.java |   52 +-
 .../apache/sis/coverage/grid/GridDerivation.java   |    1 +
 .../sis/coverage/privy/ColorModelFactory.java      |    2 -
 .../sis/feature/builder/AttributeTypeBuilder.java  |    2 +-
 .../apache/sis/geometry/wrapper/GeometryType.java  |  178 +-
 .../apache/sis/geometry/wrapper/esri/Factory.java  |    2 +-
 .../apache/sis/geometry/wrapper/j2d/Factory.java   |    2 +-
 .../sis/geometry/wrapper/GeometryTypeTest.java     |   25 +-
 .../org.apache.sis.metadata/main/module-info.java  |    1 +
 .../org/apache/sis/metadata/iso/ISOMetadata.java   |    2 +-
 .../iso/quality/DefaultQuantitativeResult.java     |    2 +-
 .../apache/sis/metadata/sql/MetadataWriter.java    |   14 +-
 .../org/apache/sis/metadata/sql/privy/Dialect.java |   81 +-
 .../apache/sis/metadata/sql/privy/SQLBuilder.java  |   40 +
 .../sis/metadata/sql/privy/SQLUtilities.java       |   22 +-
 .../sis/metadata/sql/privy/ScriptRunner.java       |    6 +-
 .../apache/sis/metadata/sql/privy/Supports.java    |   74 +
 .../org/apache/sis/util/iso/DefaultScopedName.java |    5 +-
 .../sis/metadata/sql/IdentifierGeneratorTest.java  |    2 +-
 .../org/apache/sis/metadata/sql/TestDatabase.java  |   10 +-
 .../gazetteer/ReferencingByIdentifiers.java        |    6 +-
 .../main/module-info.java                          |    1 +
 .../org/apache/sis/geometry/ArrayEnvelope.java     |   22 +-
 .../org/apache/sis/geometry/DirectPosition1D.java  |    2 +-
 .../apache/sis/geometry/GeneralDirectPosition.java |    6 +-
 .../main/org/apache/sis/io/wkt/Formatter.java      |    5 +-
 .../sis/parameter/DefaultParameterDescriptor.java  |    3 +
 .../parameter/DefaultParameterDescriptorGroup.java |    4 +
 .../org/apache/sis/parameter/TensorValues.java     |    6 +
 .../sis/referencing/AbstractIdentifiedObject.java  |    3 +
 .../apache/sis/referencing/crs/AbstractCRS.java    |    2 +
 .../sis/referencing/crs/AbstractDerivedCRS.java    |    6 +-
 .../sis/referencing/crs/AbstractSingleCRS.java     |    6 +-
 .../sis/referencing/crs/DefaultCompoundCRS.java    |    6 +-
 .../sis/referencing/crs/DefaultDerivedCRS.java     |    4 +-
 .../sis/referencing/crs/DefaultProjectedCRS.java   |    4 +-
 .../org/apache/sis/referencing/cs/AbstractCS.java  |    4 +
 .../cs/DefaultCoordinateSystemAxis.java            |    2 +
 .../sis/referencing/datum/AbstractDatum.java       |   65 +-
 .../sis/referencing/datum/BursaWolfParameters.java |   20 +-
 .../referencing/datum/DefaultDatumEnsemble.java    |    5 +-
 .../sis/referencing/datum/DefaultEllipsoid.java    |    4 +
 .../referencing/datum/DefaultGeodeticDatum.java    |  159 +-
 .../sis/referencing/datum/DefaultImageDatum.java   |    4 +
 .../referencing/datum/DefaultPrimeMeridian.java    |    4 +
 .../referencing/datum/DefaultTemporalDatum.java    |    4 +
 .../referencing/datum/DefaultVerticalDatum.java    |   91 +
 .../apache/sis/referencing/datum/PseudoDatum.java  |   33 +-
 .../sis/referencing/datum/TimeDependentBWP.java    |   16 +-
 .../referencing/factory/GeodeticObjectFactory.java |   70 +-
 .../referencing/factory/sql/EPSGDataAccess.java    |   14 +-
 .../operation/AbstractCoordinateOperation.java     |    2 +
 .../operation/DefaultConcatenatedOperation.java    |    6 +-
 .../operation/DefaultOperationMethod.java          |    2 +
 .../operation/DefaultPassThroughOperation.java     |    6 +-
 .../operation/matrix/GeneralMatrix.java            |    2 +-
 .../operation/matrix/NonSquareMatrix.java          |    2 +-
 .../operation/transform/ConcatenatedTransform.java |    2 +-
 .../operation/transform/CopyTransform.java         |    2 +-
 .../operation/transform/DatumShiftTransform.java   |    6 +-
 .../transform/EllipsoidToCentricTransform.java     |    6 +-
 .../transform/ExponentialTransform1D.java          |    4 +-
 .../operation/transform/InterpolatedTransform.java |    6 +-
 .../operation/transform/LinearTransform1D.java     |    4 +-
 .../operation/transform/MolodenskyTransform.java   |    6 +-
 .../operation/transform/PassThroughTransform.java  |    6 +-
 .../operation/transform/PoleRotation.java          |    4 +
 .../operation/transform/PowerTransform1D.java      |    4 +-
 .../operation/transform/ProjectiveTransform.java   |    4 +-
 .../operation/transform/ScaleTransform.java        |    4 +-
 .../transform/SpecializableTransform.java          |    9 +-
 .../operation/transform/TranslationTransform.java  |    4 +-
 .../operation/transform/WraparoundTransform.java   |    4 +
 .../main/org/apache/sis/storage/landsat/Band.java  |    2 +-
 .../apache/sis/storage/landsat/LandsatStore.java   |    7 +-
 .../apache/sis/storage/landsat/package-info.java   |    2 +-
 .../apache/sis/storage/geotiff/GeoTiffStore.java   |    6 +-
 .../sis/storage/geotiff/GeoTiffStoreProvider.java  |    8 +-
 .../org/apache/sis/storage/geotiff/Writer.java     |    5 +
 .../apache/sis/storage/geotiff/package-info.java   |    3 +-
 .../sis/storage/geotiff/spi/SchemaModifier.java    |    2 +-
 .../storage/geotiff/writer/CompressionChannel.java |    6 +-
 .../sis/storage/geotiff/writer/PixelChannel.java   |    1 +
 .../storage/geotiff/writer/PredictorChannel.java   |    1 +
 .../org/apache/sis/storage/geotiff/writer/ZIP.java |    1 +
 .../org/apache/sis/storage/netcdf/NetcdfStore.java |    7 +-
 .../apache/sis/storage/netcdf/package-info.java    |    2 +-
 .../main/module-info.java                          |   24 +-
 .../org/apache/sis/storage/sql/DataAccess.java     |  338 +++
 .../apache/sis/storage/sql/ResourceDefinition.java |   47 +-
 .../main/org/apache/sis/storage/sql/SQLStore.java  |  503 +++-
 .../apache/sis/storage/sql/SQLStoreProvider.java   |   16 +-
 .../apache/sis/storage/sql/SimpleFeatureStore.java |  168 ++
 .../apache/sis/storage/sql/feature/Analyzer.java   |  172 +-
 .../sis/storage/sql/feature/CRSEncoding.java}      |   28 +-
 .../apache/sis/storage/sql/feature/Database.java   |  507 ++--
 .../sis/storage/sql/feature/FeatureAnalyzer.java   |    3 +-
 .../sis/storage/sql/feature/FeatureIterator.java   |   14 +-
 .../sis/storage/sql/feature/FeatureStream.java     |   11 +-
 .../sis/storage/sql/feature/GeometryGetter.java    |    4 +-
 .../storage/sql/feature/GeometryTypeEncoding.java  |   56 +
 .../sis/storage/sql/feature/InfoStatements.java    |  729 +++--
 .../sis/storage/sql/feature/QueryAnalyzer.java     |    2 +-
 .../sis/storage/sql/feature/SchemaModifier.java    |    2 +-
 .../sis/storage/sql/feature/SpatialSchema.java     |  212 ++
 .../org/apache/sis/storage/sql/feature/Table.java  |    5 +-
 .../sis/storage/sql/feature/TableAnalyzer.java     |    1 -
 .../sis/storage/sql/feature/ValueGetter.java       |    6 +-
 .../org/apache/sis/storage/sql/package-info.java   |   32 +-
 .../sis/storage/sql/postgis/ExtendedInfo.java      |    7 +-
 .../sis/storage/sql/postgis/ExtentEstimator.java   |    2 +-
 .../apache/sis/storage/sql/postgis/Postgres.java   |   42 +-
 .../org/apache/sis/util/stream/DeferredStream.java |   64 +-
 .../org/apache/sis/storage/sql/DataAccessTest.java |   69 +
 .../org/apache/sis/storage/sql/SQLStoreTest.java   |   45 +-
 .../storage/sql/feature/InfoStatementsTest.java    |  161 ++
 .../sql/feature/SelectionClauseWriterTest.java     |    2 +-
 .../sql/feature/TemporalValueGetterTest.java       |    2 +-
 .../sis/storage/sql/postgis/PostgresTest.java      |    4 +-
 .../org.apache.sis.storage/main/module-info.java   |    4 +
 .../apache/sis/io/stream/InternalOptionKey.java    |   25 +
 .../main/org/apache/sis/storage/DataStore.java     |   22 +-
 .../org/apache/sis/storage/StorageConnector.java   |   91 +-
 .../main/org/apache/sis/storage/URLDataSource.java |  198 ++
 .../org/apache/sis/storage/WritableAggregate.java  |    2 +-
 .../apache/sis/storage/base/MetadataFetcher.java   |  126 +-
 .../apache/sis/storage/base/TiledGridCoverage.java |   18 +-
 .../org/apache/sis/storage/internal/Resources.java |    5 +
 .../sis/storage/internal/Resources.properties      |    1 +
 .../sis/storage/internal/Resources_fr.properties   |    1 +
 .../src/org.apache.sis.util/main/module-info.java  |    1 +
 .../main/org/apache/sis/util/privy/Constants.java  |    2 +-
 .../org/apache/sis/test/TestConfiguration.java     |   18 +-
 incubator/build.gradle.kts                         |   66 +
 .../org.apache.sis.storage.DataStoreProvider       |    4 +
 .../main/module-info.java                          |   57 +
 .../org/apache/sis/storage/geopackage/Content.java |  466 ++++
 .../sis/storage/geopackage/ContentHandler.java     |   91 +
 .../sis/storage/geopackage/ContentWriter.java      |  232 ++
 .../org/apache/sis/storage/geopackage/Core.sql     |  126 +
 .../sis/storage/geopackage/Extension Metadata.sql  |   38 +
 .../sis/storage/geopackage/Extension Schema.sql    |   40 +
 .../Extension Tiled Gridded Coverage Data.sql      |   47 +
 .../apache/sis/storage/geopackage/GpkgStore.java   |  456 +++
 .../sis/storage/geopackage/GpkgStoreProvider.java  |  284 ++
 .../apache/sis/storage/geopackage/Initializer.java |  178 ++
 .../sis/storage/geopackage/package-info.java       |   22 +-
 .../apache/sis/storage/geopackage/FeatureSet.sql   |  164 ++
 .../storage/geopackage/GpkgStoreProviderTest.java  |   32 +-
 .../sis/storage/geopackage/GpkgStoreTest.java      |  339 +++
 .../conformance/CoreConformanceTest.java           |  219 ++
 .../org.apache.sis.storage.DataStoreProvider       |    4 +
 ...org.apache.sis.storage.gimi.isobmff.BoxRegistry |    8 +
 .../main/module-info.java                          |   25 +-
 .../org/apache/sis/storage/gimi/GimiProvider.java  |  108 +
 .../org/apache/sis/storage/gimi/GimiStore.java     |  177 ++
 .../main/org/apache/sis/storage/gimi/GimiTile.java |   66 +
 .../apache/sis/storage/gimi/GimiTileMatrix.java    |  116 +
 .../apache/sis/storage/gimi/GimiTileMatrixSet.java |   72 +
 .../org/apache/sis/storage/gimi/GimiUtils.java     |   52 +
 .../main/org/apache/sis/storage/gimi/Item.java     |  136 +
 .../org/apache/sis/storage/gimi/ResourceGrid.java  |  134 +
 .../apache/sis/storage/gimi/ResourceImageJpeg.java |   61 +
 .../storage/gimi/ResourceImageUncompressed.java    |  181 ++
 .../apache/sis/storage/gimi/ResourcePyramid.java   |  145 +
 .../apache/sis/storage/gimi/ResourceUnknown.java   |   34 +-
 .../storage/gimi/internal/MatrixGridRessource.java |  137 +
 .../sis/storage/gimi/internal/ScaleSortedMap.java  |  104 +
 .../sis/storage/gimi/internal/StringUtilities.java |   63 +
 .../sis/storage/gimi/internal/TileMatrices.java    |   64 +
 .../org/apache/sis/storage/gimi/isobmff/Box.java   |  278 ++
 .../sis/storage/gimi/isobmff/BoxRegistry.java      |   64 +
 .../apache/sis/storage/gimi/isobmff/FullBox.java   |   23 +-
 .../sis/storage/gimi/isobmff/ISOBMFFReader.java    |  157 ++
 .../apache/sis/storage/gimi/isobmff/gimi/GIMI.java |   65 +
 .../gimi/isobmff/gimi/ModelTiePointProperty.java   |   63 +
 .../isobmff/gimi/ModelTransformationProperty.java  |   34 +-
 .../gimi/isobmff/gimi/WellKnownText2Property.java  |   27 +-
 .../gimi/isobmff/iso14496_10/ContentDescribes.java |   19 +-
 .../gimi/isobmff/iso14496_10/ISO14496_10.java      |   60 +
 .../isobmff/iso14496_12/ColourInformation.java     |   55 +
 .../gimi/isobmff/iso14496_12/CombinaisonType.java  |   33 +-
 .../gimi/isobmff/iso14496_12/EntityToGroup.java    |   28 +-
 .../gimi/isobmff/iso14496_12/ExtendedType.java     |   25 +-
 .../gimi/isobmff/iso14496_12/Extension.java        |   19 +-
 .../isobmff/iso14496_12/FDItemInfoExtension.java   |   28 +-
 .../storage/gimi/isobmff/iso14496_12/FileType.java |   16 +-
 .../gimi/isobmff/iso14496_12/FreeSpace.java        |   19 +-
 .../gimi/isobmff/iso14496_12/GeneralType.java      |   52 +
 .../gimi/isobmff/iso14496_12/GroupList.java        |   23 +-
 .../gimi/isobmff/iso14496_12/HandlerReference.java |   33 +-
 .../gimi/isobmff/iso14496_12/ISO14496_12.java      |  114 +
 .../isobmff/iso14496_12/IdentifiedMediaData.java   |   49 +
 .../storage/gimi/isobmff/iso14496_12/ItemData.java |   28 +-
 .../gimi/isobmff/iso14496_12/ItemFullProperty.java |   16 +-
 .../storage/gimi/isobmff/iso14496_12/ItemInfo.java |   42 +-
 .../gimi/isobmff/iso14496_12/ItemInfoEntry.java    |   74 +
 .../gimi/isobmff/iso14496_12/ItemLocation.java     |  155 ++
 .../gimi/isobmff/iso14496_12/ItemProperties.java   |   23 +-
 .../gimi/isobmff/iso14496_12/ItemProperty.java     |   16 +-
 .../iso14496_12/ItemPropertyAssociation.java       |   68 +
 .../isobmff/iso14496_12/ItemPropertyContainer.java |   23 +-
 .../gimi/isobmff/iso14496_12/ItemReference.java    |   49 +
 .../gimi/isobmff/iso14496_12/MediaData.java        |   28 +-
 .../sis/storage/gimi/isobmff/iso14496_12/Meta.java |   24 +-
 .../storage/gimi/isobmff/iso14496_12/Movie.java    |   24 +-
 .../gimi/isobmff/iso14496_12/MovieHeader.java      |   30 +-
 .../gimi/isobmff/iso14496_12/OriginalFileType.java |   23 +-
 .../gimi/isobmff/iso14496_12/PrimaryItem.java      |   32 +-
 .../iso14496_12/ProgressiveDownloadInfo.java       |   28 +-
 .../iso14496_12/SingleItemTypeReference.java       |   49 +
 .../iso14496_12/SingleItemTypeReferenceLarge.java  |   26 +-
 .../storage/gimi/isobmff/iso14496_12/Track.java    |   24 +-
 .../gimi/isobmff/iso14496_12/TrackHeader.java      |   28 +-
 .../gimi/isobmff/iso23001_17/ChromaLocation.java   |   27 +-
 .../isobmff/iso23001_17/ComponentDefinition.java   |   67 +
 .../gimi/isobmff/iso23001_17/ComponentPalette.java |   57 +
 .../iso23001_17/ComponentPatternDefinition.java    |   49 +
 .../iso23001_17/ComponentReferenceLevel.java       |   27 +-
 .../iso23001_17/DepthMappingInformation.java       |   27 +-
 .../isobmff/iso23001_17/DisparityInformation.java  |   27 +-
 .../iso23001_17/FieldInterlaceProperty.java        |   27 +-
 .../isobmff/iso23001_17/FieldInterlaceType.java    |   27 +-
 .../iso23001_17/FramePackingInformation.java       |   27 +-
 .../gimi/isobmff/iso23001_17/ISO23001_17.java      |   92 +
 .../iso23001_17/PolarizationPatternDefinition.java |   27 +-
 .../isobmff/iso23001_17/SensorBadPixelsMap.java    |   27 +-
 .../iso23001_17/SensorNonUniformityCorrection.java |   27 +-
 .../gimi/isobmff/iso23001_17/TAIClockInfo.java     |   48 +
 .../gimi/isobmff/iso23001_17/TAITimeStamp.java     |   45 +
 .../iso23001_17/UncompressedFrameConfig.java       |   92 +
 .../isobmff/iso23008_12/DerivedImageReference.java |   19 +-
 .../gimi/isobmff/iso23008_12/ISO23008_12.java      |   71 +
 .../iso23008_12/ImagePyramidEntityGroup.java       |   62 +
 .../isobmff/iso23008_12/ImageSpatialExtents.java   |   44 +-
 .../iso23008_12/PixelInformationProperty.java      |   30 +-
 .../iso23008_12/UserDescriptionProperty.java       |   33 +-
 .../org/apache/sis/storage/gimi/package-info.java  |   14 +-
 .../org/apache/sis/storage/gimi/StoreTest.java     |   17 +-
 .../main/module-info.java                          |   15 +-
 .../main/org/apache/sis/storage/gsf/Attitude.java  |   89 +
 .../main/org/apache/sis/storage/gsf/Comment.java   |   63 +
 .../main/org/apache/sis/storage/gsf/DataID.java    |   69 +
 .../main/org/apache/sis/storage/gsf/GSF.java       | 1786 ++++++++++++
 .../apache/sis/storage/gsf/GSFRecordReader.java    |   88 +
 .../apache/sis/storage/gsf/HVNavigationError.java  |   89 +
 .../main/org/apache/sis/storage/gsf/Header.java    |   40 +-
 .../main/org/apache/sis/storage/gsf/History.java   |  105 +
 .../apache/sis/storage/gsf/NavigationError.java    |   96 +
 .../main/org/apache/sis/storage/gsf/Position.java  |   74 +
 .../apache/sis/storage/gsf/PositionOffsets.java    |   86 +
 .../sis/storage/gsf/ProcessingParameters.java      |   65 +
 .../main/org/apache/sis/storage/gsf/Records.java   |  146 +
 .../apache/sis/storage/gsf/SBSensorSpecific.java   |  110 +
 .../main/org/apache/sis/storage/gsf/SVP.java       |   90 +
 .../org/apache/sis/storage/gsf/ScaleFactors.java   |   72 +
 .../main/org/apache/sis/storage/gsf/ScaleInfo.java |   85 +
 .../apache/sis/storage/gsf/SensorParameters.java   |  106 +
 .../org/apache/sis/storage/gsf/SensorSpecific.java |  108 +
 .../org/apache/sis/storage/gsf/SingleBeamPing.java |  216 ++
 .../org/apache/sis/storage/gsf/StructClass.java    |  219 ++
 .../org/apache/sis/storage/gsf/SwathBathyPing.java |  354 +++
 .../apache/sis/storage/gsf/SwathBathySummary.java  |   94 +
 .../main/org/apache/sis/storage/gsf/TimeSpec.java  |   60 +
 .../org/apache/sis/storage/gsf/package-info.java   |   14 +-
 .../sis/storage/gsf/specific/BDBSpecific.java      |  133 +
 .../sis/storage/gsf/specific/CmpSassSpecific.java  |   73 +
 .../sis/storage/gsf/specific/DeltaTSpecific.java   |  405 +++
 .../sis/storage/gsf/specific/EM100Specific.java    |  152 +
 .../sis/storage/gsf/specific/EM121ASpecific.java   |  454 +++
 .../sis/storage/gsf/specific/EM12Specific.java     |  356 +++
 .../storage/gsf/specific/EM3ImagerySpecific.java   |  488 ++++
 .../sis/storage/gsf/specific/EM3RawSpecific.java   |  875 ++++++
 .../sis/storage/gsf/specific/EM3RawTxSector.java   |  488 ++++
 .../sis/storage/gsf/specific/EM3RunTime.java       | 1313 +++++++++
 .../sis/storage/gsf/specific/EM3Specific.java      |  539 ++++
 .../storage/gsf/specific/EM4ImagerySpecific.java   |  670 +++++
 .../sis/storage/gsf/specific/EM4Specific.java      |  832 ++++++
 .../sis/storage/gsf/specific/EM4TxSector.java      |  533 ++++
 .../sis/storage/gsf/specific/EM950Specific.java    |  320 +++
 .../sis/storage/gsf/specific/EMPUStatus.java       |  356 +++
 .../apache/sis/storage/gsf/specific/EMRunTime.java | 1531 ++++++++++
 .../sis/storage/gsf/specific/EchotracSpecific.java |  184 ++
 .../sis/storage/gsf/specific/ElacMkIISpecific.java |  364 +++
 .../storage/gsf/specific/GeoSwathPlusSpecific.java | 1212 ++++++++
 .../sis/storage/gsf/specific/KMALLExtraDet.java    |  218 ++
 .../storage/gsf/specific/KMALLImagerySpecific.java |  126 +
 .../sis/storage/gsf/specific/KMALLSpecific.java    |  988 +++++++
 .../sis/storage/gsf/specific/KMALLTxSector.java    |  670 +++++
 .../gsf/specific/Klein5410BssImagerySpecific.java  |  297 ++
 .../storage/gsf/specific/Klein5410BssSpecific.java |  851 ++++++
 .../sis/storage/gsf/specific/MGD77Specific.java    |  320 +++
 .../sis/storage/gsf/specific/NOSHDBSpecific.java   |  140 +
 .../gsf/specific/R2SonicImagerySpecific.java       | 1355 +++++++++
 .../sis/storage/gsf/specific/R2SonicSpecific.java  | 1568 +++++++++++
 .../gsf/specific/Reson7100ImagerySpecific.java     |  173 ++
 .../storage/gsf/specific/Reson7100Specific.java    | 2267 +++++++++++++++
 .../gsf/specific/Reson8100ImagerySpecific.java     |  127 +
 .../storage/gsf/specific/Reson8100Specific.java    | 1301 +++++++++
 .../gsf/specific/ResonTSeriesImagerySpecific.java  |  173 ++
 .../storage/gsf/specific/ResonTSeriesSpecific.java | 2923 ++++++++++++++++++++
 .../sis/storage/gsf/specific/SBAmpSpecific.java    |  320 +++
 .../sis/storage/gsf/specific/SBBDBSpecific.java    |  444 +++
 .../storage/gsf/specific/SBEchotracSpecific.java   |  309 +++
 .../sis/storage/gsf/specific/SBMGD77Specific.java  |  400 +++
 .../sis/storage/gsf/specific/SBNOSHDBSpecific.java |  218 ++
 .../storage/gsf/specific/SBNavisoundSpecific.java  |  173 ++
 .../storage/gsf/specific/SeaBat8101Specific.java   |  850 ++++++
 .../sis/storage/gsf/specific/SeaBatIISpecific.java |  490 ++++
 .../sis/storage/gsf/specific/SeaBatSpecific.java   |  320 +++
 .../storage/gsf/specific/SeaBeam2112Specific.java  |  524 ++++
 .../sis/storage/gsf/specific/SeabeamSpecific.java  |   94 +
 .../sis/storage/gsf/specific/SeamapSpecific.java   |  521 ++++
 .../sis/storage/gsf/specific/TypeIIISpecific.java  |  319 +++
 .../test/org/apache/sis/storage/gsf/StoreTest.java |   17 +-
 .../apache/sis/storage/shapefile/dbf/DBFField.java |   22 +-
 .../sis/storage/shapefile/dbf/DBFHeader.java       |    3 +-
 netbeans-project/ivy.xml                           |   17 +-
 netbeans-project/nbproject/project.properties      |    9 +
 netbeans-project/nbproject/project.xml             |    1 +
 optional/build.gradle.kts                          |   95 +-
 .../apache/sis/gui/coverage/CoverageCanvas.java    |    4 +-
 .../main/META-INF/NOTICE                           |    1 +
 .../org.apache.sis.setup.InstallationResources     |    4 +
 .../main/module-info.java                          |   43 +
 .../sis/resources/embedded/EmbeddedResources.java  |  153 +
 .../sis/resources/embedded/package-info.java       |   35 +
 .../resources/embedded/EmbeddedResourcesTest.java  |  141 +
 .../apache/sis/resources/embedded/Generator.java   |  222 ++
 .../main/META-INF/NOTICE                           |   10 +
 .../org.apache.sis.setup.InstallationResources     |    4 +
 .../main/module-info.java                          |   41 +
 .../sis/referencing/factory/sql/epsg/.gitignore    |    7 +
 .../sis/referencing/factory/sql/epsg/README.md     |   23 +
 .../factory/sql/epsg/ScriptProvider.java           |  104 +
 .../referencing/factory/sql/epsg/package-info.java |   37 +
 .../factory/sql/epsg/DataScriptFormatter.java      |    3 +-
 .../factory/sql/epsg/DataScriptFormatterTest.java  |    2 +-
 .../referencing/factory/sql/epsg/DebugTools.sql    |    0
 .../sis/referencing/factory/sql/epsg/README.md     |    3 +-
 .../factory/sql/epsg/ScriptProviderTest.java       |   91 +
 settings.gradle.kts                                |   20 +-
 346 files changed, 46246 insertions(+), 1672 deletions(-)

diff --cc 
buildSrc/src/main/java/org/apache/sis/buildtools/gradle/Dependency.java
index 1ac9152a01,45f821fdd7..b95757d8c3
--- a/buildSrc/src/main/java/org/apache/sis/buildtools/gradle/Dependency.java
+++ b/buildSrc/src/main/java/org/apache/sis/buildtools/gradle/Dependency.java
@@@ -70,7 -72,11 +72,10 @@@ public final class Dependency 
          Map.entry("gui",                      "application:sis-javafx"),      
      // Optional.
          Map.entry("cql",                      "core:sis-cql"),                
      // Incubator.
          Map.entry("storage.shapefile",        "core:sis-shapefile"),
+         Map.entry("storage.gimi",             "core:sis-gimi"),
+         Map.entry("storage.gsf",              "core:sis-gsf"),
+         Map.entry("storage.geopackage",       "core:sis-geopackage"),
          Map.entry("storage.coveragejson",     "core:sis-coveragejson"),
 -        Map.entry("portrayal.map",            "core:sis-portrayal-map"),
          Map.entry("webapp",                   "application:sis-webapp")
      );
  
diff --cc 
endorsed/src/org.apache.sis.referencing/main/org/apache/sis/io/wkt/Formatter.java
index aade867a1e,d99f986546..1bb9d6cded
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/io/wkt/Formatter.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/io/wkt/Formatter.java
@@@ -87,11 -87,9 +87,12 @@@ import org.apache.sis.geometry.Abstract
  import org.apache.sis.geometry.AbstractEnvelope;
  import org.apache.sis.xml.NilObject;
  
 -// Specific to the geoapi-3.1 and geoapi-4.0 branches:
 -import org.opengis.util.ControlledVocabulary;
 -import org.opengis.referencing.ObjectDomain;
 +// Specific to the main branch:
 +import org.opengis.util.CodeList;
 +import org.opengis.referencing.ReferenceIdentifier;
 +import org.apache.sis.referencing.DefaultObjectDomain;
++import org.apache.sis.referencing.datum.AbstractDatum;
 +import org.apache.sis.referencing.internal.Legacy;
  
  
  /**
@@@ -889,8 -887,8 +890,8 @@@ public class Formatter implements Local
      private void appendForSubtypes(final IdentifiedObject object) {
          InternationalString anchor = null, scope = null;
          Extent area = null;
--        if (object instanceof Datum) {
-             anchor = ((Datum) object).getAnchorPoint();
 -            anchor = ((Datum) object).getAnchorDefinition().orElse(null);
++        if (object instanceof AbstractDatum) {
++            anchor = ((AbstractDatum) object).getAnchorPoint();
          } else if (!(object instanceof ReferenceSystem || object instanceof 
CoordinateOperation)) {
              return;
          }
diff --cc 
endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/AbstractIdentifiedObject.java
index 8619fca435,7454658ac6..a4bcb99c7e
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/AbstractIdentifiedObject.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/AbstractIdentifiedObject.java
@@@ -576,6 -567,6 +576,9 @@@ public class AbstractIdentifiedObject e
       * The domain includes a scope (description of the primary purpose of 
this object) together
       * with a domain of validity (spatial and temporal extent in which the 
object can be used).
       *
++     * <div class="note"><b>Upcoming API change:</b>
++     * the type of collection elements may become {@code ObjectDomain} after 
the next GeoAPI release.</div>
++     *
       * @return scopes and domains of validity of this object.
       *
       * @since 1.4
diff --cc 
endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/AbstractDatum.java
index 3cd29c850f,7fc8ec1690..cabcc1e865
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/AbstractDatum.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/AbstractDatum.java
@@@ -46,10 -46,9 +46,11 @@@ import org.apache.sis.io.wkt.Formatter
  import static org.apache.sis.util.Utilities.deepEquals;
  import static org.apache.sis.util.collection.Containers.property;
  
 -// Specific to the geoapi-3.1 and geoapi-4.0 branches:
 -import org.opengis.referencing.datum.DynamicReferenceFrame;
 -import org.opengis.metadata.Identifier;
 +// Specific to the main branch:
 +import org.opengis.referencing.ReferenceIdentifier;
 +import org.opengis.metadata.extent.Extent;
 +import org.apache.sis.referencing.internal.Legacy;
++import org.apache.sis.pending.geoapi.referencing.DynamicReferenceFrame;
  
  
  /**
@@@ -360,35 -335,20 +361,46 @@@ public class AbstractDatum extends Abst
      @XmlSchemaType(name = "date")
      @XmlElement(name = "realizationEpoch")
      public Date getRealizationEpoch() {
 -        return Datum.super.getRealizationEpoch();
 +        return getAnchorEpoch().map(Legacy::toDate).orElse(null);
 +    }
 +
 +    /**
 +     * Returns the region or timeframe in which this datum is valid, or 
{@code null} if unspecified.
 +     *
 +     * @return area or region or timeframe in which this datum is valid, or 
{@code null}.
 +     *
 +     * @deprecated Replaced by {@link #getDomains()} as of ISO 19111:2019.
 +     */
 +    @Override
 +    @Deprecated(since = "1.4")
 +    public Extent getDomainOfValidity() {
 +        return Legacy.getDomainOfValidity(getDomains());
 +    }
 +
 +    /**
 +     * Returns the domain or limitations of usage, or {@code null} if 
unspecified.
 +     *
 +     * @return description of domain of usage, or limitations of usage, for 
which this datum object is valid.
 +     *
 +     * @deprecated Replaced by {@link #getDomains()} as of ISO 19111:2019.
 +     */
 +    @Override
 +    @Deprecated(since = "1.4")
 +    public InternationalString getScope() {
 +        return Legacy.getScope(getDomains());
      }
  
+     /**
+      * Returns the frame reference epoch if this datum is dynamic, or {@code 
null} if this datum is static.
+      * This method is overridden with public access in Apache SIS {@code 
Dynamic} subclasses.
+      * The default implementation should be suitable for non-SIS 
implementations.
+      *
+      * @return the reference epoch if this datum is dynamic, or {@code null} 
if this datum is static.
+      */
+     Temporal getFrameReferenceEpoch() {
+         return (this instanceof DynamicReferenceFrame) ? 
((DynamicReferenceFrame) this).getFrameReferenceEpoch() : null;
+     }
+ 
      /**
       * Returns the date on which the datum definition was published.
       *
@@@ -471,6 -433,12 +483,12 @@@
       * {@linkplain #getAnchorDefinition() anchor definition}, {@linkplain 
#getAnchorEpoch() anchor epoch},
       * and the {@linkplain #getDomains() domains}.
       *
+      * <h4>Static versus dynamic datum</h4>
 -     * If this datum implements the {@link DynamicReferenceFrame} interface, 
then the given object needs
++     * If this datum implements the {@code DynamicReferenceFrame} interface, 
then the given object needs
+      * to also implement that interfaces and provide the same reference epoch 
for being considered equal.
 -     * Conversely, if this datum does not implement {@link 
DynamicReferenceFrame}, then the given object
++     * Conversely, if this datum does not implement {@code 
DynamicReferenceFrame}, then the given object
+      * also need to <em>not</em> implement that interface for being 
considered equal.
+      *
       * @param  object  the object to compare to {@code this}.
       * @param  mode    {@link ComparisonMode#STRICT STRICT} for performing a 
strict comparison, or
       *                 {@link ComparisonMode#IGNORE_METADATA IGNORE_METADATA} 
for comparing only
@@@ -485,13 -453,18 +503,24 @@@
          switch (mode) {
              case STRICT: {
                  final var that = (AbstractDatum) object;
-                 return Objects.equals(anchorEpoch,      that.anchorEpoch) &&
-                        Objects.equals(anchorDefinition, 
that.anchorDefinition);
+                 return Objects.equals(anchorEpoch,       that.anchorEpoch) &&
+                        Objects.equals(anchorDefinition,  
that.anchorDefinition) &&
+                        Objects.equals(publicationDate,   
that.publicationDate) &&
+                        Objects.equals(conventionalRS,    that.conventionalRS);
              }
              case BY_CONTRACT: {
-                 final Datum that = (Datum) object;
-                 return deepEquals(getRealizationEpoch(), 
that.getRealizationEpoch(), mode) &&
-                        deepEquals(getAnchorPoint(),      
that.getAnchorPoint(),      mode);
 -                final var that = (Datum) object;
++                if (!(object instanceof AbstractDatum)) {
++                    return getAnchorEpoch().isEmpty() &&
++                           getAnchorDefinition().isEmpty() &&
++                           getPublicationDate().isEmpty() &&
++                           getConventionalRS().isEmpty();
++                }
++                final var that = (AbstractDatum) object;
+                 return compareDynamicReferenceFrames(that, mode) &&
+                        deepEquals(getAnchorEpoch(),      
that.getAnchorEpoch(), mode) &&
+                        deepEquals(getAnchorDefinition(), 
that.getAnchorDefinition(), mode) &&
+                        deepEquals(getPublicationDate(),  
that.getPublicationDate(), mode) &&
+                        deepEquals(getConventionalRS(),   
that.getConventionalRS(), mode);
              }
              default: {
                  /*
diff --cc 
endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultGeodeticDatum.java
index d01ff32b00,b95286c5f9..bb9ca3308d
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultGeodeticDatum.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultGeodeticDatum.java
@@@ -54,8 -52,9 +52,9 @@@ import static org.apache.sis.util.Argum
  import static org.apache.sis.util.ArgumentChecks.ensureNonNullElement;
  import static org.apache.sis.referencing.privy.WKTUtilities.toFormattable;
  
 -// Specific to the geoapi-3.1 and geoapi-4.0 branches:
 -import org.opengis.referencing.datum.DynamicReferenceFrame;
 -import org.opengis.metadata.Identifier;
 +// Specific to the main branch:
 +import org.opengis.referencing.ReferenceIdentifier;
++import org.apache.sis.pending.geoapi.referencing.DynamicReferenceFrame;
  
  
  /**
@@@ -511,6 -495,112 +495,115 @@@ public class DefaultGeodeticDatum exten
          return selector.best();
      }
  
+     /**
+      * Returns the position vector transformation (geocentric domain) as an 
affine transform.
+      * If this datum is dynamic, the frame reference epoch is used.
+      * Otherwise, a time is computed from the temporal area of interest.
+      *
 -     * @see DynamicReferenceFrame#getFrameReferenceEpoch()
+      * @see BursaWolfParameters#getPositionVectorTransformation(Temporal)
+      */
+     private Matrix createTransformation(final BursaWolfParameters bursaWolf, 
final Extent areaOfInterest) {
+         Temporal epoch = null;
+         /*
+          * Implementation note: we know that we do not need to compute an 
instant if the parameters is
+          * not a subclass of BursaWolfParameters. This optimisation covers 
the vast majority of cases.
+          */
+         if (bursaWolf.getClass() != BursaWolfParameters.class) {
+             epoch = getFrameReferenceEpoch();
+             if (epoch == null) {
+                 epoch = Extents.getInstant(areaOfInterest, null, 
0.5).orElse(null);
+                 // 0.5 is for choosing the instant midway between start and 
end.
+             }
+         }
+         return bursaWolf.getPositionVectorTransformation(epoch);
+     }
+ 
+     /**
+      * A geodetic reference frame in which some of the defining parameters 
have time dependency.
+      * The parameter values are valid at the time given by the
+      * {@linkplain #getFrameReferenceEpoch() frame reference epoch}.
+      *
++     * <div class="note"><b>Upcoming API change:</b>
++     * this class may implement a {@code DynamicReferenceFrame} interface 
from the GeoAPI standard
++     * after the next GeoAPI release. In the meantime, {@code 
DynamicReferenceFrame} is not a public API.</div>
++     *
+      * @author  Martin Desruisseaux (Geomatys)
+      * @version 1.5
+      * @since   1.5
+      */
+     public static class Dynamic extends DefaultGeodeticDatum implements 
DynamicReferenceFrame {
+         /**
+          * For cross-version compatibility.
+          */
+         private static final long serialVersionUID = 6117199873814779662L;
+ 
+         /**
+          * The epoch to which the definition of the dynamic reference frame 
is referenced.
+          */
+         @SuppressWarnings("serial")                     // Standard Java 
implementations are serializable.
+         private final Temporal frameReferenceEpoch;
+ 
+         /**
+          * Creates a dynamic reference frame from the given properties.
+          * See super-class constructor for more information.
+          *
+          * @param  properties     the properties to be given to the 
identified object.
+          * @param  ellipsoid      the ellipsoid.
+          * @param  primeMeridian  the prime meridian.
+          * @param  epoch          the epoch to which the definition of the 
dynamic reference frame is referenced.
+          */
+         public Dynamic(Map<String,?> properties, Ellipsoid ellipsoid, 
PrimeMeridian primeMeridian, Temporal epoch) {
+             super(properties, ellipsoid, primeMeridian);
+             frameReferenceEpoch = Objects.requireNonNull(epoch);
+         }
+ 
+         /**
+          * Creates a new datum with the same values as the specified datum, 
which must be dynamic.
+          *
+          * @param  datum  the datum to copy.
 -         * @throws ClassCastException if the given datum is not an instance 
of {@link DynamicReferenceFrame}.
++         * @throws ClassCastException if the given datum is not an instance 
of {@code DynamicReferenceFrame}.
+          *
+          * @see #castOrCopy(GeodeticDatum)
+          */
+         protected Dynamic(final GeodeticDatum datum) {
+             super(datum);
+             frameReferenceEpoch = 
Objects.requireNonNull(((DynamicReferenceFrame) 
datum).getFrameReferenceEpoch());
+         }
+ 
+         /**
+          * Returns the epoch to which the coordinates of stations defining 
the dynamic reference frame are referenced.
+          * The type of the returned object depends on the epoch accuracy and 
the calendar in use.
+          * It may be merely a {@link java.time.Year}.
+          *
+          * @return the epoch to which the definition of the dynamic reference 
frame is referenced.
+          */
+         @Override
+         public Temporal getFrameReferenceEpoch() {
+             return frameReferenceEpoch;
+         }
+ 
+         /**
+          * Compares the specified object with this datum for equality.
+          *
+          * @hidden because nothing new to said.
+          */
+         @Override
+         public boolean equals(final Object object, final ComparisonMode mode) 
{
+             return super.equals(object) && (mode != ComparisonMode.STRICT ||
+                     frameReferenceEpoch.equals(((Dynamic) 
object).frameReferenceEpoch));
+         }
+ 
+         /**
+          * Invoked by {@code hashCode()} for computing the hash code when 
first needed.
+          *
+          * @hidden because nothing new to said.
+          */
+         @Override
+         protected long computeHashCode() {
+             return super.computeHashCode() + 31 * 
frameReferenceEpoch.hashCode();
+         }
+     }
+ 
      /**
       * Returns {@code true} if either the {@linkplain #getName() primary 
name} or at least
       * one {@linkplain #getAlias() alias} matches the given string according 
heuristic rules.
diff --cc 
endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultVerticalDatum.java
index fd46ba3bd7,0ea312039c..2f07233dd9
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultVerticalDatum.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultVerticalDatum.java
@@@ -35,8 -36,11 +36,9 @@@ import org.apache.sis.metadata.privy.Im
  // Specific to the main and geoapi-3.1 branches:
  import org.opengis.referencing.datum.VerticalDatumType;
  
 -// Specific to the geoapi-3.1 and geoapi-4.0 branches:
 -import java.util.Optional;
 -import org.opengis.referencing.datum.DynamicReferenceFrame;
 -import org.opengis.referencing.datum.RealizationMethod;
 -import org.opengis.metadata.Identifier;
 +// Specific to the main branch:
 +import org.opengis.referencing.ReferenceIdentifier;
++import org.apache.sis.pending.geoapi.referencing.DynamicReferenceFrame;
  
  
  /**
@@@ -231,12 -248,96 +233,97 @@@ public class DefaultVerticalDatum exten
       * but in a programmatic way more suitable to coordinate transformation 
engines.
       *
       * @return the type of this vertical datum.
 -     *
 -     * @deprecated As of ISO 19111:2019, the {@code VerticalDatumType} 
argument is replaced by {@code RealizationMethod}.
       */
      @Override
 -    @Deprecated(since = "2.0")  // Temporary version number until this branch 
is released.
      public VerticalDatumType getVerticalDatumType() {
 -        return type;
 +        return type();
      }
  
+     /**
+      * A vertical reference frame in which some of the defining parameters 
have time dependency.
+      * The parameter values are valid at the time given by the
+      * {@linkplain #getFrameReferenceEpoch() frame reference epoch}.
+      *
++     * <div class="note"><b>Upcoming API change:</b>
++     * this class may implement a {@code DynamicReferenceFrame} interface 
from the GeoAPI standard
++     * after the next GeoAPI release. In the meantime, {@code 
DynamicReferenceFrame} is not a public API.</div>
++     *
+      * @author  Martin Desruisseaux (Geomatys)
+      * @version 1.5
+      * @since   1.5
+      */
+     public static class Dynamic extends DefaultVerticalDatum implements 
DynamicReferenceFrame {
+         /**
+          * For cross-version compatibility.
+          */
+         private static final long serialVersionUID = -2047994195060747008L;
+ 
+         /**
+          * The epoch to which the definition of the dynamic reference frame 
is referenced.
+          */
+         @SuppressWarnings("serial")                     // Standard Java 
implementations are serializable.
+         private final Temporal frameReferenceEpoch;
+ 
+         /**
+          * Creates a dynamic reference frame from the given properties.
+          * See super-class constructor for more information.
+          *
 -         * @param  properties     the properties to be given to the 
identified object.
 -         * @param  epoch          the epoch to which the definition of the 
dynamic reference frame is referenced.
++         * @param  properties  the properties to be given to the identified 
object.
++         * @param  type        the type of this vertical datum.
++         * @param  epoch       the epoch to which the definition of the 
dynamic reference frame is referenced.
+          */
 -        public Dynamic(Map<String,?> properties, RealizationMethod method, 
Temporal epoch) {
 -            super(properties, method);
++        public Dynamic(Map<String,?> properties, VerticalDatumType type, 
Temporal epoch) {
++            super(properties, type);
+             frameReferenceEpoch = Objects.requireNonNull(epoch);
+         }
+ 
+         /**
+          * Creates a new datum with the same values as the specified datum, 
which must be dynamic.
+          *
+          * @param  datum   the datum to copy.
 -         * @param  method  the realization method (geoid, tidal, 
<i>etc.</i>), or {@code null} if unspecified.
 -         * @throws ClassCastException if the given datum is not an instance 
of {@link DynamicReferenceFrame}.
++         * @throws ClassCastException if the given datum is not an instance 
of {@code DynamicReferenceFrame}.
+          *
+          * @see #castOrCopy(VerticalDatum)
+          */
+         protected Dynamic(final VerticalDatum datum) {
+             super(datum);
+             frameReferenceEpoch = 
Objects.requireNonNull(((DynamicReferenceFrame) 
datum).getFrameReferenceEpoch());
+         }
+ 
+         /**
+          * Returns the epoch to which the coordinates of stations defining 
the dynamic reference frame are referenced.
+          * The type of the returned object depends on the epoch accuracy and 
the calendar in use.
+          * It may be merely a {@link java.time.Year}.
+          *
+          * @return the epoch to which the definition of the dynamic reference 
frame is referenced.
+          */
+         @Override
+         public Temporal getFrameReferenceEpoch() {
+             return frameReferenceEpoch;
+         }
+ 
+         /**
+          * Compares the specified object with this datum for equality.
+          *
+          * @hidden because nothing new to said.
+          */
+         @Override
+         public boolean equals(final Object object, final ComparisonMode mode) 
{
+             return super.equals(object) && (mode != ComparisonMode.STRICT ||
+                     frameReferenceEpoch.equals(((Dynamic) 
object).frameReferenceEpoch));
+         }
+ 
+         /**
+          * Invoked by {@code hashCode()} for computing the hash code when 
first needed.
+          *
+          * @hidden because nothing new to said.
+          */
+         @Override
+         protected long computeHashCode() {
+             return super.computeHashCode() + 31 * 
frameReferenceEpoch.hashCode();
+         }
+     }
+ 
      /**
       * Compares this vertical datum with the specified object for equality.
       *
@@@ -245,8 -346,11 +332,10 @@@
       *                 {@link ComparisonMode#IGNORE_METADATA IGNORE_METADATA} 
for comparing only
       *                 properties relevant to coordinate transformations.
       * @return {@code true} if both objects are equal.
+      *
+      * @hidden because nothing new to said.
       */
      @Override
 -    @SuppressWarnings("deprecation")
      public boolean equals(final Object object, final ComparisonMode mode) {
          if (object == this) {
              return true;                                                    
// Slight optimization.
diff --cc 
endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/PseudoDatum.java
index c215bd1211,eed9024fad..ed0dc4a523
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/PseudoDatum.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/PseudoDatum.java
@@@ -43,8 -43,9 +43,9 @@@ import java.util.Date
  import org.opengis.referencing.ReferenceIdentifier;
  import org.opengis.metadata.extent.Extent;
  
 -// Specific to the geoapi-3.1 and geoapi-4.0 branches:
 -import java.time.temporal.Temporal;
 -import org.opengis.referencing.ObjectDomain;
 +// Specific to the main branch:
++import org.apache.sis.referencing.DefaultObjectDomain;
 +import static 
org.apache.sis.pending.geoapi.referencing.MissingMethods.getDatumEnsemble;
  
  
  /**
@@@ -427,6 -465,18 +431,20 @@@ public abstract class PseudoDatum<D ext
          return ensemble.getIdentifiers();
      }
  
+     /**
+      * Returns the usage of the datum ensemble.
+      *
++     * <div class="note"><b>Upcoming API change:</b>
++     * the type of collection elements may become {@code ObjectDomain} after 
the next GeoAPI release.</div>
++     *
+      * @return {@code ensemble.getDomains()}.
+      *
+      * @hidden because nothing new to said.
+      */
 -    @Override
 -    public Collection<ObjectDomain> getDomains() {
++    public Collection<DefaultObjectDomain> getDomains() {
+         return ensemble.getDomains();
+     }
+ 
      /**
       * Returns the domain of validity common to all datum members, if any.
       *
diff --cc 
endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/GeodeticObjectFactory.java
index ae296a5de6,098500a7cf..2a4cf23176
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/GeodeticObjectFactory.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/GeodeticObjectFactory.java
@@@ -25,6 -25,6 +25,7 @@@ import java.util.logging.Level
  import java.util.logging.Logger;
  import java.util.logging.LogRecord;
  import java.util.concurrent.atomic.AtomicReference;
++import java.time.temporal.Temporal;
  import java.lang.reflect.Constructor;
  import jakarta.xml.bind.JAXBException;
  import javax.measure.Unit;
@@@ -721,6 -701,39 +722,37 @@@ public class GeodeticObjectFactory exte
          return unique("createGeodeticDatum", datum);
      }
  
+     /**
+      * Creates a dynamic geodetic reference frame from ellipsoid and frame 
reference epoch.
+      * The arguments are the same as for the {@linkplain 
#createGeodeticDatum(Map, Ellipsoid,
+      * PrimeMeridian) static datum}, with the addition of a mandatory frame 
reference epoch.
 -     * The returned object implements the {@link DynamicReferenceFrame} 
interface.
+      *
+      * @param  properties     name and other properties to give to the new 
object.
+      * @param  ellipsoid      the ellipsoid to use in new geodetic reference 
frame.
+      * @param  primeMeridian  the prime meridian to use in new geodetic 
reference frame.
+      * @param  epoch          the epoch to which the definition of the 
dynamic reference frame is referenced.
+      * @throws FactoryException if the object creation failed.
+      *
+      * @see DefaultGeodeticDatum.Dynamic#Dynamic(Map, Ellipsoid, 
PrimeMeridian, Temporal)
+      * @see GeodeticAuthorityFactory#createGeodeticDatum(String)
+      *
+      * @since 1.5
+      */
 -    @Override
+     public GeodeticDatum createGeodeticDatum(final Map<String,?> properties,
+                                              final Ellipsoid     ellipsoid,
+                                              final PrimeMeridian 
primeMeridian,
+                                              final Temporal      epoch)
+             throws FactoryException
+     {
+         final DefaultGeodeticDatum datum;
+         try {
+             datum = new DefaultGeodeticDatum.Dynamic(complete(properties), 
ellipsoid, primeMeridian, epoch);
+         } catch (IllegalArgumentException exception) {
+             throw new InvalidGeodeticParameterException(exception);
+         }
+         return unique("createGeodeticDatum", datum);
+     }
+ 
      /**
       * Creates a prime meridian, relative to Greenwich.
       * Defines the origin from which longitude values are determined.
@@@ -1068,22 -1082,30 +1100,21 @@@
      }
  
      /**
-      * Creates a vertical <abbr>CRS</abbr> from a reference frame.
-      * This is a shortcut for the {@linkplain #createVerticalCRS(Map, 
VerticalDatum, DefaultDatumEnsemble, VerticalCS)
-      * more generic method} without datum ensemble.
+      * Creates a static vertical datum from a realization method.
+      * The default implementation creates a {@link DefaultVerticalDatum} 
instance.
       *
       * @param  properties  name and other properties to give to the new 
object.
 -     * @param  method      the realization method of the vertical datum, or 
{@code null} if none.
 +     *                     Available properties are {@linkplain ObjectFactory 
listed there}.
 +     * @param  datum       vertical datum to use in created CRS.
 +     * @param  cs          the vertical coordinate system for the created CRS.
 +     * @return the coordinate reference system for the given properties.
       * @throws FactoryException if the object creation failed.
 -     *
 -     * @see DefaultVerticalDatum#DefaultVerticalDatum(Map, RealizationMethod)
 -     * @see GeodeticAuthorityFactory#createVerticalDatum(String)
 -     *
 -     * @since 2.0 (temporary version number until this branch is released)
       */
      @Override
 -    public VerticalDatum createVerticalDatum(final Map<String,?> properties,
 -                                             final RealizationMethod method)
 +    public VerticalCRS createVerticalCRS(Map<String, ?> properties, 
VerticalDatum datum, VerticalCS cs)
              throws FactoryException
      {
 -        final DefaultVerticalDatum datum;
 -        try {
 -            datum = new DefaultVerticalDatum(complete(properties), method);
 -        } catch (IllegalArgumentException exception) {
 -            throw new InvalidGeodeticParameterException(exception);
 -        }
 -        return unique("createVerticalDatum", datum);
 +        return createVerticalCRS(properties, datum, null, cs);
      }
  
      /**
@@@ -1110,6 -1135,37 +1141,35 @@@
          return unique("createVerticalDatum", datum);
      }
  
+     /**
 -     * Creates a dynamic vertical datum from a realization method and a frame 
reference epoch.
 -     * The arguments are the same as for the {@linkplain 
#createVerticalDatum(Map, RealizationMethod)
++     * Creates a dynamic vertical datum from an enumerated type value and a 
frame reference epoch.
++     * The arguments are the same as for the {@linkplain 
#createVerticalDatum(Map, VerticalDatumType)
+      * static datum}, with the addition of a mandatory frame reference epoch.
 -     * The returned object implements the {@link DynamicReferenceFrame} 
interface.
+      *
+      * @param  properties  name and other properties to give to the new 
object.
 -     * @param  method      the realization method of the vertical datum, or 
{@code null} if none.
++     * @param  type        the type of this vertical datum (often geoidal).
+      * @param  epoch       the epoch to which the definition of the dynamic 
reference frame is referenced.
+      * @throws FactoryException if the object creation failed.
+      *
 -     * @see DefaultVerticalDatum.Dynamic#Dynamic(Map, RealizationMethod, 
Temporal)
++     * @see DefaultVerticalDatum.Dynamic#Dynamic(Map, VerticalDatumType, 
Temporal)
+      * @see GeodeticAuthorityFactory#createVerticalDatum(String)
+      *
 -     * @since 2.0 (temporary version number until this branch is released)
++     * @since 1.5
+      */
 -    @Override
+     public VerticalDatum createVerticalDatum(final Map<String,?> properties,
 -                                             final RealizationMethod method,
++                                             final VerticalDatumType type,
+                                              final Temporal epoch)
+             throws FactoryException
+     {
+         final DefaultVerticalDatum datum;
+         try {
 -            datum = new DefaultVerticalDatum.Dynamic(complete(properties), 
method, epoch);
++            datum = new DefaultVerticalDatum.Dynamic(complete(properties), 
type, epoch);
+         } catch (IllegalArgumentException exception) {
+             throw new InvalidGeodeticParameterException(exception);
+         }
+         return unique("createVerticalDatum", datum);
+     }
+ 
      /**
       * Creates a vertical coordinate system.
       * This coordinate system can be used with vertical and derived CRS.
diff --cc 
endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/SQLStore.java
index fd0419bee0,468f211524..6011e0d287
--- 
a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/SQLStore.java
+++ 
b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/SQLStore.java
@@@ -82,10 -124,14 +124,14 @@@ public abstract class SQLStore extends 
      private final GeometryLibrary geomLibrary;
  
      /**
 -     * The result of inspecting database schema for deriving {@link 
org.opengis.feature.FeatureType}s.
 +     * The result of inspecting database schema for deriving {@code 
FeatureType}s.
       * Created when first needed. May be discarded and recreated if the store 
needs a refresh.
+      *
+      * @see #model()
+      * @see #model(Connection)
+      * @see #refresh()
       */
-     private Database<?> model;
+     private volatile Database<?> model;
  
      /**
       * Fully qualified names (including catalog and schema) of the tables to 
include in this store.
diff --cc 
endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/FeatureIterator.java
index d8d2e69b31,ba0836a33d..30b9a8dfce
--- 
a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/FeatureIterator.java
+++ 
b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/FeatureIterator.java
@@@ -27,14 -27,13 +27,13 @@@ import java.sql.ResultSet
  import java.sql.SQLException;
  import org.apache.sis.metadata.sql.privy.SQLBuilder;
  import org.apache.sis.storage.InternalDataStoreException;
- import org.apache.sis.util.collection.BackingStoreException;
  import org.apache.sis.util.collection.WeakValueHashMap;
  
 -// Specific to the geoapi-3.1 and geoapi-4.0 branches:
 -import org.opengis.feature.Feature;
 -import org.opengis.filter.SortOrder;
 -import org.opengis.filter.SortProperty;
 -import org.opengis.filter.SortBy;
 +// Specific to the main branch:
 +import org.apache.sis.feature.AbstractFeature;
 +import org.apache.sis.pending.geoapi.filter.SortOrder;
 +import org.apache.sis.pending.geoapi.filter.SortProperty;
 +import org.apache.sis.pending.geoapi.filter.SortBy;
  
  
  /**
@@@ -214,13 -214,11 +214,11 @@@ final class FeatureIterator implements 
       * Gives the next feature to the given consumer.
       */
      @Override
 -    public boolean tryAdvance(final Consumer<? super Feature> action) {
 +    public boolean tryAdvance(final Consumer<? super AbstractFeature> action) 
{
          try {
              return fetch(action, false);
-         } catch (RuntimeException e) {
-             throw e;
          } catch (Exception e) {
-             throw new BackingStoreException(e);
+             throw FeatureStream.cannotExecute(e);
          }
      }
  
@@@ -228,13 -226,11 +226,11 @@@
       * Gives all remaining features to the given consumer.
       */
      @Override
 -    public void forEachRemaining(final Consumer<? super Feature> action) {
 +    public void forEachRemaining(final Consumer<? super AbstractFeature> 
action) {
          try {
              fetch(action, true);
-         } catch (RuntimeException e) {
-             throw e;
          } catch (Exception e) {
-             throw new BackingStoreException(e);
+             throw FeatureStream.cannotExecute(e);
          }
      }
  
diff --cc 
endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/FeatureStream.java
index 78d71280e5,f1485ce02f..1ad73ac900
--- 
a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/FeatureStream.java
+++ 
b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/FeatureStream.java
@@@ -34,14 -35,12 +35,12 @@@ import org.apache.sis.util.privy.String
  import org.apache.sis.util.stream.DeferredStream;
  import org.apache.sis.util.stream.PaginedStream;
  import org.apache.sis.filter.privy.SortByComparator;
- import org.apache.sis.util.privy.Strings;
  import org.apache.sis.storage.DataStoreException;
- import org.apache.sis.util.collection.BackingStoreException;
  
 -// Specific to the geoapi-3.1 and geoapi-4.0 branches:
 -import org.opengis.feature.Feature;
 -import org.opengis.filter.Filter;
 -import org.opengis.filter.SortBy;
 +// Specific to the main branch:
 +import org.apache.sis.filter.Filter;
 +import org.apache.sis.feature.AbstractFeature;
 +import org.apache.sis.pending.geoapi.filter.SortBy;
  
  
  /**
diff --cc 
endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/InfoStatements.java
index 50d68debce,35f9163eab..a535c3b43b
--- 
a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/InfoStatements.java
+++ 
b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/InfoStatements.java
@@@ -49,10 -57,9 +57,9 @@@ import org.apache.sis.util.privy.Consta
  import org.apache.sis.io.wkt.Convention;
  import org.apache.sis.io.wkt.WKTFormat;
  import org.apache.sis.io.wkt.Warnings;
- import org.apache.sis.referencing.factory.IdentifiedObjectFinder;
  
 -// Specific to the geoapi-3.1 and geoapi-4.0 branches:
 -import org.opengis.metadata.Identifier;
 +// Specific to the main branch:
 +import org.opengis.referencing.ReferenceIdentifier;
  
  
  /**
@@@ -475,23 -624,25 +624,25 @@@ public class InfoStatements implements 
              /*
               * First, iterate over the identifiers declared in the CRS object.
               * If we cannot find an identifier that we can map to a SRID, 
then this loop may be
-              * executed more times with CRS from EPSG database that are 
equal, ignore axis order.
+              * executed more times with CRS from EPSG database that are 
equal, ignoring axis order.
               */
 -            for (final Identifier id : candidate.getIdentifiers()) {
 +            for (final ReferenceIdentifier id : candidate.getIdentifiers()) {
                  final String authority = id.getCodeSpace();
                  if (authority == null) continue;
-                 final String code = id.getCode();
-                 if (!done.add(new SimpleImmutableEntry<>(authority, code))) {
-                     continue;                           // Skip 
"authority:code" that we already tried.
-                 }
-                 final int codeValue;
+                 final int code;
                  try {
-                     codeValue = Integer.parseInt(code);
+                     code = Integer.parseInt(id.getCode());
                  } catch (NumberFormatException e) {
-                     if (error == null) error = e;
-                     else error.addSuppressed(e);
+                     if (tryWithGivenCRS) {
+                         if (error == null) error = e;
+                         else error.addSuppressed(e);
+                     }
                      continue;                           // Ignore codes that 
are not integers.
                  }
+                 final SRID search = new SRID(crs, authority, code);
+                 if (done.putIfAbsent(search, code > 0) != null) {
+                     continue;                           // Skip 
"authority:code" that we already tried.
+                 }
                  /*
                   * Found an "authority:code" pair that we did not tested 
before.
                   * Get the WKT and verifies if the CRS is approximately equal.
diff --cc 
endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/package-info.java
index bf8c64d3e1,ec2b149779..f650b19a16
--- 
a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/package-info.java
+++ 
b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/package-info.java
@@@ -17,26 -17,31 +17,31 @@@
  
  
  /**
-  * Data store capable to read and create features from a JDBC connection to a 
database.
-  * {@link org.apache.sis.storage.sql.SQLStore} takes a one or more tables at 
construction time.
+  * Data store capable to read and write features using a JDBC connection to a 
database.
+  * {@link org.apache.sis.storage.sql.SimpleFeatureStore} takes one or more 
tables at construction time.
 - * Each enumerated table is represented by a {@link 
org.opengis.feature.FeatureType}.
 - * Each row in those table represents a {@link org.opengis.feature.Feature} 
instance.
 - * Each relation defined by a foreigner key is represented by an {@link 
org.opengis.feature.FeatureAssociationRole}
 + * Each enumerated table is represented by a {@code FeatureType}.
 + * Each row in those table represents a {@code Feature} instance.
 + * Each relation defined by a foreigner key is represented by an {@code 
FeatureAssociationRole}
   * to another feature (with transitive dependencies automatically resolved), 
and the other columns are represented
 - * by {@link org.opengis.feature.AttributeType}.
 + * by {@code AttributeType}.
   *
-  * <p>The storage of spatial features in SQL databases is described by the
+  * <p>The storage of spatial features in <abbr>SQL</abbr> databases is 
described by the
   * <a href="https://www.ogc.org/standards/sfs";>OGC Simple feature access - 
Part 2: SQL option</a>
-  * international standard, also known as ISO 19125-2. Implementation of 
geometric types and operations must
-  * be provided by the database (sometimes through an extension, for example 
PostGIS on PostgreSQL databases).
-  * This Java package uses those provided types and operations.</p>
+  * international standard, also known as ISO 19125-2.
+  * The implementation of geometric objects and their operations must be 
provided by the database.
+  * This is sometimes provided by an extension that needs to be installed 
explicitly.
+  * For example, when using PostgreSQL, the PostGIS extension is 
recommended.</p>
+  *
+  * <p>The tables to use as {@linkplain 
org.apache.sis.storage.sql.ResourceDefinition resource definitions}
+  * must be specified at construction time. There is no automatic discovery 
mechanism. Note that discovery
+  * may be done by other modules. For example, Geopackage module uses the 
{@code "gpkg_contents"} table.</p>
   *
   * <h2>Performance tips</h2>
   * <p>A subset of features can be obtained by applying filters on the stream 
returned by
   * {@link org.apache.sis.storage.FeatureSet#features(boolean)}.
   * While the filter can be any {@link java.util.function.Predicate},
 - * performances will be much better if they are instances of {@link 
org.opengis.filter.Filter}
 + * performances will be much better if they are instances of {@link 
org.apache.sis.filter.Filter}
-  * because Apache SIS will know how to translate some of them to SQL 
statements.</p>
+  * because Apache SIS will know how to translate some of them to 
<abbr>SQL</abbr> statements.</p>
   *
   * <p>In filter expressions like {@code ST_Intersects(A,B)} where the 
<var>A</var> and <var>B</var> parameters are
   * two sub-expressions evaluating to geometry values, if one of those 
expressions is a literal, then that literal
diff --cc 
endorsed/src/org.apache.sis.storage.sql/test/org/apache/sis/storage/sql/SQLStoreTest.java
index ed3f348bfb,80a484c799..3f8bb5a415
--- 
a/endorsed/src/org.apache.sis.storage.sql/test/org/apache/sis/storage/sql/SQLStoreTest.java
+++ 
b/endorsed/src/org.apache.sis.storage.sql/test/org/apache/sis/storage/sql/SQLStoreTest.java
@@@ -163,10 -174,10 +172,10 @@@ public final class SQLStoreTest extend
       * Creates a {@link SQLStore} instance with the specified table as a 
resource, then tests some queries.
       */
      private void testTableQuery(final StorageConnector connector, final 
ResourceDefinition table) throws Exception {
-         try (SQLStore store = new SQLStore(new SQLStoreProvider(), connector, 
table)) {
+         try (SimpleFeatureStore store = new SimpleFeatureStore(new 
SQLStoreProvider(), connector, table)) {
              verifyFeatureTypes(store);
              final Map<String,Integer> countryCount = new HashMap<>();
 -            try (Stream<Feature> features = 
store.findResource("Cities").features(false)) {
 +            try (Stream<AbstractFeature> features = 
store.findResource("Cities").features(false)) {
                  features.forEach((f) -> verifyContent(f, countryCount));
              }
              assertEquals(Integer.valueOf(2), countryCount.remove("CAN"));
@@@ -490,8 -503,8 +499,8 @@@
       * Tests a user supplied query followed by another query built from 
filters.
       */
      private void verifyNestedSQLQuery(final StorageConnector connector) 
throws Exception {
-         try (SQLStore store = new SQLStore(null, connector, 
ResourceDefinition.query("MyParks",
+         try (SimpleFeatureStore store = new SimpleFeatureStore(null, 
connector, ResourceDefinition.query("MyParks",
 -                "SELECT * FROM " + SCHEMA + ".\"Parks\"")))
 +                "SELECT * FROM " + SCHEMA + ".\"Parks\" ORDER BY 
\"native_name\" DESC")))
          {
              final FeatureSet parks = store.findResource("MyParks");
              /*
diff --cc 
endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/MetadataFetcher.java
index 5d3028e619,b9d249cb25..9cd162687b
--- 
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/MetadataFetcher.java
+++ 
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/MetadataFetcher.java
@@@ -40,11 -44,13 +44,13 @@@ import org.opengis.metadata.spatial.Cel
  import org.opengis.metadata.spatial.Georectified;
  import org.opengis.metadata.spatial.SpatialRepresentation;
  import org.opengis.metadata.spatial.GridSpatialRepresentation;
+ import org.apache.sis.storage.event.StoreListeners;
  import org.apache.sis.util.collection.CodeListSet;
+ import org.apache.sis.temporal.TemporalDate;
  
 -// Specific to the geoapi-3.1 and geoapi-4.0 branches:
 -import org.opengis.metadata.citation.Party;
 -import org.opengis.metadata.citation.Responsibility;
 +// Specific to the main branch:
 +import org.opengis.metadata.citation.ResponsibleParty;
 +import org.apache.sis.metadata.iso.DefaultMetadata;
  
  
  /**
@@@ -58,6 -64,21 +64,21 @@@
   * @param  <T>  type of temporal objects.
   */
  public abstract class MetadataFetcher<T> {
+     /**
+      * Types of date to accept as a date of last update, in preference order.
+      */
+     private static final DateType[] LAST_UPDATE_TYPES = {
 -            DateType.LAST_UPDATE,
 -            DateType.LAST_REVISION,
++            DateType.valueOf("LAST_UPDATE"),
++            DateType.valueOf("LAST_REVISION"),
+             DateType.REVISION,
 -            DateType.IN_FORCE,
 -            DateType.RELEASED,
 -            DateType.DISTRIBUTION,
++            DateType.valueOf("IN_FORCE"),
++            DateType.valueOf("RELEASED"),
++            DateType.valueOf("DISTRIBUTION"),
+             DateType.PUBLICATION,
 -            DateType.ADOPTED,
++            DateType.valueOf("ADOPTED"),
+             DateType.CREATION
+     };
+ 
      /**
       * Title of the resource, or {@code null} if none.
       *
@@@ -397,4 -457,47 +460,47 @@@
       * @return subclass-dependent object representing the given date.
       */
      protected abstract T convertDate(final Date date);
+ 
+     /**
+      * Returns the first date of type {@link DateType#LAST_UPDATE}.
+      * If there is no last update, then this method fallbacks on {@link 
DateType#LAST_REVISION}.
+      * If there is no last revision, then this method fallbacks on {@link 
DateType#REVISION}, <i>etc.</i>
+      *
+      * @param  metadata  the metadata from which to get the date of last 
update.
+      * @param  zone      the timezone to use if the time is local, or {@code 
null} if none.
+      * @param  listeners where to report warnings, or {@code null} if none.
+      * @return date of last update, or {@code null} if none.
+      *
+      * @see #lastUpdate
+      */
+     public static Instant lastUpdate(final Metadata metadata, final ZoneId 
zone, final StoreListeners listeners) {
+         Temporal lastUpdate = null;
+         if (metadata != null) {
+             int lastUpdateType = LAST_UPDATE_TYPES.length;
+ search:     for (Identification info : metadata.getIdentificationInfo()) {
+                 final Citation citation = info.getCitation();
+                 if (citation != null) {
+                     for (CitationDate date : citation.getDates()) {
+                         final DateType type = date.getDateType();
+                         for (int i = lastUpdateType; --i >= 0;) {
+                             if (LAST_UPDATE_TYPES[i].equals(type)) {
+                                 lastUpdateType = i;
 -                                lastUpdate = date.getReferenceDate();
++                                lastUpdate = 
TemporalDate.toTemporal(date.getDate());
+                                 if (i == 0) break search;
+                             }
+                         }
+                     }
+                 }
+             }
+         }
+         try {
+             return TemporalDate.toInstant(lastUpdate, zone);
+         } catch (DateTimeException e) {
+             if (listeners == null) {
+                 throw e;
+             }
+             listeners.warning(e);
+             return null;
+         }
+     }
  }
diff --cc endorsed/src/org.apache.sis.util/main/module-info.java
index a0950420fe,150970c359..08bbfa92ad
--- a/endorsed/src/org.apache.sis.util/main/module-info.java
+++ b/endorsed/src/org.apache.sis.util/main/module-info.java
@@@ -119,7 -119,10 +119,8 @@@ module org.apache.sis.util 
              org.apache.sis.storage.netcdf,
              org.apache.sis.storage.geotiff,
              org.apache.sis.storage.earthobservation,
 -            org.apache.sis.cql,                         // In the "incubator" 
sub-project.
 -            org.apache.sis.portrayal.map,               // In the "incubator" 
sub-project.
              org.apache.sis.portrayal,
+             org.apache.sis.storage.gsf,                 // In the "incubator" 
sub-project.
              org.apache.sis.cloud.aws,
              org.apache.sis.console,
              org.apache.sis.gui,                         // In the "optional" 
sub-project.
diff --cc incubator/build.gradle.kts
index 8dbc812029,a60706f6ae..d4a0e06d86
--- a/incubator/build.gradle.kts
+++ b/incubator/build.gradle.kts
@@@ -47,6 -47,8 +47,7 @@@ dependencies 
      
api(files("../endorsed/build/classes/java/main/org.apache.sis.referencing"))
      api(files("../endorsed/build/classes/java/main/org.apache.sis.feature"))
      api(files("../endorsed/build/classes/java/main/org.apache.sis.storage"))
+     
api(files("../endorsed/build/classes/java/main/org.apache.sis.storage.sql"))
 -    api(files("../endorsed/build/classes/java/main/org.apache.sis.portrayal"))
  
      // Test dependencies
      testImplementation(tests.junit5)
@@@ -95,11 -102,45 +98,38 @@@ fun addRead(args : MutableList<String>
      args.add(module + '=' + dependencies)
  }
  
+ /*
+  * Adds a JVM argument for making an internal package accessible to another 
module.
+  * This is for making internal packages accessible to JUnit or to some test 
classes
+  * defined in other modules.
+  */
+ fun addExport(args : MutableList<String>, module : String, pkg : String, 
consumers : String) {
+     args.add("--add-exports")
+     args.add(module + '/' + pkg + '=' + consumers)
+ }
+ 
+ /*
+  * Add compiler and runtime options for patching the Apache SIS main modules 
with the test classes.
+  * The same options are required for both compiling and executing the tests.
+  */
+ fun addExportForTests(args : MutableList<String>) {
+     /*
+      * Some test classes need access to more internal packages than requested 
by the main classes.
+      * The following lines may need to be edited when export statements are 
added or removed in a
+      * module-info.java file of main source code, or when a test class starts 
using or stop using
+      * an internal API.
+      */
+     // ――――――――――――― Module name ――――――――――――――――――――――― Package to export 
―――――――――――――――
 -    addExport(args, "org.apache.sis.feature",           
"org.apache.sis.geometry.wrapper.jts",
 -                    "org.apache.sis.portrayal.map")
 -
+     addExport(args, "org.apache.sis.feature",           
"org.apache.sis.geometry.wrapper",
+                     "org.apache.sis.storage.geopackage")
 -
 -    addExport(args, "org.apache.sis.storage",           
"org.apache.sis.storage.base",
 -                    "org.apache.sis.portrayal.map")
+ }
+ 
  /*
   * Discover and execute JUnit-based tests.
   */
  tasks.test {
      val args = mutableListOf("-enableassertions")
+     addExportForTests(args)
 -    addExport(args, "org.apache.sis.cql", "org.apache.sis.cql", "ALL-UNNAMED")
      setAllJvmArgs(args)
      testLogging {
          events("FAILED", "STANDARD_OUT", "STANDARD_ERROR")
diff --cc 
incubator/src/org.apache.sis.storage.geopackage/test/org/apache/sis/storage/geopackage/GpkgStoreTest.java
index 0000000000,950a00ba9c..a64f58c76d
mode 000000,100644..100644
--- 
a/incubator/src/org.apache.sis.storage.geopackage/test/org/apache/sis/storage/geopackage/GpkgStoreTest.java
+++ 
b/incubator/src/org.apache.sis.storage.geopackage/test/org/apache/sis/storage/geopackage/GpkgStoreTest.java
@@@ -1,0 -1,338 +1,339 @@@
+ /*
+  * Licensed to the Apache Software Foundation (ASF) under one or more
+  * contributor license agreements.  See the NOTICE file distributed with
+  * this work for additional information regarding copyright ownership.
+  * The ASF licenses this file to You under the Apache License, Version 2.0
+  * (the "License"); you may not use this file except in compliance with
+  * the License.  You may obtain a copy of the License at
+  *
+  *     http://www.apache.org/licenses/LICENSE-2.0
+  *
+  * Unless required by applicable law or agreed to in writing, software
+  * distributed under the License is distributed on an "AS IS" BASIS,
+  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  * See the License for the specific language governing permissions and
+  * limitations under the License.
+  */
+ package org.apache.sis.storage.geopackage;
+ 
+ import java.net.URL;
+ import java.net.URI;
+ import java.net.URISyntaxException;
+ import java.io.IOException;
+ import java.io.File;
+ import java.nio.file.Files;
+ import java.nio.file.Path;
+ import java.nio.file.StandardOpenOption;
+ import java.sql.Connection;
+ import java.sql.DriverManager;
+ import java.sql.SQLException;
+ import java.sql.Statement;
+ import java.util.List;
+ import java.util.stream.Stream;
 -import org.opengis.feature.AttributeType;
 -import org.opengis.feature.Feature;
 -import org.opengis.feature.PropertyType;
+ import org.apache.sis.geometry.wrapper.Geometries;
+ import org.apache.sis.geometry.wrapper.GeometryWrapper;
+ import org.apache.sis.referencing.IdentifiedObjects;
+ import org.apache.sis.setup.OptionKey;
+ import org.apache.sis.storage.DataStore;
+ import org.apache.sis.storage.DataStoreException;
+ import org.apache.sis.storage.DataStores;
+ import org.apache.sis.storage.FeatureSet;
+ import org.apache.sis.storage.StorageConnector;
+ 
++import org.apache.sis.feature.AbstractFeature;
++import org.apache.sis.feature.AbstractIdentifiedType;
++import org.apache.sis.feature.DefaultAttributeType;
++
+ import org.junit.jupiter.api.Test;
+ import org.junit.jupiter.api.parallel.Execution;
+ import org.junit.jupiter.api.parallel.ExecutionMode;
+ import static org.junit.jupiter.api.Assertions.*;
+ import static org.junit.jupiter.api.Assumptions.assumeTrue;
+ 
+ 
+ /**
+  * Tests the Geopackage store.
+  *
+  * @author Johann Sorel (Geomatys)
+  */
+ @Execution(ExecutionMode.SAME_THREAD)
+ public final class GpkgStoreTest {
+     /**
+      * Creates a new test case.
+      */
+     public GpkgStoreTest() {
+     }
+ 
+     /**
+      * Returns the URL to the {@code FeatureSet.gpkg} file.
+      * If that file does not exists, it is created from the SQL script.
+      *
+      * @return {@link URL} or {@link Path} to the test Geopackage file.
+      * @throws DataStoreException if the Geopackage test file cannot be 
created.
+      */
+     private static Object getTestFile() throws DataStoreException {
+         URL file = GpkgStoreTest.class.getResource("FeatureSet.gpkg");
+         if (file == null) try {
+             final Path source = 
Path.of(GpkgStoreTest.class.getResource("FeatureSet.sql").toURI());
+             final Path target = 
source.resolveSibling("FeatureSet.gpkg").toAbsolutePath();
+             try (Connection c = DriverManager.getConnection("jdbc:sqlite:" + 
target)) {
+                 try (Statement s = c.createStatement()) {
+                     s.executeUpdate("PRAGMA main.application_id = " + 
GpkgStoreProvider.APPLICATION_ID + ';'
+                                   + "PRAGMA main.user_version = " + 
GpkgStoreProvider.VERSION + ';');
+                     s.executeUpdate(Files.readString(source));
+                 }
+             }
+             return target;
+         } catch (URISyntaxException | IOException | SQLException e) {
+             throw new DataStoreException("Cannot create the Geopackage test 
file.", e);
+         }
+         return file;
+     }
+ 
+     /**
+      * Tests reading a feature set from a Geopackage file.
+      *
+      * @throws DataStoreException if the Geopackage test file cannot be 
created or read.
+      */
+     @Test
+     public void testFeatureSet() throws DataStoreException {
+         try (DataStore store = DataStores.open(getTestFile())) {
+             final var gpkg = assertInstanceOf(GpkgStore.class, store);
+             assertEquals(7, gpkg.components().size());
+             /*
+              * Reads and verifies two features from the Geopackage test file.
+              */
+             FeatureSet fs = getResource(gpkg, "nogeom");
+             assertPropertiesEqual(fs, null, "sis:identifier", "fid", "id");
+             {   // For keeping `the features` scope locale.
 -                final Feature[] features = 
getFeatures(fs).toArray(Feature[]::new);
++                final AbstractFeature[] features = 
getFeatures(fs).toArray(AbstractFeature[]::new);
+                 assertEquals(2, features.length);
+                 assertEquals(1, features[0].getPropertyValue("fid"));
+                 assertEquals(1, features[0].getPropertyValue("id"));
+                 assertEquals(2, features[1].getPropertyValue("fid"));
+                 assertEquals(2, features[1].getPropertyValue("id"));
+             }
+             /*
+              * Reads and verifies a feature containing a point.
+              */
+             fs = getResource(gpkg, "point");
+             assertPropertiesEqual(fs, "Point",
+                     "sis:identifier",
+                     "sis:envelope",
+                     "sis:geometry",
+                     "fid",
+                     "geometry",
+                     "text",
+                     "int32",
+                     "int64",
+                     "float",
+                     "date",
+                     "datetime",
+                     "bool");
+ 
+             assertGeometryEquals(fs, "OGC:CRS84", "POINT (3.8691634241245 
43.64618798638135)");
+             {   // For keeping the `feature` scope local.
 -                final Feature feature = getFeatures(fs).get(0);
++                final AbstractFeature feature = getFeatures(fs).get(0);
+                 assertEquals("some text",               
feature.getPropertyValue("text"));
+                 assertEquals(123,                       
feature.getPropertyValue("int32"));
+                 assertEquals(123456,                    
feature.getPropertyValue("int64"));
+                 assertEquals(123.456,                   
feature.getPropertyValue("float"));
+                 assertEquals("2024-08-07",              
feature.getPropertyValue("date"));
+                 assertEquals("2024-08-07T12:34:56.000", 
feature.getPropertyValue("datetime"));
+                 assertEquals(1,                         
feature.getPropertyValue("bool"));
+             }
+             /*
+              * Reads and verifies a feature containing a multi-point geometry.
+              */
+             fs = getResource(gpkg, "multipoint");
+             assertPropertiesEqual(fs, "MultiPoint",
+                     "sis:identifier",
+                     "sis:envelope",
+                     "sis:geometry",
+                     "fid",
+                     "geom");
+ 
+             assertGeometryEquals(fs, "EPSG:2154",
+                     "MULTIPOINT ((770147.9089501069 6283260.318250759), 
(770224.707537184 6283233.947140678))");
+             /*
+              * Reads and verifies a feature containing a line string.
+              */
+             fs = getResource(gpkg, "line");
+             assertPropertiesEqual(fs, "LineString",
+                     "sis:identifier",
+                     "sis:envelope",
+                     "sis:geometry",
+                     "fid",
+                     "geom");
+ 
+             assertGeometryEquals(fs, "EPSG:3857",
+                     "LINESTRING (430669.3103960207 5411208.981453437, 
430655.7744657199 5410643.121416978)");
+             /*
+              * Reads and verifies a feature containing a multi-lines geometry.
+              */
+             fs = getResource(gpkg, "multiline");
+             assertPropertiesEqual(fs, "MultiLineString",
+                     "sis:identifier",
+                     "sis:envelope",
+                     "sis:geometry",
+                     "fid",
+                     "geom");
+ 
+             assertGeometryEquals(fs, "EPSG:3395",
+                     "MULTILINESTRING ((430750.52597782505 5381116.187674649, 
430967.10086263693 5380950.761655851), "
+                             + "(430753.08021745336 5381153.51545164, 
430917.9595504581 5381182.577731505))");
+             /*
+              * Reads and verifies a feature containing a polygon.
+              */
+             fs = getResource(gpkg, "polygon");
+             assertPropertiesEqual(fs, "Polygon",
+                     "sis:identifier",
+                     "sis:envelope",
+                     "sis:geometry",
+                     "fid",
+                     "geom");
+ 
+             assertGeometryEquals(fs, "EPSG:3857",
+                     "POLYGON ((430696.4716464551 5410782.480781593, 
430696.03196823364 5410729.011400482, "
+                             + "430716.6968446369 5410730.2266102545, 
430718.01587930095 5410780.050348468, "
+                             + "430696.4716464551 5410782.480781593))");
+             /*
+              * Reads and verifies a feature containing a multi-polygons.
+              */
+             fs = getResource(gpkg, "multipolygon");
+             assertPropertiesEqual(fs, "MultiPolygon",
+                     "sis:identifier",
+                     "sis:envelope",
+                     "sis:geometry",
+                     "fid",
+                     "geom");
+ 
+             assertGeometryEquals(fs, "EPSG:3395",
+                     "MULTIPOLYGON (((430795.5091658133 5381110.830395544, "
+                             + "430796.60836136667 5381129.2969556395, 
430832.2222972956 5381126.118282802, "
+                             + "430831.0131821869 5381108.711284476, 
430795.5091658133 5381110.830395544)), "
+                             + "((430849.25982837286 5381155.483207256, 
430849.1499088174 5381151.396330676, "
+                             + "430855.0855648057 5381151.547696444, 
430855.4153234717 5381154.877743942, "
+                             + "430849.25982837286 5381155.483207256)))");
+         }
+     }
+ 
+     /**
+      * Returns the resource of the given name.
+      *
+      * @param  store  the store from which to get the feature set.
+      * @param  name   name of the desired feature set.
+      * @return the requested feature set.
+      * @throws DataStoreException if no feature set of the given name was 
found.
+      */
+     private static FeatureSet getResource(final GpkgStore store, final String 
name) throws DataStoreException {
+         return assertInstanceOf(FeatureSet.class, store.findResource(name));
+     }
+ 
+     /**
+      * Asserts that the properties of the given feature set have the 
specified names.
+      * The geometry is expected to be always in the property at index 4 for 
all features
+      * (this is the case for the {@code FeatureSet.gpkg} test file).
+      *
+      * @param  fs             the feature set from which to get the 
properties.
+      * @param  geometryIndex  index of the geometry property.
+      * @param  propertyNames  the expected names of all properties.
+      */
+     private static void assertPropertiesEqual(final FeatureSet fs, final 
String geometryType,
+             final String... propertyNames) throws DataStoreException
+     {
 -        final PropertyType[] properties = 
fs.getType().getProperties(true).toArray(PropertyType[]::new);
++        final AbstractIdentifiedType[] properties = 
fs.getType().getProperties(true).toArray(AbstractIdentifiedType[]::new);
+         assertEquals(propertyNames.length, properties.length, 
"properties.size()");
+         for (int i=0; i<propertyNames.length; i++) {
+             assertEquals(propertyNames[i], 
properties[i].getName().toString());
+         }
+         if (geometryType != null) {
 -            var attribute = assertInstanceOf(AttributeType.class, 
properties[4]);
++            var attribute = assertInstanceOf(DefaultAttributeType.class, 
properties[4]);
+             assertEquals(geometryType, 
attribute.getValueClass().getSimpleName());
+         }
+     }
+ 
+     /**
+      * Returns all feature instances as a list.
+      *
+      * @param  fs  the feature set from which to get the feature instances.
+      * @return all feature instances.
+      */
 -    private static List<Feature> getFeatures(final FeatureSet fs) throws 
DataStoreException {
 -        try (Stream<Feature> stream = fs.features(false)) {
++    private static List<AbstractFeature> getFeatures(final FeatureSet fs) 
throws DataStoreException {
++        try (Stream<AbstractFeature> stream = fs.features(false)) {
+             return stream.toList();
+         }
+     }
+ 
+     /**
+      * Asserts that the geometry of the given feature set is equal to the 
expected WKT.
+      * This method opportunistically verifies that the value of the {@code 
"fid"} property is 1.
+      *
+      * @param resource     the feature set from which to get a single feature 
instance.
+      * @param expectedCRS  identifier of the expected CRS.
+      * @param expectedWKT  expected geometry in WKT format.
+      */
+     private static void assertGeometryEquals(final FeatureSet resource, final 
String expectedCRS, final String expectedWKT)
+             throws DataStoreException
+     {
 -        final List<Feature> features = getFeatures(resource);
++        final List<AbstractFeature> features = getFeatures(resource);
+         assertEquals(1, features.size());
 -        final Feature feature = features.get(0);
++        final AbstractFeature feature = features.get(0);
+ 
+         GeometryWrapper geometry = 
Geometries.wrap(feature.getPropertyValue("sis:geometry")).get();
+         assertEquals(expectedCRS, 
IdentifiedObjects.getIdentifierOrName(geometry.getCoordinateReferenceSystem()));
+         assertEquals(expectedWKT, geometry.formatWKT(1));
+         assertEquals(1, feature.getPropertyValue("fid"));
+     }
+ 
+     /**
+      * Tests opening the file from a JDBC URL.
+      *
+      * @throws Exception if a <abbr>SQL</abbr>, I/O or other error occurred.
+      */
+     @Test
+     public void testOpeningFromJDBC_URL() throws Exception {
+         final Object file = getTestFile();
+         final Path path = (file instanceof Path) ? (Path) file : 
Path.of(((URL) file).toURI());
+         try (DataStore store = DataStores.open(new URI("jdbc:sqlite:" + 
path))) {
+             final var gpkg = assertInstanceOf(GpkgStore.class, store);
+             final var fs   = getResource(gpkg, "polygon");
+             final var fi   = fs.features(false).findAny().orElseThrow();
+             assertEquals(1, fi.getPropertyValue("fid"));
+         }
+     }
+ 
+     /**
+      * Tests creating an empty database, then opening it as a read-only file.
+      *
+      * @throws Exception if a <abbr>SQL</abbr>, I/O or other error occurred.
+      */
+     @Test
+     public void testOpeningReadOnlyDatabase() throws Exception {
+         final File file = File.createTempFile("sis-test", ".gpkg");
+         try {
+             // Create an empty database.
+             final var connector = new StorageConnector(file);
+             connector.setOption(OptionKey.OPEN_OPTIONS, new 
StandardOpenOption[] {
+                 StandardOpenOption.CREATE_NEW,
+                 StandardOpenOption.WRITE,
+                 StandardOpenOption.READ
+             });
+             try (GpkgStore store = new GpkgStore(null, connector)) {
+                 final var gpkg = assertInstanceOf(GpkgStore.class, store);
+                 assertTrue(gpkg.components().isEmpty());
+             }
+             assertNotEquals(0, file.length());
+ 
+             // Make file read only, skip test if we cannot do that.
+             assumeTrue(file.setWritable(false));
+             try (DataStore store = DataStores.open(file)) {
+                 final var gpkg = assertInstanceOf(GpkgStore.class, store);
+                 assertTrue(gpkg.components().isEmpty());        // Should not 
raise any exception.
+             }
+         } finally {
+             assertTrue(file.setWritable(true));
+             assertTrue(file.delete());
+         }
+     }
+ }
diff --cc 
incubator/src/org.apache.sis.storage.gimi/main/org/apache/sis/storage/gimi/internal/TileMatrices.java
index 0000000000,ab1b01b566..1f4398b0e3
mode 000000,100644..100644
--- 
a/incubator/src/org.apache.sis.storage.gimi/main/org/apache/sis/storage/gimi/internal/TileMatrices.java
+++ 
b/incubator/src/org.apache.sis.storage.gimi/main/org/apache/sis/storage/gimi/internal/TileMatrices.java
@@@ -1,0 -1,60 +1,64 @@@
+ /*
+  * Licensed to the Apache Software Foundation (ASF) under one or more
+  * contributor license agreements.  See the NOTICE file distributed with
+  * this work for additional information regarding copyright ownership.
+  * The ASF licenses this file to You under the Apache License, Version 2.0
+  * (the "License"); you may not use this file except in compliance with
+  * the License.  You may obtain a copy of the License at
+  *
+  *     http://www.apache.org/licenses/LICENSE-2.0
+  *
+  * Unless required by applicable law or agreed to in writing, software
+  * distributed under the License is distributed on an "AS IS" BASIS,
+  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  * See the License for the specific language governing permissions and
+  * limitations under the License.
+  */
+ package org.apache.sis.storage.gimi.internal;
+ 
+ import java.util.stream.LongStream;
+ import java.util.stream.Stream;
+ import org.apache.sis.coverage.grid.GridExtent;
+ 
+ /**
+  *
+  * @author Johann Sorel (Geomatys)
+  */
+ public class TileMatrices {
+ 
+     private TileMatrices(){}
+ 
+ 
+     /**
+      * Create a stream of point in the GridExtent.
+      *
+      * TODO : make a more efficient implementation.
+      */
+     public static Stream<long[]> pointStream(GridExtent extent) {
+         final int dimension = extent.getDimension();
 -        final long[] low = extent.getLow().getCoordinateValues();
 -        final long[] high = extent.getHigh().getCoordinateValues();
++        final long[] low = new long[dimension];
++        final long[] high = new long[dimension];
++        for (int i=0; i<dimension; i++) {
++            low[i] = extent.getLow(i);
++            high[i] = extent.getHigh(i);
++        }
+ 
+         Stream<long[]> stream = LongStream.range(low[0], high[0]+1)
+                 .mapToObj((long value) -> {
+                     final long[] array = new long[dimension];
+                     array[0] = value;
+                     return array;
+         });
+         for (int i = 1; i <dimension; i++) {
+             final int idx = i;
+             stream = stream.flatMap((long[] t) -> LongStream.range(low[idx], 
high[idx]+1)
+                     .mapToObj((long value) -> {
+                         final long[] array = t.clone();
+                         array[idx] = value;
+                         return array;
+                     }));
+         }
+         return stream;
+     }
+ 
+ }
diff --cc netbeans-project/ivy.xml
index 9532a5ffd2,02d580d8dc..5f97b17f2c
--- a/netbeans-project/ivy.xml
+++ b/netbeans-project/ivy.xml
@@@ -11,10 -11,8 +11,10 @@@
  <ivy-module version="2.0">
      <info organisation="org.apache" module="sis"/>
      <dependencies defaultconf="default">
 +        <dependency org="org.opengis"            name="geoapi"                
  rev="3.0.2"/>
 +        <dependency org="org.opengis"            name="geoapi-conformance"    
  rev="3.0.2"/>
          <dependency org="javax.measure"          name="unit-api"              
  rev="2.1.3"/>
-         <dependency org="org.glassfish.jaxb"     name="jaxb-runtime"          
  rev="4.0.4"/>
+         <dependency org="org.glassfish.jaxb"     name="jaxb-runtime"          
  rev="4.0.5"/>
          <dependency org="org.eclipse"            name="yasson"                
  rev="3.0.3"/>
          <dependency org="com.esri.geometry"      name="esri-geometry-api"     
  rev="2.2.4"/>
          <dependency org="org.locationtech.jts"   name="jts-core"              
  rev="1.19.0"/>
diff --cc netbeans-project/nbproject/project.properties
index da94918e12,b157430750..9f48612b3e
--- a/netbeans-project/nbproject/project.properties
+++ b/netbeans-project/nbproject/project.properties
@@@ -110,9 -117,12 +117,11 @@@ test.options = --add-modules jama,Geogr
                 --add-exports 
org.apache.sis.metadata/org.apache.sis.xml.privy=org.apache.sis.storage.geotiff 
\
                 --add-exports 
org.apache.sis.metadata/org.apache.sis.xml.bind.gcx=org.apache.sis.referencing \
                 --add-exports 
org.apache.sis.metadata/org.apache.sis.metadata.privy=org.apache.sis.referencing.gazetteer
 \
+                --add-exports 
org.apache.sis.metadata/org.apache.sis.metadata.sql.privy=org.apache.sis.referencing.epsg
 \
                 --add-exports 
org.apache.sis.referencing/org.apache.sis.referencing.internal=org.apache.sis.openoffice
 \
                 --add-exports 
org.apache.sis.feature/org.apache.sis.feature.privy=org.apache.sis.storage.sql \
 -               --add-exports 
org.apache.sis.feature/org.apache.sis.geometry.wrapper.jts=org.apache.sis.storage.sql,org.apache.sis.portrayal.map
 \
 +               --add-exports 
org.apache.sis.feature/org.apache.sis.geometry.wrapper.jts=org.apache.sis.storage.sql
 \
+                --add-exports 
org.apache.sis.feature/org.apache.sis.geometry.wrapper=org.apache.sis.storage.geopackage
 \
 -               --add-exports 
org.apache.sis.storage/org.apache.sis.storage.base=org.apache.sis.portrayal.map 
\
                 --add-exports 
org.apache.sis.storage/org.apache.sis.storage.test=${modules.list}
  
  #
diff --cc 
optional/src/org.apache.sis.referencing.database/test/org/apache/sis/resources/embedded/EmbeddedResourcesTest.java
index 0000000000,80e2813ab1..8858f0d601
mode 000000,100644..100644
--- 
a/optional/src/org.apache.sis.referencing.database/test/org/apache/sis/resources/embedded/EmbeddedResourcesTest.java
+++ 
b/optional/src/org.apache.sis.referencing.database/test/org/apache/sis/resources/embedded/EmbeddedResourcesTest.java
@@@ -1,0 -1,140 +1,141 @@@
+ /*
+  * Licensed to the Apache Software Foundation (ASF) under one or more
+  * contributor license agreements.  See the NOTICE file distributed with
+  * this work for additional information regarding copyright ownership.
+  * The ASF licenses this file to You under the Apache License, Version 2.0
+  * (the "License"); you may not use this file except in compliance with
+  * the License.  You may obtain a copy of the License at
+  *
+  *     http://www.apache.org/licenses/LICENSE-2.0
+  *
+  * Unless required by applicable law or agreed to in writing, software
+  * distributed under the License is distributed on an "AS IS" BASIS,
+  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  * See the License for the specific language governing permissions and
+  * limitations under the License.
+  */
+ package org.apache.sis.resources.embedded;
+ 
+ import java.io.IOException;
+ import java.sql.Connection;
+ import java.sql.ResultSet;
+ import java.sql.Statement;
+ import java.util.ServiceLoader;
+ import javax.sql.DataSource;
+ import org.opengis.util.FactoryException;
 -import org.opengis.referencing.crs.CoordinateReferenceSystem;
+ import org.apache.sis.setup.InstallationResources;
+ import org.apache.sis.metadata.sql.privy.Initializer;
+ import org.apache.sis.system.DataDirectory;
+ import org.apache.sis.referencing.CRS;
+ import org.apache.sis.referencing.factory.sql.epsg.ScriptProvider;
+ 
++import org.apache.sis.referencing.crs.AbstractCRS;
++
+ // Test dependencies
+ import org.junit.jupiter.api.Test;
+ import static org.junit.jupiter.api.Assertions.*;
+ import static org.junit.jupiter.api.Assumptions.assumeTrue;
+ import org.apache.sis.test.TestUtilities;
+ 
+ 
+ /**
+  * Tests {@link EmbeddedResources}.
+  *
+  * @author  Martin Desruisseaux (Geomatys)
+  */
+ public final strictfp class EmbeddedResourcesTest {
+     /**
+      * Whether the database has been created.
+      */
+     private static boolean databaseCreated;
+ 
+     /**
+      * Creates a new test case.
+      */
+     public EmbeddedResourcesTest() {
+     }
+ 
+     /**
+      * Skips the test if the EPSG scripts are not present.
+      * This method uses {@code LICENSE.txt} as a sentinel file.
+      */
+     private static void assumeDataPresent() {
+         assumeTrue(ScriptProvider.class.getResource("LICENSE.txt") != null,
+                 "EPSG resources not found. See `README.md` for manual 
installation.");
+     }
+ 
+     /**
+      * Returns the {@link EmbeddedResources} instance declared in the {@code 
META-INF/services/} directory.
+      * The provider may coexist with providers defined in other modules, so 
we need to filter them.
+      */
+     private static synchronized InstallationResources getInstance() {
+         if (!databaseCreated) try {
+             new Generator().run();
+             databaseCreated = true;
+         } catch (Exception e) {
+             throw new AssertionError(e);
+         }
+ 
+         InstallationResources provider = null;
+         for (InstallationResources candidate : 
ServiceLoader.load(InstallationResources.class)) {
+             if (candidate instanceof EmbeddedResources) {
+                 assertNull(provider, "Expected only one instance.");
+                 provider = candidate;
+             }
+         }
+         assertNotNull(provider, "Expected an instance.");
+         return provider;
+     }
+ 
+     /**
+      * Tests fetching the licenses.
+      *
+      * @throws IOException if an error occurred while reading a license.
+      */
+     @Test
+     public void testLicences() throws IOException {
+         assumeDataPresent();
+         final InstallationResources provider = getInstance();
+         assertTrue(provider.getLicense("Embedded", null, 
"text/plain").contains("IOGP"));
+         assertTrue(provider.getLicense("Embedded", null, "text/html" 
).contains("IOGP"));
+     }
+ 
+     /**
+      * Tests connecting to the database.
+      *
+      * @throws Exception if an error occurred while fetching the data source, 
or connecting to the database.
+      */
+     @Test
+     public void testConnection() throws Exception {
+         assumeDataPresent();
+         final String dir = DataDirectory.getenv();
+         assertTrue((dir == null) || dir.isEmpty(), "The SIS_DATA environment 
variable must be unset for enabling this test.");
+         final DataSource ds = Initializer.getDataSource();
+         assertNotNull(ds, "Cannot find the data source.");
+         try (Connection c = ds.getConnection()) {
+             
assertEquals("jdbc:derby:classpath:SIS_DATA/Databases/spatial-metadata", 
c.getMetaData().getURL(), "URL");
+             try (Statement s = c.createStatement()) {
+                 try (ResultSet r = s.executeQuery("SELECT COORD_REF_SYS_NAME 
FROM EPSG.\"Coordinate Reference System\" WHERE COORD_REF_SYS_CODE = 4326")) {
+                     assertTrue(r.next(), "ResultSet.next()");
+                     assertEquals(r.getString(1), "WGS 84");
+                     assertFalse(r.next(), "ResultSet.next()");
+                 }
+             }
+         }
+     }
+ 
+     /**
+      * Tests {@link CRS#forCode(String)} with the embedded database. This 
test asks for a CRS for which
+      * no hard-coded fallback exists in {@link 
org.apache.sis.referencing.CommonCRS}. Consequently this
+      * test should fail if we do not have a connection to a complete EPSG 
database.
+      *
+      * @throws FactoryException if an error occurred while creating the CRS.
+      */
+     @Test
+     public void testCrsforCode() throws FactoryException {
+         assumeDataPresent();
 -        CoordinateReferenceSystem crs = CRS.forCode("EPSG:6676");
++        var crs = assertInstanceOf(AbstractCRS.class, 
CRS.forCode("EPSG:6676"));
+         String area = 
TestUtilities.getSingleton(crs.getDomains()).getDomainOfValidity().getDescription().toString();
+         assertTrue(area.contains("Japan"), area);
+     }
+ }
diff --cc settings.gradle.kts
index f312fd21d4,f2921b2bec..a5e37ecf51
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@@ -57,10 -59,10 +57,10 @@@ dependencyResolutionManagement 
       */
      versionCatalogs {
          create("libs") {
 -            library("geoapi",        "org.opengis",            
"geoapi-pending")      .version {strictly(geoapiVersion)}
 +            library("geoapi",        "org.opengis",            "geoapi")      
        .version {strictly(geoapiVersion)}
              library("units",         "javax.measure",          "unit-api")    
        .version {strictly("[2.1, 3.0[");  prefer("2.1.3")}
-             library("jaxb.api",      "jakarta.xml.bind",       
"jakarta.xml.bind-api").version {strictly("[4.0, 5.0[");  prefer("4.0.1")}
-             library("jaxb.impl",     "org.glassfish.jaxb",     
"jaxb-runtime")        .version {strictly("[4.0, 5.0[");  prefer("4.0.4")}
+             library("jaxb.api",      "jakarta.xml.bind",       
"jakarta.xml.bind-api").version {strictly("[4.0, 5.0[");  prefer("4.0.2")}
+             library("jaxb.impl",     "org.glassfish.jaxb",     
"jaxb-runtime")        .version {strictly("[4.0, 5.0[");  prefer("4.0.5")}
              library("yasson",        "org.eclipse",            "yasson")      
        .version {strictly("[3.0, 4.0[");  prefer("3.0.3")}
              library("jts.core",      "org.locationtech.jts",   "jts-core")    
        .version {strictly("[1.15, 2.0["); prefer("1.19.0")}
              library("esri.geometry", "com.esri.geometry",      
"esri-geometry-api")   .version {strictly("[2.0, 3.0[");  prefer("2.2.4")}

Reply via email to