This is an automated email from the ASF dual-hosted git repository. asf-gitbox-commits pushed a commit to branch geoapi-4.0 in repository https://gitbox.apache.org/repos/asf/sis.git
commit cc658331f72db20c8b46a795022bbb646771214b Author: Martin Desruisseaux <[email protected]> AuthorDate: Wed Jul 8 14:21:58 2026 +0200 Consolidate the use of default resolution in a new `GridGeometry.defaultToGridCRS(Identifier)` method. The goal is to handle missing "grid to CRS" transform in a way which is consistent between TIFF and HEIF. --- .../apache/sis/coverage/grid/GridDerivation.java | 12 +++--- .../org/apache/sis/coverage/grid/GridGeometry.java | 45 ++++++++++++++++++++++ .../grid/IncompleteGridGeometryException.java | 8 +++- .../sis/storage/geotiff/ImageFileDirectory.java | 28 ++------------ .../apache/sis/storage/geoheif/ImageResource.java | 6 ++- .../org/apache/sis/storage/geoheif/Pyramid.java | 25 +----------- 6 files changed, 67 insertions(+), 57 deletions(-) diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridDerivation.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridDerivation.java index eb36c478f8..9babcebf44 100644 --- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridDerivation.java +++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridDerivation.java @@ -547,12 +547,12 @@ public class GridDerivation { if (areaOfInterest.extent == null && areaOfInterest.envelope == null && base.gridToCRS == null) { /* * Case when `areaOfInterest` specifies only a resolution (typically in a pyramid) - * and we cannot use that resolution by a concatenation of "grid to CRS" transforms. - * The given resolution is relative to the base grid which was used for deriving `areaOfInterest`. - * We don't really know if it was the same base as `this.base`, but there is good chances that the - * fact that `base.gridToCRS` is null is the reason why `areaOfInterest` has no information other - * than resolution. On the assumption that both bases are the same, we need to divide the specified - * resolution by `base.resolution` in order to get subsampling factors. + * and we cannot use that resolution in a concatenation of "grid to CRS" transforms. + * + * Assume that the given resolution is relative to `base`. We don't really know if this is true, + * but the fact that `base.gridToCRS` is null is probably the reason why `areaOfInterest` has no + * information other than the resolution. On the assumption that both bases are the same, we need + * to divide the specified resolution by `base.resolution` in order to get subsampling factors. */ scales = areaOfInterest.getResolution(true); final double[] resolution = base.resolution; diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridGeometry.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridGeometry.java index 81eaa83f18..92660fc262 100644 --- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridGeometry.java +++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridGeometry.java @@ -1589,6 +1589,7 @@ public class GridGeometry implements LenientComparable, Serializable { * @see #getExtent() * @see #getResolution(boolean) * @see #getGridToCRS(PixelInCell) + * @see #defaultToGridCRS(Identifier) */ public boolean isDefined(final int bitmask) { if ((bitmask & ~(CRS | ENVELOPE | EXTENT | GRID_TO_CRS | ORIGIN | RESOLUTION | GEOGRAPHIC_EXTENT | TEMPORAL_EXTENT)) != 0) { @@ -1839,6 +1840,50 @@ public class GridGeometry implements LenientComparable, Serializable { return this; } + /** + * Returns this grid geometry with missing properties defaulting to the properties of a grid <abbr>CRS</abbr>. + * This method usually returns {@code this} unchanged, except in the following rare circumstance: + * if this grid geometry has no {@linkplain #getResolution(boolean) resolution}, no "grid to <abbr>CRS</abbr>" + * transform and no <abbr>CRS</abbr>, then this method returns a new grid geometry with the resolution set to 1, + * <i>i.e.</i> the resolution is defined as one unit of grid cell. + * With such default, the <abbr>CRS</abbr> become implicitly the <abbr>CRS</abbr> of the grid. + * If {@code datum} is non-null, then this implicit assumption is made explicit + * by setting the <abbr>CRS</abbr> to the value returned by {@link GridExtent#createGridCRS(Identified)} and + * by setting "grid to <abbr>CRS</abbr>" to the identity transform. + * If {@code datum} is null, then the <abbr>CRS</abbr> and "grid to <abbr>CRS</abbr>" properties stay undefined. + * + * <h4>Usage in context of image pyramids</h4> + * The "grid to <abbr>CRS</abbr>" information is sometime missing, for example because a file + * is an ordinary <abbr>TIFF</abbr> file instead of GeoTIFF, or because of encoding error. + * Missing "grid to <abbr>CRS</abbr>" information usually causes missing grid resolution, + * since the latter is derived from the former. + * However, the {@inkplain org.apache.sis.storage.tiling tiling} package needs a resolution + * at each pyramid level, even if the exact "grid to <abbr>CRS</abbr>" transform is unknown. + * This method can be used for setting a default resolution to the base level + * (the level with the finest resolution) before to derive the grid geometry of other levels. + * + * @param datum name of the engineering datum, or {@code null} for not creating a <abbr>CRS</abbr>. + * @return a grid geometry with a resolution if possible. + * + * @since 1.7 + */ + public GridGeometry defaultToGridCRS(final Identifier datum) { + if (resolution != null || gridToCRS != null || getCoordinateReferenceSystem(envelope) != null) { + return this; + } + final double[] newResolution = new double[getDimension()]; + Arrays.fill(newResolution, 1); + MathTransform tr = null; + ImmutableEnvelope env = envelope; + if (env == null && datum != null && extent != null) { + final GeneralEnvelope t = extent.toEnvelope(false); + t.setCoordinateReferenceSystem(extent.createGridCRS(datum)); + env = new ImmutableEnvelope(t); + tr = MathTransforms.identity(env.getDimension()); + } + return new GridGeometry(extent, tr, tr, env, newResolution, 0); + } + /** * Creates a one-, two- or three-dimensional coordinate reference system for cell indices in the grid. * This method returns a CRS which is derived from the "real world" CRS or a subset of it. diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/IncompleteGridGeometryException.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/IncompleteGridGeometryException.java index d99bd7d7b4..66a0c46d60 100644 --- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/IncompleteGridGeometryException.java +++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/IncompleteGridGeometryException.java @@ -16,6 +16,8 @@ */ package org.apache.sis.coverage.grid; +import org.opengis.metadata.Identifier; + /** * Thrown by {@link GridGeometry} when a grid geometry cannot provide the requested information. @@ -36,7 +38,11 @@ package org.apache.sis.coverage.grid; * * @author Martin Desruisseaux (IRD, Geomatys) * @version 1.0 - * @since 1.0 + * + * @see GridGeometry#isDefined(int) + * @see GridGeometrydefaultToGridCRS(Identifier) + * + * @since 1.0 */ public class IncompleteGridGeometryException extends IllegalStateException { /** 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 a5647855a6..b3063a1a72 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 @@ -1498,10 +1498,10 @@ final class ImageFileDirectory extends DataCube { if (source != null) { domain = reader.store.customizer.customize(source, domain); } - gridGeometry = domain; if (overviews != null) { - ensureResolutionExists(); + domain = domain.defaultToGridCRS(null); } + gridGeometry = domain; } return domain; } @@ -2037,31 +2037,11 @@ final class ImageFileDirectory extends DataCube { if (!images.isEmpty()) { overviews = new Overviews(images); if (gridGeometry != null) { - ensureResolutionExists(); + gridGeometry = gridGeometry.defaultToGridCRS(null); } } } - /** - * Ensures that the grid geometry declares a resolution. - * This method should be invoked only when {@link #gridGeometry} <strong>and</strong> - * {@link #overviews} are non-null, i.e. when we determined that a pyramid exists. - * The pyramid system of Apache <abbr>SIS</abbr> needs a resolution in all levels. - * Therefore, if the base level has no resolution, we need to add an arbitrary one. - * A resolution is missing when there is no "grid to <abbr>CRS</abbr>" transform. - * Arbitrary resolution values are okay when there is no <abbr>CRS</abbr>. - * The arbitrary value is 1, which means that resolutions are in units of pixels - * of the base level. - */ - private void ensureResolutionExists() throws DataStoreException { - if (!gridGeometry.isDefined(GridGeometry.RESOLUTION) && !gridGeometry.isDefined(GridGeometry.CRS)) try { - gridGeometry = new GridGeometry(gridGeometry, gridGeometry.getExtent(), - MathTransforms.identity(gridGeometry.getDimension())); - } catch (TransformException e) { - throw new DataStoreReferencingException(e); // Should never happen. - } - } - /** * A list of Image File Directories (<abbr>FID</abbr>) where the first element is the image at the coarsest * resolution and next elements are images at finer resolutions. The element at the finest resolution is the @@ -2157,7 +2137,7 @@ final class ImageFileDirectory extends DataCube { case 1: size = image.imageHeight; break; default: scales[i] = 1; continue; } - scales[i] = fullExtent.getSize(i, false) / size; + scales[i] = Numerics.divide(fullExtent.getSize(i), size); high[i] = size - 1; } image.gridGeometry = new GridGeometry( diff --git a/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/geoheif/ImageResource.java b/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/geoheif/ImageResource.java index 2e205ae0a0..0a293ed803 100644 --- a/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/geoheif/ImageResource.java +++ b/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/geoheif/ImageResource.java @@ -37,6 +37,7 @@ import org.opengis.metadata.Metadata; import org.opengis.util.GenericName; import org.opengis.referencing.operation.TransformException; import org.apache.sis.referencing.operation.transform.MathTransforms; +import org.apache.sis.referencing.operation.transform.LinearTransform; import org.apache.sis.coverage.SampleDimension; import org.apache.sis.coverage.grid.GridExtent; import org.apache.sis.coverage.grid.GridGeometry; @@ -194,9 +195,10 @@ final class ImageResource extends TiledGridCoverageResource implements StoreReso final GridExtent baseExtent = base.getExtent(); final var factors = new double[baseExtent.getDimension()]; for (int i = 0; i < factors.length; i++) { - factors[i] = 1 / Numerics.divide(levelExtent.getSize(i), baseExtent.getSize(i)); + factors[i] = Numerics.divide(baseExtent.getSize(i), levelExtent.getSize(i)); } - gridGeometry = new GridGeometry(base, levelExtent, MathTransforms.scale(factors)); + final LinearTransform toLevel = MathTransforms.scale(factors); + gridGeometry = toLevel.isIdentity() ? base : new GridGeometry(base, levelExtent, toLevel); } } diff --git a/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/geoheif/Pyramid.java b/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/geoheif/Pyramid.java index 234dcbb2e9..8596c14d7c 100644 --- a/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/geoheif/Pyramid.java +++ b/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/geoheif/Pyramid.java @@ -88,32 +88,9 @@ final class Pyramid extends TiledGridCoverageResource implements TiledGridCovera tileSizeY = pyramid.tileSizeY; this.levels = levels; Arrays.sort(levels, LEVEL_COMPARATOR); - /* - * Select the finest level for which the "grid to CRS" transform, and therefore the resolution, - * is defined. If none, select the very fist level, which should have the finest resolution. - */ - GridGeometry base = null; - boolean updateBase = true; - for (final ImageResource level : levels) { - final GridGeometry grid = level.getGridGeometry(); - if (grid.isDefined(GridGeometry.GRID_TO_CRS)) { - updateBase = false; // Base is already complete. - base = grid; - break; - } else if (base == null) { - base = grid; - } - } - /* - * Now compute the "grid to CRS" transform for each pyramid level. - * It may include the base level itself if the transform was not specified anywhere. - */ + final GridGeometry base = levels[0].getGridGeometry().defaultToGridCRS(null); for (ImageResource level : levels) { level.setPyramidLevelOf(base); - if (updateBase) { - updateBase = false; - base = level.getGridGeometry(); - } } }
