You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sis.apache.org by js...@apache.org on 2023/05/12 15:09:25 UTC
[sis] 01/01: feat(coveragejson): initial binding and grid coverage implementation of CoverageJson
This is an automated email from the ASF dual-hosted git repository.
jsorel pushed a commit to branch feat/coverage-json
in repository https://gitbox.apache.org/repos/asf/sis.git
commit 48c170932fed91e60c6c7d02529dbd72dd5b00f5
Author: jsorel <jo...@geomatys.com>
AuthorDate: Fri May 12 17:08:22 2023 +0200
feat(coveragejson): initial binding and grid coverage implementation of CoverageJson
---
storage/pom.xml | 1 +
storage/{ => sis-coveragejson}/pom.xml | 153 +++----
.../internal/coveragejson/CoverageJsonStore.java | 128 ++++++
.../coveragejson/CoverageJsonStoreProvider.java | 99 +++++
.../internal/coveragejson/CoverageResource.java | 457 +++++++++++++++++++++
.../sis/internal/coveragejson/binding/Axe.java | 136 ++++++
.../sis/internal/coveragejson/binding/Axes.java | 58 +++
.../internal/coveragejson/binding/Category.java | 68 +++
.../coveragejson/binding/CategoryEncoding.java | 31 ++
.../internal/coveragejson/binding/Coverage.java | 131 ++++++
.../coveragejson/binding/CoverageCollection.java | 90 ++++
.../coveragejson/binding/CoverageJsonObject.java | 55 +++
.../internal/coveragejson/binding/Dictionary.java | 69 ++++
.../sis/internal/coveragejson/binding/Domain.java | 90 ++++
.../coveragejson/binding/GeographicCRS.java | 72 ++++
.../sis/internal/coveragejson/binding/I18N.java | 119 ++++++
.../coveragejson/binding/IdentifierRS.java | 84 ++++
.../internal/coveragejson/binding/Identifiers.java | 29 ++
.../sis/internal/coveragejson/binding/NdArray.java | 104 +++++
.../coveragejson/binding/ObservedProperty.java | 88 ++++
.../internal/coveragejson/binding/Parameter.java | 91 ++++
.../coveragejson/binding/ParameterGroup.java | 86 ++++
.../internal/coveragejson/binding/Parameters.java | 71 ++++
.../coveragejson/binding/ProjectedCRS.java | 64 +++
.../sis/internal/coveragejson/binding/Ranges.java | 67 +++
.../binding/ReferenceSystemConnection.java | 67 +++
.../sis/internal/coveragejson/binding/Symbol.java | 60 +++
.../coveragejson/binding/TargetConcept.java | 54 +++
.../internal/coveragejson/binding/TemporalRS.java | 79 ++++
.../sis/internal/coveragejson/binding/TileSet.java | 75 ++++
.../coveragejson/binding/TiledNdArray.java | 79 ++++
.../sis/internal/coveragejson/binding/Unit.java | 79 ++++
.../internal/coveragejson/binding/VerticalCRS.java | 61 +++
.../org.apache.sis.storage.DataStoreProvider | 1 +
.../coveragejson/CoverageJsonStoreTest.java | 86 ++++
.../coveragejson/CoverageJsonTestSuite.java | 44 ++
.../internal/coveragejson/binding/BindingTest.java | 234 +++++++++++
.../internal/coveragejson/binding/axe_bounds.json | 12 +
.../internal/coveragejson/binding/axe_polygon.json | 33 ++
.../internal/coveragejson/binding/axe_regular.json | 5 +
.../internal/coveragejson/binding/axe_tuples.json | 20 +
.../binding/coverage_vertical_profile.json | 91 ++++
.../binding/coverage_vertical_profile_nocs.json | 80 ++++
.../coveragejson/binding/coveragecollection.json | 92 +++++
.../internal/coveragejson/binding/domain_grid.json | 26 ++
.../coveragejson/binding/domain_trajectory.json | 27 ++
.../coveragejson/binding/domaintype_grid.json | 5 +
.../binding/domaintype_multipoint.json | 30 ++
.../binding/domaintype_multipointseries.json | 31 ++
.../binding/domaintype_multipolygon.json | 32 ++
.../binding/domaintype_multipolygonseries.json | 32 ++
.../coveragejson/binding/domaintype_point.json | 24 ++
.../binding/domaintype_pointseries.json | 26 ++
.../coveragejson/binding/domaintype_polygon.json | 29 ++
.../binding/domaintype_polygonseries.json | 31 ++
.../coveragejson/binding/domaintype_section.json | 31 ++
.../binding/domaintype_trajectory.json | 30 ++
.../binding/domaintype_vertical_profile.json | 26 ++
.../binding/geographiccrs_longlat.json | 4 +
.../binding/geographiccrs_longlatheight.json | 4 +
.../sis/internal/coveragejson/binding/ndarray.json | 10 +
.../binding/parameter_categoricaldata.json | 33 ++
.../binding/parameter_continuousdata.json | 24 ++
.../binding/parametergroup_uncertainty.json | 13 +
.../binding/parametergroup_vectorquantity.json | 9 +
.../binding/projectedcrs_britishnationalgrid.json | 4 +
.../binding/reference_system_connection.json | 7 +
.../internal/coveragejson/binding/temporalrs.json | 4 +
.../coveragejson/binding/tiledndarray.json | 16 +
.../coveragejson/binding/verticalcrs_navd88.json | 4 +
.../sis/internal/coveragejson/coverage_xyzt.json | 58 +++
71 files changed, 4177 insertions(+), 86 deletions(-)
diff --git a/storage/pom.xml b/storage/pom.xml
index 4287379b06..b44712ecf0 100644
--- a/storage/pom.xml
+++ b/storage/pom.xml
@@ -175,6 +175,7 @@
<module>sis-netcdf</module>
<module>sis-geotiff</module>
<module>sis-earth-observation</module>
+ <module>sis-coveragejson</module>
</modules>
</project>
diff --git a/storage/pom.xml b/storage/sis-coveragejson/pom.xml
similarity index 51%
copy from storage/pom.xml
copy to storage/sis-coveragejson/pom.xml
index 4287379b06..7ae7a10ba8 100644
--- a/storage/pom.xml
+++ b/storage/sis-coveragejson/pom.xml
@@ -27,7 +27,7 @@
<parent>
<groupId>org.apache.sis</groupId>
- <artifactId>parent</artifactId>
+ <artifactId>storage</artifactId>
<version>2.0-SNAPSHOT</version>
</parent>
@@ -35,12 +35,11 @@
<!-- ===========================================================
Module Description
=========================================================== -->
- <artifactId>storage</artifactId>
- <packaging>pom</packaging>
- <name>Apache SIS storage</name>
+ <groupId>org.apache.sis.storage</groupId>
+ <artifactId>sis-coveragejson</artifactId>
+ <name>Apache SIS Coverage-JSON storage</name>
<description>
- Group of modules for reading and writing data from/to various storages.
- Storages are typically file formats or a database schemas.
+ DataStore for Coverage-JSON format.
</description>
@@ -59,40 +58,7 @@
<role>developer</role>
</roles>
</developer>
- <developer>
- <name>Martin Desruisseaux</name>
- <id>desruisseaux</id>
- <email>desruisseaux@apache.org</email>
- <organization>Geomatys</organization>
- <organizationUrl>https://www.geomatys.com</organizationUrl>
- <timezone>+1</timezone>
- <roles>
- <role>developer</role>
- </roles>
- </developer>
</developers>
- <contributors>
- <contributor>
- <name>Thi Phuong Hao Nguyen</name>
- <email>nguyenthiphuonghao243@gmail.com</email>
- <organization>VNSC</organization>
- <organizationUrl>https://vnsc.org.vn</organizationUrl>
- <timezone>+7</timezone>
- <roles>
- <role>developer</role>
- </roles>
- </contributor>
- <contributor>
- <name>Minh Chinh Vu</name>
- <email>chinhvm.uet.1995@gmail.com</email>
- <organization>VNSC</organization>
- <organizationUrl>https://vnsc.org.vn</organizationUrl>
- <timezone>+7</timezone>
- <roles>
- <role>developer</role>
- </roles>
- </contributor>
- </contributors>
<!-- ===========================================================
@@ -100,21 +66,18 @@
=========================================================== -->
<build>
<plugins>
-
- <!-- Compile properties files into resources UTF files and
- collect JAR files in <root>/target/binaries directory. -->
<plugin>
- <groupId>org.apache.sis.core</groupId>
- <artifactId>sis-build-helper</artifactId>
- <version>${sis.plugin.version}</version>
- <executions>
- <execution>
- <goals>
- <goal>compile-resources</goal>
- <goal>collect-jars</goal>
- </goals>
- </execution>
- </executions>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-jar-plugin</artifactId>
+ <configuration>
+ <archive>
+ <manifestEntries>
+ <Automatic-Module-Name>
+ org.apache.sis.storage.coveragejson
+ </Automatic-Module-Name>
+ </manifestEntries>
+ </archive>
+ </configuration>
</plugin>
</plugins>
</build>
@@ -125,56 +88,74 @@
=========================================================== -->
<dependencies>
<dependency>
- <groupId>org.apache.sis.core</groupId>
- <artifactId>sis-metadata</artifactId>
+ <groupId>org.apache.sis.storage</groupId>
+ <artifactId>sis-storage</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.sis.storage</groupId>
+ <artifactId>sis-storage</artifactId>
<version>${project.version}</version>
+ <type>test-jar</type>
+ <scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.sis.core</groupId>
<artifactId>sis-referencing</artifactId>
<version>${project.version}</version>
+ <type>test-jar</type>
+ <scope>test</scope>
</dependency>
+
+ <!--dependency>
+ <groupId>jakarta.json</groupId>
+ <artifactId>jakarta.json-api</artifactId>
+ <version>2.1.1</version>
+ </dependency>
+ <dependency>
+ <groupId>jakarta.json.bind</groupId>
+ <artifactId>jakarta.json.bind-api</artifactId>
+ <version>3.0.0</version>
+ </dependency-->
+
+ <!-- this dependencis pulls jakarta jsonb-api -->
<dependency>
- <groupId>org.opengis</groupId>
- <artifactId>geoapi-pending</artifactId>
+ <groupId>org.eclipse</groupId>
+ <artifactId>yasson</artifactId>
+ <version>3.0.3</version>
</dependency>
+
+ <!--dependency>
+ <groupId>javax.json.bind</groupId>
+ <artifactId>javax.json.bind-api</artifactId>
+ <version>1.0</version>
+ </dependency-->
+ <!--dependency>
+ <groupId>org.apache.johnzon</groupId>
+ <artifactId>johnzon-core</artifactId>
+ <version>1.2.19</version>
+ </dependency>
- <!-- Test dependencies -->
<dependency>
- <groupId>org.opengis</groupId>
- <artifactId>geoapi-conformance</artifactId>
+ <groupId>org.apache.geronimo.specs</groupId>
+ <artifactId>geronimo-json_1.1_spec</artifactId>
+ <version>1.5</version>
</dependency>
<dependency>
- <groupId>org.apache.derby</groupId>
- <artifactId>derby</artifactId>
- <scope>test</scope>
+ <groupId>org.apache.johnzon</groupId>
+ <artifactId>johnzon-mapper</artifactId>
+ <version>1.2.19</version>
</dependency>
<dependency>
- <groupId>org.apache.derby</groupId>
- <artifactId>derbytools</artifactId>
- <scope>test</scope>
+ <groupId>org.apache.johnzon</groupId>
+ <artifactId>johnzon-jsonb</artifactId>
+ <version>1.2.19</version>
</dependency>
<dependency>
- <groupId>org.apache.sis.core</groupId>
- <artifactId>sis-utility</artifactId>
- <version>${project.version}</version>
- <type>test-jar</type>
- <scope>test</scope>
- </dependency>
+ <groupId>org.apache.johnzon</groupId>
+ <artifactId>johnzon-jsonb-extras</artifactId>
+ <version>1.2.19</version>
+ </dependency-->
</dependencies>
-
- <!-- ===========================================================
- Sub-modules included in the build
- =========================================================== -->
- <modules>
- <module>sis-storage</module>
- <module>sis-shapefile</module>
- <module>sis-xmlstore</module>
- <module>sis-sqlstore</module>
- <module>sis-netcdf</module>
- <module>sis-geotiff</module>
- <module>sis-earth-observation</module>
- </modules>
-
</project>
diff --git a/storage/sis-coveragejson/src/main/java/org/apache/sis/internal/coveragejson/CoverageJsonStore.java b/storage/sis-coveragejson/src/main/java/org/apache/sis/internal/coveragejson/CoverageJsonStore.java
new file mode 100644
index 0000000000..ac1800821f
--- /dev/null
+++ b/storage/sis-coveragejson/src/main/java/org/apache/sis/internal/coveragejson/CoverageJsonStore.java
@@ -0,0 +1,128 @@
+/*
+ * 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.internal.coveragejson;
+
+import jakarta.json.bind.Jsonb;
+import jakarta.json.bind.JsonbBuilder;
+import java.io.BufferedInputStream;
+import java.io.InputStream;
+import java.net.URI;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+import org.apache.sis.internal.coveragejson.binding.Coverage;
+import org.apache.sis.internal.coveragejson.binding.CoverageCollection;
+import org.apache.sis.internal.coveragejson.binding.CoverageJsonObject;
+import org.apache.sis.internal.storage.MetadataBuilder;
+import org.apache.sis.internal.storage.URIDataStore;
+import org.apache.sis.internal.storage.io.IOUtilities;
+import org.apache.sis.storage.DataStore;
+import org.apache.sis.storage.DataStoreException;
+import org.apache.sis.storage.Resource;
+import org.apache.sis.storage.StorageConnector;
+import org.apache.sis.storage.WritableAggregate;
+import org.opengis.metadata.Metadata;
+import org.opengis.parameter.ParameterValueGroup;
+
+/**
+ * A data store backed by Coverage-JSON files.
+ *
+ * @author Johann Sorel (Geomatys)
+ */
+public class CoverageJsonStore extends DataStore implements WritableAggregate {
+
+ /**
+ * The {@link CoverageJsonStoreProvider#LOCATION} parameter value, or {@code null} if none.
+ * This is used for information purpose only, not for actual reading operations.
+ *
+ * @see #getOpenParameters()
+ */
+ private final URI location;
+
+ /**
+ * Same value than {@link #location} but as a path, or {@code null} if none.
+ * Stored separately because conversion from path to URI back to path is not
+ * looseness (relative paths become absolutes).
+ */
+ private final Path path;
+
+ private boolean parsed = false;
+ private final List<Resource> components = new ArrayList<>();
+
+ CoverageJsonStore(CoverageJsonStoreProvider provider, StorageConnector connector) throws DataStoreException {
+ super(provider, connector);
+ location = connector.getStorageAs(URI.class);
+ path = connector.getStorageAs(Path.class);
+ }
+
+ @Override
+ public Optional<ParameterValueGroup> getOpenParameters() {
+ return Optional.ofNullable(URIDataStore.parameters(provider, location));
+ }
+
+ @Override
+ public Metadata getMetadata() throws DataStoreException {
+ final MetadataBuilder builder = new MetadataBuilder();
+ builder.addIdentifier(null, IOUtilities.filename(path), MetadataBuilder.Scope.ALL);
+ return builder.buildAndFreeze();
+ }
+
+ @Override
+ public synchronized Collection<? extends Resource> components() throws DataStoreException {
+ if (!parsed) {
+ parsed = true;
+ if (Files.exists(path)) {
+ try (final Jsonb b = JsonbBuilder.create();
+ final InputStream in = new BufferedInputStream(Files.newInputStream(path))) {
+ final CoverageJsonObject obj = b.fromJson(in, CoverageJsonObject.class);
+
+ if (obj instanceof Coverage) {
+ final Coverage coverage = (Coverage) obj;
+ components.add(new CoverageResource(this, coverage));
+
+ } else if (obj instanceof CoverageCollection) {
+ throw new UnsupportedOperationException("Coverage collection not supported yet.");
+ }
+
+ } catch (Exception ex) {
+ throw new DataStoreException("Failed to parse coverage json object.", ex);
+ }
+ }
+ }
+
+ return Collections.unmodifiableList(components);
+ }
+
+
+ @Override
+ public Resource add(Resource resource) throws DataStoreException {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public void remove(Resource resource) throws DataStoreException {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public void close() throws DataStoreException {
+ }
+}
diff --git a/storage/sis-coveragejson/src/main/java/org/apache/sis/internal/coveragejson/CoverageJsonStoreProvider.java b/storage/sis-coveragejson/src/main/java/org/apache/sis/internal/coveragejson/CoverageJsonStoreProvider.java
new file mode 100644
index 0000000000..b9c48eb7f0
--- /dev/null
+++ b/storage/sis-coveragejson/src/main/java/org/apache/sis/internal/coveragejson/CoverageJsonStoreProvider.java
@@ -0,0 +1,99 @@
+/*
+ * 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.internal.coveragejson;
+
+import java.net.URI;
+import java.util.logging.Logger;
+import org.apache.sis.internal.storage.Capability;
+import org.apache.sis.internal.storage.StoreMetadata;
+import org.apache.sis.internal.storage.URIDataStore;
+import org.apache.sis.storage.Aggregate;
+import org.apache.sis.storage.DataStore;
+import org.apache.sis.storage.DataStoreException;
+import org.apache.sis.storage.DataStoreProvider;
+import org.apache.sis.storage.GridCoverageResource;
+import org.apache.sis.storage.ProbeResult;
+import org.apache.sis.storage.StorageConnector;
+import org.apache.sis.util.Version;
+import org.opengis.parameter.ParameterDescriptorGroup;
+
+/**
+ * The provider of {@link CoverageJsonStore} instances. Given a {@link StorageConnector} input,
+ * this class tries to instantiate a {@code CoverageJsonStore}.
+ *
+ * Draft specification : https://github.com/opengeospatial/CoverageJSON
+ *
+ * @author Johann Sorel (Geomatys)
+ *
+ * @see CoverageJsonStore
+ */
+@StoreMetadata(formatName = CoverageJsonStoreProvider.NAME,
+ fileSuffixes = {"covjson"},
+ capabilities = Capability.READ,
+ resourceTypes = {Aggregate.class, GridCoverageResource.class})
+public class CoverageJsonStoreProvider extends DataStoreProvider {
+
+ public static final String NAME = "CoverageJSON";
+ /**
+ * The MIME type for Coverage-JSON files.
+ */
+ private static final String MIME_TYPE = "application/vnd.cov+json";
+
+ /**
+ * The logger used by Coverage-JSON stores.
+ *
+ * @see #getLogger()
+ */
+ private static final Logger LOGGER = Logger.getLogger("org.apache.sis.internal.coveragejson");
+
+ /**
+ * The parameter descriptor to be returned by {@link #getOpenParameters()}.
+ */
+ private static final ParameterDescriptorGroup OPEN_DESCRIPTOR = URIDataStore.Provider.descriptor(NAME);
+
+ @Override
+ public String getShortName() {
+ return NAME;
+ }
+
+ @Override
+ public ParameterDescriptorGroup getOpenParameters() {
+ return OPEN_DESCRIPTOR;
+ }
+
+ @Override
+ public ProbeResult probeContent(StorageConnector connector) throws DataStoreException {
+ final URI uri = connector.getStorageAs(URI.class);
+ if (uri != null && uri.toString().toLowerCase().endsWith(".covjson")) {
+ return new ProbeResult(true, MIME_TYPE, new Version("0.9"));
+ }
+ return ProbeResult.UNSUPPORTED_STORAGE;
+ }
+
+ @Override
+ public DataStore open(StorageConnector connector) throws DataStoreException {
+ return new CoverageJsonStore(this, connector);
+ }
+
+ /**
+ * {@return the logger used by CoverageJSON stores}.
+ */
+ @Override
+ public Logger getLogger() {
+ return LOGGER;
+ }
+}
diff --git a/storage/sis-coveragejson/src/main/java/org/apache/sis/internal/coveragejson/CoverageResource.java b/storage/sis-coveragejson/src/main/java/org/apache/sis/internal/coveragejson/CoverageResource.java
new file mode 100644
index 0000000000..50f7e9d56c
--- /dev/null
+++ b/storage/sis-coveragejson/src/main/java/org/apache/sis/internal/coveragejson/CoverageResource.java
@@ -0,0 +1,457 @@
+/*
+ * 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.internal.coveragejson;
+
+import java.awt.image.DataBuffer;
+import java.awt.image.DataBufferDouble;
+import java.time.Instant;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeFormatterBuilder;
+import java.time.format.DateTimeParseException;
+import java.time.format.SignStyle;
+import java.time.temporal.ChronoField;
+import java.time.temporal.TemporalAccessor;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import javax.measure.Unit;
+import org.apache.sis.coverage.SampleDimension;
+import org.apache.sis.coverage.grid.BufferedGridCoverage;
+import org.apache.sis.coverage.grid.DisjointExtentException;
+import org.apache.sis.coverage.grid.GridCoverage;
+import org.apache.sis.coverage.grid.GridExtent;
+import org.apache.sis.coverage.grid.GridGeometry;
+import org.apache.sis.coverage.grid.GridRoundingMode;
+import org.apache.sis.internal.coveragejson.binding.Axe;
+import org.apache.sis.internal.coveragejson.binding.Axes;
+import org.apache.sis.internal.coveragejson.binding.Coverage;
+import org.apache.sis.internal.coveragejson.binding.CoverageJsonObject;
+import org.apache.sis.internal.coveragejson.binding.Domain;
+import org.apache.sis.internal.coveragejson.binding.GeographicCRS;
+import org.apache.sis.internal.coveragejson.binding.IdentifierRS;
+import org.apache.sis.internal.coveragejson.binding.NdArray;
+import org.apache.sis.internal.coveragejson.binding.Parameter;
+import org.apache.sis.internal.coveragejson.binding.ProjectedCRS;
+import org.apache.sis.internal.coveragejson.binding.ReferenceSystemConnection;
+import org.apache.sis.internal.coveragejson.binding.TemporalRS;
+import org.apache.sis.internal.coveragejson.binding.VerticalCRS;
+import org.apache.sis.measure.Units;
+import org.apache.sis.referencing.CRS;
+import org.apache.sis.referencing.CommonCRS;
+import org.apache.sis.referencing.operation.matrix.Matrices;
+import org.apache.sis.referencing.operation.matrix.MatrixSIS;
+import org.apache.sis.referencing.operation.transform.MathTransforms;
+import org.apache.sis.storage.AbstractGridCoverageResource;
+import org.apache.sis.storage.DataStoreException;
+import org.apache.sis.storage.NoSuchDataException;
+import org.opengis.metadata.spatial.DimensionNameType;
+import org.opengis.referencing.crs.CoordinateReferenceSystem;
+import org.opengis.referencing.datum.PixelInCell;
+import org.opengis.referencing.operation.MathTransform;
+import org.opengis.referencing.operation.MathTransform1D;
+import org.opengis.util.FactoryException;
+
+/**
+ *
+ * @author Johann Sorel (Geomatys)
+ */
+final class CoverageResource extends AbstractGridCoverageResource {
+
+ private static final DateTimeFormatter YEAR = new DateTimeFormatterBuilder()
+ .appendValue(ChronoField.YEAR, 1, 19, SignStyle.EXCEEDS_PAD)
+ .toFormatter();
+
+
+ private static final DateTimeFormatter YEAR_MONTH = new DateTimeFormatterBuilder()
+ .appendValue(ChronoField.YEAR, 1, 19, SignStyle.EXCEEDS_PAD)
+ .appendLiteral('-')
+ .appendValue(ChronoField.MONTH_OF_YEAR, 1)
+ .toFormatter();
+
+ private static final DateTimeFormatter YEAR_MONTH_DAY = new DateTimeFormatterBuilder()
+ .appendValue(ChronoField.YEAR, 1, 19, SignStyle.EXCEEDS_PAD)
+ .appendLiteral('-')
+ .appendValue(ChronoField.MONTH_OF_YEAR, 1)
+ .appendLiteral('-')
+ .appendValue(ChronoField.DAY_OF_MONTH, 1)
+ .toFormatter();
+
+ private static final DateTimeFormatter DATE_TIME = DateTimeFormatter.ISO_DATE_TIME;
+
+ private final CoverageJsonStore store;
+ private final Coverage binding;
+
+ private final GridGeometry gridGeometry;
+ private final List<SampleDimension> sampleDimensions;
+ private final Map<String,double[]> datas;
+
+ public CoverageResource(CoverageJsonStore store, Coverage binding) throws DataStoreException {
+ super(null);
+ this.store = store;
+ this.binding = binding;
+
+ //rebuild grid geometry
+ try {
+ gridGeometry = jsonToGridGeometry(binding.domain);
+ } catch (FactoryException ex) {
+ throw new DataStoreException("Failed to create GridGeometry from JSON Domain", ex);
+ }
+ //rebuild sample dimensions
+ sampleDimensions = new ArrayList<>();
+ for (Entry<String,Parameter> entry : binding.parameters.any.entrySet()) {
+ final SampleDimension sd = jsonToSampleDimension(entry.getKey(), entry.getValue());
+ sampleDimensions.add(sd);
+ }
+ if (binding.parameterGroups != null) {
+ throw new UnsupportedOperationException("Parameter groups not supported yet.");
+ }
+ //read datas
+ datas = new HashMap<>();
+ for (Entry<String,NdArray> entry : binding.ranges.any.entrySet()) {
+ datas.put(entry.getKey(), jsonToDataBuffer(entry.getValue()));
+ }
+ }
+
+ @Override
+ public GridGeometry getGridGeometry() throws DataStoreException {
+ return gridGeometry;
+ }
+
+ @Override
+ public List<SampleDimension> getSampleDimensions() throws DataStoreException {
+ return Collections.unmodifiableList(sampleDimensions);
+ }
+
+ @Override
+ public GridCoverage read(GridGeometry domain, int... ranges) throws DataStoreException {
+
+ final GridGeometry intersection;
+ if (domain != null) {
+ try {
+ intersection = gridGeometry.derive().rounding(GridRoundingMode.ENCLOSING).subgrid(domain).build();
+ } catch (DisjointExtentException ex) {
+ throw new NoSuchDataException(ex);
+ }
+ } else {
+ intersection = gridGeometry;
+ }
+
+ final double[][] rawDatas;
+ final List<SampleDimension> selected;
+ if (ranges == null || ranges.length == 0) {
+ selected = sampleDimensions;
+ rawDatas = new double[sampleDimensions.size()][0];
+ for (int i = 0; i < rawDatas.length; i++) {
+ rawDatas[i] = datas.get(sampleDimensions.get(i).getName().toString());
+ }
+
+ } else {
+ selected = new ArrayList<>();
+ rawDatas = new double[ranges.length][0];
+ for (int i = 0; i < rawDatas.length; i++) {
+ final SampleDimension sd = sampleDimensions.get(ranges[i]);
+ selected.add(sd);
+ rawDatas[i] = datas.get(sd.getName().toString());
+ }
+ }
+
+ final DataBuffer buffer = new DataBufferDouble(rawDatas, rawDatas[0].length);
+ return new BufferedGridCoverage(intersection, selected, buffer);
+ }
+
+ /**
+ * Transform JSON domain to GridGeometry.
+ */
+ private static GridGeometry jsonToGridGeometry(Domain domain) throws DataStoreException, FactoryException {
+
+ if (Domain.DOMAINTYPE_GRID.equalsIgnoreCase(domain.domainType)) {
+
+ //build coordinate system
+ final List<ReferenceSystemConnection> referencing = domain.referencing;
+ final List<String> axeNames = new ArrayList<>();
+ final List<CoordinateReferenceSystem> crss = new ArrayList<>();
+ if (referencing != null && !referencing.isEmpty()) {
+ for (ReferenceSystemConnection rsc : referencing) {
+ axeNames.addAll(rsc.coordinates);
+ final CoordinateReferenceSystem crs = jsonToCoordinateReferenceSystem(rsc.system);
+ if (crs.getCoordinateSystem().getDimension() != rsc.coordinates.size()) {
+ throw new DataStoreException("Declared CRS " + rsc.system.toString() + " do not match coordinates length");
+ }
+ crss.add(crs);
+ }
+ } else {
+ throw new DataStoreException("Coverage domain must be defined, Coverage as part of CoverageCollection not supported yet.");
+ }
+
+ //build extent
+ final int dimension = axeNames.size();
+ final Axes axes = domain.axes;
+ final GridGeometry[] axeGrids = new GridGeometry[dimension];
+
+ //check if axes declared on crs are ordered in the same way as the grid extent.
+ final int[] reorder = new int[dimension];
+ boolean inOrder = true;
+
+ for (int i = 0; i < dimension; i++) {
+ final String axeName = axeNames.get(i);
+ final Axe axe;
+ final int realIdx;
+ switch (axeName) {
+ case "x" :
+ if (axes.x == null) throw new DataStoreException("X axe is undefined");
+ axe = axes.x;
+ realIdx = 0;
+ reorder[i] = realIdx;
+ inOrder &= (i == realIdx);
+ break;
+ case "y" :
+ if (axes.y == null) throw new DataStoreException("Y axe is undefined");
+ axe = axes.y;
+ realIdx = 1;
+ reorder[i] = realIdx;
+ inOrder &= (i == realIdx);
+ break;
+ case "z" :
+ if (axes.z == null) throw new DataStoreException("Z axe is undefined");
+ axe = axes.z;
+ realIdx = 2;
+ reorder[i] = realIdx;
+ inOrder &= (i == realIdx);
+ break;
+ case "t" :
+ if (axes.t == null) throw new DataStoreException("T axe is undefined");
+ axe = axes.t;
+ realIdx = reorder.length == 3 ? 2 : 3;
+ reorder[i] = realIdx;
+ inOrder &= (i == realIdx);
+ break;
+ default: throw new DataStoreException("Unexpected axe name :" + axeName);
+ }
+ axeGrids[realIdx] = jsonAxeToGridGeometry(axeName, axe);
+ }
+
+ final DimensionNameType[] dnt = new DimensionNameType[dimension];
+ final long[] lower = new long[dimension];
+ final long[] upper = new long[dimension]; //inclusive
+ MathTransform gridToCrs = null;
+ for (int i = 0; i < dimension; i++) {
+ dnt[i] = axeGrids[i].getExtent().getAxisType(0).get();
+ upper[i] = axeGrids[i].getExtent().getHigh(0);
+ if (gridToCrs == null) {
+ gridToCrs = axeGrids[i].getGridToCRS(PixelInCell.CELL_CENTER);
+ } else {
+ gridToCrs = MathTransforms.compound(gridToCrs, axeGrids[i].getGridToCRS(PixelInCell.CELL_CENTER));
+ }
+ }
+
+
+ if (!inOrder) {
+ final MatrixSIS m = Matrices.createZero(dimension+1, dimension+1);
+ for (int i = 0; i < dimension; i++) {
+ m.setElement(i, reorder[i], 1.0);
+ }
+ m.setElement(dimension, dimension, 1.0);
+ final MathTransform reorderTrs = MathTransforms.linear(m);
+ gridToCrs = MathTransforms.concatenate(reorderTrs, gridToCrs);
+ }
+
+
+ final CoordinateReferenceSystem crs = CRS.compound(crss.toArray(CoordinateReferenceSystem[]::new));
+ final GridExtent extent = new GridExtent(dnt, lower, upper, true);
+ return new GridGeometry(extent, PixelInCell.CELL_CENTER, gridToCrs, crs);
+
+ } else {
+ throw new DataStoreException("Unsupported domain type " + domain.domainType);
+ }
+ }
+
+ /**
+ * Transform JSON axe to 1D GridGeometry without CRS.
+ */
+ private static GridGeometry jsonAxeToGridGeometry(String axeName, Axe axe) throws DataStoreException {
+
+ if (axe.dataType == null || Axe.DATATYPE_PRIMITIVE.equals(axe.dataType)) {
+
+ } else if (Axe.DATATYPE_TUPLE.equals(axe.dataType) ) {
+ throw new UnsupportedOperationException("Tuple axe data type not supported yet.");
+ } else if (Axe.DATATYPE_POLYGON.equals(axe.dataType) ) {
+ throw new UnsupportedOperationException("Polygon axe data type not supported yet.");
+ } else {
+ throw new DataStoreException("Unexpected axe data type :" + axe.dataType);
+ }
+
+ //rebuild axe transform
+ final MathTransform1D axeTrs;
+ final int size;
+ if (axe.values != null) {
+ final double[] values = new double[axe.values.size()];
+ for (int i = 0; i < values.length; i++) {
+ values[i] = asDouble(axe.values.get(i));
+ }
+ size = values.length;
+ axeTrs = MathTransforms.interpolate(null, values);
+ } else if (axe.start != null) {
+ size = axe.num;
+ if (axe.num == 1) {
+ axeTrs = (MathTransform1D) MathTransforms.linear(1.0, axe.start);
+ } else {
+ final double step = (axe.stop - axe.start) / (axe.num -1);
+ axeTrs = (MathTransform1D) MathTransforms.linear(step, axe.start);
+ }
+ } else {
+ throw new DataStoreException("Axe must have values or star/stop values");
+ }
+
+ final GridExtent extent = new GridExtent(new DimensionNameType[]{DimensionNameType.valueOf(axeName)}, new long[]{0}, new long[]{size-1}, true);
+ return new GridGeometry(extent, PixelInCell.CELL_CENTER, axeTrs, null);
+ }
+
+ /**
+ * Transform JSON system object to CoordinateReferenceSystem.
+ */
+ private static CoordinateReferenceSystem jsonToCoordinateReferenceSystem(CoverageJsonObject obj) throws FactoryException {
+ if (obj instanceof GeographicCRS) {
+ final GeographicCRS jcrs = (GeographicCRS) obj;
+ if (jcrs.id != null) {
+ if (jcrs.id.equals("http://www.opengis.net/def/crs/EPSG/0/4979")) {
+ return CommonCRS.WGS84.geographic3D();
+ }
+ return CRS.forCode(jcrs.id);
+ } else {
+ throw new UnsupportedOperationException("Geographic CRS wihout id not supported");
+ }
+ } else if (obj instanceof ProjectedCRS) {
+ final ProjectedCRS jcrs = (ProjectedCRS) obj;
+ throw new UnsupportedOperationException("ProjectedCRS not supported yet");
+
+ } else if (obj instanceof VerticalCRS) {
+ final VerticalCRS jcrs = (VerticalCRS) obj;
+ throw new UnsupportedOperationException("VerticalCRS not supported yet");
+
+ } else if (obj instanceof TemporalRS) {
+ final TemporalRS jcrs = (TemporalRS) obj;
+ if (jcrs.timeScale != null) {
+ throw new UnsupportedOperationException("TemporalRS timeScale not supported yet");
+ }
+ if ("Gregorian".equalsIgnoreCase(jcrs.calendar)) {
+ return CommonCRS.Temporal.JAVA.crs();
+ } else {
+ throw new UnsupportedOperationException(jcrs.calendar + "calendar not supported yet");
+ }
+
+ } else if (obj instanceof IdentifierRS) {
+ final IdentifierRS jcrs = (IdentifierRS) obj;
+ throw new UnsupportedOperationException("IdentifierRS not supported yet");
+
+ } else {
+ throw new UnsupportedOperationException("Unsupported system " + String.valueOf(obj));
+ }
+ }
+
+ /**
+ * Transform JSON parameter to SampleDimension.
+ */
+ private static SampleDimension jsonToSampleDimension(String name, Parameter parameter) {
+ final SampleDimension.Builder builder = new SampleDimension.Builder();
+
+ builder.setName(name);
+
+// if (parameter.id != null) {
+// builder.setName(parameter.id);
+// } else if (parameter.label != null) {
+// builder.setName(parameter.label);
+// } else if (parameter.description != null) {
+// builder.setName(parameter.description);
+// }
+
+ Unit unit = jsonToUnit(parameter.unit);
+
+ //TODO categories
+ //parameter.categoryEncoding;
+ //parameter.observedProperty;
+
+ return builder.build();
+ }
+
+ /**
+ * Transform JSON unit to SIS Unit.
+ */
+ private static Unit jsonToUnit(org.apache.sis.internal.coveragejson.binding.Unit unit) {
+ if (unit == null) return Units.UNITY;
+
+ if (unit.symbol instanceof String) {
+ return Units.valueOf(unit.symbol.toString());
+ }
+ return Units.UNITY;
+ }
+
+ /**
+ * Transform JSON NdArray to number array.
+ */
+ private static double[] jsonToDataBuffer(NdArray array) throws DataStoreException {
+ //TODO more work on checking axes order
+ double[] values = new double[array.values.size()];
+ for (int i = 0; i < values.length; i++) {
+ values[i] = asDouble(array.values.get(i));
+ }
+
+ return values;
+ }
+
+ private static double asDouble(Object cdt) throws DataStoreException {
+ if (cdt == null) {
+ return Double.NaN;
+ } else if (cdt instanceof String) {
+ final Instant instant = parseDataTime(String.valueOf(cdt));
+ return instant.toEpochMilli();
+ } else if (cdt instanceof Number) {
+ return ((Number) cdt).doubleValue();
+ } else {
+ throw new DataStoreException("Unexpected value : " + cdt);
+ }
+ }
+
+ /**
+ * If the calendar is based on years, months, days, then the referenced
+ * values SHOULD use one of the following ISO8601-based lexical representations:
+ * YYYY
+ * ±XYYYY (where X stands for extra year digits)
+ * YYYY-MM
+ * YYYY-MM-DD
+ * YYYY-MM-DDTHH:MM:SS[.F]Z where Z is either “Z” or a time scale offset +|-HH:MM
+ *
+ * If calendar dates with reduced precision are used in a lexical
+ * representation (e.g. "2016"), then a client SHOULD interpret those dates
+ * in that reduced precision.
+ */
+ private static Instant parseDataTime(String str) throws DataStoreException {
+
+ for (DateTimeFormatter dtf : Arrays.asList(YEAR,YEAR_MONTH, YEAR_MONTH_DAY, DATE_TIME)) {
+ try {
+ TemporalAccessor accesser = dtf.parse(str);
+ return Instant.from(accesser);
+ } catch (DateTimeParseException ex) {
+ //do nothing
+ }
+ }
+ throw new DataStoreException("Unable to parse date : " + str);
+ }
+}
diff --git a/storage/sis-coveragejson/src/main/java/org/apache/sis/internal/coveragejson/binding/Axe.java b/storage/sis-coveragejson/src/main/java/org/apache/sis/internal/coveragejson/binding/Axe.java
new file mode 100644
index 0000000000..1e8533c3b7
--- /dev/null
+++ b/storage/sis-coveragejson/src/main/java/org/apache/sis/internal/coveragejson/binding/Axe.java
@@ -0,0 +1,136 @@
+/*
+ * 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.internal.coveragejson.binding;
+
+import jakarta.json.bind.annotation.JsonbNillable;
+import jakarta.json.bind.annotation.JsonbPropertyOrder;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * An axis object MUST have either a "values" member or, as a compact notation
+ * for a regularly spaced numeric axis, have all the members "start", "stop",
+ * and "num".
+ *
+ * @author Johann Sorel (Geomatys)
+ */
+@JsonbNillable(false)
+@JsonbPropertyOrder({"start","stop","num","dataType","coordinates","values","bounds"})
+public final class Axe extends Dictionary<Object> {
+
+ public static final String DATATYPE_PRIMITIVE = "primitive";
+ public static final String DATATYPE_TUPLE = "tuple";
+ public static final String DATATYPE_POLYGON = "polygon";
+
+ /**
+ * The values of "start" and "stop" MUST be numbers
+ */
+ public Double start;
+ /**
+ * The values of "start" and "stop" MUST be numbers
+ */
+ public Double stop;
+ /**
+ * the value of "num" an integer greater than zero.
+ *
+ * If the value of "num" is 1, then "start" and "stop" MUST have identical
+ * values. For num > 1, the array elements of "values" MAY be reconstructed
+ * with the formula start + i * step where i is the ith element and in the
+ * interval [0, num-1] and step = (stop - start) / (num - 1).
+ *
+ * If num = 1 then "values" is [start]. Note that "start" can be greater
+ * than "stop" in which case the axis values are descending.
+ */
+ public Integer num;
+ /**
+ * The value of "dataType" determines the structure of an axis value and its
+ * coordinates that are made available for referencing. The values of
+ * "dataType" defined in this Community Standard are "primitive", "tuple",
+ * and "polygon". Custom values MAY be used as detailed in the Extensions
+ * section. For "primitive", there is a single coordinate identifier and
+ * each axis value MUST be a number or string. For "tuple", each axis value
+ * MUST be an array of fixed size of primitive values in a defined order,
+ * where the tuple size corresponds to the number of coordinate identifiers.
+ * For "polygon", each axis value MUST be a GeoJSON Polygon coordinate
+ * array, where the order of coordinates is given by the "coordinates"
+ * array.
+ *
+ * If missing, the member "dataType" defaults to "primitive" and MUST not be
+ * included for that default case.
+ *
+ * If "dataType" is "primitive" and the associated reference system (see
+ * 6.1.2) defines a natural ordering of values then the array values in
+ * "values", if existing, MUST be ordered monotonically, that is, increasing
+ * or decreasing.
+ */
+ public String dataType;
+ /**
+ * The value of "coordinates" is a non-empty array of coordinate identifiers
+ * corresponding to the order of the coordinates defined by "dataType".
+ *
+ * If missing, the member "coordinates" defaults to a one-element array of
+ * the axis identifier and MUST NOT be included for that default case.
+ *
+ * A coordinate identifier SHALL NOT be defined more than once in all axis
+ * objects of a domain object.
+ */
+ public List<String> coordinates;
+ /**
+ * The value of "values" is a non-empty array of axis values.
+ */
+ public List<Object> values;
+ /**
+ * An axis object MAY have axis value bounds defined in the member "bounds"
+ * where the value is an array of values of length len*2 with len being the
+ * length of the "values" array. For each axis value at array index i in the
+ * "values" array, a lower and upper bounding value at positions 2*i and
+ * 2*i+1, respectively, are given in the bounds array.
+ *
+ * If a domain axis object has no "bounds" member then a bounds array MAY be
+ * derived automatically.
+ */
+ public List<Object> bounds;
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) return true;
+ if (!(other instanceof Axe)) return false;
+
+ final Axe cdt = ((Axe) other);
+ return super.equals(other)
+ && Objects.equals(start, cdt.start)
+ && Objects.equals(stop, cdt.stop)
+ && Objects.equals(num, cdt.num)
+ && Objects.equals(dataType, cdt.dataType)
+ && Objects.equals(coordinates, cdt.coordinates)
+ && Objects.equals(values, cdt.values)
+ && Objects.equals(bounds, cdt.bounds);
+ }
+
+ @Override
+ public int hashCode() {
+ return super.hashCode() + Objects.hash(
+ start,
+ stop,
+ num,
+ dataType,
+ coordinates,
+ values,
+ bounds);
+ }
+
+}
diff --git a/storage/sis-coveragejson/src/main/java/org/apache/sis/internal/coveragejson/binding/Axes.java b/storage/sis-coveragejson/src/main/java/org/apache/sis/internal/coveragejson/binding/Axes.java
new file mode 100644
index 0000000000..1f34d2c25c
--- /dev/null
+++ b/storage/sis-coveragejson/src/main/java/org/apache/sis/internal/coveragejson/binding/Axes.java
@@ -0,0 +1,58 @@
+/*
+ * 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.internal.coveragejson.binding;
+
+import jakarta.json.bind.annotation.JsonbNillable;
+import jakarta.json.bind.annotation.JsonbPropertyOrder;
+import java.util.Objects;
+
+/**
+ * The "axes" member MUST NOT be empty.
+ *
+ * @author Johann Sorel (Geomatys)
+ */
+@JsonbNillable(false)
+@JsonbPropertyOrder({"x","y","z","t"})
+public final class Axes extends Dictionary<Object> {
+
+ public Axe x;
+ public Axe y;
+ public Axe z;
+ public Axe t;
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) return true;
+ if (!(other instanceof Axes)) return false;
+
+ final Axes cdt = ((Axes) other);
+ return super.equals(other)
+ && Objects.equals(x, cdt.x)
+ && Objects.equals(y, cdt.y)
+ && Objects.equals(y, cdt.y)
+ && Objects.equals(t, cdt.t);
+ }
+
+ @Override
+ public int hashCode() {
+ return super.hashCode() + Objects.hash(
+ x,
+ y,
+ z,
+ t);
+ }
+}
diff --git a/storage/sis-coveragejson/src/main/java/org/apache/sis/internal/coveragejson/binding/Category.java b/storage/sis-coveragejson/src/main/java/org/apache/sis/internal/coveragejson/binding/Category.java
new file mode 100644
index 0000000000..cd2e7d14b3
--- /dev/null
+++ b/storage/sis-coveragejson/src/main/java/org/apache/sis/internal/coveragejson/binding/Category.java
@@ -0,0 +1,68 @@
+/*
+ * 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.internal.coveragejson.binding;
+
+import jakarta.json.bind.annotation.JsonbNillable;
+import jakarta.json.bind.annotation.JsonbPropertyOrder;
+import java.util.Objects;
+
+/**
+ * A category object MUST an "id" and a "label" member, and MAY have a
+ * "description" member.
+ *
+ * @author Johann Sorel (Geomatys)
+ */
+@JsonbNillable(false)
+@JsonbPropertyOrder({"id","label","description"})
+public final class Category extends Dictionary<Object> {
+
+ /**
+ * The value of "id" MUST be a string and SHOULD be a common identifier.
+ */
+ public String id;
+ /**
+ * The value of "label" MUST be an i18n object of the name of the
+ * category and SHOULD be short.
+ */
+ public I18N label;
+ /**
+ * If given, the value of "description" MUST be an i18n object with a
+ * textual description of the* category.
+ */
+ public I18N description;
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) return true;
+ if (!(other instanceof Category)) return false;
+
+ final Category cdt = ((Category) other);
+ return super.equals(other)
+ && Objects.equals(id, cdt.id)
+ && Objects.equals(label, cdt.label)
+ && Objects.equals(description, cdt.description);
+ }
+
+ @Override
+ public int hashCode() {
+ return super.hashCode() + Objects.hash(
+ id,
+ label,
+ description);
+ }
+
+}
diff --git a/storage/sis-coveragejson/src/main/java/org/apache/sis/internal/coveragejson/binding/CategoryEncoding.java b/storage/sis-coveragejson/src/main/java/org/apache/sis/internal/coveragejson/binding/CategoryEncoding.java
new file mode 100644
index 0000000000..94cf4a3cc0
--- /dev/null
+++ b/storage/sis-coveragejson/src/main/java/org/apache/sis/internal/coveragejson/binding/CategoryEncoding.java
@@ -0,0 +1,31 @@
+/*
+ * 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.internal.coveragejson.binding;
+
+
+/**
+ * CategoryEncoding is an object where each key is equal to an "id" value of
+ * the "categories" array within the "observedProperty" member of the
+ * parameter object. There MUST be no duplicate keys. The value is either
+ * an integer or an array of integers where each integer MUST be unique
+ * within the object.
+ *
+ * @author Johann Sorel (Geomatys)
+ */
+public final class CategoryEncoding extends Dictionary<String> {
+
+}
diff --git a/storage/sis-coveragejson/src/main/java/org/apache/sis/internal/coveragejson/binding/Coverage.java b/storage/sis-coveragejson/src/main/java/org/apache/sis/internal/coveragejson/binding/Coverage.java
new file mode 100644
index 0000000000..db43a0cc02
--- /dev/null
+++ b/storage/sis-coveragejson/src/main/java/org/apache/sis/internal/coveragejson/binding/Coverage.java
@@ -0,0 +1,131 @@
+/*
+ * 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.internal.coveragejson.binding;
+
+import jakarta.json.bind.JsonbException;
+import jakarta.json.bind.annotation.JsonbNillable;
+import jakarta.json.bind.annotation.JsonbPropertyOrder;
+import jakarta.json.bind.serializer.DeserializationContext;
+import jakarta.json.bind.serializer.JsonbDeserializer;
+import jakarta.json.stream.JsonParser;
+import java.lang.reflect.Type;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * A CoverageJSON object with the type "Coverage" is a coverage object.
+ *
+ * @author Johann Sorel (Geomatys)
+ */
+@JsonbNillable(false)
+@JsonbPropertyOrder({"type","id","domain","parameters","parameterGroups","ranges"})
+public final class Coverage extends CoverageJsonObject {
+
+ /**
+ * If a coverage has a commonly used identifier, that identifier SHOULD be
+ * included as a member of the coverage object with the name "id".
+ */
+ public String id;
+ /**
+ * A coverage object MUST have a member with the name "domain" where the
+ * value is either a domain object or a URL.
+ *
+ * If the value of "domain" is a URL and the referenced domain has a
+ * "domainType" member, then the coverage object SHOULD have the member
+ * "domainType" where the value MUST equal that of the referenced domain.
+ *
+ * If the coverage object is part of a coverage collection which has a
+ * "domainType" member then that member SHOULD be omitted in the coverage
+ * object.
+ */
+ //@JsonbTypeDeserializer(Coverage.DomainDeserializer.class)
+ //TODO should be a Domain or an URL, DomainDeserializer not working as expected
+ public Domain domain;
+ /**
+ * A coverage object MAY have a member with the name "parameters" where the
+ * value is an object where each member has as name a short identifier and
+ * as value a parameter object. The identifier corresponds to the commonly
+ * known concept of “variable name” and is merely used in clients for
+ * conveniently accessing the corresponding range object.
+ *
+ * A coverage object MUST have a "parameters" member if the coverage object
+ * is not part of a coverage collection or if the coverage collection does
+ * not have a "parameters" member.
+ */
+ public Parameters parameters;
+ /**
+ * A coverage object MAY have a member with the name "parameterGroups" where
+ * the value is an array of ParameterGroup objects.
+ */
+ public List<ParameterGroup> parameterGroups;
+ /**
+ * A coverage object MUST have a member with the name "ranges" where the
+ * value is a range set object. Any member of a range set object has as
+ * name any of the names in a "parameters" object in scope and as value
+ * either an NdArray or TiledNdArray object or a URL resolving to a
+ * CoverageJSON document of such object. A "parameters" member in scope is
+ * either within the enclosing coverage object or, if part of a coverage
+ * collection, in the parent coverage collection object. The shape and axis
+ * names of each NdArray or TiledNdArray object MUST correspond to the
+ * domain axes defined by "domain", while single-valued axes MAY be omitted.
+ * If the referenced parameter object has a "categoryEncoding" member, then
+ * each non-null array element of the "values" member of the NdArray object,
+ * or the linked NdArray objects within a TiledNdArray object, MUST be equal
+ * to one of the values defined in the "categoryEncoding" object and be
+ * interpreted as the matching category.
+ */
+ public Ranges ranges;
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) return true;
+ if (!(other instanceof Coverage)) return false;
+
+ final Coverage cdt = ((Coverage) other);
+ return super.equals(other)
+ && Objects.equals(id, cdt.id)
+ && Objects.equals(domain, cdt.domain)
+ && Objects.equals(parameters, cdt.parameters)
+ && Objects.equals(parameterGroups, cdt.parameterGroups)
+ && Objects.equals(ranges, cdt.ranges);
+ }
+
+ @Override
+ public int hashCode() {
+ return super.hashCode() + Objects.hash(
+ id,
+ domain,
+ parameters,
+ parameterGroups,
+ ranges);
+ }
+
+ public static class DomainDeserializer implements JsonbDeserializer<Object> {
+ @Override
+ public Object deserialize(JsonParser parser, DeserializationContext ctx, Type rtType) {
+ final JsonParser.Event event = parser.next();
+ if (event == JsonParser.Event.START_OBJECT) {
+ // Deserialize inner object
+ return ctx.deserialize(Domain.class, parser);
+ } else if (event == JsonParser.Event.VALUE_STRING) {
+ return parser.getString();
+ } else {
+ throw new JsonbException("Unexpected json element");
+ }
+ }
+ }
+}
diff --git a/storage/sis-coveragejson/src/main/java/org/apache/sis/internal/coveragejson/binding/CoverageCollection.java b/storage/sis-coveragejson/src/main/java/org/apache/sis/internal/coveragejson/binding/CoverageCollection.java
new file mode 100644
index 0000000000..6e80503edd
--- /dev/null
+++ b/storage/sis-coveragejson/src/main/java/org/apache/sis/internal/coveragejson/binding/CoverageCollection.java
@@ -0,0 +1,90 @@
+/*
+ * 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.internal.coveragejson.binding;
+
+import jakarta.json.bind.annotation.JsonbNillable;
+import jakarta.json.bind.annotation.JsonbPropertyOrder;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * A CoverageJSON object with the type "CoverageCollection" is a coverage collection object.
+ *
+ * @author Johann Sorel (Geomatys)
+ */
+@JsonbNillable(false)
+@JsonbPropertyOrder({"type","domainType","parameters","parameterGroups","referencing","coverages"})
+public final class CoverageCollection extends CoverageJsonObject {
+
+ /**
+ * A coverage collection object MAY have the member "domainType" with a
+ * string value to indicate that the coverage collection only contains
+ * coverages of the given domain type. See the section Common Domain Types
+ * for details. Custom domain types may be used as recommended in the
+ * section Extensions.
+ *
+ * If a coverage collection object has the member "domainType", then this
+ * member is inherited to all included coverages.
+ */
+ public String domainType;
+ /**
+ * A coverage collection object MAY have a member with the name "parameters"
+ * where the value is an object where each member has as name a short
+ * identifier and as value a parameter object.
+ */
+ public Parameter parameters;
+ /**
+ * A coverage collection object MAY have a member with the name
+ * "parameterGroups" where the value is an array of ParameterGroup objects.
+ */
+ public List<ParameterGroup> parameterGroups;
+ /**
+ * A coverage collection object MAY have a member with the name "referencing"
+ * where the value is an array of reference system connection objects.
+ */
+ public List<ReferenceSystemConnection> referencing;
+ /**
+ * A coverage collection object MUST have a member with the name "coverages".
+ * The value corresponding to "coverages" is an array. Each element in the
+ * array is a coverage object as defined above.
+ */
+ public List<Coverage> coverages;
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) return true;
+ if (!(other instanceof CoverageCollection)) return false;
+
+ final CoverageCollection cdt = ((CoverageCollection) other);
+ return super.equals(other)
+ && Objects.equals(domainType, cdt.domainType)
+ && Objects.equals(parameters, cdt.parameters)
+ && Objects.equals(coverages, cdt.coverages)
+ && Objects.equals(parameterGroups, cdt.parameterGroups)
+ && Objects.equals(referencing, cdt.referencing);
+ }
+
+ @Override
+ public int hashCode() {
+ return super.hashCode() + Objects.hash(
+ domainType,
+ coverages,
+ parameters,
+ parameterGroups,
+ referencing);
+ }
+}
diff --git a/storage/sis-coveragejson/src/main/java/org/apache/sis/internal/coveragejson/binding/CoverageJsonObject.java b/storage/sis-coveragejson/src/main/java/org/apache/sis/internal/coveragejson/binding/CoverageJsonObject.java
new file mode 100644
index 0000000000..1b80490a85
--- /dev/null
+++ b/storage/sis-coveragejson/src/main/java/org/apache/sis/internal/coveragejson/binding/CoverageJsonObject.java
@@ -0,0 +1,55 @@
+/*
+ * 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.internal.coveragejson.binding;
+
+import jakarta.json.bind.annotation.JsonbSubtype;
+import jakarta.json.bind.annotation.JsonbTypeInfo;
+
+/**
+ * A CoverageJSON document can be extended with custom members and types in a
+ * robust and interoperable way. For that, it makes use of absolute URIs and
+ * compact URIs (prefix:suffix) in order to avoid conflicts with other extensions
+ * and future versions of the format. A central registry of compact URI prefixes
+ * is provided which anyone can extend and which is a simple mapping from compact
+ * URI prefix to namespace URI in order to avoid collisions with other extensions
+ * that are based on compact URIs as well. Extensions that do not follow this
+ * approach MAY use simple names instead of absolute or compact URIs but have to
+ * accept the consequence of the document being less interoperable and future-proof.
+ * In certain use cases this is not an issue and may be a preferred solution for
+ * simplicity reasons, for example, if such CoverageJSON documents are only used
+ * internally and are not meant to be shared to a wider audience.
+ *
+ * @author Johann Sorel (Geomatys)
+ */
+@JsonbTypeInfo ( key = "type",value = {
+ @JsonbSubtype(alias = "Coverage", type = Coverage.class),
+ @JsonbSubtype(alias = "CoverageCollection", type = CoverageCollection.class),
+ @JsonbSubtype(alias = "Domain", type = Domain.class),
+ @JsonbSubtype(alias = "NdArray", type = NdArray.class),
+ @JsonbSubtype(alias = "Parameter", type = Parameter.class),
+ @JsonbSubtype(alias = "ParameterGroup", type = ParameterGroup.class),
+
+ //system subtypes
+ @JsonbSubtype(alias = "GeographicCRS", type = GeographicCRS.class),
+ @JsonbSubtype(alias = "ProjectedCRS", type = ProjectedCRS.class),
+ @JsonbSubtype(alias = "IdentifierRS", type = IdentifierRS.class),
+ @JsonbSubtype(alias = "VerticalCRS", type = VerticalCRS.class),
+ @JsonbSubtype(alias = "TemporalRS", type = TemporalRS.class)
+})
+public class CoverageJsonObject extends Dictionary<Object>{
+
+}
diff --git a/storage/sis-coveragejson/src/main/java/org/apache/sis/internal/coveragejson/binding/Dictionary.java b/storage/sis-coveragejson/src/main/java/org/apache/sis/internal/coveragejson/binding/Dictionary.java
new file mode 100644
index 0000000000..b46add8557
--- /dev/null
+++ b/storage/sis-coveragejson/src/main/java/org/apache/sis/internal/coveragejson/binding/Dictionary.java
@@ -0,0 +1,69 @@
+/*
+ * 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.internal.coveragejson.binding;
+
+import jakarta.json.bind.Jsonb;
+import jakarta.json.bind.JsonbBuilder;
+import jakarta.json.bind.annotation.JsonbTransient;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * @author Johann Sorel (Geomatys)
+ */
+public class Dictionary<T> {
+
+ /**
+ * could be anything.
+ * TODO find how to cath any other property as in Jackson @JsonAnyGetter and Johnzon @JohnzonAny
+ */
+ @JsonbTransient
+ public final LinkedHashMap<String, T> any = new LinkedHashMap<>();
+
+ public final Map<String, T> getAny() {
+ return this.any;
+ }
+
+ public final void setAnyProperty(String name, T value) {
+ this.any.put(name, value);
+ }
+
+ @Override
+ public String toString() {
+ try (Jsonb jsonb = JsonbBuilder.create()) {
+ return jsonb.toJson(this);
+ } catch (Exception ex) {
+ ex.printStackTrace();
+ return getClass().getName() + " : to_string_exception" + ex.getMessage();
+ }
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) return true;
+ if (!(other instanceof Dictionary)) return false;
+
+ final Dictionary cdt = ((Dictionary) other);
+ return Objects.equals(any, cdt.any);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(any);
+ }
+}
diff --git a/storage/sis-coveragejson/src/main/java/org/apache/sis/internal/coveragejson/binding/Domain.java b/storage/sis-coveragejson/src/main/java/org/apache/sis/internal/coveragejson/binding/Domain.java
new file mode 100644
index 0000000000..5291a1b09f
--- /dev/null
+++ b/storage/sis-coveragejson/src/main/java/org/apache/sis/internal/coveragejson/binding/Domain.java
@@ -0,0 +1,90 @@
+/*
+ * 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.internal.coveragejson.binding;
+
+import jakarta.json.bind.annotation.JsonbNillable;
+import jakarta.json.bind.annotation.JsonbPropertyOrder;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * A domain object is a CoverageJSON object which defines a set of positions and
+ * their extent in one or more referencing systems.
+ *
+ * @author Johann Sorel (Geomatys)
+ */
+@JsonbNillable(false)
+@JsonbPropertyOrder({"type","domainType","axes","referencing"})
+public final class Domain extends CoverageJsonObject {
+
+ public static final String DOMAINTYPE_GRID = "Grid";
+ public static final String DOMAINTYPE_VERTICALPROFILE = "VerticalProfile";
+ public static final String DOMAINTYPE_POINTSERIES = "PointSeries";
+ public static final String DOMAINTYPE_POINT = "Point";
+ public static final String DOMAINTYPE_MULTIPOINTSERIES = "MultiPointSeries";
+ public static final String DOMAINTYPE_MULTIPOINT = "MultiPoint";
+ public static final String DOMAINTYPE_POLYGONSERIES = "PolygonSeries";
+ public static final String DOMAINTYPE_POLYGON = "Polygon";
+ public static final String DOMAINTYPE_MULTIPOLYGONSERIES = "MultiPolygonSeries";
+ public static final String DOMAINTYPE_MULTIPOLYGON = "MultiPolygon";
+ public static final String DOMAINTYPE_TRAJECTORY = "Trajectory";
+ public static final String DOMAINTYPE_SECTION = "Section";
+
+ /**
+ * For interoperability reasons it is RECOMMENDED that a domain object has
+ * the member "domainType" with a string value to indicate that the domain
+ * follows a certain structure (e.g. a time series, a vertical profile, a
+ * spatio-temporal 4D grid). See the section Common Domain Types for details.
+ * Custom domain types may be used as recommended in the section Extensions.
+ */
+ public String domainType;
+ /**
+ * A domain object MUST have the member "axes" which has as value an object
+ * where each key is an axis identifier and each value an axis object as defined below.
+ */
+ public Axes axes;
+ /**
+ * A domain object MAY have the member "referencing" where the value is an
+ * array of reference system connection objects as defined below.
+ *
+ * A domain object MUST have a "referencing" member if the domain object is
+ * not part of a coverage collection or if the coverage collection does not
+ * have a "referencing" member.
+ */
+ public List<ReferenceSystemConnection> referencing;
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) return true;
+ if (!(other instanceof Domain)) return false;
+
+ final Domain cdt = ((Domain) other);
+ return super.equals(other)
+ && Objects.equals(domainType, cdt.domainType)
+ && Objects.equals(axes, cdt.axes)
+ && Objects.equals(referencing, cdt.referencing);
+ }
+
+ @Override
+ public int hashCode() {
+ return super.hashCode() + Objects.hash(
+ domainType,
+ axes,
+ referencing);
+ }
+
+}
diff --git a/storage/sis-coveragejson/src/main/java/org/apache/sis/internal/coveragejson/binding/GeographicCRS.java b/storage/sis-coveragejson/src/main/java/org/apache/sis/internal/coveragejson/binding/GeographicCRS.java
new file mode 100644
index 0000000000..0edc1a4420
--- /dev/null
+++ b/storage/sis-coveragejson/src/main/java/org/apache/sis/internal/coveragejson/binding/GeographicCRS.java
@@ -0,0 +1,72 @@
+/*
+ * 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.internal.coveragejson.binding;
+
+import jakarta.json.bind.annotation.JsonbNillable;
+import jakarta.json.bind.annotation.JsonbPropertyOrder;
+import java.util.Objects;
+
+/**
+ * Geographic CRSs anchor coordinate values to an ellipsoidal approximation of
+ * the Earth. They have coordinate axes of geodetic longitude and geodetic
+ * latitude, and perhaps height above the ellipsoid (i.e. they can be two- or three-dimensional).
+ * The origin of the CRS is on the surface of the ellipsoid.
+ *
+ * Note that sometimes (e.g. for numerical model data) the exact CRS may not be
+ * known or may be undefined. In this case the "id" may be omitted, but the "type"
+ * still indicates that this is a geographic CRS. Therefore clients can still use
+ * geodetic longitude, geodetic latitude (and maybe height) axes, even if they
+ * cannot accurately georeference the information.
+ * If a Coverage conforms to one of the defined domain types then the coordinate
+ * identifier "x" is used to denote geodetic longitude, "y" is used for geodetic
+ * latitude and "z" for ellipsoidal height.
+ *
+ * @author Johann Sorel (Geomatys)
+ */
+@JsonbNillable(false)
+@JsonbPropertyOrder({"type","id","description"})
+public final class GeographicCRS extends CoverageJsonObject {
+
+ /**
+ * The object MAY have an "id" member, whose value MUST be a string and
+ * SHOULD be a common identifier for the reference system.
+ */
+ public String id;
+ /**
+ * The object MAY have a "description" member, where the value MUST be an
+ * i18n object, but no standardized content is interpreted from this description.
+ */
+ public I18N description;
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) return true;
+ if (!(other instanceof GeographicCRS)) return false;
+
+ final GeographicCRS cdt = ((GeographicCRS) other);
+ return super.equals(other)
+ && Objects.equals(id, cdt.id)
+ && Objects.equals(description, cdt.description);
+ }
+
+ @Override
+ public int hashCode() {
+ return super.hashCode() + Objects.hash(
+ id,
+ description);
+ }
+}
diff --git a/storage/sis-coveragejson/src/main/java/org/apache/sis/internal/coveragejson/binding/I18N.java b/storage/sis-coveragejson/src/main/java/org/apache/sis/internal/coveragejson/binding/I18N.java
new file mode 100644
index 0000000000..7ec02f650f
--- /dev/null
+++ b/storage/sis-coveragejson/src/main/java/org/apache/sis/internal/coveragejson/binding/I18N.java
@@ -0,0 +1,119 @@
+/*
+ * 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.internal.coveragejson.binding;
+
+import jakarta.json.bind.annotation.JsonbTypeDeserializer;
+import jakarta.json.bind.annotation.JsonbTypeSerializer;
+import jakarta.json.bind.serializer.DeserializationContext;
+import jakarta.json.bind.serializer.JsonbDeserializer;
+import jakarta.json.bind.serializer.JsonbSerializer;
+import jakarta.json.bind.serializer.SerializationContext;
+import jakarta.json.stream.JsonGenerator;
+import jakarta.json.stream.JsonParser;
+import java.lang.reflect.Type;
+import java.util.Locale;
+import java.util.Map;
+import org.apache.sis.internal.coveragejson.binding.I18N.Serializer;
+import org.opengis.util.InternationalString;
+
+/**
+ * The special language tag "und" can be used to identify a value whose language
+ * is unknown or undetermined.
+ *
+ * @author Johann Sorel (Geomatys)
+ */
+@JsonbTypeDeserializer(I18N.Deserializer.class)
+@JsonbTypeSerializer(Serializer.class)
+public final class I18N extends Dictionary<String> implements InternationalString {
+
+ public static final String UNDETERMINED = "und";
+
+ public I18N() {
+ }
+
+ public I18N(String lang, String text) {
+ setAnyProperty(lang, text);
+ }
+
+ private String getDefault() {
+ String str = any.get(UNDETERMINED);
+ if (str == null && !any.isEmpty()) str = any.get(any.keySet().iterator().next());
+ if (str == null) str = "";
+ return str;
+ }
+
+ public String toString() {
+ return getDefault();
+ }
+
+ @Override
+ public String toString(Locale locale) {
+ String str = any.get(locale.getLanguage());
+ if (str == null) str = any.get(locale.getISO3Language());
+ return getDefault();
+ }
+
+ @Override
+ public int length() {
+ return getDefault().length();
+ }
+
+ @Override
+ public char charAt(int index) {
+ return getDefault().charAt(index);
+ }
+
+ @Override
+ public CharSequence subSequence(int start, int end) {
+ return getDefault().subSequence(start, end);
+ }
+
+ @Override
+ public int compareTo(InternationalString o) {
+ return getDefault().compareTo(o.toString());
+ }
+
+ public static class Deserializer implements JsonbDeserializer<I18N> {
+ @Override
+ public I18N deserialize(JsonParser parser, DeserializationContext ctx, Type rtType) {
+ final I18N candidate = new I18N();
+ while (parser.hasNext()) {
+ final JsonParser.Event event = parser.next();
+ if (event == JsonParser.Event.KEY_NAME) {
+ // Deserialize inner object
+ final String name = parser.getString();
+ String value = ctx.deserialize(String.class, parser);
+ candidate.setAnyProperty(name, value);
+ }
+ }
+ return candidate;
+ }
+ }
+
+ public static class Serializer implements JsonbSerializer<I18N> {
+
+ @Override
+ public void serialize(I18N ranges, JsonGenerator jg, SerializationContext sc) {
+ jg.writeStartObject();
+ for (Map.Entry<String,String> entry : ranges.any.entrySet()) {
+ jg.write(entry.getKey(), entry.getValue());
+ }
+ jg.writeEnd();
+ }
+
+ }
+}
diff --git a/storage/sis-coveragejson/src/main/java/org/apache/sis/internal/coveragejson/binding/IdentifierRS.java b/storage/sis-coveragejson/src/main/java/org/apache/sis/internal/coveragejson/binding/IdentifierRS.java
new file mode 100644
index 0000000000..8ff531369e
--- /dev/null
+++ b/storage/sis-coveragejson/src/main/java/org/apache/sis/internal/coveragejson/binding/IdentifierRS.java
@@ -0,0 +1,84 @@
+/*
+ * 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.internal.coveragejson.binding;
+
+import jakarta.json.bind.annotation.JsonbNillable;
+import jakarta.json.bind.annotation.JsonbPropertyOrder;
+import java.util.Objects;
+
+/**
+ * Identifier-based reference systems (identifier RS) .
+ *
+ * Coordinate values associated with an identifier RS MUST be strings.
+ *
+ * @author Johann Sorel (Geomatys)
+ */
+@JsonbNillable(false)
+@JsonbPropertyOrder({"type","id","label","description","targetConcept","identifiers"})
+public final class IdentifierRS extends CoverageJsonObject {
+
+ /**
+ * An identifier RS object MAY have a member "id" where the value MUST be a
+ * string and SHOULD be a common identifier for the reference system.
+ */
+ public String id;
+ /**
+ * An identifier RS object MAY have a member "label" where the value MUST be
+ * an i18n object that is the name of the reference system.
+ */
+ public I18N label;
+ /**
+ * An identifier RS object MAY have a member "description" where the value
+ * MUST be an i18n object that is the (perhaps lengthy) description of the
+ * reference system.
+ */
+ public I18N description;
+ /**
+ * An identifier RS object MUST have a member "targetConcept"
+ */
+ public TargetConcept targetConcept;
+ /**
+ * An identifier RS object MAY have a member "identifiers".
+ */
+ public Identifiers identifiers;
+
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) return true;
+ if (!(other instanceof IdentifierRS)) return false;
+
+ final IdentifierRS cdt = ((IdentifierRS) other);
+ return super.equals(other)
+ && Objects.equals(id, cdt.id)
+ && Objects.equals(label, cdt.label)
+ && Objects.equals(description, cdt.description)
+ && Objects.equals(targetConcept, cdt.targetConcept)
+ && Objects.equals(identifiers, cdt.identifiers);
+ }
+
+ @Override
+ public int hashCode() {
+ return super.hashCode() + Objects.hash(
+ id,
+ label,
+ description,
+ targetConcept,
+ identifiers);
+ }
+
+}
diff --git a/storage/sis-coveragejson/src/main/java/org/apache/sis/internal/coveragejson/binding/Identifiers.java b/storage/sis-coveragejson/src/main/java/org/apache/sis/internal/coveragejson/binding/Identifiers.java
new file mode 100644
index 0000000000..ac4f495183
--- /dev/null
+++ b/storage/sis-coveragejson/src/main/java/org/apache/sis/internal/coveragejson/binding/Identifiers.java
@@ -0,0 +1,29 @@
+/*
+ * 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.internal.coveragejson.binding;
+
+
+/**
+ * An identifier RS object MAY have a member "identifiers" where the value is
+ * an object where each key is an identifier referenced by the identifier RS
+ * and each value an object describing the referenced concept, equal to "targetConcept".
+ *
+ * @author Johann Sorel (Geomatys)
+ */
+public final class Identifiers extends Dictionary<TargetConcept> {
+
+}
diff --git a/storage/sis-coveragejson/src/main/java/org/apache/sis/internal/coveragejson/binding/NdArray.java b/storage/sis-coveragejson/src/main/java/org/apache/sis/internal/coveragejson/binding/NdArray.java
new file mode 100644
index 0000000000..dc1a6a2c1e
--- /dev/null
+++ b/storage/sis-coveragejson/src/main/java/org/apache/sis/internal/coveragejson/binding/NdArray.java
@@ -0,0 +1,104 @@
+/*
+ * 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.internal.coveragejson.binding;
+
+import jakarta.json.bind.annotation.JsonbNillable;
+import jakarta.json.bind.annotation.JsonbPropertyOrder;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * A CoverageJSON object with the type "NdArray" is an NdArray object.
+ * It represents a multidimensional (>= 0D) array with named axes, encoded as a
+ * flat, one-dimensional JSON array in row-major order.
+ *
+ * Note that common JSON implementations use IEEE 754-2008 64-bit
+ * (double precision) floating point numbers as the data type for "values". Users
+ * SHOULD be aware of the limitations in precision when encoding numbers in this way.
+ * For example, when encoding integers, users SHOULD be aware that only values
+ * within the range [-253+1, 253-1] can be represented in a way that will ensure
+ * exact interoperability among such implementations [IETF RFC 7159].
+ *
+ * @author Johann Sorel (Geomatys)
+ */
+@JsonbNillable(false)
+@JsonbPropertyOrder({"type","dataType","axisNames","shape","values"})
+public final class NdArray extends CoverageJsonObject {
+
+ public static final String DATATYPE_FLOAT = "float";
+ public static final String DATATYPE_INTEGER = "integer";
+ public static final String DATATYPE_STRING = "string";
+
+ /**
+ * An NdArray object MUST have a member with the name "dataType" where the
+ * value is either "float", "integer", or "string" and MUST correspond to
+ * the data type of the non-null values in the "values" array.
+ */
+ public String dataType;
+ /**
+ * An NdArray object MAY have a member with the name "axisNames" where the
+ * value is an array of strings of the same length as "shape", such that
+ * each string assigns a name to the corresponding dimension. For 0D arrays,
+ * "axisNames" MAY be omitted (defaulting to []). For >= 1D arrays it MUST
+ * be included.
+ */
+ public String[] axisNames;
+ /**
+ * An NdArray object MAY have a member with the name "shape" where the value
+ * is an array of integers. For 0D arrays, "shape" MAY be omitted
+ * (defaulting to []). For >= 1D arrays it MUST be included.
+ *
+ * Where "shape" is present and non-empty, the product of its values MUST
+ * equal the number of elements in the "values" array.
+ */
+ public int[] shape;
+ /**
+ * An NdArray object MUST have a member with the name "values" where the
+ * value is a non-empty array of numbers and nulls, or strings and nulls,
+ * where nulls represent missing data.
+ *
+ * Zero-dimensional NdArrays MUST have exactly one item in the "values" array.
+ *
+ * Within the "values" array, the elements MUST be ordered such that the
+ * last dimension in "axisNames" varies fastest, i.e. row-major order.
+ * (This mimics the approach taken in NetCDF; see the example below.)
+ */
+ public List<Object> values; //because of null and string values
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) return true;
+ if (!(other instanceof NdArray)) return false;
+
+ final NdArray cdt = ((NdArray) other);
+ return super.equals(other)
+ && Objects.equals(dataType, cdt.dataType)
+ && Arrays.equals(axisNames, cdt.axisNames)
+ && Arrays.equals(shape, cdt.shape)
+ && Objects.equals(values, cdt.values);
+ }
+
+ @Override
+ public int hashCode() {
+ return super.hashCode() + Objects.hash(
+ dataType,
+ Arrays.hashCode(axisNames),
+ Arrays.hashCode(shape),
+ values);
+ }
+}
diff --git a/storage/sis-coveragejson/src/main/java/org/apache/sis/internal/coveragejson/binding/ObservedProperty.java b/storage/sis-coveragejson/src/main/java/org/apache/sis/internal/coveragejson/binding/ObservedProperty.java
new file mode 100644
index 0000000000..62f49f163e
--- /dev/null
+++ b/storage/sis-coveragejson/src/main/java/org/apache/sis/internal/coveragejson/binding/ObservedProperty.java
@@ -0,0 +1,88 @@
+/*
+ * 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.internal.coveragejson.binding;
+
+import jakarta.json.bind.annotation.JsonbNillable;
+import jakarta.json.bind.annotation.JsonbPropertyOrder;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Observed property is an object which MUST have the member "label" and which
+ * MAY have the members "id", "description", and "categories".
+ *
+ * @author Johann Sorel (Geomatys)
+ */
+@JsonbNillable(false)
+@JsonbPropertyOrder({"id","label","description","categories"})
+public final class ObservedProperty extends Dictionary<Object> {
+
+ /**
+ * If given, the value of "id" MUST be a string and SHOULD be a common
+ * identifier.
+ */
+ public String id;
+ /**
+ * The value of "label" MUST be an i18n object that is the name of the
+ * observed property and which SHOULD be short.
+ */
+ public I18N label;
+ /**
+ * If given, the value of "description" MUST be an i18n object with a
+ * textual description of the observed property.
+ */
+ public I18N description;
+ /**
+ *
+ * If given, the value of "categories" MUST be a non-empty array of category
+ * objects.
+ */
+ public List<Category> categories;
+
+ public ObservedProperty() {
+ }
+
+ public ObservedProperty(String id, I18N label, I18N description, List<Category> categories) {
+ this.id = id;
+ this.label = label;
+ this.description = description;
+ this.categories = categories;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) return true;
+ if (!(other instanceof ObservedProperty)) return false;
+
+ final ObservedProperty cdt = ((ObservedProperty) other);
+ return super.equals(other)
+ && Objects.equals(id, cdt.id)
+ && Objects.equals(label, cdt.label)
+ && Objects.equals(description, cdt.description)
+ && Objects.equals(categories, cdt.categories);
+ }
+
+ @Override
+ public int hashCode() {
+ return super.hashCode() + Objects.hash(
+ id,
+ label,
+ description,
+ categories);
+ }
+
+}
diff --git a/storage/sis-coveragejson/src/main/java/org/apache/sis/internal/coveragejson/binding/Parameter.java b/storage/sis-coveragejson/src/main/java/org/apache/sis/internal/coveragejson/binding/Parameter.java
new file mode 100644
index 0000000000..dc5c6645e4
--- /dev/null
+++ b/storage/sis-coveragejson/src/main/java/org/apache/sis/internal/coveragejson/binding/Parameter.java
@@ -0,0 +1,91 @@
+/*
+ * 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.internal.coveragejson.binding;
+
+import jakarta.json.bind.annotation.JsonbNillable;
+import jakarta.json.bind.annotation.JsonbPropertyOrder;
+import java.util.Objects;
+
+/**
+ * A parameter object MAY have any number of members (name/value pairs).
+ *
+ * @author Johann Sorel (Geomatys)
+ */
+@JsonbNillable(false)
+@JsonbPropertyOrder({"type","id","label","description","unit","observedProperty","categoryEncoding"})
+public final class Parameter extends CoverageJsonObject {
+
+ /**
+ * A parameter object MAY have a member with the name "id" where the value
+ * MUST be a string and SHOULD be a common identifier.
+ */
+ public String id;
+ /**
+ * A parameter object MAY have a member with the name "label" where the value
+ * MUST be an i18n object that is the name of the parameter and which SHOULD be short.
+ * Note that this SHOULD be left out if it would be identical to the "label"
+ * of the "observedProperty" member.
+ */
+ public I18N label;
+ /**
+ * A parameter object MAY have a member with the name "description" where
+ * the value MUST be an i18n object which is a, perhaps lengthy, textual
+ * description of the parameter.
+ */
+ public I18N description;
+ /**
+ * A parameter object MAY have a member with the name "unit".
+ * A parameter object MUST NOT have a "unit" member if the "observedProperty"
+ * member has a "categories" member.
+ */
+ public Unit unit;
+ /**
+ * A parameter object MUST have a member with the name "observedProperty".
+ */
+ public ObservedProperty observedProperty;
+ /**
+ * A parameter object MAY have a member with the name "categoryEncoding".
+ */
+ public CategoryEncoding categoryEncoding;
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) return true;
+ if (!(other instanceof Parameter)) return false;
+
+ final Parameter cdt = ((Parameter) other);
+ return super.equals(other)
+ && Objects.equals(id, cdt.id)
+ && Objects.equals(label, cdt.label)
+ && Objects.equals(description, cdt.description)
+ && Objects.equals(unit, cdt.unit)
+ && Objects.equals(observedProperty, cdt.observedProperty)
+ && Objects.equals(categoryEncoding, cdt.categoryEncoding);
+ }
+
+ @Override
+ public int hashCode() {
+ return super.hashCode() + Objects.hash(
+ id,
+ label,
+ description,
+ unit,
+ observedProperty,
+ categoryEncoding);
+ }
+
+}
diff --git a/storage/sis-coveragejson/src/main/java/org/apache/sis/internal/coveragejson/binding/ParameterGroup.java b/storage/sis-coveragejson/src/main/java/org/apache/sis/internal/coveragejson/binding/ParameterGroup.java
new file mode 100644
index 0000000000..d18ddfd3ed
--- /dev/null
+++ b/storage/sis-coveragejson/src/main/java/org/apache/sis/internal/coveragejson/binding/ParameterGroup.java
@@ -0,0 +1,86 @@
+/*
+ * 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.internal.coveragejson.binding;
+
+import jakarta.json.bind.annotation.JsonbNillable;
+import jakarta.json.bind.annotation.JsonbPropertyOrder;
+import java.util.Arrays;
+import java.util.Objects;
+
+/**
+ * A parameter group object MUST have either or both the members "label" or/and "observedProperty".
+ *
+ * @author Johann Sorel (Geomatys)
+ */
+@JsonbNillable(false)
+@JsonbPropertyOrder({"type","id","label","description","observedProperty","members"})
+public final class ParameterGroup extends CoverageJsonObject {
+
+ /**
+ * A parameter group object MAY have a member with the name "id" where the
+ * value MUST be a string and SHOULD be a common identifier.
+ */
+ public String id;
+ /**
+ * A parameter group object MAY have a member with the name "label" where
+ * the value MUST be an i18n object that is the name of the parameter group
+ * and which SHOULD be short. Note that this SHOULD be left out if it would
+ * be identical to the "label" of the "observedProperty" member.
+ */
+ public I18N label;
+ /**
+ * A parameter group object MAY have a member with the name "description"
+ * where the value MUST be an i18n object which is a, perhaps lengthy,
+ * textual description of the parameter group.
+ */
+ public String description;
+ /**
+ * A parameter group object MAY have a member with the name "observedProperty"
+ * where the value is an object as specified for parameter objects.
+ */
+ public ObservedProperty observedProperty;
+ /**
+ * A parameter group object MUST have a member with the name "members"
+ * where the value is a non-empty array of parameter identifiers
+ * (see 6.3 Coverage objects).
+ */
+ public String[] members;
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) return true;
+ if (!(other instanceof ParameterGroup)) return false;
+
+ final ParameterGroup cdt = ((ParameterGroup) other);
+ return super.equals(other)
+ && Objects.equals(id, cdt.id)
+ && Objects.equals(label, cdt.label)
+ && Objects.equals(description, cdt.description)
+ && Objects.equals(observedProperty, cdt.observedProperty)
+ && Arrays.equals(members, cdt.members);
+ }
+
+ @Override
+ public int hashCode() {
+ return super.hashCode() + Objects.hash(
+ id,
+ label,
+ description,
+ observedProperty,
+ Arrays.hashCode(members));
+ }
+}
diff --git a/storage/sis-coveragejson/src/main/java/org/apache/sis/internal/coveragejson/binding/Parameters.java b/storage/sis-coveragejson/src/main/java/org/apache/sis/internal/coveragejson/binding/Parameters.java
new file mode 100644
index 0000000000..7c29c5fb26
--- /dev/null
+++ b/storage/sis-coveragejson/src/main/java/org/apache/sis/internal/coveragejson/binding/Parameters.java
@@ -0,0 +1,71 @@
+/*
+ * 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.internal.coveragejson.binding;
+
+import jakarta.json.bind.annotation.JsonbTypeDeserializer;
+import jakarta.json.bind.annotation.JsonbTypeSerializer;
+import jakarta.json.bind.serializer.DeserializationContext;
+import jakarta.json.bind.serializer.JsonbDeserializer;
+import jakarta.json.bind.serializer.JsonbSerializer;
+import jakarta.json.bind.serializer.SerializationContext;
+import jakarta.json.stream.JsonGenerator;
+import jakarta.json.stream.JsonParser;
+import jakarta.json.stream.JsonParser.Event;
+import java.lang.reflect.Type;
+import java.util.Map;
+import org.apache.sis.internal.coveragejson.binding.Parameters.Deserializer;
+import org.apache.sis.internal.coveragejson.binding.Parameters.Serializer;
+
+/**
+ * Constains a map of parameter objects.
+ *
+ * @author Johann Sorel (Geomatys)
+ */
+@JsonbTypeDeserializer(Deserializer.class)
+@JsonbTypeSerializer(Serializer.class)
+public final class Parameters extends Dictionary<Parameter> {
+
+ public static class Deserializer implements JsonbDeserializer<Parameters> {
+ @Override
+ public Parameters deserialize(JsonParser parser, DeserializationContext ctx, Type rtType) {
+ final Parameters parameters = new Parameters();
+ while (parser.hasNext()) {
+ final Event event = parser.next();
+ if (event == JsonParser.Event.KEY_NAME) {
+ // Deserialize inner object
+ final String name = parser.getString();
+ final Parameter value = ctx.deserialize(Parameter.class, parser);
+ parameters.setAnyProperty(name, value);
+ }
+ }
+ return parameters;
+ }
+ }
+
+ public static class Serializer implements JsonbSerializer<Parameters> {
+
+ @Override
+ public void serialize(Parameters parameters, JsonGenerator jg, SerializationContext sc) {
+ jg.writeStartObject();
+ for (Map.Entry<String,Parameter> entry : parameters.any.entrySet()) {
+ sc.serialize(entry.getKey(), entry.getValue(), jg);
+ }
+ jg.writeEnd();
+ }
+
+ }
+}
diff --git a/storage/sis-coveragejson/src/main/java/org/apache/sis/internal/coveragejson/binding/ProjectedCRS.java b/storage/sis-coveragejson/src/main/java/org/apache/sis/internal/coveragejson/binding/ProjectedCRS.java
new file mode 100644
index 0000000000..ebff794f48
--- /dev/null
+++ b/storage/sis-coveragejson/src/main/java/org/apache/sis/internal/coveragejson/binding/ProjectedCRS.java
@@ -0,0 +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.internal.coveragejson.binding;
+
+import jakarta.json.bind.annotation.JsonbNillable;
+import jakarta.json.bind.annotation.JsonbPropertyOrder;
+import java.util.Objects;
+
+/**
+ * Projected CRSs use two coordinates to denote positions on a Cartesian plane,
+ * which is derived from projecting the ellipsoid according to some defined transformation.
+ *
+ * If a Coverage conforms to one of the defined domain types then the coordinate
+ * identifier "x" is used to denote easting and "y" is used for northing.
+ *
+ * @author Johann Sorel (Geomatys)
+ */
+@JsonbNillable(false)
+@JsonbPropertyOrder({"type","id","description"})
+public final class ProjectedCRS extends CoverageJsonObject {
+
+ /**
+ * The object MAY have an "id" member, whose value MUST be a string and
+ * SHOULD be a common identifier for the reference system.
+ */
+ public String id;
+ /**
+ * The object MAY have a "description" member, where the value MUST be an
+ * i18n object, but no standardized content is interpreted from this description.
+ */
+ public I18N description;
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) return true;
+ if (!(other instanceof ProjectedCRS)) return false;
+
+ final ProjectedCRS cdt = ((ProjectedCRS) other);
+ return super.equals(other)
+ && Objects.equals(id, cdt.id)
+ && Objects.equals(description, cdt.description);
+ }
+
+ @Override
+ public int hashCode() {
+ return super.hashCode() + Objects.hash(
+ id,
+ description);
+ }
+}
diff --git a/storage/sis-coveragejson/src/main/java/org/apache/sis/internal/coveragejson/binding/Ranges.java b/storage/sis-coveragejson/src/main/java/org/apache/sis/internal/coveragejson/binding/Ranges.java
new file mode 100644
index 0000000000..3a322ad65a
--- /dev/null
+++ b/storage/sis-coveragejson/src/main/java/org/apache/sis/internal/coveragejson/binding/Ranges.java
@@ -0,0 +1,67 @@
+/*
+ * 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.internal.coveragejson.binding;
+
+import jakarta.json.bind.annotation.JsonbTypeDeserializer;
+import jakarta.json.bind.annotation.JsonbTypeSerializer;
+import jakarta.json.bind.serializer.DeserializationContext;
+import jakarta.json.bind.serializer.JsonbDeserializer;
+import jakarta.json.bind.serializer.JsonbSerializer;
+import jakarta.json.bind.serializer.SerializationContext;
+import jakarta.json.stream.JsonGenerator;
+import jakarta.json.stream.JsonParser;
+import java.lang.reflect.Type;
+import java.util.Map.Entry;
+
+/**
+ *
+ * @author Johann Sorel (Geomatys)
+ */
+@JsonbTypeDeserializer(Ranges.Deserializer.class)
+@JsonbTypeSerializer(Ranges.Serializer.class)
+public final class Ranges extends Dictionary<NdArray> {
+
+ public static class Deserializer implements JsonbDeserializer<Ranges> {
+ @Override
+ public Ranges deserialize(JsonParser parser, DeserializationContext ctx, Type rtType) {
+ final Ranges candidate = new Ranges();
+ while (parser.hasNext()) {
+ final JsonParser.Event event = parser.next();
+ if (event == JsonParser.Event.KEY_NAME) {
+ // Deserialize inner object
+ final String name = parser.getString();
+ final NdArray value = ctx.deserialize(NdArray.class, parser);
+ candidate.setAnyProperty(name, value);
+ }
+ }
+ return candidate;
+ }
+ }
+
+ public static class Serializer implements JsonbSerializer<Ranges> {
+
+ @Override
+ public void serialize(Ranges ranges, JsonGenerator jg, SerializationContext sc) {
+ jg.writeStartObject();
+ for (Entry<String,NdArray> entry : ranges.any.entrySet()) {
+ sc.serialize(entry.getKey(), entry.getValue(), jg);
+ }
+ jg.writeEnd();
+ }
+
+ }
+}
diff --git a/storage/sis-coveragejson/src/main/java/org/apache/sis/internal/coveragejson/binding/ReferenceSystemConnection.java b/storage/sis-coveragejson/src/main/java/org/apache/sis/internal/coveragejson/binding/ReferenceSystemConnection.java
new file mode 100644
index 0000000000..c072bf206d
--- /dev/null
+++ b/storage/sis-coveragejson/src/main/java/org/apache/sis/internal/coveragejson/binding/ReferenceSystemConnection.java
@@ -0,0 +1,67 @@
+/*
+ * 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.internal.coveragejson.binding;
+
+import jakarta.json.bind.annotation.JsonbNillable;
+import jakarta.json.bind.annotation.JsonbPropertyOrder;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * A reference system connection object creates a link between values within
+ * domain axes and a reference system to be able to interpret those values, e.g.
+ * as coordinates in a certain coordinate reference system.
+ *
+ * @author Johann Sorel (Geomatys)
+ */
+@JsonbNillable(false)
+@JsonbPropertyOrder({"coordinates","system"})
+public final class ReferenceSystemConnection extends Dictionary<Object> {
+
+ /**
+ * A reference system connection object MUST have a member "coordinates"
+ * which has as value an array of coordinate identifiers that are referenced
+ * in this object. Depending on the type of referencing, the ordering of the
+ * identifiers MAY be relevant, e.g. for 2D/3D coordinate reference systems.
+ * In this case, the order of the identifiers MUST match the order of axes
+ * in the coordinate reference system.
+ */
+ public List<String> coordinates;
+ /**
+ * A reference system connection object MUST have a member "system" whose
+ * value MUST be a Reference System Object.
+ */
+ public CoverageJsonObject system;
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) return true;
+ if (!(other instanceof ReferenceSystemConnection)) return false;
+
+ final ReferenceSystemConnection cdt = ((ReferenceSystemConnection) other);
+ return super.equals(other)
+ && Objects.equals(system, cdt.system)
+ && Objects.equals(coordinates, cdt.coordinates);
+ }
+
+ @Override
+ public int hashCode() {
+ return super.hashCode() + Objects.hash(
+ system,
+ coordinates);
+ }
+}
diff --git a/storage/sis-coveragejson/src/main/java/org/apache/sis/internal/coveragejson/binding/Symbol.java b/storage/sis-coveragejson/src/main/java/org/apache/sis/internal/coveragejson/binding/Symbol.java
new file mode 100644
index 0000000000..5621f9be4d
--- /dev/null
+++ b/storage/sis-coveragejson/src/main/java/org/apache/sis/internal/coveragejson/binding/Symbol.java
@@ -0,0 +1,60 @@
+/*
+ * 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.internal.coveragejson.binding;
+
+import jakarta.json.bind.annotation.JsonbNillable;
+import jakarta.json.bind.annotation.JsonbPropertyOrder;
+import java.util.Objects;
+
+/**
+ *
+ * @author Johann Sorel (Geomatys)
+ */
+@JsonbNillable(false)
+@JsonbPropertyOrder({"value","type"})
+public final class Symbol extends Dictionary<Object> {
+
+ /**
+ * "value" is the symbolic unit notation
+ */
+ public String value;
+ /**
+ * "type" references the unit serialization scheme that is used. "type" MUST
+ * HAVE the value "http://www.opengis.net/def/uom/UCUM/" if UCUM is used, or
+ * a custom value as recommended in section Extensions.
+ */
+ public String type;
+
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) return true;
+ if (!(other instanceof Symbol)) return false;
+
+ final Symbol cdt = ((Symbol) other);
+ return super.equals(other)
+ && Objects.equals(value, cdt.value)
+ && Objects.equals(type, cdt.type);
+ }
+
+ @Override
+ public int hashCode() {
+ return super.hashCode() + Objects.hash(
+ value,
+ type);
+ }
+}
diff --git a/storage/sis-coveragejson/src/main/java/org/apache/sis/internal/coveragejson/binding/TargetConcept.java b/storage/sis-coveragejson/src/main/java/org/apache/sis/internal/coveragejson/binding/TargetConcept.java
new file mode 100644
index 0000000000..e56b177317
--- /dev/null
+++ b/storage/sis-coveragejson/src/main/java/org/apache/sis/internal/coveragejson/binding/TargetConcept.java
@@ -0,0 +1,54 @@
+/*
+ * 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.internal.coveragejson.binding;
+
+import jakarta.json.bind.annotation.JsonbNillable;
+import jakarta.json.bind.annotation.JsonbPropertyOrder;
+import java.util.Objects;
+
+/**
+ * TargetConcept is an object that MUST have a member "label" and MAY have a member
+ * "description" where the value of each MUST be an i18n object that is the
+ * name or description, respectively, of the concept which is referenced in the system.
+ *
+ * @author Johann Sorel (Geomatys)
+ */
+@JsonbNillable(false)
+@JsonbPropertyOrder({"label","description"})
+public final class TargetConcept extends Dictionary<Object> {
+
+ public I18N label;
+ public I18N description;
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) return true;
+ if (!(other instanceof TargetConcept)) return false;
+
+ final TargetConcept cdt = ((TargetConcept) other);
+ return super.equals(other)
+ && Objects.equals(label, cdt.label)
+ && Objects.equals(description, cdt.description);
+ }
+
+ @Override
+ public int hashCode() {
+ return super.hashCode() + Objects.hash(
+ label,
+ description);
+ }
+}
diff --git a/storage/sis-coveragejson/src/main/java/org/apache/sis/internal/coveragejson/binding/TemporalRS.java b/storage/sis-coveragejson/src/main/java/org/apache/sis/internal/coveragejson/binding/TemporalRS.java
new file mode 100644
index 0000000000..d3eacc3159
--- /dev/null
+++ b/storage/sis-coveragejson/src/main/java/org/apache/sis/internal/coveragejson/binding/TemporalRS.java
@@ -0,0 +1,79 @@
+/*
+ * 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.internal.coveragejson.binding;
+
+import jakarta.json.bind.annotation.JsonbNillable;
+import jakarta.json.bind.annotation.JsonbPropertyOrder;
+import java.util.Objects;
+
+/**
+ * Time is referenced by a temporal reference system (temporal RS). In the current
+ * version of this Community Standard, only a string-based notation for time
+ * values is defined. Future versions of this Community Standard may allow for
+ * alternative notations, such as recording time values as numeric offsets from
+ * a given temporal datum (e.g. “days since 1970-01-01”).
+ *
+ * If the calendar is based on years, months, days, then the referenced values
+ * SHOULD use one of the following ISO8601-based lexical representations:
+ * YYYY
+ * ±XYYYY (where X stands for extra year digits)
+ * YYYY-MM
+ * YYYY-MM-DD
+ * YYYY-MM-DDTHH:MM:SS[.F]Z where Z is either “Z” or a time scale offset +|-HH:MM
+ *
+ * If calendar dates with reduced precision are used in a lexical representation
+ * (e.g. "2016"), then a client SHOULD interpret those dates in that reduced precision.
+ *
+ * If "type" is "TemporalRS" and "calendar" is "Gregorian", then the above lexical
+ * representation MUST be used.
+ *
+ * @author Johann Sorel (Geomatys)
+ */
+@JsonbNillable(false)
+@JsonbPropertyOrder({"type","calendar","timeScale"})
+public final class TemporalRS extends CoverageJsonObject {
+
+ /**
+ * A temporal RS object MUST have a member "calendar" with value "Gregorian" or a URI.
+ * If the Gregorian calendar is used, then "calendar" MUST have the value "Gregorian" and cannot be a URI.
+ */
+ public String calendar;
+ /**
+ * A temporal RS object MAY have a member "timeScale" with a URI as value.
+ * If omitted, the time scale defaults to "UTC". If the time scale is UTC,
+ * the "timeScale" member MUST be omitted.
+ */
+ public String timeScale;
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) return true;
+ if (!(other instanceof TemporalRS)) return false;
+
+ final TemporalRS cdt = ((TemporalRS) other);
+ return super.equals(other)
+ && Objects.equals(calendar, cdt.calendar)
+ && Objects.equals(timeScale, cdt.timeScale);
+ }
+
+ @Override
+ public int hashCode() {
+ return super.hashCode() + Objects.hash(
+ calendar,
+ timeScale);
+ }
+}
diff --git a/storage/sis-coveragejson/src/main/java/org/apache/sis/internal/coveragejson/binding/TileSet.java b/storage/sis-coveragejson/src/main/java/org/apache/sis/internal/coveragejson/binding/TileSet.java
new file mode 100644
index 0000000000..4e626c2266
--- /dev/null
+++ b/storage/sis-coveragejson/src/main/java/org/apache/sis/internal/coveragejson/binding/TileSet.java
@@ -0,0 +1,75 @@
+/*
+ * 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.internal.coveragejson.binding;
+
+import jakarta.json.bind.annotation.JsonbNillable;
+import jakarta.json.bind.annotation.JsonbPropertyOrder;
+import java.util.Arrays;
+import java.util.Objects;
+
+/**
+ *
+ * @author Johann Sorel (Geomatys)
+ */
+@JsonbNillable(false)
+@JsonbPropertyOrder({"tileShape","urlTemplate"})
+public final class TileSet extends Dictionary<Object> {
+
+ /**
+ * A TileSet object MUST have a member with the name "tileShape" where
+ * the value is an array of the same length as "shape" and where each
+ * array element is either null or an integer lower or equal than the
+ * corresponding element in "shape". A null value denotes that the axis
+ * is not tiled.
+ */
+ public Integer[] tileShape;
+ /**
+ * A TileSet object MUST have a member with the name "urlTemplate" where
+ * the value is a Level 1 URI template as defined in RFC 6570 .
+ * The URI template MUST contain a variable for each axis name whose
+ * corresponding element in "tileShape" is not null. A variable for an
+ * axis of total size totalSize (from "shape") and tile size tileSize
+ * (from "tileShape") has as value one of the integers 0, 1, …, q + r - 1
+ * where q and r are the quotient and remainder obtained by dividing
+ * totalSize by tileSize. Each URI that can be generated from the URI
+ * template MUST resolve to an NdArray CoverageJSON document where the
+ * members "dataType" and "axisNames`" are identical to the ones of the
+ * TiledNdArray object, and where each value of `"shape" is an integer
+ * equal, or lower if an edge tile, to the corresponding element in
+ * "tileShape" while replacing null with the corresponding element of
+ * "shape" of the TiledNdArray.
+ */
+ public String urlTemplate;
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) return true;
+ if (!(other instanceof TileSet)) return false;
+
+ final TileSet cdt = ((TileSet) other);
+ return super.equals(other)
+ && Objects.equals(urlTemplate, cdt.urlTemplate)
+ && Arrays.equals(tileShape, cdt.tileShape);
+ }
+
+ @Override
+ public int hashCode() {
+ return super.hashCode() + Objects.hash(
+ urlTemplate,
+ Arrays.hashCode(tileShape));
+ }
+}
diff --git a/storage/sis-coveragejson/src/main/java/org/apache/sis/internal/coveragejson/binding/TiledNdArray.java b/storage/sis-coveragejson/src/main/java/org/apache/sis/internal/coveragejson/binding/TiledNdArray.java
new file mode 100644
index 0000000000..43d7f710c7
--- /dev/null
+++ b/storage/sis-coveragejson/src/main/java/org/apache/sis/internal/coveragejson/binding/TiledNdArray.java
@@ -0,0 +1,79 @@
+/*
+ * 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.internal.coveragejson.binding;
+
+import jakarta.json.bind.annotation.JsonbNillable;
+import jakarta.json.bind.annotation.JsonbPropertyOrder;
+import java.util.Arrays;
+import java.util.Objects;
+
+/**
+ * A CoverageJSON object with the type "TiledNdArray" is a TiledNdArray object.
+ * It represents a multidimensional (>= 1D) array with named axes that is split
+ * up into sets of linked NdArray documents. Each tileset typically covers a
+ * specific data access scenario, for example, loading a single time slice of a
+ * grid vs. loading a time series of a spatial subset of a grid.
+ *
+ * @author Johann Sorel (Geomatys)
+ */
+@JsonbNillable(false)
+@JsonbPropertyOrder({"dataType","shape","axisNames","tileSets"})
+public final class TiledNdArray extends Dictionary<Object> {
+
+ /**
+ * A TiledNdArray object MUST have a member with the name "dataType" where
+ * the value is either "float", "integer", or "string".
+ */
+ public String dataType;
+ /**
+ * A TiledNdArray object MUST have a member with the name "shape" where the
+ * value is a non-empty array of integers.
+ */
+ public int[] shape;
+ /**
+ * A TiledNdArray object MUST have a member with the name "axisNames" where
+ * the value is a string array of the same length as "shape".
+ */
+ public String[] axisNames;
+ /**
+ * A TiledNdArray object MUST have a member with the name "tileSets" where
+ * the value is a non-empty array of TileSet objects.
+ */
+ public TileSet[] tileSets;
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) return true;
+ if (!(other instanceof TiledNdArray)) return false;
+
+ final TiledNdArray cdt = ((TiledNdArray) other);
+ return super.equals(other)
+ && Objects.equals(dataType, cdt.dataType)
+ && Arrays.equals(shape, cdt.shape)
+ && Arrays.equals(axisNames, cdt.axisNames)
+ && Arrays.equals(tileSets, cdt.tileSets);
+ }
+
+ @Override
+ public int hashCode() {
+ return super.hashCode() + Objects.hash(
+ dataType,
+ Arrays.hashCode(shape),
+ Arrays.hashCode(axisNames),
+ Arrays.hashCode(tileSets));
+ }
+}
diff --git a/storage/sis-coveragejson/src/main/java/org/apache/sis/internal/coveragejson/binding/Unit.java b/storage/sis-coveragejson/src/main/java/org/apache/sis/internal/coveragejson/binding/Unit.java
new file mode 100644
index 0000000000..78b94bacce
--- /dev/null
+++ b/storage/sis-coveragejson/src/main/java/org/apache/sis/internal/coveragejson/binding/Unit.java
@@ -0,0 +1,79 @@
+/*
+ * 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.internal.coveragejson.binding;
+
+import jakarta.json.bind.annotation.JsonbNillable;
+import jakarta.json.bind.annotation.JsonbPropertyOrder;
+import java.util.Objects;
+
+/**
+ * A "unit" where the value is an object which MUST have either or both the members
+ * "label" or/and "symbol".
+ *
+ * @author Johann Sorel (Geomatys)
+ */
+@JsonbNillable(false)
+@JsonbPropertyOrder({"id","label","symbol"})
+public final class Unit extends Dictionary<Object> {
+
+ /**
+ * MAY have the member "id".
+ * If given, the value of "id" MUST be a string and
+ * SHOULD be a common identifier. It is RECOMMENDED to reference a unit
+ * serialization scheme to allow automatic unit conversion.
+ */
+ public String id;
+ /**
+ * If given, the value of "label" MUST be an i18n object of the name of
+ * the unit and SHOULD be short.
+ */
+ public I18N label;
+ /**
+ * If given, the value of "symbol" MUST either be a string of the symbolic notation of the unit,
+ * or an object with the members "value" and "type".
+ */
+ public Object symbol;
+
+ public Unit() {
+ }
+
+ public Unit(String id, I18N label, Object symbol) {
+ this.id = id;
+ this.label = label;
+ this.symbol = symbol;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) return true;
+ if (!(other instanceof Unit)) return false;
+
+ final Unit cdt = ((Unit) other);
+ return super.equals(other)
+ && Objects.equals(id, cdt.id)
+ && Objects.equals(label, cdt.label)
+ && Objects.equals(symbol, cdt.symbol);
+ }
+
+ @Override
+ public int hashCode() {
+ return super.hashCode() + Objects.hash(
+ id,
+ label,
+ symbol);
+ }
+}
diff --git a/storage/sis-coveragejson/src/main/java/org/apache/sis/internal/coveragejson/binding/VerticalCRS.java b/storage/sis-coveragejson/src/main/java/org/apache/sis/internal/coveragejson/binding/VerticalCRS.java
new file mode 100644
index 0000000000..d3090a9670
--- /dev/null
+++ b/storage/sis-coveragejson/src/main/java/org/apache/sis/internal/coveragejson/binding/VerticalCRS.java
@@ -0,0 +1,61 @@
+/*
+ * 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 anz "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.internal.coveragejson.binding;
+
+import jakarta.json.bind.annotation.JsonbNillable;
+import jakarta.json.bind.annotation.JsonbPropertyOrder;
+import java.util.Objects;
+
+/**
+ * Vertical CRSs use a single coordinate to denote some measure of height or depth,
+ * usually approximately oriented with gravity.
+ *
+ * @author Johann Sorel (Geomatys)
+ */
+@JsonbNillable(false)
+@JsonbPropertyOrder({"type","id","description"})
+public final class VerticalCRS extends CoverageJsonObject {
+
+ /**
+ * The object MAY have an "id" member, whose value MUST be a string and
+ * SHOULD be a common identifier for the reference system.
+ */
+ public String id;
+ /**
+ * The object MAY have a "description" member, where the value MUST be an
+ * i18n object, but no standardised content is interpreted from this description.
+ */
+ public I18N description;
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) return true;
+ if (!(other instanceof VerticalCRS)) return false;
+
+ final VerticalCRS cdt = ((VerticalCRS) other);
+ return super.equals(other)
+ && Objects.equals(id, cdt.id)
+ && Objects.equals(description, cdt.description);
+ }
+
+ @Override
+ public int hashCode() {
+ return super.hashCode() + Objects.hash(
+ id,
+ description);
+ }
+}
diff --git a/storage/sis-coveragejson/src/main/resources/META-INF/services/org.apache.sis.storage.DataStoreProvider b/storage/sis-coveragejson/src/main/resources/META-INF/services/org.apache.sis.storage.DataStoreProvider
new file mode 100644
index 0000000000..458f567f12
--- /dev/null
+++ b/storage/sis-coveragejson/src/main/resources/META-INF/services/org.apache.sis.storage.DataStoreProvider
@@ -0,0 +1 @@
+org.apache.sis.internal.coveragejson.CoverageJsonStoreProvider
\ No newline at end of file
diff --git a/storage/sis-coveragejson/src/test/java/org/apache/sis/internal/coveragejson/CoverageJsonStoreTest.java b/storage/sis-coveragejson/src/test/java/org/apache/sis/internal/coveragejson/CoverageJsonStoreTest.java
new file mode 100644
index 0000000000..23fe71e7fd
--- /dev/null
+++ b/storage/sis-coveragejson/src/test/java/org/apache/sis/internal/coveragejson/CoverageJsonStoreTest.java
@@ -0,0 +1,86 @@
+/*
+ * 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.internal.coveragejson;
+
+import jakarta.json.bind.JsonbBuilder;
+import java.awt.image.Raster;
+import org.apache.sis.coverage.grid.GridCoverage;
+import org.apache.sis.coverage.grid.GridExtent;
+import org.apache.sis.coverage.grid.GridGeometry;
+import org.apache.sis.referencing.CRS;
+import org.apache.sis.referencing.CommonCRS;
+import org.apache.sis.storage.Aggregate;
+import org.apache.sis.storage.DataStore;
+import org.apache.sis.storage.GridCoverageResource;
+import org.apache.sis.storage.Resource;
+import org.apache.sis.storage.StorageConnector;
+import org.apache.sis.test.TestCase;
+import org.eclipse.yasson.internal.JsonBindingBuilder;
+import org.junit.Assert;
+import org.junit.Test;
+import org.opengis.metadata.spatial.DimensionNameType;
+
+/**
+ *
+ * @author Johann Sorel (Geomatys)
+ */
+public class CoverageJsonStoreTest extends TestCase {
+
+ /**
+ * Test coverage example from https://covjson.org/playground/.
+ */
+ @Test
+ public void testCoverageXYZT() throws Exception {
+
+ try (final DataStore store = new CoverageJsonStoreProvider().open(new StorageConnector(CoverageJsonStoreTest.class.getResource("coverage_xyzt.json")))) {
+
+ //test grid coverage resource exist
+ Assert.assertTrue(store instanceof Aggregate);
+ final Aggregate aggregate = (Aggregate) store;
+ Assert.assertEquals(1, aggregate.components().size());
+ final Resource candidate = aggregate.components().iterator().next();
+ Assert.assertTrue(candidate instanceof GridCoverageResource);
+ final GridCoverageResource gcr = (GridCoverageResource) candidate;
+
+ JsonbBuilder jcb = new JsonBindingBuilder();
+ { //test grid geometry
+ final GridGeometry result = gcr.getGridGeometry();
+ System.out.println(result);
+
+ Assert.assertEquals(4, result.getDimension());
+
+ final GridExtent expectedExtent = new GridExtent(new DimensionNameType[]{
+ DimensionNameType.valueOf("x"),
+ DimensionNameType.valueOf("y"),
+ DimensionNameType.valueOf("z"),
+ DimensionNameType.valueOf("t")},
+ new long[]{0,0,0,0}, new long[]{2,1,0,0}, true);
+ Assert.assertEquals(expectedExtent, result.getExtent());
+ Assert.assertEquals(CRS.compound(CommonCRS.WGS84.geographic3D(), CommonCRS.Temporal.JAVA.crs()), result.getCoordinateReferenceSystem());
+ //TODO test transform
+ }
+
+
+ { //test data
+ GridCoverage coverage = gcr.read(null);
+ Raster data = coverage.render(null).getData();
+ }
+ }
+
+ }
+
+}
diff --git a/storage/sis-coveragejson/src/test/java/org/apache/sis/internal/coveragejson/CoverageJsonTestSuite.java b/storage/sis-coveragejson/src/test/java/org/apache/sis/internal/coveragejson/CoverageJsonTestSuite.java
new file mode 100644
index 0000000000..3de67e2b7d
--- /dev/null
+++ b/storage/sis-coveragejson/src/test/java/org/apache/sis/internal/coveragejson/CoverageJsonTestSuite.java
@@ -0,0 +1,44 @@
+/*
+ * 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.internal.coveragejson;
+
+import org.apache.sis.internal.coveragejson.binding.BindingTest;
+import org.apache.sis.test.TestSuite;
+import org.junit.BeforeClass;
+import org.junit.runners.Suite;
+
+
+/**
+ * All tests from the {@code sis-coveragejson} module, in rough dependency order.
+ *
+ * @author Johann Sorel (Geomatys)
+ */
+@Suite.SuiteClasses({
+ BindingTest.class,
+ CoverageJsonStoreTest.class
+})
+public final class CoverageJsonTestSuite extends TestSuite {
+ /**
+ * Verifies the list of tests before to run the suite.
+ * See {@link #verifyTestList(Class, Class[])} for more information.
+ */
+ @BeforeClass
+ public static void verifyTestList() {
+ assertNoMissingTest(CoverageJsonTestSuite.class);
+ verifyTestList(CoverageJsonTestSuite.class);
+ }
+}
diff --git a/storage/sis-coveragejson/src/test/java/org/apache/sis/internal/coveragejson/binding/BindingTest.java b/storage/sis-coveragejson/src/test/java/org/apache/sis/internal/coveragejson/binding/BindingTest.java
new file mode 100644
index 0000000000..6455364261
--- /dev/null
+++ b/storage/sis-coveragejson/src/test/java/org/apache/sis/internal/coveragejson/binding/BindingTest.java
@@ -0,0 +1,234 @@
+/*
+ * 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 anz "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.internal.coveragejson.binding;
+
+import jakarta.json.JsonObject;
+import jakarta.json.bind.Jsonb;
+import jakarta.json.bind.JsonbBuilder;
+import jakarta.json.bind.JsonbConfig;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.math.BigDecimal;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import org.apache.sis.test.TestCase;
+import org.eclipse.yasson.YassonConfig;
+import org.junit.AfterClass;
+import static org.junit.Assert.*;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+/**
+ * Test coverage-json bindings.
+ *
+ * @author Johann Sorel (Geomatys)
+ */
+public class BindingTest extends TestCase {
+
+ private static final JsonbConfig CONFIG = new YassonConfig().withFormatting(true);
+
+ private static Jsonb jsonb;
+
+ public static String readResource(String path) throws IOException {
+ final ByteArrayOutputStream buffer = new ByteArrayOutputStream();
+
+ int nRead;
+ byte[] data = new byte[16384];
+ try (InputStream in = BindingTest.class.getResourceAsStream(path)) {
+ while ((nRead = in.read(data, 0, data.length)) != -1) {
+ buffer.write(data, 0, nRead);
+ }
+ }
+ buffer.flush();
+ return new String(buffer.toByteArray(), StandardCharsets.UTF_8);
+ }
+
+ private void compare(String jsonpath, Object expected) throws IOException {
+ String json = readResource(jsonpath);
+ //reformat it the same way.
+ JsonObject map = jsonb.fromJson(json, JsonObject.class);
+ String formattedJson = jsonb.toJson(map);
+
+ final Object candidate = jsonb.fromJson(json, expected.getClass());
+ expected.equals(candidate);
+ assertEquals(expected, candidate);
+ assertEquals(formattedJson, jsonb.toJson(candidate));
+ }
+
+ @BeforeClass
+ public static void beforeClass() {
+ jsonb = JsonbBuilder.create(CONFIG);
+ }
+
+ @AfterClass
+ public static void afterClass() throws Exception {
+ jsonb.close();
+ }
+
+ @Test
+ public void testAxeBounds() throws Exception {
+ final Axe expected = new Axe();
+ expected.values = asList(20,21);
+ expected.bounds = asList(19.5,20.5,20.5,21.5);
+ compare("axe_bounds.json", expected);
+ }
+
+ @Test
+ public void testAxePolygon() throws Exception {
+ final Axe expected = new Axe();
+ expected.dataType = "polygon";
+ expected.coordinates = Arrays.asList("x","y");
+ expected.values = Arrays.asList(
+ Arrays.asList(
+ Arrays.asList(
+ asList(100.0, 0.0),
+ asList(101.0, 0.0),
+ asList(101.0, 1.0),
+ asList(100.0, 1.0),
+ asList(100.0, 0.0)
+ )
+ )
+ );
+ compare("axe_polygon.json", expected);
+ }
+
+ @Test
+ public void testAxeRegular() throws Exception {
+ final Axe expected = new Axe();
+ expected.start = 0.0;
+ expected.stop = 5.0;
+ expected.num = 6;
+ compare("axe_regular.json", expected);
+ }
+
+ @Test
+ public void testAxeTuples() throws Exception {
+ final Axe expected = new Axe();
+ expected.dataType = "tuple";
+ expected.coordinates = Arrays.asList("t","x","y");
+ expected.values = Arrays.asList(
+ asList("2008-01-01T04:00:00Z",1,20),
+ asList("2008-01-01T04:30:00Z",2,21)
+ );
+ compare("axe_tuples.json", expected);
+ }
+
+ @Test
+ public void testCoverageVerticalProfile() throws Exception {
+
+ final GeographicCRS geoCrs = new GeographicCRS();
+ geoCrs.id = "http://www.opengis.net/def/crs/OGC/1.3/CRS84";
+
+ final VerticalCRS zCrs = new VerticalCRS();
+ //"cs":{"csAxes":[{"name":{"en":"Pressure"},"direction":"down","unit":{"symbol":"Pa"}}]}
+ final Map<String,Object> axe = new LinkedHashMap<>();
+ axe.put("name", Map.of("en", "Pressure"));
+ axe.put("direction", "down");
+ axe.put("unit", Map.of("symbol", "Pa"));
+ final Map<String,Object> cs = new HashMap<>();
+ cs.put("csAxes", Arrays.asList(axe));
+ //zCrs.setAnyProperty("cs", cs); //TODO undefined attributes ignored
+
+ final TemporalRS tCrs = new TemporalRS();
+ tCrs.calendar = "Gregorian";
+
+ final ReferenceSystemConnection georsc = new ReferenceSystemConnection();
+ georsc.coordinates = Arrays.asList("x", "y");
+ georsc.system = geoCrs;
+
+ final ReferenceSystemConnection zrsc = new ReferenceSystemConnection();
+ zrsc.coordinates = Arrays.asList("z");
+ zrsc.system = zCrs;
+
+ final ReferenceSystemConnection trsc = new ReferenceSystemConnection();
+ trsc.coordinates = Arrays.asList("t");
+ trsc.system = tCrs;
+
+ final Domain domain = new Domain();
+ domain.domainType = "VerticalProfile";
+ domain.axes = new Axes();
+ domain.axes.x = new Axe();
+ domain.axes.y = new Axe();
+ domain.axes.z = new Axe();
+ domain.axes.t = new Axe();
+ domain.axes.x.values = asList(-10.1);
+ domain.axes.y.values = asList(-40.2);
+ domain.axes.z.values = asList(5.4562, 8.9282);
+ domain.axes.t.values = asList("2013-01-13T11:12:20Z");
+ domain.referencing = Arrays.asList(georsc, zrsc, trsc);
+
+ final Parameter PSAL = new Parameter();
+ PSAL.description = new I18N("en", "The measured salinity, in practical salinity units (psu) of the sea water ");
+ PSAL.unit = new Unit(null,null,"psu");
+ PSAL.observedProperty = new ObservedProperty("http://vocab.nerc.ac.uk/standard_name/sea_water_salinity/", new I18N("en", "Sea Water Salinity"), null, null);
+
+ final Parameter POTM = new Parameter();
+ POTM.description = new I18N("en", "The potential temperature, in degrees celcius, of the sea water");
+ POTM.unit = new Unit(null,null,"°C");
+ POTM.observedProperty = new ObservedProperty("http://vocab.nerc.ac.uk/standard_name/sea_water_potential_temperature/", new I18N("en", "Sea Water Potential Temperature"), null, null);
+
+ final Parameters parameters = new Parameters();
+ parameters.setAnyProperty("PSAL", PSAL);
+ parameters.setAnyProperty("POTM", POTM);
+
+ final NdArray PSALr = new NdArray();
+ PSALr.dataType ="float";
+ PSALr.shape = new int[]{2};
+ PSALr.axisNames = new String[]{"z"};
+ PSALr.values = asList(43.9599, 43.9599);
+
+ final NdArray POTMr = new NdArray();
+ POTMr.dataType ="float";
+ POTMr.shape = new int[]{2};
+ POTMr.axisNames = new String[]{"z"};
+ POTMr.values = asList(23.8, 23.7);
+
+ final Ranges ranges = new Ranges();
+ ranges.setAnyProperty("PSAL", PSALr);
+ ranges.setAnyProperty("POTM", POTMr);
+
+ final Coverage expected = new Coverage();
+ expected.domain = domain;
+ expected.parameters = parameters;
+ expected.ranges = ranges;
+
+ compare("coverage_vertical_profile_nocs.json", expected);
+ }
+
+ /**
+ * Convert numeric values to BigDecimal for equality tests.
+ */
+ private static List<Object> asList(Object... array) {
+ final List<Object> lst = new ArrayList<>(array.length);
+ for (int i=0;i<array.length;i++) {
+ if (array[i] instanceof Integer) {
+ lst.add(BigDecimal.valueOf((Integer) array[i]));
+ } else if (array[i] instanceof Double) {
+ lst.add(BigDecimal.valueOf((Double) array[i]));
+ } else {
+ lst.add(array[i]);
+ }
+ }
+ return lst;
+ }
+}
diff --git a/storage/sis-coveragejson/src/test/resources/org/apache/sis/internal/coveragejson/binding/axe_bounds.json b/storage/sis-coveragejson/src/test/resources/org/apache/sis/internal/coveragejson/binding/axe_bounds.json
new file mode 100644
index 0000000000..a4d3c7c93b
--- /dev/null
+++ b/storage/sis-coveragejson/src/test/resources/org/apache/sis/internal/coveragejson/binding/axe_bounds.json
@@ -0,0 +1,12 @@
+{
+ "values":[
+ 20,
+ 21
+ ],
+ "bounds":[
+ 19.5,
+ 20.5,
+ 20.5,
+ 21.5
+ ]
+}
\ No newline at end of file
diff --git a/storage/sis-coveragejson/src/test/resources/org/apache/sis/internal/coveragejson/binding/axe_polygon.json b/storage/sis-coveragejson/src/test/resources/org/apache/sis/internal/coveragejson/binding/axe_polygon.json
new file mode 100644
index 0000000000..8fbaf14501
--- /dev/null
+++ b/storage/sis-coveragejson/src/test/resources/org/apache/sis/internal/coveragejson/binding/axe_polygon.json
@@ -0,0 +1,33 @@
+{
+ "dataType":"polygon",
+ "coordinates":[
+ "x",
+ "y"
+ ],
+ "values":[
+ [
+ [
+ [
+ 100.0,
+ 0.0
+ ],
+ [
+ 101.0,
+ 0.0
+ ],
+ [
+ 101.0,
+ 1.0
+ ],
+ [
+ 100.0,
+ 1.0
+ ],
+ [
+ 100.0,
+ 0.0
+ ]
+ ]
+ ]
+ ]
+}
\ No newline at end of file
diff --git a/storage/sis-coveragejson/src/test/resources/org/apache/sis/internal/coveragejson/binding/axe_regular.json b/storage/sis-coveragejson/src/test/resources/org/apache/sis/internal/coveragejson/binding/axe_regular.json
new file mode 100644
index 0000000000..010b46d9a9
--- /dev/null
+++ b/storage/sis-coveragejson/src/test/resources/org/apache/sis/internal/coveragejson/binding/axe_regular.json
@@ -0,0 +1,5 @@
+{
+ "start": 0.0,
+ "stop": 5.0,
+ "num": 6
+}
\ No newline at end of file
diff --git a/storage/sis-coveragejson/src/test/resources/org/apache/sis/internal/coveragejson/binding/axe_tuples.json b/storage/sis-coveragejson/src/test/resources/org/apache/sis/internal/coveragejson/binding/axe_tuples.json
new file mode 100644
index 0000000000..79918750f3
--- /dev/null
+++ b/storage/sis-coveragejson/src/test/resources/org/apache/sis/internal/coveragejson/binding/axe_tuples.json
@@ -0,0 +1,20 @@
+{
+ "dataType":"tuple",
+ "coordinates":[
+ "t",
+ "x",
+ "y"
+ ],
+ "values":[
+ [
+ "2008-01-01T04:00:00Z",
+ 1,
+ 20
+ ],
+ [
+ "2008-01-01T04:30:00Z",
+ 2,
+ 21
+ ]
+ ]
+}
\ No newline at end of file
diff --git a/storage/sis-coveragejson/src/test/resources/org/apache/sis/internal/coveragejson/binding/coverage_vertical_profile.json b/storage/sis-coveragejson/src/test/resources/org/apache/sis/internal/coveragejson/binding/coverage_vertical_profile.json
new file mode 100644
index 0000000000..a975d21e4c
--- /dev/null
+++ b/storage/sis-coveragejson/src/test/resources/org/apache/sis/internal/coveragejson/binding/coverage_vertical_profile.json
@@ -0,0 +1,91 @@
+{
+ "type" : "Coverage",
+ "domain" : {
+ "type" : "Domain",
+ "domainType" : "VerticalProfile",
+ "axes": {
+ "x" : { "values": [-10.1] },
+ "y" : { "values": [ -40.2] },
+ "z" : { "values": [
+ 5.4562, 8.9282 ] },
+ "t" : { "values": ["2013-01-13T11:12:20Z"] }
+ },
+ "referencing": [{
+ "coordinates": ["x","y"],
+ "system": {
+ "type": "GeographicCRS",
+ "id": "http://www.opengis.net/def/crs/OGC/1.3/CRS84"
+ }
+ }, {
+ "coordinates": ["z"],
+ "system": {
+ "type": "VerticalCRS",
+ "cs": {
+ "csAxes": [{
+ "name": {
+ "en": "Pressure"
+ },
+ "direction": "down",
+ "unit": {
+ "symbol": "Pa"
+ }
+ }]
+ }
+ }
+ }, {
+ "coordinates": ["t"],
+ "system": {
+ "type": "TemporalRS",
+ "calendar": "Gregorian"
+ }
+ }]
+ },
+ "parameters" : {
+ "PSAL": {
+ "type" : "Parameter",
+ "description" : {
+ "en": "The measured salinity, in practical salinity units (psu) of the sea water "
+ },
+ "unit" : {
+ "symbol" : "psu"
+ },
+ "observedProperty" : {
+ "id" : "http://vocab.nerc.ac.uk/standard_name/sea_water_salinity/",
+ "label" : {
+ "en": "Sea Water Salinity"
+ }
+ }
+ },
+ "POTM": {
+ "type" : "Parameter",
+ "description" : {
+ "en": "The potential temperature, in degrees celcius, of the sea water"
+ },
+ "unit" : {
+ "symbol" : "°C"
+ },
+ "observedProperty" : {
+ "id" : "http://vocab.nerc.ac.uk/standard_name/sea_water_potential_temperature/",
+ "label" : {
+ "en": "Sea Water Potential Temperature"
+ }
+ }
+ }
+ },
+ "ranges" : {
+ "PSAL" : {
+ "type" : "NdArray",
+ "dataType": "float",
+ "axisNames": ["z"],
+ "shape": [2],
+ "values" : [ 43.9599, 43.9599 ]
+ },
+ "POTM" : {
+ "type" : "NdArray",
+ "dataType": "float",
+ "axisNames": ["z"],
+ "shape": [2],
+ "values" : [ 23.8, 23.7 ]
+ }
+ }
+}
\ No newline at end of file
diff --git a/storage/sis-coveragejson/src/test/resources/org/apache/sis/internal/coveragejson/binding/coverage_vertical_profile_nocs.json b/storage/sis-coveragejson/src/test/resources/org/apache/sis/internal/coveragejson/binding/coverage_vertical_profile_nocs.json
new file mode 100644
index 0000000000..fd77ff48d6
--- /dev/null
+++ b/storage/sis-coveragejson/src/test/resources/org/apache/sis/internal/coveragejson/binding/coverage_vertical_profile_nocs.json
@@ -0,0 +1,80 @@
+{
+ "type" : "Coverage",
+ "domain" : {
+ "type" : "Domain",
+ "domainType" : "VerticalProfile",
+ "axes": {
+ "x" : { "values": [-10.1] },
+ "y" : { "values": [ -40.2] },
+ "z" : { "values": [
+ 5.4562, 8.9282 ] },
+ "t" : { "values": ["2013-01-13T11:12:20Z"] }
+ },
+ "referencing": [{
+ "coordinates": ["x","y"],
+ "system": {
+ "type": "GeographicCRS",
+ "id": "http://www.opengis.net/def/crs/OGC/1.3/CRS84"
+ }
+ }, {
+ "coordinates": ["z"],
+ "system": {
+ "type": "VerticalCRS"
+ }
+ }, {
+ "coordinates": ["t"],
+ "system": {
+ "type": "TemporalRS",
+ "calendar": "Gregorian"
+ }
+ }]
+ },
+ "parameters" : {
+ "PSAL": {
+ "type" : "Parameter",
+ "description" : {
+ "en": "The measured salinity, in practical salinity units (psu) of the sea water "
+ },
+ "unit" : {
+ "symbol" : "psu"
+ },
+ "observedProperty" : {
+ "id" : "http://vocab.nerc.ac.uk/standard_name/sea_water_salinity/",
+ "label" : {
+ "en": "Sea Water Salinity"
+ }
+ }
+ },
+ "POTM": {
+ "type" : "Parameter",
+ "description" : {
+ "en": "The potential temperature, in degrees celcius, of the sea water"
+ },
+ "unit" : {
+ "symbol" : "°C"
+ },
+ "observedProperty" : {
+ "id" : "http://vocab.nerc.ac.uk/standard_name/sea_water_potential_temperature/",
+ "label" : {
+ "en": "Sea Water Potential Temperature"
+ }
+ }
+ }
+ },
+ "ranges" : {
+ "PSAL" : {
+ "type" : "NdArray",
+ "dataType": "float",
+ "axisNames": ["z"],
+ "shape": [2],
+ "values" : [ 43.9599, 43.9599 ]
+ },
+ "POTM" : {
+ "type" : "NdArray",
+ "dataType": "float",
+ "axisNames": ["z"],
+ "shape": [2],
+ "values" : [ 23.8, 23.7 ]
+ }
+ }
+}
\ No newline at end of file
diff --git a/storage/sis-coveragejson/src/test/resources/org/apache/sis/internal/coveragejson/binding/coveragecollection.json b/storage/sis-coveragejson/src/test/resources/org/apache/sis/internal/coveragejson/binding/coveragecollection.json
new file mode 100644
index 0000000000..e1af6e39d2
--- /dev/null
+++ b/storage/sis-coveragejson/src/test/resources/org/apache/sis/internal/coveragejson/binding/coveragecollection.json
@@ -0,0 +1,92 @@
+{
+ "type" : "CoverageCollection",
+ "domainType" : "VerticalProfile",
+ "parameters" : {
+ "PSAL": {
+ "type" : "Parameter",
+ "description" : {
+ "en": "The measured salinity, in practical salinity units (psu) of the sea water"
+ },
+ "unit" : {
+ "symbol" : "psu"
+ },
+ "observedProperty" : {
+ "id": "http://vocab.nerc.ac.uk/standard_name/sea_water_salinity/",
+ "label" : {
+ "en": "Sea Water Salinity"
+ }
+ }
+ }
+ },
+ "referencing": [{
+ "coordinates": ["x","y"],
+ "system": {
+ "type": "GeographicCRS",
+ "id": "http://www.opengis.net/def/crs/OGC/1.3/CRS84"
+ }
+ }, {
+ "coordinates": ["z"],
+ "system": {
+ "type": "VerticalCRS",
+ "cs": {
+ "csAxes": [{
+ "name": {
+ "en": "Pressure"
+ },
+ "direction": "down",
+ "unit": {
+ "symbol": "Pa"
+ }
+ }]
+ }
+ }
+ }, {
+ "coordinates": ["t"],
+ "system": {
+ "type": "TemporalRS",
+ "calendar": "Gregorian"
+ }
+ }],
+ "coverages": [
+ {
+ "type" : "Coverage",
+ "domain" : {
+ "type": "Domain",
+ "axes": {
+ "x": { "values": [-10.1] },
+ "y": { "values": [-40.2] },
+ "z": { "values": [ 5, 8, 14 ] },
+ "t": { "values": ["2013-01-13T11:12:20Z"] }
+ }
+ },
+ "ranges" : {
+ "PSAL" : {
+ "type" : "NdArray",
+ "dataType": "float",
+ "shape": [3],
+ "axisNames": ["z"],
+ "values" : [ 43.7, 43.8, 43.9 ]
+ }
+ }
+ }, {
+ "type" : "Coverage",
+ "domain" : {
+ "type": "Domain",
+ "axes": {
+ "x": { "values": [-11.1] },
+ "y": { "values": [-45.2] },
+ "z": { "values": [ 4, 7, 9 ] },
+ "t": { "values": ["2013-01-13T12:12:20Z"] }
+ }
+ },
+ "ranges" : {
+ "PSAL" : {
+ "type" : "NdArray",
+ "dataType": "float",
+ "shape": [3],
+ "axisNames": ["z"],
+ "values" : [ 42.7, 41.8, 40.9 ]
+ }
+ }
+ }]
+}
\ No newline at end of file
diff --git a/storage/sis-coveragejson/src/test/resources/org/apache/sis/internal/coveragejson/binding/domain_grid.json b/storage/sis-coveragejson/src/test/resources/org/apache/sis/internal/coveragejson/binding/domain_grid.json
new file mode 100644
index 0000000000..042aed9e21
--- /dev/null
+++ b/storage/sis-coveragejson/src/test/resources/org/apache/sis/internal/coveragejson/binding/domain_grid.json
@@ -0,0 +1,26 @@
+{
+ "type" : "Coverage",
+ "domain" : {
+ "type" : "Domain",
+ "domainType" : "Grid",
+ "axes": {
+ "x": { "values": [1,2,3] },
+ "y": { "values": [20,21] },
+ "z": { "values": [1] },
+ "t": { "values": ["2008-01-01T04:00:00Z"] }
+ },
+ "referencing": []
+ },
+ "parameters" : {
+ "temperature": {}
+ },
+ "ranges" : {
+ "temperature" : {
+ "type" : "NdArray",
+ "dataType": "float",
+ "axisNames": ["t", "z", "y", "x"],
+ "shape": [1, 1, 2, 3],
+ "values" : []
+ }
+ }
+}
\ No newline at end of file
diff --git a/storage/sis-coveragejson/src/test/resources/org/apache/sis/internal/coveragejson/binding/domain_trajectory.json b/storage/sis-coveragejson/src/test/resources/org/apache/sis/internal/coveragejson/binding/domain_trajectory.json
new file mode 100644
index 0000000000..43b421ded3
--- /dev/null
+++ b/storage/sis-coveragejson/src/test/resources/org/apache/sis/internal/coveragejson/binding/domain_trajectory.json
@@ -0,0 +1,27 @@
+{
+ "type": "Domain",
+ "domainType": "Trajectory",
+ "axes": {
+ "composite": {
+ "dataType": "tuple",
+ "coordinates": ["t","x","y"],
+ "values": [
+ ["2008-01-01T04:00:00Z", 1, 20],
+ ["2008-01-01T04:30:00Z", 2, 21]
+ ]
+ }
+ },
+ "referencing": [{
+ "coordinates": ["t"],
+ "system": {
+ "type": "TemporalRS",
+ "calendar": "Gregorian"
+ }
+ }, {
+ "coordinates": ["x","y"],
+ "system": {
+ "type": "GeographicCRS",
+ "id": "http://www.opengis.net/def/crs/OGC/1.3/CRS84"
+ }
+ }]
+}
\ No newline at end of file
diff --git a/storage/sis-coveragejson/src/test/resources/org/apache/sis/internal/coveragejson/binding/domaintype_grid.json b/storage/sis-coveragejson/src/test/resources/org/apache/sis/internal/coveragejson/binding/domaintype_grid.json
new file mode 100644
index 0000000000..ee521192e1
--- /dev/null
+++ b/storage/sis-coveragejson/src/test/resources/org/apache/sis/internal/coveragejson/binding/domaintype_grid.json
@@ -0,0 +1,5 @@
+{
+ "start": 0,
+ "stop": 5,
+ "num": 6
+}
\ No newline at end of file
diff --git a/storage/sis-coveragejson/src/test/resources/org/apache/sis/internal/coveragejson/binding/domaintype_multipoint.json b/storage/sis-coveragejson/src/test/resources/org/apache/sis/internal/coveragejson/binding/domaintype_multipoint.json
new file mode 100644
index 0000000000..47ca3264b5
--- /dev/null
+++ b/storage/sis-coveragejson/src/test/resources/org/apache/sis/internal/coveragejson/binding/domaintype_multipoint.json
@@ -0,0 +1,30 @@
+{
+ "type" : "Coverage",
+ "domain" : {
+ "type": "Domain",
+ "domainType": "MultiPoint",
+ "axes": {
+ "t": { "values": ["2008-01-01T04:00:00Z"] },
+ "composite": {
+ "dataType": "tuple",
+ "coordinates": ["x","y","z"],
+ "values": [
+ [1, 20, 1],
+ [2, 21, 3]
+ ]
+ }
+ }
+ },
+ "parameters" : {
+ "temperature": {...}
+ },
+ "ranges" : {
+ "temperature" : {
+ "type" : "NdArray",
+ "dataType": "float",
+ "axisNames": ["composite"],
+ "shape": [2],
+ "values" : [...]
+ }
+ }
+}
\ No newline at end of file
diff --git a/storage/sis-coveragejson/src/test/resources/org/apache/sis/internal/coveragejson/binding/domaintype_multipointseries.json b/storage/sis-coveragejson/src/test/resources/org/apache/sis/internal/coveragejson/binding/domaintype_multipointseries.json
new file mode 100644
index 0000000000..298b8cfb1c
--- /dev/null
+++ b/storage/sis-coveragejson/src/test/resources/org/apache/sis/internal/coveragejson/binding/domaintype_multipointseries.json
@@ -0,0 +1,31 @@
+{
+ "type" : "Coverage",
+ "domain" : {
+ "type": "Domain",
+ "domainType": "MultiPointSeries",
+ "axes": {
+ "t": { "values": ["2008-01-01T04:00:00Z", "2008-01-01T05:00:00Z"] },
+ "composite": {
+ "dataType": "tuple",
+ "coordinates": ["x","y","z"],
+ "values": [
+ [1, 20, 1],
+ [2, 21, 3],
+ [2, 20, 4]
+ ]
+ }
+ }
+ },
+ "parameters" : {
+ "temperature": {...}
+ },
+ "ranges" : {
+ "temperature" : {
+ "type" : "NdArray",
+ "dataType": "float",
+ "axisNames": ["t", "composite"],
+ "shape": [2, 3],
+ "values" : [...]
+ }
+ }
+}
\ No newline at end of file
diff --git a/storage/sis-coveragejson/src/test/resources/org/apache/sis/internal/coveragejson/binding/domaintype_multipolygon.json b/storage/sis-coveragejson/src/test/resources/org/apache/sis/internal/coveragejson/binding/domaintype_multipolygon.json
new file mode 100644
index 0000000000..e24f503100
--- /dev/null
+++ b/storage/sis-coveragejson/src/test/resources/org/apache/sis/internal/coveragejson/binding/domaintype_multipolygon.json
@@ -0,0 +1,32 @@
+{
+ "type" : "Coverage",
+ "domain" : {
+ "type": "Domain",
+ "domainType": "MultiPolygon",
+ "axes": {
+ "composite": {
+ "dataType": "polygon",
+ "coordinates": ["x","y"],
+ "values": [
+ [ [ [100.0, 0.0], [101.0, 0.0], [101.0, 1.0], [100.0, 1.0], [100.0, 0.0] ] ],
+ [ [ [200.0, 10.0], [201.0, 10.0], [201.0, 11.0], [200.0, 11.0], [200.0, 10.0] ] ]
+ ]
+ },
+ "z": { "values": [2] },
+ "t": { "values": ["2008-01-01T04:00:00Z"] }
+ },
+ "referencing": [...]
+ },
+ "parameters" : {
+ "temperature": {...}
+ },
+ "ranges" : {
+ "temperature" : {
+ "type" : "NdArray",
+ "dataType": "float",
+ "axisNames": ["composite"],
+ "shape": [2],
+ "values" : [...]
+ }
+ }
+}
\ No newline at end of file
diff --git a/storage/sis-coveragejson/src/test/resources/org/apache/sis/internal/coveragejson/binding/domaintype_multipolygonseries.json b/storage/sis-coveragejson/src/test/resources/org/apache/sis/internal/coveragejson/binding/domaintype_multipolygonseries.json
new file mode 100644
index 0000000000..a3ff18886f
--- /dev/null
+++ b/storage/sis-coveragejson/src/test/resources/org/apache/sis/internal/coveragejson/binding/domaintype_multipolygonseries.json
@@ -0,0 +1,32 @@
+{
+ "type" : "Coverage",
+ "domain" : {
+ "type": "Domain",
+ "domainType": "MultiPolygonSeries",
+ "axes": {
+ "composite": {
+ "dataType": "polygon",
+ "coordinates": ["x","y"],
+ "values": [
+ [ [ [100.0, 0.0], [101.0, 0.0], [101.0, 1.0], [100.0, 1.0], [100.0, 0.0] ] ],
+ [ [ [200.0, 10.0], [201.0, 10.0], [201.0, 11.0], [200.0, 11.0], [200.0, 10.0] ] ]
+ ]
+ },
+ "z": { "values": [2] },
+ "t": { "values": ["2008-01-01T04:00:00Z", "2010-01-01T00:00:00Z", "2012-01-01T00:00:00Z"] }
+ },
+ "referencing": [...]
+ },
+ "parameters" : {
+ "temperature": {...}
+ },
+ "ranges" : {
+ "temperature" : {
+ "type" : "NdArray",
+ "dataType": "float",
+ "axisNames": ["t", "composite"],
+ "shape": [3, 2],
+ "values" : [...]
+ }
+ }
+}
\ No newline at end of file
diff --git a/storage/sis-coveragejson/src/test/resources/org/apache/sis/internal/coveragejson/binding/domaintype_point.json b/storage/sis-coveragejson/src/test/resources/org/apache/sis/internal/coveragejson/binding/domaintype_point.json
new file mode 100644
index 0000000000..ab71e6e3f4
--- /dev/null
+++ b/storage/sis-coveragejson/src/test/resources/org/apache/sis/internal/coveragejson/binding/domaintype_point.json
@@ -0,0 +1,24 @@
+{
+ "type" : "Coverage",
+ "domain" : {
+ "type": "Domain",
+ "domainType": "Point",
+ "axes": {
+ "x": { "values": [1] },
+ "y": { "values": [20] },
+ "z": { "values": [1] },
+ "t": { "values": ["2008-01-01T04:00:00Z"] }
+ },
+ "referencing": [...]
+ },
+ "parameters" : {
+ "temperature": {...}
+ },
+ "ranges" : {
+ "temperature" : {
+ "type" : "NdArray",
+ "dataType": "float",
+ "values" : [...]
+ }
+ }
+}
\ No newline at end of file
diff --git a/storage/sis-coveragejson/src/test/resources/org/apache/sis/internal/coveragejson/binding/domaintype_pointseries.json b/storage/sis-coveragejson/src/test/resources/org/apache/sis/internal/coveragejson/binding/domaintype_pointseries.json
new file mode 100644
index 0000000000..cdb412c1c0
--- /dev/null
+++ b/storage/sis-coveragejson/src/test/resources/org/apache/sis/internal/coveragejson/binding/domaintype_pointseries.json
@@ -0,0 +1,26 @@
+{
+ "type" : "Coverage",
+ "domain" : {
+ "type": "Domain",
+ "domainType": "PointSeries",
+ "axes": {
+ "x": { "values": [1] },
+ "y": { "values": [20] },
+ "z": { "values": [1] },
+ "t": { "values": ["2008-01-01T04:00:00Z","2008-01-01T05:00:00Z"] }
+ },
+ "referencing": [...]
+ },
+ "parameters" : {
+ "temperature": {...}
+ },
+ "ranges" : {
+ "temperature" : {
+ "type" : "NdArray",
+ "dataType": "float",
+ "axisNames": ["t"],
+ "shape": [2],
+ "values" : [...]
+ }
+ }
+}
\ No newline at end of file
diff --git a/storage/sis-coveragejson/src/test/resources/org/apache/sis/internal/coveragejson/binding/domaintype_polygon.json b/storage/sis-coveragejson/src/test/resources/org/apache/sis/internal/coveragejson/binding/domaintype_polygon.json
new file mode 100644
index 0000000000..7e1053008f
--- /dev/null
+++ b/storage/sis-coveragejson/src/test/resources/org/apache/sis/internal/coveragejson/binding/domaintype_polygon.json
@@ -0,0 +1,29 @@
+{
+ "type" : "Coverage",
+ "domain" : {
+ "type": "Domain",
+ "domainType": "Polygon",
+ "axes": {
+ "composite": {
+ "dataType": "polygon",
+ "coordinates": ["x","y"],
+ "values": [
+ [ [ [100.0, 0.0], [101.0, 0.0], [101.0, 1.0], [100.0, 1.0], [100.0, 0.0] ] ]
+ ]
+ },
+ "z": { "values": [2] },
+ "t": { "values": ["2008-01-01T04:00:00Z"] }
+ },
+ "referencing": [...]
+ },
+ "parameters" : {
+ "temperature": {...}
+ },
+ "ranges" : {
+ "temperature" : {
+ "type" : "NdArray",
+ "dataType": "float",
+ "values" : [...]
+ }
+ }
+}
\ No newline at end of file
diff --git a/storage/sis-coveragejson/src/test/resources/org/apache/sis/internal/coveragejson/binding/domaintype_polygonseries.json b/storage/sis-coveragejson/src/test/resources/org/apache/sis/internal/coveragejson/binding/domaintype_polygonseries.json
new file mode 100644
index 0000000000..ca48a18c21
--- /dev/null
+++ b/storage/sis-coveragejson/src/test/resources/org/apache/sis/internal/coveragejson/binding/domaintype_polygonseries.json
@@ -0,0 +1,31 @@
+{
+ "type" : "Coverage",
+ "domain" : {
+ "type": "Domain",
+ "domainType": "PolygonSeries",
+ "axes": {
+ "composite": {
+ "dataType": "polygon",
+ "coordinates": ["x","y"],
+ "values": [
+ [ [ [100.0, 0.0], [101.0, 0.0], [101.0, 1.0], [100.0, 1.0], [100.0, 0.0] ] ]
+ ]
+ },
+ "z": { "values": [2] },
+ "t": { "values": ["2008-01-01T04:00:00Z","2008-01-01T05:00:00Z"] }
+ },
+ "referencing": [...]
+ },
+ "parameters" : {
+ "temperature": {...}
+ },
+ "ranges" : {
+ "temperature" : {
+ "type" : "NdArray",
+ "dataType": "float",
+ "axisNames": ["t"],
+ "shape": [2],
+ "values" : [...]
+ }
+ }
+}
\ No newline at end of file
diff --git a/storage/sis-coveragejson/src/test/resources/org/apache/sis/internal/coveragejson/binding/domaintype_section.json b/storage/sis-coveragejson/src/test/resources/org/apache/sis/internal/coveragejson/binding/domaintype_section.json
new file mode 100644
index 0000000000..48d360f6fa
--- /dev/null
+++ b/storage/sis-coveragejson/src/test/resources/org/apache/sis/internal/coveragejson/binding/domaintype_section.json
@@ -0,0 +1,31 @@
+{
+ "type" : "Coverage",
+ "domain" : {
+ "type": "Domain",
+ "domainType": "Section",
+ "axes": {
+ "z": { "values": [10,20,30] },
+ "composite": {
+ "dataType": "tuple",
+ "coordinates": ["t","x","y"],
+ "values": [
+ ["2008-01-01T04:00:00Z", 1, 20],
+ ["2008-01-01T04:30:00Z", 2, 21]
+ ]
+ }
+ },
+ "referencing": [...]
+ },
+ "parameters" : {
+ "temperature": {...}
+ },
+ "ranges" : {
+ "temperature" : {
+ "type" : "NdArray",
+ "dataType": "float",
+ "axisNames": ["z", "composite"],
+ "shape": [3, 2],
+ "values" : [...]
+ }
+ }
+}
\ No newline at end of file
diff --git a/storage/sis-coveragejson/src/test/resources/org/apache/sis/internal/coveragejson/binding/domaintype_trajectory.json b/storage/sis-coveragejson/src/test/resources/org/apache/sis/internal/coveragejson/binding/domaintype_trajectory.json
new file mode 100644
index 0000000000..6c6937cfa0
--- /dev/null
+++ b/storage/sis-coveragejson/src/test/resources/org/apache/sis/internal/coveragejson/binding/domaintype_trajectory.json
@@ -0,0 +1,30 @@
+{
+ "type" : "Coverage",
+ "domain" : {
+ "type": "Domain",
+ "domainType": "Trajectory",
+ "axes": {
+ "composite": {
+ "dataType": "tuple",
+ "coordinates": ["t","x","y","z"],
+ "values": [
+ ["2008-01-01T04:00:00Z", 1, 20, 1],
+ ["2008-01-01T04:30:00Z", 2, 21, 3]
+ ]
+ }
+ },
+ "referencing": [...]
+ },
+ "parameters" : {
+ "temperature": {...}
+ },
+ "ranges" : {
+ "temperature" : {
+ "type" : "NdArray",
+ "dataType": "float",
+ "axisNames": ["composite"],
+ "shape": [2],
+ "values" : [...]
+ }
+ }
+}
\ No newline at end of file
diff --git a/storage/sis-coveragejson/src/test/resources/org/apache/sis/internal/coveragejson/binding/domaintype_vertical_profile.json b/storage/sis-coveragejson/src/test/resources/org/apache/sis/internal/coveragejson/binding/domaintype_vertical_profile.json
new file mode 100644
index 0000000000..92ff3a7adf
--- /dev/null
+++ b/storage/sis-coveragejson/src/test/resources/org/apache/sis/internal/coveragejson/binding/domaintype_vertical_profile.json
@@ -0,0 +1,26 @@
+{
+ "type" : "Coverage",
+ "domain" : {
+ "type": "Domain",
+ "domainType": "VerticalProfile",
+ "axes": {
+ "x": { "values": [1] },
+ "y": { "values": [21] },
+ "z": { "values": [1,5,20] },
+ "t": { "values": ["2008-01-01T04:00:00Z"] }
+ },
+ "referencing": [...]
+ },
+ "parameters" : {
+ "temperature": {...}
+ },
+ "ranges" : {
+ "temperature" : {
+ "type" : "NdArray",
+ "dataType": "float",
+ "axisNames": ["z"],
+ "shape": [3],
+ "values" : [...]
+ }
+ }
+}
\ No newline at end of file
diff --git a/storage/sis-coveragejson/src/test/resources/org/apache/sis/internal/coveragejson/binding/geographiccrs_longlat.json b/storage/sis-coveragejson/src/test/resources/org/apache/sis/internal/coveragejson/binding/geographiccrs_longlat.json
new file mode 100644
index 0000000000..90814122a1
--- /dev/null
+++ b/storage/sis-coveragejson/src/test/resources/org/apache/sis/internal/coveragejson/binding/geographiccrs_longlat.json
@@ -0,0 +1,4 @@
+{
+ "type": "GeographicCRS",
+ "id": "http://www.opengis.net/def/crs/OGC/1.3/CRS84"
+}
\ No newline at end of file
diff --git a/storage/sis-coveragejson/src/test/resources/org/apache/sis/internal/coveragejson/binding/geographiccrs_longlatheight.json b/storage/sis-coveragejson/src/test/resources/org/apache/sis/internal/coveragejson/binding/geographiccrs_longlatheight.json
new file mode 100644
index 0000000000..0c736fd05e
--- /dev/null
+++ b/storage/sis-coveragejson/src/test/resources/org/apache/sis/internal/coveragejson/binding/geographiccrs_longlatheight.json
@@ -0,0 +1,4 @@
+{
+ "type": "GeographicCRS",
+ "id": "http://www.opengis.net/def/crs/EPSG/0/4979"
+}
\ No newline at end of file
diff --git a/storage/sis-coveragejson/src/test/resources/org/apache/sis/internal/coveragejson/binding/ndarray.json b/storage/sis-coveragejson/src/test/resources/org/apache/sis/internal/coveragejson/binding/ndarray.json
new file mode 100644
index 0000000000..da6f336406
--- /dev/null
+++ b/storage/sis-coveragejson/src/test/resources/org/apache/sis/internal/coveragejson/binding/ndarray.json
@@ -0,0 +1,10 @@
+{
+ "type": "NdArray",
+ "dataType": "float",
+ "shape": [4, 2],
+ "axisNames": ["y", "x"],
+ "values": [
+ 12.3, 12.5, 11.5, 23.1,
+ null, null, 10.1, 9.1
+ ]
+}
\ No newline at end of file
diff --git a/storage/sis-coveragejson/src/test/resources/org/apache/sis/internal/coveragejson/binding/parameter_categoricaldata.json b/storage/sis-coveragejson/src/test/resources/org/apache/sis/internal/coveragejson/binding/parameter_categoricaldata.json
new file mode 100644
index 0000000000..3afb80e7dc
--- /dev/null
+++ b/storage/sis-coveragejson/src/test/resources/org/apache/sis/internal/coveragejson/binding/parameter_categoricaldata.json
@@ -0,0 +1,33 @@
+{
+ "type" : "Parameter",
+ "description" : {
+ "en": "The land cover category."
+ },
+ "observedProperty" : {
+ "id" : "http://example.com/land_cover",
+ "label" : {
+ "en": "Land Cover"
+ },
+ "description" : {
+ "en": "longer description..."
+ },
+ "categories": [{
+ "id": "http://example.com/land_cover/categories/grass",
+ "label": {
+ "en": "Grass"
+ },
+ "description": {
+ "en": "Very green grass."
+ }
+ }, {
+ "id": "http://example.com/land_cover/categories/forest",
+ "label": {
+ "en": "Forest"
+ }
+ }]
+ },
+ "categoryEncoding": {
+ "http://example.com/land_cover/categories/grass": 1,
+ "http://example.com/land_cover/categories/forest": [2,3]
+ }
+}
\ No newline at end of file
diff --git a/storage/sis-coveragejson/src/test/resources/org/apache/sis/internal/coveragejson/binding/parameter_continuousdata.json b/storage/sis-coveragejson/src/test/resources/org/apache/sis/internal/coveragejson/binding/parameter_continuousdata.json
new file mode 100644
index 0000000000..2ff6c92184
--- /dev/null
+++ b/storage/sis-coveragejson/src/test/resources/org/apache/sis/internal/coveragejson/binding/parameter_continuousdata.json
@@ -0,0 +1,24 @@
+{
+ "type" : "Parameter",
+ "description" : {
+ "en": "The sea surface temperature in degrees Celsius."
+ },
+ "observedProperty" : {
+ "id" : "http://vocab.nerc.ac.uk/standard_name/sea_surface_temperature/",
+ "label" : {
+ "en": "Sea Surface Temperature"
+ },
+ "description" : {
+ "en": "The temperature of sea water near the surface (including the part under sea-ice, if any), and not the skin temperature."
+ }
+ },
+ "unit" : {
+ "label" : {
+ "en": "Degree Celsius"
+ },
+ "symbol": {
+ "value": "Cel",
+ "type": "http://www.opengis.net/def/uom/UCUM/"
+ }
+ }
+}
\ No newline at end of file
diff --git a/storage/sis-coveragejson/src/test/resources/org/apache/sis/internal/coveragejson/binding/parametergroup_uncertainty.json b/storage/sis-coveragejson/src/test/resources/org/apache/sis/internal/coveragejson/binding/parametergroup_uncertainty.json
new file mode 100644
index 0000000000..7b871885e3
--- /dev/null
+++ b/storage/sis-coveragejson/src/test/resources/org/apache/sis/internal/coveragejson/binding/parametergroup_uncertainty.json
@@ -0,0 +1,13 @@
+{
+ "type": "ParameterGroup",
+ "label": {
+ "en": "Daily sea surface temperature with uncertainty information"
+ },
+ "observedProperty": {
+ "id": "http://vocab.nerc.ac.uk/standard_name/sea_surface_temperature/",
+ "label": {
+ "en": "Sea surface temperature"
+ }
+ },
+ "members": ["SST_mean", "SST_stddev"]
+}
\ No newline at end of file
diff --git a/storage/sis-coveragejson/src/test/resources/org/apache/sis/internal/coveragejson/binding/parametergroup_vectorquantity.json b/storage/sis-coveragejson/src/test/resources/org/apache/sis/internal/coveragejson/binding/parametergroup_vectorquantity.json
new file mode 100644
index 0000000000..696eaaa3d9
--- /dev/null
+++ b/storage/sis-coveragejson/src/test/resources/org/apache/sis/internal/coveragejson/binding/parametergroup_vectorquantity.json
@@ -0,0 +1,9 @@
+{
+ "type": "ParameterGroup",
+ "observedProperty": {
+ "label": {
+ "en": "Wind velocity"
+ }
+ },
+ "members": ["WIND_SPEED", "WIND_DIR"]
+}
\ No newline at end of file
diff --git a/storage/sis-coveragejson/src/test/resources/org/apache/sis/internal/coveragejson/binding/projectedcrs_britishnationalgrid.json b/storage/sis-coveragejson/src/test/resources/org/apache/sis/internal/coveragejson/binding/projectedcrs_britishnationalgrid.json
new file mode 100644
index 0000000000..199edd7e4e
--- /dev/null
+++ b/storage/sis-coveragejson/src/test/resources/org/apache/sis/internal/coveragejson/binding/projectedcrs_britishnationalgrid.json
@@ -0,0 +1,4 @@
+{
+ "type": "ProjectedCRS",
+ "id": "http://www.opengis.net/def/crs/EPSG/0/27700"
+}
\ No newline at end of file
diff --git a/storage/sis-coveragejson/src/test/resources/org/apache/sis/internal/coveragejson/binding/reference_system_connection.json b/storage/sis-coveragejson/src/test/resources/org/apache/sis/internal/coveragejson/binding/reference_system_connection.json
new file mode 100644
index 0000000000..a94c3f36d5
--- /dev/null
+++ b/storage/sis-coveragejson/src/test/resources/org/apache/sis/internal/coveragejson/binding/reference_system_connection.json
@@ -0,0 +1,7 @@
+{
+ "coordinates": ["y","x","z"],
+ "system": {
+ "type": "GeographicCRS",
+ "id": "http://www.opengis.net/def/crs/EPSG/0/4979"
+ }
+}
\ No newline at end of file
diff --git a/storage/sis-coveragejson/src/test/resources/org/apache/sis/internal/coveragejson/binding/temporalrs.json b/storage/sis-coveragejson/src/test/resources/org/apache/sis/internal/coveragejson/binding/temporalrs.json
new file mode 100644
index 0000000000..ed5fb370a5
--- /dev/null
+++ b/storage/sis-coveragejson/src/test/resources/org/apache/sis/internal/coveragejson/binding/temporalrs.json
@@ -0,0 +1,4 @@
+{
+ "type": "TemporalRS",
+ "calendar": "Gregorian"
+}
\ No newline at end of file
diff --git a/storage/sis-coveragejson/src/test/resources/org/apache/sis/internal/coveragejson/binding/tiledndarray.json b/storage/sis-coveragejson/src/test/resources/org/apache/sis/internal/coveragejson/binding/tiledndarray.json
new file mode 100644
index 0000000000..34dd2674da
--- /dev/null
+++ b/storage/sis-coveragejson/src/test/resources/org/apache/sis/internal/coveragejson/binding/tiledndarray.json
@@ -0,0 +1,16 @@
+{
+ "type" : "TiledNdArray",
+ "dataType": "integer",
+ "axisNames": ["t", "y", "x"],
+ "shape": [2, 5, 10],
+ "tileSets": [{
+ "tileShape": [null, null, null],
+ "urlTemplate": "http://example.com/a/all.covjson"
+ }, {
+ "tileShape": [1, null, null],
+ "urlTemplate": "http://example.com/b/{t}.covjson"
+ }, {
+ "tileShape": [null, 2, 3],
+ "urlTemplate": "http://example.com/c/{y}-{x}.covjson"
+ }]
+}
\ No newline at end of file
diff --git a/storage/sis-coveragejson/src/test/resources/org/apache/sis/internal/coveragejson/binding/verticalcrs_navd88.json b/storage/sis-coveragejson/src/test/resources/org/apache/sis/internal/coveragejson/binding/verticalcrs_navd88.json
new file mode 100644
index 0000000000..4bd091956f
--- /dev/null
+++ b/storage/sis-coveragejson/src/test/resources/org/apache/sis/internal/coveragejson/binding/verticalcrs_navd88.json
@@ -0,0 +1,4 @@
+{
+ "type": "VerticalCRS",
+ "id": "http://www.opengis.net/def/crs/EPSG/0/5703"
+}
\ No newline at end of file
diff --git a/storage/sis-coveragejson/src/test/resources/org/apache/sis/internal/coveragejson/coverage_xyzt.json b/storage/sis-coveragejson/src/test/resources/org/apache/sis/internal/coveragejson/coverage_xyzt.json
new file mode 100644
index 0000000000..332921893e
--- /dev/null
+++ b/storage/sis-coveragejson/src/test/resources/org/apache/sis/internal/coveragejson/coverage_xyzt.json
@@ -0,0 +1,58 @@
+{
+ "type" : "Coverage",
+ "domain" : {
+ "type" : "Domain",
+ "domainType" : "Grid",
+ "axes": {
+ "x" : { "values": [-10,-5,0] },
+ "y" : { "values": [40,50] },
+ "z" : { "values": [ 5] },
+ "t" : { "values": ["2010-01-01T00:12:20Z"] }
+ },
+ "referencing": [{
+ "coordinates": ["y","x","z"],
+ "system": {
+ "type": "GeographicCRS",
+ "id": "http://www.opengis.net/def/crs/EPSG/0/4979"
+ }
+ }, {
+ "coordinates": ["t"],
+ "system": {
+ "type": "TemporalRS",
+ "calendar": "Gregorian"
+ }
+ }]
+ },
+ "parameters" : {
+ "ICEC": {
+ "type" : "Parameter",
+ "description": {
+ "en": "Sea Ice concentration (ice=1;no ice=0)"
+ },
+ "unit" : {
+ "label": {
+ "en": "Ratio"
+ },
+ "symbol": {
+ "value": "1",
+ "type": "http://www.opengis.net/def/uom/UCUM/"
+ }
+ },
+ "observedProperty" : {
+ "id" : "http://vocab.nerc.ac.uk/standard_name/sea_ice_area_fraction/",
+ "label" : {
+ "en": "Sea Ice Concentration"
+ }
+ }
+ }
+ },
+ "ranges" : {
+ "ICEC" : {
+ "type" : "NdArray",
+ "dataType": "float",
+ "axisNames": ["t","z","y","x"],
+ "shape": [1, 1, 2, 3],
+ "values" : [ 0.5, 0.6, 0.4, 0.6, 0.2, null ]
+ }
+ }
+}
\ No newline at end of file