This is an automated email from the ASF dual-hosted git repository.
desruisseaux pushed a commit to branch geoapi-4.0
in repository https://gitbox.apache.org/repos/asf/sis.git
The following commit(s) were added to refs/heads/geoapi-4.0 by this push:
new 17ef8c51f8 Retrofit the pyramid system of the GeoTIFF reader into the
pyramid system of `TileMatrixSet`.
17ef8c51f8 is described below
commit 17ef8c51f83e4398012c1a0cb61d148c37232813
Author: Martin Desruisseaux <[email protected]>
AuthorDate: Mon Feb 23 00:07:52 2026 +0100
Retrofit the pyramid system of the GeoTIFF reader into the pyramid system
of `TileMatrixSet`.
---
.../org/apache/sis/coverage/grid/GridCoverage.java | 2 +-
.../coverage/MultiResolutionCoverageLoader.java | 8 +-
.../org/apache/sis/storage/geotiff/DataCube.java | 3 -
.../sis/storage/geotiff/ImageFileDirectory.java | 196 +++++++++----
.../sis/storage/geotiff/MultiResolutionImage.java | 286 -------------------
.../org/apache/sis/storage/geotiff/Reader.java | 20 +-
.../sis/storage/AbstractGridCoverageResource.java | 82 +++++-
.../org/apache/sis/storage/CoverageSubset.java | 5 +-
.../aggregate/ConcatenatedGridResource.java | 8 +-
.../apache/sis/storage/tiling/ImagePyramid.java | 18 +-
.../storage/tiling/TiledGridCoverageResource.java | 306 ++++++++++++++++-----
.../src/org.apache.sis.gui/main/module-info.java | 2 +-
.../apache/sis/gui/coverage/CoverageCanvas.java | 10 +-
.../org/apache/sis/gui/coverage/package-info.java | 2 +-
14 files changed, 503 insertions(+), 445 deletions(-)
diff --git
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridCoverage.java
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridCoverage.java
index 5f0489bd42..b72aed0a67 100644
---
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridCoverage.java
+++
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridCoverage.java
@@ -63,7 +63,7 @@ import org.opengis.coverage.CannotEvaluateException;
*/
public abstract class GridCoverage extends BandedCoverage {
/**
- * A constant for making easier to identify codes working on two
dimensional data.
+ * Number of dimensions in a two-dimensional slice of data represented as
a rendered image.
* This constant can be used for making easier to identify codes where a
two-dimensional slice is assumed.
*
* @see #render(GridExtent)
diff --git
a/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/map/coverage/MultiResolutionCoverageLoader.java
b/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/map/coverage/MultiResolutionCoverageLoader.java
index ef1276110e..6fc7eafc60 100644
---
a/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/map/coverage/MultiResolutionCoverageLoader.java
+++
b/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/map/coverage/MultiResolutionCoverageLoader.java
@@ -38,6 +38,7 @@ import org.apache.sis.math.DecimalFunctions;
import org.apache.sis.io.TableAppender;
import org.apache.sis.system.Configuration;
import org.apache.sis.pending.jdk.JDK16;
+import org.apache.sis.util.collection.BackingStoreException;
/**
@@ -124,7 +125,12 @@ public class MultiResolutionCoverageLoader {
this.resource = resource;
areaOfInterest = domain;
readRanges = range;
- double[][] resolutions =
resource.getResolutions().toArray(double[][]::new);
+ double[][] resolutions;
+ try {
+ resolutions = resource.getResolutions().toArray(double[][]::new);
+ } catch (BackingStoreException e) {
+ throw e.unwrapOrRethrow(DataStoreException.class);
+ }
if (resolutions.length <= 1) {
final GridGeometry gg = resource.getGridGeometry();
if (resolutions.length != 0) {
diff --git
a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/DataCube.java
b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/DataCube.java
index 3c408c4e70..e5a3c4ec95 100644
---
a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/DataCube.java
+++
b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/DataCube.java
@@ -99,9 +99,6 @@ abstract class DataCube extends TiledGridCoverageResource
implements StoreResour
* The namespace should be the {@linkplain #filename() filename}
* and the tip can be an image index, citation, or overview level.
*
- * <p>The returned value should never be empty. An empty value would be a
failure
- * to {@linkplain ImageFileDirectory#setOverviewIdentifier initialize
overviews}.</p>
- *
* @return a persistent identifier unique within the data store.
* @throws DataStoreException if an error occurred while computing an
identifier.
*/
diff --git
a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/ImageFileDirectory.java
b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/ImageFileDirectory.java
index 990a2d49be..ddfde1137e 100644
---
a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/ImageFileDirectory.java
+++
b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/ImageFileDirectory.java
@@ -22,6 +22,7 @@ import java.time.format.DateTimeParseException;
import java.util.List;
import java.util.Arrays;
import java.util.Optional;
+import java.util.OptionalInt;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.nio.charset.Charset;
@@ -37,11 +38,13 @@ import org.opengis.metadata.Metadata;
import org.opengis.metadata.citation.DateType;
import org.opengis.util.GenericName;
import org.opengis.util.NameSpace;
+import org.opengis.util.NameFactory;
import org.opengis.util.FactoryException;
import org.opengis.referencing.operation.TransformException;
import org.apache.sis.referencing.operation.transform.MathTransforms;
import org.apache.sis.storage.DataStoreException;
import org.apache.sis.storage.DataStoreContentException;
+import org.apache.sis.storage.DataStoreReferencingException;
import org.apache.sis.storage.geotiff.base.Tags;
import org.apache.sis.storage.geotiff.base.Resources;
import org.apache.sis.storage.geotiff.base.Predictor;
@@ -50,6 +53,7 @@ import org.apache.sis.storage.geotiff.reader.Type;
import org.apache.sis.storage.geotiff.reader.GridGeometryBuilder;
import org.apache.sis.storage.geotiff.reader.ImageMetadataBuilder;
import org.apache.sis.storage.modifier.CoverageModifier;
+import org.apache.sis.storage.tiling.TiledGridCoverageResource;
import org.apache.sis.io.stream.ChannelDataInput;
import org.apache.sis.coverage.SampleDimension;
import org.apache.sis.coverage.grid.GridGeometry;
@@ -72,7 +76,7 @@ import org.apache.sis.pending.jdk.JDK18;
/**
- * An Image File Directory (FID) in a <abbr>TIFF</abbr> image.
+ * An Image File Directory (<abbr>FID</abbr>) in a <abbr>TIFF</abbr> image.
*
* <h2>Thread-safety</h2>
* Public methods should be synchronized because they can be invoked directly
by users.
@@ -452,6 +456,11 @@ final class ImageFileDirectory extends DataCube {
*/
private ColorModel colorModel;
+ /**
+ * The overviews of this image, or {@code null} if none.
+ */
+ private Overviews overviews;
+
/**
* Creates a new image file directory.
* The index arguments is used for metadata identifier only.
@@ -485,9 +494,6 @@ final class ImageFileDirectory extends DataCube {
* If this image is an overview, then its namespace should be the name of
the base image
* and the tip should be "overview-level" where "level" is a number
starting at 1.
*
- * <p>The returned value should never be empty. An empty value would be a
- * failure to {@linkplain #setOverviewIdentifier initialize overviews}.</p>
- *
* @see #getMetadata()
*/
@Override
@@ -495,7 +501,7 @@ final class ImageFileDirectory extends DataCube {
synchronized (getSynchronizationLock()) {
if (identifier == null) {
if (isReducedResolution()) {
- // Should not happen because `setOverviewIdentifier(…)`
should have been invoked.
+ // Checked for satefy, but should never happen.
return Optional.empty();
}
GenericName name =
reader.store.createLocalName(String.valueOf(index + 1));
@@ -510,17 +516,6 @@ final class ImageFileDirectory extends DataCube {
}
}
- /**
- * Sets the identifier for an overview level. This is used only for a
pyramid.
- * The image with finest resolution is used as the namespace for all
overviews.
- *
- * @param base name of the image with finest resolution.
- * @param overview 1 for the first overview, 2 for the next one, etc.
- */
- final void setOverviewIdentifier(final NameSpace base, final int overview)
{
- identifier = reader.store.nameFactory.createLocalName(base,
"overview-" + overview);
- }
-
/**
* Adds the value read from the current position in the given stream for
the entry identified
* by the given GeoTIFF tag. This method may store the value either in a
field of this class,
@@ -1469,46 +1464,6 @@ final class ImageFileDirectory extends DataCube {
return (subfileType & NEW_SUBFILE_TYPE_REDUCED_RESOLUTION) != 0;
}
- /**
- * If this <abbr>IFD</abbr> has no grid geometry, derives this information
- * by scaling the grid geometry of the specified image at full resolution.
- * Information about bands are also copied if compatible. The scale
factors are returned,
- * with the scale of the temporal dimension defined to 1 for telling that
the time does not change.
- *
- * <h4>Conditions</h4>
- * This method should be invoked only when {@link #isReducedResolution()}
is {@code true}.
- *
- * @param fullResolution the full-resolution image.
- * @return <var>size of full resolution image</var> / <var>size of this
image</var> for each grid axis.
- */
- final double[] initReducedResolution(final ImageFileDirectory
fullResolution) throws DataStoreException, TransformException {
- final GridGeometry geometry = fullResolution.getGridGeometry();
- final GridExtent fullExtent = geometry.getExtent();
- final int dimension = fullExtent.getDimension();
- final var scales = new double[dimension];
- final var high = new long[dimension];
- for (int i=0; i<dimension; i++) {
- final long size;
- switch (i) {
- case 0: size = imageWidth; break;
- case 1: size = imageHeight; break;
- default: scales[i] = 1; continue;
- }
- scales[i] = fullExtent.getSize(i, false) / size;
- high[i] = size - 1;
- }
- if (referencing == null) {
- gridGeometry = new GridGeometry(
- geometry,
- fullExtent.reshape(null, high, true),
- MathTransforms.scale(scales));
- }
- if (samplesPerPixel == fullResolution.samplesPerPixel) {
- sampleDimensions = fullResolution.getSampleDimensions();
- }
- return scales;
- }
-
/**
* Returns the source to declare when invoking a {@link CoverageModifier}
method.
* This method returns {@code null} if the {@link #index} value would be
invalid.
@@ -2059,4 +2014,133 @@ final class ImageFileDirectory extends DataCube {
return new DataStoreContentException(reader.resources().getString(
Resources.Keys.MissingValue_2, filename(),
Tags.name(missing)));
}
+
+ /**
+ * Returns information about the overviews which form the pyramid.
+ */
+ @Override
+ protected List<Pyramid> getPyramids() throws DataStoreException {
+ return (overviews != null) ? List.of(overviews) : super.getPyramids();
+ }
+
+ /**
+ * Sets a list of overviews from finest resolution to coarsest resolution.
+ * The full-resolution image shall be {@code this} and shall not be
included in the given list.
+ */
+ final void setOverviews(final List<ImageFileDirectory> images) {
+ if (!images.isEmpty()) {
+ overviews = new Overviews(images);
+ }
+ }
+
+ /**
+ * A list of Image File Directories (FID) where the first entry is the
image at finest resolution
+ * and following entries are images at finer resolutions. The entry at
finest resolution is the
+ * enclosing {@link ImageFileDirectory}.
+ */
+ private final class Overviews implements Pyramid {
+ /**
+ * Name of the image at finest resolution.
+ * This is used as the namespace for overviews.
+ */
+ private NameSpace namespace;
+
+ /**
+ * Descriptions of all overviews in the GeoTIFF file. This array
should contain at least one element.
+ * Does not include the image at finest resolution, which is the
enclosing {@link ImageFileDirectory}.
+ */
+ private final ImageFileDirectory[] levels;
+
+ /**
+ * Creates a list of overviews from finest resolution to coarsest
resolution.
+ * The full-resolution image shall be the enclosing {@link
ImageFileDirectory}
+ * and is not included in the given list.
+ */
+ Overviews(final List<ImageFileDirectory> overviews) {
+ levels = overviews.toArray(ImageFileDirectory[]::new);
+ }
+
+ /**
+ * Returns the number of pyramid levels.
+ */
+ @Override
+ public OptionalInt numberOfLevels() {
+ return OptionalInt.of(levels.length + 1);
+ }
+
+ /**
+ * Completes and returns the image at the given pyramid level.
+ * Indices are in the same order as the images appear in the
<abbr>TIFF</abbr> file,
+ * with 0 for the full resolution image.
+ *
+ * @param level image index (level) in the pyramid, with 0 for
finest resolution.
+ * @return image at the given pyramid level, or {@code null} if the
given level is out of bounds.
+ */
+ @Override
+ public TiledGridCoverageResource forPyramidLevel(final int level)
throws DataStoreException {
+ if (level == 0) {
+ return ImageFileDirectory.this;
+ }
+ if (level > levels.length) {
+ return null;
+ }
+ synchronized (getSynchronizationLock()) {
+ final ImageFileDirectory image = levels[level - 1];
+ final Reader reader = image.reader;
+ try {
+ // Effective the first time that this method is invoked,
no-op on other invocations.
+ if (reader.resolveDeferredEntries(image)) {
+ final NameFactory nameFactory = nameFactory();
+ if (namespace == null) {
+ // Identifier should never be empty (see
`DataCube.getIdentifier()` contract).
+ namespace =
nameFactory.createNameSpace(getIdentifier().get(), null);
+ }
+ image.identifier =
nameFactory.createLocalName(namespace, identifierOfLevel(level));
+ /*
+ * Computes the grid geometry if the overview does not
already contain georeferencing information.
+ * This is computed by scaling the grid geometry of
the enclosing `ImageFileDirectory` instance,
+ * which is the image at full resolution. Information
about bands are also copied if compatible.
+ */
+ if (image.referencing == null) {
+ final GridGeometry geometry = getGridGeometry();
+ final GridExtent fullExtent = geometry.getExtent();
+ final int dimension = fullExtent.getDimension();
+ final var scales = new double[dimension];
+ final var high = new long[dimension];
+ for (int i=0; i<dimension; i++) {
+ final long size;
+ switch (i) {
+ case 0: size = image.imageWidth; break;
+ case 1: size = image.imageHeight; break;
+ default: scales[i] = 1; continue;
+ }
+ scales[i] = fullExtent.getSize(i, false) /
size;
+ high[i] = size - 1;
+ }
+ image.gridGeometry = new GridGeometry(
+ geometry,
+ fullExtent.reshape(null, high, true),
+ MathTransforms.scale(scales));
+ }
+ if (image.samplesPerPixel == samplesPerPixel) {
+ image.sampleDimensions = getSampleDimensions();
+ }
+ }
+ } catch (IOException e) {
+ throw reader.store.errorIO(e);
+ } catch (TransformException e) {
+ throw new DataStoreReferencingException(e.getMessage(), e);
+ }
+ return image;
+ }
+ }
+
+ /**
+ * Returns the name factory to use for creating identifiers of tiles
and tile matrices.
+ */
+ @Override
+ public NameFactory nameFactory() {
+ return reader.store.nameFactory;
+ }
+ }
}
diff --git
a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/MultiResolutionImage.java
b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/MultiResolutionImage.java
deleted file mode 100644
index 8de693b5a4..0000000000
---
a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/MultiResolutionImage.java
+++ /dev/null
@@ -1,286 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.sis.storage.geotiff;
-
-import java.util.List;
-import java.util.Arrays;
-import java.util.Optional;
-import java.io.IOException;
-import org.opengis.util.NameSpace;
-import org.opengis.util.FactoryException;
-import org.opengis.geometry.DirectPosition;
-import org.opengis.referencing.operation.MathTransform;
-import org.opengis.referencing.operation.TransformException;
-import org.opengis.referencing.operation.CoordinateOperation;
-import org.opengis.referencing.crs.CoordinateReferenceSystem;
-import org.apache.sis.coverage.grid.PixelInCell;
-import org.apache.sis.coverage.grid.GridExtent;
-import org.apache.sis.coverage.grid.GridGeometry;
-import org.apache.sis.coverage.grid.GridCoverage;
-import org.apache.sis.storage.GridCoverageResource;
-import org.apache.sis.storage.DataStore;
-import org.apache.sis.storage.DataStoreException;
-import org.apache.sis.storage.DataStoreReferencingException;
-import org.apache.sis.storage.base.StoreResource;
-import org.apache.sis.storage.base.GridResourceWrapper;
-import org.apache.sis.referencing.CRS;
-import org.apache.sis.referencing.internal.shared.DirectPositionView;
-import org.apache.sis.referencing.operation.CoordinateOperationContext;
-import org.apache.sis.referencing.operation.matrix.MatrixSIS;
-import static
org.apache.sis.storage.geotiff.reader.GridGeometryBuilder.BIDIMENSIONAL;
-
-
-/**
- * A list of Image File Directory (FID) where the first entry is the image at
finest resolution
- * and following entries are images at finer resolutions.
- *
- * @author Martin Desruisseaux (Geomatys)
- */
-final class MultiResolutionImage extends GridResourceWrapper implements
StoreResource {
- /**
- * Name of the image at finest resolution.
- * This is used as the namespace for overviews.
- */
- private NameSpace namespace;
-
- /**
- * Descriptions of each <i>Image File Directory</i> (IFD) in the GeoTIFF
file.
- * Should have at least 2 elements. The full-resolution image shall be at
index 0.
- */
- private final ImageFileDirectory[] levels;
-
- /**
- * Resolutions (in units of CRS axes) of each level from finest to
coarsest resolution.
- * Array elements may be {@code null} if not yet computed.
- *
- * @see #resolution(int)
- * @see #getResolutions()
- */
- private final double[][] resolutions;
-
- /**
- * The last coordinate operation returned by {@link
#getTransformFrom(CoordinateReferenceSystem)}.
- * Used as an optimization in the common case where the same
<abbr>CRS</abbr> is used for many requests.
- */
- private volatile CoordinateOperation lastOperation;
-
- /**
- * Creates a multi-resolution images with all the given reduced-resolution
(overview) images,
- * from finest resolution to coarsest resolution. The full-resolution
image shall be at index 0.
- */
- MultiResolutionImage(final List<ImageFileDirectory> overviews) {
- levels = overviews.toArray(ImageFileDirectory[]::new);
- resolutions = new double[levels.length][];
- }
-
- /**
- * Returns the data store that produced this resource.
- */
- @Override
- public final DataStore getOriginator() {
- return levels[0].getOriginator();
- }
-
- /**
- * Gets the paths to files used by this resource, or an empty value if
unknown.
- */
- @Override
- public final Optional<FileSet> getFileSet() throws DataStoreException {
- return levels[0].getFileSet();
- }
-
- /**
- * Returns the object on which to perform all synchronizations for
thread-safety.
- */
- @Override
- protected final Object getSynchronizationLock() {
- return levels[0].getSynchronizationLock();
- }
-
- /**
- * Creates the resource to which to delegate operations.
- * The source is the first image, the one having finest resolution.
- * By Cloud Optimized GeoTIFF (COG) convention, this is the image
containing metadata (CRS).
- * This method is invoked in a synchronized block when first needed and
the result is cached.
- */
- @Override
- protected GridCoverageResource createSource() throws DataStoreException {
- try {
- return getImageFileDirectory(0);
- } catch (IOException e) {
- throw levels[0].reader.store.errorIO(e);
- }
- }
-
- /**
- * Completes and returns the image at the given pyramid level.
- * Indices are in the same order as the images appear in the TIFF file,
- * with 0 for the full resolution image.
- *
- * @param index image index (level) in the pyramid, with 0 for finest
resolution.
- * @return image at the given pyramid level.
- */
- private ImageFileDirectory getImageFileDirectory(final int index) throws
IOException, DataStoreException {
- assert Thread.holdsLock(getSynchronizationLock());
- final ImageFileDirectory dir = levels[index];
- if (dir.hasDeferredEntries) {
- dir.reader.resolveDeferredEntries(dir);
- }
- if (dir.validateMandatoryTags() && index != 0) {
- if (namespace == null) {
- final ImageFileDirectory base = levels[0];
- // Identifier should never be empty (see
`DataCube.getIdentifier()` contract).
- namespace =
base.reader.store.nameFactory.createNameSpace(base.getIdentifier().get(), null);
- }
- dir.setOverviewIdentifier(namespace, index);
- }
- return dir;
- }
-
- /**
- * Returns the resolution (in units of <abbr>CRS</abbr> axes) for the
given level.
- * If there is a temporal dimension, its resolution is set to NaN because
we don't
- * know the duration.
- *
- * @param level the desired resolution level, numbered from finest to
coarsest resolution.
- * @return resolution at the specified level, not cloned (caller shall not
modify).
- */
- private double[] resolution(final int level) throws DataStoreException {
- double[] resolution = resolutions[level];
- if (resolution == null) try {
- final ImageFileDirectory image = getImageFileDirectory(level);
- final ImageFileDirectory base = getImageFileDirectory(0);
- final double[] scales = image.initReducedResolution(base);
- final GridGeometry geometry = base.getGridGeometry();
- if (geometry.isDefined(GridGeometry.GRID_TO_CRS)) {
- final GridExtent fullExtent = geometry.getExtent();
- DirectPosition poi = new
DirectPositionView.Double(fullExtent.getPointOfInterest(PixelInCell.CELL_CENTER));
- MatrixSIS gridToCRS =
MatrixSIS.castOrCopy(geometry.getGridToCRS(PixelInCell.CELL_CENTER).derivative(poi));
- resolution = gridToCRS.multiply(scales);
- } else {
- // Assume an identity transform for the `gridToCRS` of full
resolution image.
- resolution = scales;
- }
- // Set to NaN only after all matrix multiplications are done.
- int i = Math.min(BIDIMENSIONAL, resolution.length);
- Arrays.fill(scales, BIDIMENSIONAL, i, Double.NaN);
- while (--i >= 0) {
- resolution[i] = Math.abs(resolution[i]);
- }
- resolutions[level] = resolution;
- } catch (TransformException e) {
- throw new DataStoreReferencingException(e.getMessage(), e);
- } catch (IOException e) {
- throw levels[level].reader.store.errorIO(e);
- }
- return resolution;
- }
-
- /**
- * Returns the preferred resolutions (in units of CRS axes) for read
operations in this data store.
- * Elements are ordered from finest (smallest numbers) to coarsest
(largest numbers) resolution.
- */
- @Override
- public List<double[]> getResolutions() throws DataStoreException {
- final double[][] copy = new double[resolutions.length][];
- synchronized (getSynchronizationLock()) {
- for (int i=0; i<copy.length; i++) {
- copy[i] = resolution(i).clone();
- }
- }
- return Arrays.asList(copy);
- }
-
- /**
- * Returns the resolution of the given grid geometry, but in units of this
coverage <abbr>CRS</abbr>.
- *
- * @param domain the geometry from which to get the resolution.
- * @return resolution from the given grid geometry in units of this
coverage CRS, or {@code null}.
- */
- private double[] convertResolutionOf(final GridGeometry domain) throws
DataStoreException {
- if (domain == null || !domain.isDefined(GridGeometry.RESOLUTION)) {
- return null;
- }
- double[] resolution = domain.getResolution(true);
- if (domain.isDefined(GridGeometry.CRS | GridGeometry.ENVELOPE)) try {
- CoordinateOperation op = lastOperation;
- if (op == null ||
!domain.getCoordinateReferenceSystem().equals(op.getSourceCRS())) {
- /*
- * The resolution in the user-supplied domain is associated to
a CRS different than the CRS
- * of the last resolution that we computed. We must update the
operation from user-supplied
- * resolution to the units of this grid coverage.
- */
- final GridGeometry targetGrid = getGridGeometry();
- final var context = new CoordinateOperationContext();
-
targetGrid.getGeographicExtent().ifPresent(context::addAreaOfInterest);
-
targetGrid.getConstantCoordinates().ifPresent(context::setConstantCoordinates);
- op = CRS.findOperation(domain.getCoordinateMetadata(),
targetGrid.getCoordinateMetadata(), context);
- lastOperation = op;
- }
- final MathTransform domainToCoverage = op.getMathTransform();
- if (!domainToCoverage.isIdentity()) {
- /*
- * If the `domain` grid geometry has a resolution and an
envelope, then it should have
- * an extent and a "grid to CRS" transform (otherwise it may
be a `GridGeometry` bug)
- */
- DirectPosition poi = new
DirectPositionView.Double(domain.getExtent().getPointOfInterest(PixelInCell.CELL_CENTER));
- poi =
domain.getGridToCRS(PixelInCell.CELL_CENTER).transform(poi, null);
- final MatrixSIS derivative =
MatrixSIS.castOrCopy(domainToCoverage.derivative(poi));
- resolution = derivative.multiply(resolution);
- for (int i=0; i<resolution.length; i++) {
- resolution[i] = Math.abs(resolution[i]);
- }
- }
- } catch (FactoryException | TransformException e) {
- throw new DataStoreReferencingException(e.getMessage(), e);
- }
- return resolution;
- }
-
- /**
- * Loads a subset of the grid coverage represented by this resource.
- *
- * @param domain desired grid extent and resolution, or {@code null} for
reading the whole domain.
- * @param ranges 0-based indices of sample dimensions to read, or {@code
null} or an empty sequence for reading them all.
- * @return the grid coverage for the specified domain and ranges.
- * @throws DataStoreException if an error occurred while reading the grid
coverage data.
- */
- @Override
- public GridCoverage read(final GridGeometry domain, final int... ranges)
throws DataStoreException {
- final double[] request = convertResolutionOf(domain);
- int level = (request != null) ? resolutions.length : 1;
- synchronized (getSynchronizationLock()) {
-finer: while (--level > 0) {
- final double[] resolution = resolution(level);
- for (int i = Math.min(request.length, BIDIMENSIONAL); --i >=
0;) {
- if (!(request[i] >= resolution[i])) { // Use
`!` for catching NaN.
- continue finer;
- }
- }
- break;
- }
- final ImageFileDirectory image;
- try {
- image = getImageFileDirectory(level);
- } catch (IOException e) {
- throw levels[level].reader.store.errorIO(e);
- }
- image.setLoadingStrategy(getLoadingStrategy());
- return image.read(domain, ranges);
- }
- }
-}
diff --git
a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/Reader.java
b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/Reader.java
index 0eaeffe620..38fb035626 100644
---
a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/Reader.java
+++
b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/Reader.java
@@ -327,16 +327,18 @@ final class Reader extends IOBase {
}
/**
- * Reads all entries that were deferred.
+ * Reads all entries that were deferred, then verifies that the mandatory
tags
+ * are present and consistent with each others.
*
* @param dir the IFD for which to resolve deferred entries regardless
stream position or {@code ignoreAfter} value.
+ * @return {@code true} if the method has been invoked for the first time.
*/
- final void resolveDeferredEntries(final ImageFileDirectory dir) throws
IOException, DataStoreException {
+ final boolean resolveDeferredEntries(final ImageFileDirectory dir) throws
IOException, DataStoreException {
if (dir.hasDeferredEntries) {
resolveDeferredEntries(dir, Long.MAX_VALUE);
dir.hasDeferredEntries = false;
}
- dir.validateMandatoryTags();
+ return dir.validateMandatoryTags();
}
/**
@@ -428,16 +430,8 @@ final class Reader extends IOBase {
break;
}
}
- /*
- * All pyramid levels have been read. If there is only one level,
- * use the image directly. Otherwise, create the pyramid.
- */
- if (overviews.isEmpty()) {
- images.add(fullResolution);
- } else {
- overviews.add(0, fullResolution);
- images.add(new MultiResolutionImage(overviews));
- }
+ fullResolution.setOverviews(overviews);
+ images.add(fullResolution);
}
final GridCoverageResource image = images.get(index);
if (image instanceof ImageFileDirectory) {
diff --git
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/AbstractGridCoverageResource.java
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/AbstractGridCoverageResource.java
index ee56cc1167..dc7fdb593f 100644
---
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/AbstractGridCoverageResource.java
+++
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/AbstractGridCoverageResource.java
@@ -26,20 +26,28 @@ import java.math.RoundingMode;
import java.awt.image.RasterFormatException;
import org.opengis.geometry.Envelope;
import org.opengis.metadata.Metadata;
+import org.opengis.geometry.DirectPosition;
+import org.opengis.referencing.operation.CoordinateOperation;
+import org.opengis.referencing.operation.MathTransform;
import org.opengis.referencing.operation.TransformException;
import org.opengis.util.FactoryException;
-import org.apache.sis.storage.event.StoreListeners;
+import org.apache.sis.referencing.CRS;
+import org.apache.sis.referencing.internal.shared.DirectPositionView;
+import org.apache.sis.referencing.operation.CoordinateOperationContext;
+import org.apache.sis.referencing.operation.matrix.MatrixSIS;
import org.apache.sis.coverage.grid.GridGeometry;
import org.apache.sis.coverage.grid.GridExtent;
import org.apache.sis.coverage.grid.DisjointExtentException;
+import org.apache.sis.coverage.grid.PixelInCell;
+import org.apache.sis.storage.base.MetadataBuilder;
+import org.apache.sis.storage.event.StoreListeners;
+import org.apache.sis.storage.internal.Resources;
import org.apache.sis.measure.Latitude;
import org.apache.sis.measure.Longitude;
import org.apache.sis.measure.AngleFormat;
-import org.apache.sis.util.logging.PerformanceLevel;
import org.apache.sis.io.stream.IOUtilities;
+import org.apache.sis.util.logging.PerformanceLevel;
import org.apache.sis.util.internal.shared.Constants;
-import org.apache.sis.storage.base.MetadataBuilder;
-import org.apache.sis.storage.internal.Resources;
/**
@@ -60,10 +68,16 @@ import org.apache.sis.storage.internal.Resources;
* </ul>
*
* @author Martin Desruisseaux (Geomatys)
- * @version 1.5
+ * @version 1.7
* @since 1.2
*/
public abstract class AbstractGridCoverageResource extends AbstractResource
implements GridCoverageResource {
+ /**
+ * The last coordinate operation returned by {@link
#convertResolutionOf(GridGeometry)}.
+ * Used as an optimization in the common case where the same
<abbr>CRS</abbr> is used for many requests.
+ */
+ private volatile CoordinateOperation lastOperation;
+
/**
* Creates a new resource, potentially as a child of another resource.
* The parent resource is typically, but not necessarily, an {@link
Aggregate}.
@@ -124,6 +138,64 @@ public abstract class AbstractGridCoverageResource extends
AbstractResource impl
return builder.build();
}
+ /**
+ * Returns the resolution of the given grid geometry as a resolution in
units of this coverage <abbr>CRS</abbr>.
+ * If the <abbr>CRS</abbr> of the given domain is equivalent to the
<abbr>CRS</abbr> of this coverage,
+ * then the returned values are equal to {@code
domain.getResolution(true)}.
+ * Otherwise, a coordinate operation is applied.
+ *
+ * <p>This is a helper method for implementations of the {@code read(…)}
method by subclasses.
+ * The argument given to this method is typically the {@code domain}
argument given to {@code read(…)}.
+ * Subclasses can use the returned values for choosing a subsampling or a
pyramid level.</p>
+ *
+ * @param domain the geometry from which to get the resolution, or
{@code null} if unspecified.
+ * @return resolution of the given grid geometry in units of this coverage
CRS, or {@code null} if none.
+ * @throws DataStoreException if an error occurred while converting the
{@code domain} resolution.
+ *
+ * @see #read(GridGeometry, int...)
+ *
+ * @since 1.7
+ */
+ protected double[] convertResolutionOf(final GridGeometry domain) throws
DataStoreException {
+ if (domain == null || !domain.isDefined(GridGeometry.RESOLUTION)) {
+ return null;
+ }
+ double[] resolution = domain.getResolution(true);
+ if (domain.isDefined(GridGeometry.EXTENT | GridGeometry.GRID_TO_CRS |
GridGeometry.CRS)) try {
+ CoordinateOperation op = lastOperation;
+ if (op == null ||
!domain.getCoordinateReferenceSystem().equals(op.getSourceCRS())) {
+ /*
+ * The resolution in the user-supplied domain is associated to
a CRS different than the CRS
+ * of the last resolution that we computed. We must update the
operation from user-supplied
+ * resolution to the units of this grid coverage.
+ */
+ final GridGeometry targetGrid = getGridGeometry();
+ final var context = new CoordinateOperationContext();
+
targetGrid.getGeographicExtent().ifPresent(context::addAreaOfInterest);
+
targetGrid.getConstantCoordinates().ifPresent(context::setConstantCoordinates);
+ op = CRS.findOperation(domain.getCoordinateMetadata(),
targetGrid.getCoordinateMetadata(), context);
+ lastOperation = context.resultWasContextSensitive() ? null :
op;
+ }
+ final MathTransform domainToCoverage = op.getMathTransform();
+ if (!domainToCoverage.isIdentity()) {
+ /*
+ * If the `domain` grid geometry has a resolution and an
envelope, then it should have
+ * an extent and a "grid to CRS" transform (otherwise it may
be a `GridGeometry` bug)
+ */
+ DirectPosition poi = new
DirectPositionView.Double(domain.getExtent().getPointOfInterest(PixelInCell.CELL_CENTER));
+ poi =
domain.getGridToCRS(PixelInCell.CELL_CENTER).transform(poi, null);
+ final MatrixSIS derivative =
MatrixSIS.castOrCopy(domainToCoverage.derivative(poi));
+ resolution = derivative.multiply(resolution);
+ for (int i=0; i<resolution.length; i++) {
+ resolution[i] = Math.abs(resolution[i]);
+ }
+ }
+ } catch (FactoryException | TransformException e) {
+ throw new DataStoreReferencingException(e.getMessage(), e);
+ }
+ return resolution;
+ }
+
/**
* Creates an exception for a failure to load data.
* The exception sub-type is inferred from the arguments.
diff --git
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/CoverageSubset.java
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/CoverageSubset.java
index bb8fe3f878..e764690ba0 100644
---
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/CoverageSubset.java
+++
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/CoverageSubset.java
@@ -36,6 +36,7 @@ import
org.apache.sis.referencing.internal.shared.DirectPositionView;
import org.apache.sis.storage.internal.Resources;
import org.apache.sis.storage.base.MetadataBuilder;
import org.apache.sis.storage.base.StoreUtilities;
+import org.apache.sis.util.collection.BackingStoreException;
import org.apache.sis.pending.jdk.JDK16;
@@ -142,9 +143,11 @@ final class CoverageSubset extends
AbstractGridCoverageResource {
@Override
public List<double[]> getResolutions() throws DataStoreException {
List<double[]> resolutions = source.getResolutions();
- if (reduction != null) {
+ if (reduction != null) try {
JDK16.toList(resolutions.stream()
.map((resolution) -> reduction.apply(new
DirectPositionView.Double(resolution)).getCoordinates()));
+ } catch (BackingStoreException e) {
+ throw e.unwrapOrRethrow(DataStoreException.class);
}
return resolutions;
}
diff --git
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/aggregate/ConcatenatedGridResource.java
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/aggregate/ConcatenatedGridResource.java
index a8a37284c6..ab698f4b1c 100644
---
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/aggregate/ConcatenatedGridResource.java
+++
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/aggregate/ConcatenatedGridResource.java
@@ -40,6 +40,7 @@ import org.apache.sis.storage.event.StoreListeners;
import org.apache.sis.storage.base.MetadataBuilder;
import org.apache.sis.util.ArraysExt;
import org.apache.sis.util.ComparisonMode;
+import org.apache.sis.util.collection.BackingStoreException;
import org.apache.sis.util.collection.Containers;
@@ -245,7 +246,12 @@ final class ConcatenatedGridResource extends
AggregatedResource implements GridC
int count = 0;
double[][] resolutions = null;
for (final GridCoverageResource slice : sources) {
- final double[][] sr =
slice.getResolutions().toArray(double[][]::new);
+ final double[][] sr;
+ try {
+ sr = slice.getResolutions().toArray(double[][]::new);
+ } catch (BackingStoreException e) {
+ throw e.unwrapOrRethrow(DataStoreException.class);
+ }
if (sr != null) { // Should never be null,
but we are paranoiac.
if (resolutions == null) {
resolutions = sr;
diff --git
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/tiling/ImagePyramid.java
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/tiling/ImagePyramid.java
index cd1ca756c2..e89c072a44 100644
---
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/tiling/ImagePyramid.java
+++
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/tiling/ImagePyramid.java
@@ -208,7 +208,11 @@ final class ImagePyramid extends AbstractMap<GenericName,
ImageTileMatrix>
*/
@Override
public Comparator<GenericName> comparator() {
- return (GenericName o1, GenericName o2) -> indexOf(o1, false) -
indexOf(o2, false);
+ return (GenericName o1, GenericName o2) -> {
+ synchronized (matrices) {
+ return indexOf(o1, false) - indexOf(o2, false);
+ }
+ };
}
/**
@@ -379,7 +383,9 @@ final class ImagePyramid extends AbstractMap<GenericName,
ImageTileMatrix>
*/
@Override
public SortedMap<GenericName, ImageTileMatrix> headMap(GenericName toKey) {
- return subMap(lowerMatrixIndex, indexOf(toKey, true));
+ synchronized (matrices) {
+ return subMap(lowerMatrixIndex, indexOf(toKey, true));
+ }
}
/**
@@ -389,7 +395,9 @@ final class ImagePyramid extends AbstractMap<GenericName,
ImageTileMatrix>
*/
@Override
public SortedMap<GenericName, ImageTileMatrix> tailMap(GenericName
fromKey) {
- return subMap(indexOf(fromKey, true), upperMatrixIndex);
+ synchronized (matrices) {
+ return subMap(indexOf(fromKey, true), upperMatrixIndex);
+ }
}
/**
@@ -400,7 +408,9 @@ final class ImagePyramid extends AbstractMap<GenericName,
ImageTileMatrix>
*/
@Override
public SortedMap<GenericName, ImageTileMatrix> subMap(GenericName fromKey,
GenericName toKey) {
- return subMap(indexOf(fromKey, true), indexOf(toKey, true));
+ synchronized (matrices) {
+ return subMap(indexOf(fromKey, true), indexOf(toKey, true));
+ }
}
/**
diff --git
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/tiling/TiledGridCoverageResource.java
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/tiling/TiledGridCoverageResource.java
index 9ace524095..9df789e7fe 100644
---
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/tiling/TiledGridCoverageResource.java
+++
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/tiling/TiledGridCoverageResource.java
@@ -20,6 +20,8 @@ import java.util.List;
import java.util.Arrays;
import java.util.Objects;
import java.util.Collection;
+import java.util.Spliterator;
+import java.util.OptionalInt;
import java.lang.reflect.Array;
import java.awt.image.DataBuffer;
import java.awt.image.ColorModel;
@@ -54,6 +56,9 @@ import org.apache.sis.util.ArraysExt;
import org.apache.sis.util.ArgumentChecks;
import org.apache.sis.util.resources.Errors;
import org.apache.sis.util.internal.shared.Numerics;
+import org.apache.sis.util.collection.BackingStoreException;
+import org.apache.sis.util.collection.Containers;
+import org.apache.sis.util.collection.ListOfUnknownSize;
import org.apache.sis.util.collection.WeakValueHashMap;
import org.apache.sis.util.iso.DefaultNameFactory;
@@ -82,13 +87,10 @@ import org.opengis.coverage.CannotEvaluateException;
*/
public abstract class TiledGridCoverageResource extends
AbstractGridCoverageResource implements TiledResource {
/**
- * Number of dimensions in a rendered image.
- * Used for identifying codes where a two-dimensional slice is assumed.
- *
- * @see #xDimension
- * @see #yDimension
+ * Number of dimensions in a two-dimensional slice of data represented as
a rendered image.
+ * This constant can be used for making easier to identify codes where a
two-dimensional slice is assumed.
*/
- private static final int BIDIMENSIONAL = 2;
+ protected static final int BIDIMENSIONAL = 2;
/**
* A key in the {@link #rasters} cache of tiles.
@@ -160,16 +162,23 @@ public abstract class TiledGridCoverageResource extends
AbstractGridCoverageReso
/**
* The dimension of the grid which is mapped to the <var>x</var> axis
(column indexes) in rendered images.
- * The default value is 0.
+ * This value is used, directly or indirectly, at {@link Subset} creation
time. The default value is 0.
*/
private int xDimension;
/**
* The dimension of the grid which is mapped to the <var>y</var> axis (row
indexes) in rendered images.
- * The default value is 1.
+ * This value is used, directly or indirectly, at {@link Subset} creation
time. The default value is 1.
*/
private int yDimension;
+ /**
+ * The resolutions of each levels of the default pyramid. Computed when
first needed, then cached.
+ *
+ * @see #getResolutions()
+ */
+ private List<double[]> resolutions;
+
/**
* Creates a new resource.
*
@@ -180,35 +189,6 @@ public abstract class TiledGridCoverageResource extends
AbstractGridCoverageReso
yDimension = 1;
}
- /**
- * Sets the mapping from grid dimensions to image axes.
- * This method specifies the dimensions of the slices obtained
- * when {@linkplain TiledGridCoverage#readTiles reading tiles}.
- *
- * <p>If this method is never invoked, then by default
- * the dimension 0 of the grid is mapped to the image <var>x</var> axis and
- * the dimension 1 of the grid is mapped to the image <var>y</var>
axis.</p>
- *
- * @param xDimension dimension of the grid which is mapped to the
<var>x</var> axis (column indexes) in rendered images.
- * @param yDimension dimension of the grid which is mapped to the
<var>y</var> axis (row indexes) in rendered images.
- * @throws IllegalArgumentException if {@code xDimension} or {@code
yDimension} is negative, or the two values are equal.
- * @throws DataStoreException if another error occurred while setting the
mapping from grid dimensions to image axes.
- *
- * @see TiledGridCoverage#xDimension
- * @see TiledGridCoverage#yDimension
- * @see GridExtent#getSubspaceDimensions(int)
- */
- protected void setRasterSubspaceDimensions(final int xDimension, final int
yDimension) throws DataStoreException {
- final int max = getGridGeometry().getDimension() - 1;
- ArgumentChecks.ensureBetween("xDimension", 0, max, xDimension);
- ArgumentChecks.ensureBetween("yDimension", 0, max, yDimension);
- if (xDimension == yDimension) {
- throw new
IllegalArgumentException(Errors.format(Errors.Keys.IllegalArgumentValue_2,
"yDimension", "xDimension"));
- }
- this.xDimension = xDimension;
- this.yDimension = yDimension;
- }
-
/**
* Returns the size of tiles in this resource.
* The length of the returned array is the number of dimensions,
@@ -437,6 +417,95 @@ check: if (dataType.isInteger()) {
return fillValues;
}
+ /**
+ * Returns the preferred resolutions (in units of <abbr>CRS</abbr> axes)
for read operations in this data store.
+ * The list elements are ordered from finest (smallest numerical values)
to coarsest (largest numerical values).
+ *
+ * <p>The default implementation uses information in the first element
returned by {@link #getPyramids()}.
+ * It is generally easier for subclasses to override {@link
#getPyramids()} instead of this method.</p>
+ *
+ * <p>This returned list may defer the calculations of resolutions until
first requested.
+ * If a {@link DataStoreException} occurs during the invocation of a
{@link List} method,
+ * the exception will be wrapped in a {@link BackingStoreException}.</p>
+ *
+ * @return resolutions at all levels in the default pyramid.
+ * @throws DataStoreException if an error occurred while fetching the
resolution.
+ */
+ @Override
+ @SuppressWarnings("ReturnOfCollectionOrArrayField")
+ public List<double[]> getResolutions() throws DataStoreException {
+ synchronized (getSynchronizationLock()) {
+ final Pyramid pyramid = Containers.peekFirst(getPyramids());
+ if (pyramid == null) {
+ return super.getResolutions();
+ }
+ return new ListOfUnknownSize<double[]>() {
+ /** Returns characteristics of this collection as a
combination of {@code Spliterator} bits. */
+ @Override protected int characteristics() {
+ return super.characteristics() | Spliterator.NONNULL;
+ }
+
+ /** Returns the {@link #size()} value if it is already known,
or empty if the size is still unknown. */
+ @Override protected OptionalInt sizeIfKnown() {
+ return pyramid.numberOfLevels();
+ }
+
+ /** Returns {@code true} if the given index is valid for this
list. */
+ @Override protected boolean isValidIndex(final int level) {
+ try {
+ return pyramid.forPyramidLevel(level) != null;
+ } catch (DataStoreException e) {
+ throw new BackingStoreException(e);
+ }
+ }
+
+ /** Returns the element at the specified index. */
+ @Override public double[] get(final int level) {
+ try {
+ TiledGridCoverageResource c =
pyramid.forPyramidLevel(level);
+ if (c != null) return
c.getGridGeometry().getResolution(false);
+ } catch (DataStoreException e) {
+ throw new BackingStoreException(e);
+ }
+ throw new IndexOutOfBoundsException(level);
+ }
+ };
+ }
+ }
+
+ /**
+ * Returns the collection of all available tile matrix sets in this
resource.
+ * The returned collection typically contains exactly one instance,
+ * which describes a pyramid in the same <abbr>CRS</abbr> as this Grid
Coverage Resource.
+ *
+ * <p>The default implementation uses the information provided by {@link
#getPyramids()}
+ * for creating default {@link TileMatrixSet} instances.
+ * It is generally easier for subclasses to override {@link
#getPyramids()} instead of this method.</p>
+ *
+ * @return all available {@link TileMatrixSet} instances, or an empty
collection if none.
+ * @throws DataStoreException if an error occurred while fetching the tile
matrix sets.
+ */
+ @Override
+ @SuppressWarnings("ReturnOfCollectionOrArrayField") // The collection
is unmodifiable.
+ public Collection<? extends TileMatrixSet> getTileMatrixSets() throws
DataStoreException {
+ synchronized (getSynchronizationLock()) {
+ if (tileMatrixSets == null) {
+ final List<Pyramid> pyramids = getPyramids();
+ final var sets = new TileMatrixSet[pyramids.size()];
+ if (sets.length != 0) { // For avoiding an index out of
bounds in call to `get(0)`.
+ final GenericName scope = getIdentifier().orElseGet(
+ () ->
pyramids.get(0).nameFactory().createLocalName(null, listeners.getSourceName()));
+ final var processor = new GridCoverageProcessor();
+ for (int i=0; i<sets.length; i++) {
+ sets[i] = new ImagePyramid(scope, pyramids.get(i),
processor);
+ }
+ }
+ tileMatrixSets = List.of(sets);
+ }
+ return tileMatrixSets;
+ }
+ }
+
/**
* Parameters that describe the resource subset to be accepted by the
{@link TiledGridCoverage} constructor.
* Instances of this class are temporary and used only for transferring
information from {@link TiledGridCoverageResource}
@@ -774,12 +843,18 @@ check: if (dataType.isInteger()) {
/**
* Loads a subset of the grid coverage represented by this resource.
* While this method name suggests an immediate reading, the actual
reading may be deferred.
+ * This method performs the following steps:
*
- * <p>This method invokes {@link #read(Subset)} inside a block synchronized
- * on the {@linkplain #getSynchronizationLock() synchronization lock}.
- * Then, if the {@linkplain #getLoadingStrategy() current loading strategy}
- * is {@link RasterLoadingStrategy#AT_READ_TIME}, this method forces the
immediate reading of tiles.
- * and logs the time required for this operation.</p>
+ * <ol>
+ * <li>Selects a {@code TiledGridCoverageResource} instance for the
pyramid level
+ * considered the best fit for the resolution of the specified
{@code domain}.
+ * The selected instance may be {@code this}.</li>
+ * <li>Invokes the {@link #read(Subset)} method on that selected
instance inside a block
+ * synchronized on the {@linkplain #getSynchronizationLock()
synchronization lock}.</li>
+ * <li>If the {@linkplain #getLoadingStrategy() current loading
strategy} is
+ * {@link RasterLoadingStrategy#AT_READ_TIME}, forces the immediate
reading of tiles
+ * and logs the time required for this operation.</li>
+ * </ol>
*
* @param domain desired grid extent and resolution, or {@code null} for
reading the whole domain.
* @param ranges 0-based indices of sample dimensions to read, or {@code
null} or an empty sequence for reading them all.
@@ -788,6 +863,50 @@ check: if (dataType.isInteger()) {
*/
@Override
public GridCoverage read(final GridGeometry domain, final int... ranges)
throws DataStoreException {
+ TiledGridCoverageResource bestFit;
+ synchronized (getSynchronizationLock()) {
+ /*
+ * Select the pyramid which fits bet the request (taking in
account, for example, the CRS),
+ * then select the highest pyramid level (overview) with a
resolution equal or better than
+ * the requested resolution.
+ */
+ final Pyramid pyramid = choosePyramid(domain, ranges);
+ if (pyramid == null || (bestFit = pyramid.forPyramidLevel(0)) ==
null) {
+ return readAtThisPyramidLevel(domain, ranges);
+ }
+ final double[] request = bestFit.convertResolutionOf(domain);
+ if (request != null) {
+ int level = 0;
+ TiledGridCoverageResource c;
+ while ((c = pyramid.forPyramidLevel(level)) != null) {
+ final double[] resolution =
c.getGridGeometry().getResolution(true);
+ if (!(request[xDimension] >= resolution[xDimension] && //
Use `!` for catching NaN.
+ request[yDimension] >= resolution[yDimension]))
break;
+ bestFit = c;
+ level++;
+ }
+ }
+ if (bestFit == this) {
+ return readAtThisPyramidLevel(domain, ranges);
+ }
+ bestFit.xDimension = xDimension;
+ bestFit.yDimension = yDimension;
+ bestFit.loadingStrategy = loadingStrategy;
+ }
+ // Invoke outside the synchronization lock because the new lock may be
different.
+ return bestFit.readAtThisPyramidLevel(domain, ranges);
+ }
+
+ /**
+ * Implementation of {@link #read(GridGeometry, int...)} on the selected
pyramid level.
+ * This method may be invoked on the same instance as {@code read(…)} or a
different instance.
+ *
+ * @param domain desired grid extent and resolution, or {@code null} for
reading the whole domain.
+ * @param ranges 0-based indices of sample dimensions to read, or {@code
null} or an empty sequence for reading them all.
+ * @return the grid coverage for the specified domain and ranges.
+ * @throws DataStoreException if an error occurred while reading the grid
coverage data.
+ */
+ private GridCoverage readAtThisPyramidLevel(final GridGeometry domain,
final int... ranges) throws DataStoreException {
final TiledGridCoverage coverage;
final GridCoverage loaded;
final boolean preload;
@@ -853,6 +972,24 @@ check: if (dataType.isInteger()) {
}
}
+ /**
+ * Chooses the pyramid to use for reading the specified subset from this
resource.
+ * This method should return an element of the list returned by {@link
#getPyramids()}.
+ * The chosen pyramid should be a best match, but does not need to be an
exact match.
+ *
+ * <p>The current implementation returns the first pyramid returned by
{@link #getPyramids()}.
+ * Future versions of Apache <abbr>SIS</abbr> may improve this algorithm
for taking in account
+ * at least the <abbr>CRS</abbr>.</p>
+ *
+ * @param domain desired grid extent and resolution, or {@code null} for
reading the whole domain.
+ * @param ranges 0-based indices of sample dimensions to read, or {@code
null} or an empty sequence for reading them all.
+ * @return the pyramid to use, or {@code null} if no pyramid can satisfy
the given request.
+ * @throws DataStoreException if an error occurred while reading the grid
coverage data.
+ */
+ protected Pyramid choosePyramid(final GridGeometry domain, final int[]
ranges) throws DataStoreException {
+ return Containers.peekFirst(getPyramids()); // See javadoc about
possible change in future SIS version.
+ }
+
/**
* Whether this resource supports immediate loading of raster data.
* Current implementation does not support immediate loading if the data
cube has more than 2 dimensions.
@@ -910,45 +1047,63 @@ check: if (dataType.isInteger()) {
}
/**
- * Returns the collection of all available tile matrix sets in this
resource.
- * The returned collection typically contains exactly one instance.
+ * Sets the mapping from grid dimensions to image axes.
+ * This method specifies the dimensions of the slices obtained
+ * when {@linkplain TiledGridCoverage#readTiles reading tiles}.
+ * The values specified to this method are used, directly or indirectly,
at {@link Subset} creation time.
+ * Therefore, calls to this method have an effect on the next {@link
TiledGridCoverage} instances to be read,
+ * but not on the instances that are already read.
*
- * <p>The default implementation uses the information provided by {@link
#getPyramids()}
- * for creating default {@link TileMatrixSet} instances.
- * It is generally easier for subclasses to override {@link
#getPyramids()} instead of this method.</p>
+ * <p>If this method is never invoked, then by default
+ * the dimension 0 of the grid is mapped to the image <var>x</var> axis and
+ * the dimension 1 of the grid is mapped to the image <var>y</var>
axis.</p>
*
- * @return all available {@link TileMatrixSet} instances, or an empty
collection if none.
- * @throws DataStoreException if an error occurred while fetching the tile
matrix sets.
+ * @param xDimension dimension of the grid which is mapped to the
<var>x</var> axis (column indexes) in rendered images.
+ * @param yDimension dimension of the grid which is mapped to the
<var>y</var> axis (row indexes) in rendered images.
+ * @throws IllegalArgumentException if {@code xDimension} or {@code
yDimension} is negative, or the two values are equal.
+ * @throws DataStoreException if another error occurred while setting the
mapping from grid dimensions to image axes.
+ *
+ * @see TiledGridCoverage#xDimension
+ * @see TiledGridCoverage#yDimension
+ * @see GridExtent#getSubspaceDimensions(int)
*/
- @Override
- @SuppressWarnings("ReturnOfCollectionOrArrayField") // The collection
is unmodifiable.
- public Collection<? extends TileMatrixSet> getTileMatrixSets() throws
DataStoreException {
- synchronized (getSynchronizationLock()) {
- if (tileMatrixSets == null) {
- final List<Pyramid> pyramids = getPyramids();
- final var sets = new TileMatrixSet[pyramids.size()];
- final GenericName scope = getIdentifier().orElseGet(
- () ->
pyramids.get(0).nameFactory().createLocalName(null, listeners.getSourceName()));
- final var processor = new GridCoverageProcessor();
- for (int i=0; i<sets.length; i++) {
- sets[i] = new ImagePyramid(scope, pyramids.get(i),
processor);
- }
- tileMatrixSets = List.of(sets);
- }
- return tileMatrixSets;
+ protected void setRasterSubspaceDimensions(final int xDimension, final int
yDimension) throws DataStoreException {
+ final int max = getGridGeometry().getDimension() - 1;
+ ArgumentChecks.ensureBetween("xDimension", 0, max, xDimension);
+ ArgumentChecks.ensureBetween("yDimension", 0, max, yDimension);
+ if (xDimension == yDimension) {
+ throw new
IllegalArgumentException(Errors.format(Errors.Keys.IllegalArgumentValue_2,
"yDimension", "xDimension"));
}
+ this.xDimension = xDimension;
+ this.yDimension = yDimension;
}
/**
* Returns information about the {@code TileMatrixSet} instances to create.
- * This method is invoked by the default implementation of {@link
#getTileMatrixSets()} when first needed.
+ * The first element in the returned list <em>shall</em> be the default
pyramid
+ * using the same Coordinate Reference System (<abbr>CRS</abbr>) as this
Grid Coverage Resource.
+ * Other elements, if any, can use any <abbr>CRS</abbr>.
+ *
+ * <p>This method is invoked by the default implementation of {@link
#getTileMatrixSets()} when first needed.
* By default, this method returns a list of only one element, which
itself describes a pyramid of only one level.
- * This single level describes a {@link TileMatrix} at the resolution of
this {@code TiledGridCoverageResource}.
+ * This single level describes a {@link TileMatrix} at the resolution of
this {@code TiledGridCoverageResource}.</p>
*
* @return information about the tile matrix sets to create.
+ * @throws DataStoreException if an error occurred while fetching
information about the pyramid.
+ *
+ * @see #getResolutions()
+ * @see #getTileMatrixSets()
*/
- protected List<Pyramid> getPyramids() {
- return List.of((level) -> (level == 0) ? this : null);
+ protected List<Pyramid> getPyramids() throws DataStoreException {
+ if (!getGridGeometry().isDefined(GridGeometry.EXTENT |
GridGeometry.GRID_TO_CRS | GridGeometry.RESOLUTION)) {
+ return List.of();
+ }
+ return List.of(new Pyramid() {
+ @Override public OptionalInt numberOfLevels() {return
OptionalInt.of(1);}
+ @Override public TiledGridCoverageResource forPyramidLevel(int
level) {
+ return (level == 0) ? TiledGridCoverageResource.this : null;
+ }
+ });
}
/**
@@ -1013,6 +1168,19 @@ check: if (dataType.isInteger()) {
return Integer.parseInt(identifier.substring(1));
}
+ /**
+ * Returns the number of pyramid levels if this information is known.
+ * The returned value is empty if computing the number of levels is
costly.
+ * For iterations over pyramid levels, it is generally preferable to
invoke
+ * {@link #forPyramidLevel(int)} with increasing {@code level} values
until
+ * that method returns {@code null}.
+ *
+ * @return the number of pyramid levels if this information is known.
+ */
+ default OptionalInt numberOfLevels() {
+ return OptionalInt.empty();
+ }
+
/**
* Returns a resource for the same data as this resource but at a
different resolution level.
* The resource at index 0 shall be the resource with the finest
resolution, and resources at
diff --git a/optional/src/org.apache.sis.gui/main/module-info.java
b/optional/src/org.apache.sis.gui/main/module-info.java
index ace019036c..576d9f230b 100644
--- a/optional/src/org.apache.sis.gui/main/module-info.java
+++ b/optional/src/org.apache.sis.gui/main/module-info.java
@@ -21,7 +21,7 @@
* @author Smaniotto Enzo (GSoC)
* @author Johann Sorel (Geomatys)
* @author Martin Desruisseaux (Geomatys)
- * @version 1.4
+ * @version 1.7
* @since 1.1
*/
module org.apache.sis.gui {
diff --git
a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/coverage/CoverageCanvas.java
b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/coverage/CoverageCanvas.java
index 00f489164a..bb8983ebfc 100644
---
a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/coverage/CoverageCanvas.java
+++
b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/coverage/CoverageCanvas.java
@@ -65,6 +65,7 @@ import org.apache.sis.geometry.Shapes2D;
import org.apache.sis.image.Colorizer;
import org.apache.sis.image.PlanarImage;
import org.apache.sis.image.Interpolation;
+import org.apache.sis.storage.DataStoreException;
import org.apache.sis.storage.GridCoverageResource;
import org.apache.sis.gui.map.MapCanvas;
import org.apache.sis.gui.map.MapCanvasAWT;
@@ -80,6 +81,7 @@ import org.apache.sis.util.ArraysExt;
import org.apache.sis.util.Debug;
import org.apache.sis.util.Exceptions;
import org.apache.sis.util.logging.Logging;
+import org.apache.sis.util.collection.BackingStoreException;
import org.apache.sis.io.TableAppender;
import org.apache.sis.measure.Units;
import static org.apache.sis.gui.internal.LogHandler.LOGGER;
@@ -91,7 +93,7 @@ import static org.apache.sis.gui.internal.LogHandler.LOGGER;
* instance (given by {@link #coverageProperty}) will change automatically
according the zoom level.
*
* @author Martin Desruisseaux (Geomatys)
- * @version 1.6
+ * @version 1.7
*
* @see CoverageExplorer
*
@@ -608,10 +610,12 @@ public class CoverageCanvas extends MapCanvasAWT {
domain = coverage.getGridGeometry();
ranges = coverage.getSampleDimensions();
scales = null;
- } else {
+ } else try {
domain = resource.getGridGeometry();
ranges = resource.getSampleDimensions();
scales = lastNonNull(resource.getResolutions());
+ } catch (BackingStoreException e) {
+ throw e.unwrapOrRethrow(DataStoreException.class);
}
if (domain != null) {
/*
@@ -640,7 +644,7 @@ public class CoverageCanvas extends MapCanvasAWT {
final Envelope bounds = domain.getEnvelope();
final int dimension = Math.min(BIDIMENSIONAL,
Math.min(bounds.getDimension(), scales.length));
for (int i=0; i<dimension; i++) {
- ratio *= scales[i] / bounds.getSpan(i);
// Equivalent to /= span_in_pixels.
+ ratio *= scales[i] / bounds.getSpan(i);
// Equivalent to `ratio /= span_in_pixels`.
}
if (ratio < 1) {
ratio = Math.pow(ratio, 1d / dimension);
diff --git
a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/coverage/package-info.java
b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/coverage/package-info.java
index 81a972417e..5d49adb5af 100644
---
a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/coverage/package-info.java
+++
b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/coverage/package-info.java
@@ -19,7 +19,7 @@
* Widgets showing {@link org.apache.sis.coverage.grid.GridCoverage} images or
sample values.
*
* @author Martin Desruisseaux (Geomatys)
- * @version 1.6
+ * @version 1.7
* @since 1.1
*/
package org.apache.sis.gui.coverage;