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
commit cf73da5dd67af0042537b8e4e2fa4874b3f29975 Author: Martin Desruisseaux <martin.desruisse...@geomatys.com> AuthorDate: Tue Sep 13 18:45:16 2022 +0200 Allow deferred loading of slice in a concatenated coverage. --- .../org/apache/sis/internal/storage/Resources.java | 5 + .../sis/internal/storage/Resources.properties | 1 + .../sis/internal/storage/Resources_fr.properties | 3 +- .../aggregate/ConcatenatedGridCoverage.java | 123 ++++++++++++++----- .../aggregate/ConcatenatedGridResource.java | 133 +++++++++++++++++---- 5 files changed, 213 insertions(+), 52 deletions(-) diff --git a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources.java b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources.java index 5aabcbf31d..5934ebb244 100644 --- a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources.java +++ b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources.java @@ -124,6 +124,11 @@ public final class Resources extends IndexedResourceBundle { */ public static final short CanNotReadPixel_3 = 68; + /** + * Can not read slice at index {0}. + */ + public static final short CanNotReadSlice_1 = 78; + /** * Can not remove resource “{1}” from aggregate “{0}”. */ diff --git a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources.properties b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources.properties index 662c15ae1b..c561b2bb2f 100644 --- a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources.properties +++ b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources.properties @@ -32,6 +32,7 @@ CanNotReadFile_2 = Can not read \u201c{1}\u201d as a file in th CanNotReadFile_3 = Can not read line {2} of \u201c{1}\u201d as part of a file in the {0} format. CanNotReadFile_4 = Can not read after column {3} of line {2} of \u201c{1}\u201d as part of a file in the {0} format. CanNotReadPixel_3 = Can not read pixel at ({0}, {1}) indices in the \u201c{2}\u201d file. +CanNotReadSlice_1 = Can not read slice at index {0}. CanNotRemoveResource_2 = Can not remove resource \u201c{1}\u201d from aggregate \u201c{0}\u201d. CanNotRenderImage_1 = Can not render an image for the \u201c{0}\u201d coverage. CanNotStoreResourceType_2 = Can not save resources of type \u2018{1}\u2019 in a \u201c{0}\u201d store. diff --git a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources_fr.properties b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources_fr.properties index 2d3a34bdc5..6c8d3708e5 100644 --- a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources_fr.properties +++ b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources_fr.properties @@ -37,6 +37,7 @@ CanNotReadFile_2 = Ne peut pas lire \u00ab\u202f{1}\u202f\u00bb CanNotReadFile_3 = Ne peut pas lire la ligne {2} de \u00ab\u202f{1}\u202f\u00bb comme une partie d\u2019un fichier au format {0}. CanNotReadFile_4 = Ne peut pas lire apr\u00e8s la colonne {3} de la ligne {2} de \u00ab\u202f{1}\u202f\u00bb comme une partie d\u2019un fichier au format {0}. CanNotReadPixel_3 = Ne peut pas lire le pixel aux indices ({0}, {1}) dans le fichier \u00ab\u202f{2}\u202f\u00bb. +CanNotReadSlice_1 = Ne peut pas lire la tranche \u00e0 l\u2019index {0}. CanNotRemoveResource_2 = Ne peut pas supprimer la ressource \u00ab\u202f{1}\u202f\u00bb de l\u2019agr\u00e9gat \u00ab\u202f{0}\u202f\u00bb. CanNotRenderImage_1 = Ne peut pas produire une image pour la couverture de donn\u00e9es \u00ab\u202f{0}\u202f\u00bb. CanNotWriteResource_1 = Ne peut pas \u00e9crire la ressource \u00ab\u202f{0}\u202f\u00bb. @@ -70,7 +71,7 @@ IncompatibleGridGeometry = Toutes les couvertures de donn\u00e9es doive InvalidExpression_2 = Expression \u00ab\u202f{1}\u202f\u00bb invalide ou non-support\u00e9e \u00e0 l\u2019index {0}. InvalidSampleDimensionIndex_2 = L\u2019index de dimension d\u2019\u00e9chantillonnage {1} est invalide. On attendait un index de 0 \u00e0 {0} inclusif. InconsistentNameComponents_2 = Les \u00e9l\u00e9ments qui composent le nom \u00ab\u202f{1}\u202f\u00bb ne sont pas coh\u00e9rents avec ceux du nom qui avait \u00e9t\u00e9 pr\u00e9c\u00e9demment li\u00e9 dans les donn\u00e9es de \u00ab\u202f{0}\u202f\u00bb. -ExceptionInListener_1 = Une exception est survenue dans un capteur d'\u00e9v\u00e9nements de type \u2018{0}\u2019. +ExceptionInListener_1 = Une exception est survenue dans un capteur d\u2019\u00e9v\u00e9nements de type \u2018{0}\u2019. LoadedGridCoverage_6 = Lecture d\u2019une couverture de donn\u00e9es entre {1} \u2013 {2} et {3} \u2013 {4} \u00e0 partir du fichier \u00ab\u202f{0}\u202f\u00bb en {5} secondes. MarkNotSupported_1 = Les marques ne sont pas support\u00e9es sur le flux \u00ab\u202f{0}\u202f\u00bb. MissingResourceIdentifier_1 = La ressource \u00ab\u202f{0}\u202f\u00bb n\u2019a pas d\u2019identifiant. diff --git a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/aggregate/ConcatenatedGridCoverage.java b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/aggregate/ConcatenatedGridCoverage.java index 44faa761f6..1a5a8d280f 100644 --- a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/aggregate/ConcatenatedGridCoverage.java +++ b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/aggregate/ConcatenatedGridCoverage.java @@ -16,11 +16,20 @@ */ package org.apache.sis.internal.storage.aggregate; +import java.util.List; +import java.util.ArrayList; import java.awt.image.RenderedImage; +import org.apache.sis.coverage.SampleDimension; 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.coverage.SubspaceNotSpecifiedException; +import org.apache.sis.storage.GridCoverageResource; +import org.apache.sis.storage.DataStoreException; +import org.apache.sis.internal.storage.Resources; + +// Branch-dependent imports +import org.opengis.coverage.CannotEvaluateException; /** @@ -37,9 +46,35 @@ final class ConcatenatedGridCoverage extends GridCoverage { * The slices of this coverage, in the same order than {@link #coordinatesOfSlices}. * Each slice is not necessarily 1 cell tick; larger slices are accepted. * The length of this array shall be at least 2. + * + * <p>Some elements in the array may be {@code null} if the coverage are lazily loaded.</p> */ private final GridCoverage[] slices; + /** + * The resource from which load the coverages in the {@link #slices} array, or {@code null} if none. + * This is non-null only if the {@linkplain #slices} are lazily loaded. + */ + private final GridCoverageResource[] resources; + + /** + * The domain to request when reading a coverage from the resource. + * This is non-null only if the {@linkplain #slices} are lazily loaded. + */ + private final GridGeometry request; + + /** + * The sample dimensions to request when loading slices from the {@linkplain #resources}. + * This is non-null only if the {@linkplain #slices} are lazily loaded. + */ + private final int[] ranges; + + /** + * Whether this grid coverage should be considered as converted. + * This is used only if the {@linkplain #slices} are lazily loaded. + */ + private final boolean isConverted; + /** * The object for identifying indices in the {@link #slices} array. */ @@ -50,36 +85,46 @@ final class ConcatenatedGridCoverage extends GridCoverage { */ private final int startAt; - /** - * View over this grid coverage after conversion of sample values, or {@code null} if not yet created. - * May be {@code this} if we determined that there is no conversion or the conversion is identity. - * - * @see #forConvertedValues(boolean) - */ - private transient ConcatenatedGridCoverage convertedView; - /** * Creates a new aggregated coverage. + * + * @param source the concatenated resource which is creating this coverage. + * @param domain domain of the coverage to create. + * @param request grid geometry to request when loading data. Used only if {@code resources} is non-null. + * @param slices grid coverages for each slice. May contain {@code null} elements is lazy loading is applied. + * @param resources resources from which to load grid coverages, or {@code null} if none. + * @param startAt index of the first slice in {@link #locator}. + * @param ranges bands to request when loading coverages. Used only if {@code resources} is non-null. */ - ConcatenatedGridCoverage(final ConcatenatedGridResource source, final GridGeometry domain, - final GridCoverage[] slices, final int startAt) + ConcatenatedGridCoverage(final ConcatenatedGridResource source, final GridGeometry domain, final GridGeometry request, + final GridCoverage[] slices, final GridCoverageResource[] resources, final int startAt, + final int[] ranges) { super(domain, source.getSampleDimensions()); - this.slices = slices; - this.startAt = startAt; - this.locator = source.locator; + this.slices = slices; + this.resources = resources; + this.startAt = startAt; + this.request = request; + this.ranges = ranges; + this.isConverted = source.isConverted; + this.locator = source.locator; } /** - * Creates a new aggregated coverage for the result of a conversion from/to package values. + * Creates a new aggregated coverage for the result of a conversion from/to packed values. * This constructor assumes that all slices use the same sample dimensions. */ - private ConcatenatedGridCoverage(final ConcatenatedGridCoverage source, final GridCoverage[] slices) { - super(source.getGridGeometry(), slices[0].getSampleDimensions()); - this.slices = slices; - this.startAt = source.startAt; - this.locator = source.locator; - convertedView = source; + private ConcatenatedGridCoverage(final ConcatenatedGridCoverage source, final GridCoverage[] slices, + final List<SampleDimension> sampleDimensions, final boolean converted) + { + super(source.getGridGeometry(), sampleDimensions); + this.slices = slices; + this.resources = source.resources; + this.startAt = source.startAt; + this.request = source.request; + this.ranges = source.ranges; + this.locator = source.locator; + this.isConverted = converted; } /** @@ -92,17 +137,30 @@ final class ConcatenatedGridCoverage extends GridCoverage { * @return a coverage containing requested values. May be {@code this} but never {@code null}. */ @Override - public synchronized GridCoverage forConvertedValues(final boolean converted) { - if (convertedView == null) { - boolean changed = false; - final GridCoverage[] c = new GridCoverage[slices.length]; - for (int i=0; i<c.length; i++) { - final GridCoverage source = slices[i]; + protected GridCoverage createConvertedValues(final boolean converted) { + boolean changed = false; + int template = -1; // Index of a grid coverage to use as a template. + final GridCoverage[] c = new GridCoverage[slices.length]; + for (int i=0; i<c.length; i++) { + final GridCoverage source = slices[i]; + if (source != null) { changed |= (c[i] = source.forConvertedValues(converted)) != source; + template = i; + } else { + changed |= (converted != isConverted); } - convertedView = changed ? new ConcatenatedGridCoverage(this, c) : this; } - return convertedView; + if (!changed) { + return this; + } + final List<SampleDimension> sampleDimensions; + if (template >= 0) { + sampleDimensions = c[template].getSampleDimensions(); + } else { + sampleDimensions = new ArrayList<>(getSampleDimensions()); + sampleDimensions.replaceAll((b) -> b.forConvertedValues(converted)); + } + return new ConcatenatedGridCoverage(this, c, sampleDimensions, converted); } /** @@ -123,6 +181,13 @@ final class ConcatenatedGridCoverage extends GridCoverage { if (upper - lower != 1) { throw new SubspaceNotSpecifiedException(); } - return slices[lower].render(locator.toSliceExtent(extent, lower)); + GridCoverage slice = slices[lower]; + if (slice == null) try { + slice = resources[lower].read(request, ranges).forConvertedValues(isConverted); + slices[lower] = slice; + } catch (DataStoreException e) { + throw new CannotEvaluateException(Resources.format(Resources.Keys.CanNotReadSlice_1, lower + startAt), e); + } + return slice.render(locator.toSliceExtent(extent, lower)); } } diff --git a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/aggregate/ConcatenatedGridResource.java b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/aggregate/ConcatenatedGridResource.java index 773faa3169..9bf705d312 100644 --- a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/aggregate/ConcatenatedGridResource.java +++ b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/aggregate/ConcatenatedGridResource.java @@ -37,8 +37,10 @@ import org.apache.sis.storage.RasterLoadingStrategy; import org.apache.sis.storage.event.StoreListeners; import org.apache.sis.internal.storage.MemoryGridResource; import org.apache.sis.internal.storage.MetadataBuilder; +import org.apache.sis.internal.storage.RangeArgument; import org.apache.sis.internal.util.UnmodifiableArrayList; import org.apache.sis.internal.util.CollectionsExt; +import org.apache.sis.internal.util.Numerics; import org.apache.sis.util.ArraysExt; @@ -73,12 +75,35 @@ final class ConcatenatedGridResource extends AbstractGridCoverageResource implem */ private final List<SampleDimension> sampleDimensions; + /** + * Whether all {@link SampleDimension} represent "real world" values. + */ + final boolean isConverted; + /** * The slices of this resource, in the same order than {@link #coordinatesOfSlices}. * Each slice is not necessarily 1 cell tick; larger slices are accepted. */ private final GridCoverageResource[] slices; + /** + * Whether loading of grid coverages should be deferred to rendering time. + * This is a bit set packed as {@code long} values. A bit value of 1 means + * that the coverages at the corresponding index should be loaded from the + * {@linkplain #slices} at same index only when first needed. + * + * <p>Whether a bit is set or not depends on two factor:</p> + * <ul> + * <li>Whether deferred loading has been requested by a call to {@link #setLoadingStrategy(RasterLoadingStrategy)}.</li> + * <li>Whether the slice at the corresponding index can handle deferred loading itself. + * In such case, we let the resource manages its own lazy loading.</li> + * </ul> + * + * @see RasterLoadingStrategy#AT_READ_TIME + * @see RasterLoadingStrategy#AT_RENDER_TIME + */ + private final long[] deferredLoading; + /** * The object for identifying indices in the {@link #slices} array. */ @@ -133,6 +158,14 @@ final class ConcatenatedGridResource extends AbstractGridCoverageResource implem this.sampleDimensions = ranges; this.slices = slices; this.locator = locator; + this.deferredLoading = new long[Numerics.ceilDiv(slices.length, Long.SIZE)]; + for (final SampleDimension sd : ranges) { + if (sd.forConvertedValues(true) != sd) { + isConverted = false; + return; + } + } + isConverted = true; } /** @@ -253,19 +286,30 @@ final class ConcatenatedGridResource extends AbstractGridCoverageResource implem */ @Override public RasterLoadingStrategy getLoadingStrategy() throws DataStoreException { - RasterLoadingStrategy common = null; + /* + * If at least one bit of `deferredLoading` is set, then it means that + * `setLoadingStrategy(…)` has been invoked with anything else than `AT_READ_TIME`. + */ + int bitx = 0; + long mask = 1; + RasterLoadingStrategy conservative = RasterLoadingStrategy.AT_GET_TILE_TIME; for (final GridCoverageResource slice : slices) { - final RasterLoadingStrategy sr = slice.getLoadingStrategy(); - if (sr != null) { // Should never be null, but we are paranoiac. - if (common == null || sr.ordinal() < common.ordinal()) { - common = sr; - if (common.ordinal() == 0) { - break; - } - } + RasterLoadingStrategy s = slice.getLoadingStrategy(); + if (s == null || s.ordinal() == 0) { // Should never be null, but we are paranoiac. + s = ((deferredLoading[bitx] & mask) != 0) + ? RasterLoadingStrategy.AT_RENDER_TIME + : RasterLoadingStrategy.AT_READ_TIME; + } + if (s.ordinal() < conservative.ordinal()) { + conservative = s; + if (s.ordinal() == 0) break; + } + if ((mask <<= 1) == 0) { + mask=1; + bitx++; } } - return common; + return conservative; } /** @@ -278,9 +322,20 @@ final class ConcatenatedGridResource extends AbstractGridCoverageResource implem */ @Override public boolean setLoadingStrategy(final RasterLoadingStrategy strategy) throws DataStoreException { - boolean accepted = false; + final boolean deferred = (strategy.ordinal() != 0); + Arrays.fill(deferredLoading, 0); + boolean accepted = true; + int bitx = 0; + long mask = 1; for (final GridCoverageResource slice : slices) { - accepted |= slice.setLoadingStrategy(strategy); + if (!slice.setLoadingStrategy(strategy)) { + if (deferred) deferredLoading[bitx] |= mask; + accepted = false; + } + if ((mask <<= 1) == 0) { + mask=1; + bitx++; + } } return accepted; } @@ -289,12 +344,18 @@ final class ConcatenatedGridResource extends AbstractGridCoverageResource implem * 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 range 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 range. + * @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(GridGeometry domain, final int... ranges) throws DataStoreException { + public GridCoverage read(GridGeometry domain, int... ranges) throws DataStoreException { + /* + * Validate arguments. + */ + if (ranges != null) { + ranges = RangeArgument.validate(sampleDimensions.size(), ranges, listeners).getSelectedBands(); + } int lower = 0, upper = slices.length; if (domain != null) { final GridDerivation subgrid = gridGeometry.derive().rounding(GridRoundingMode.ENCLOSING).subgrid(domain); @@ -308,19 +369,47 @@ final class ConcatenatedGridResource extends AbstractGridCoverageResource implem * Create arrays with only the requested range, without keeping reference to this concatenated resource, * for allowing garbage-collection of resources outside that range. */ - final GridCoverage[] coverages = new GridCoverage[upper - lower]; - for (int i=0; i < coverages.length; i++) { + final int count = upper - lower; + final GridGeometry[] geometries = new GridGeometry[count]; + final GridCoverage[] coverages = new GridCoverage[count]; + GridCoverageResource[] resources = null; // Created when first needed. + int bitx = lower >>> Numerics.LONG_SHIFT; + long mask = 1L << lower; // No need for (lower & 63) because high bits are ignored. + if (count <= 1) mask = 0; // Trick for forcing coverage loading. + for (int i=0; i<count; i++) { final GridCoverageResource slice = slices[lower + i]; if (slice instanceof MemoryGridResource) { - coverages[i] = ((MemoryGridResource) slice).coverage; + final GridCoverage coverage = ((MemoryGridResource) slice).coverage; + coverages [i] = coverage; + geometries[i] = coverage.getGridGeometry(); + } else if ((deferredLoading[bitx] & mask) == 0) { + final GridCoverage coverage = slice.read(domain, ranges); + coverages [i] = coverage; + geometries[i] = coverage.getGridGeometry(); } else { - coverages[i] = slice.read(domain, ranges); + if (resources == null) { + resources = new GridCoverageResource[count]; + } + resources [i] = slice; + geometries[i] = slice.getGridGeometry(); + } + if ((mask <<= 1) == 0) { + mask=1; + bitx++; } } - if (coverages.length == 1) { + /* + * If it was not necessary to keep references to any resource, clear references to information + * which were needed only for loading coverages from resources. Then create create the coverage. + */ + if (count == 1) { return coverages[0]; } - domain = locator.union(gridGeometry, Arrays.asList(coverages), (c) -> c.getGridGeometry().getExtent()); - return new ConcatenatedGridCoverage(this, domain, coverages, lower); + if (resources == null) { + domain = null; + ranges = null; + } + final GridGeometry union = locator.union(gridGeometry, Arrays.asList(geometries), GridGeometry::getExtent); + return new ConcatenatedGridCoverage(this, union, domain, coverages, resources, lower, ranges); } }