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 eea0e6c4f6c337c270ccc6d7b7b83cf7095ed08d Author: Martin Desruisseaux <martin.desruisse...@geomatys.com> AuthorDate: Thu Sep 15 18:46:10 2022 +0200 Allow change of `MergeStrategy` after resource creation. Use this new API in the JavaFX application. --- .../org/apache/sis/gui/dataset/ResourceItem.java | 2 + .../aggregate/ConcatenatedGridCoverage.java | 2 +- .../aggregate/ConcatenatedGridResource.java | 96 ++++++++++++++-------- .../sis/storage/aggregate/GroupAggregate.java | 20 +++++ .../sis/storage/aggregate/MergeStrategy.java | 36 +++++++- 5 files changed, 120 insertions(+), 36 deletions(-) diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/dataset/ResourceItem.java b/application/sis-javafx/src/main/java/org/apache/sis/gui/dataset/ResourceItem.java index f12d78fd6b..de7d55a133 100644 --- a/application/sis-javafx/src/main/java/org/apache/sis/gui/dataset/ResourceItem.java +++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/dataset/ResourceItem.java @@ -28,6 +28,7 @@ import javafx.scene.control.TreeItem; import org.apache.sis.storage.Resource; import org.apache.sis.storage.Aggregate; import org.apache.sis.storage.DataStoreException; +import org.apache.sis.storage.aggregate.MergeStrategy; import org.apache.sis.internal.storage.folder.UnstructuredAggregate; import org.apache.sis.internal.gui.DataStoreOpener; import org.apache.sis.internal.gui.BackgroundThreads; @@ -390,6 +391,7 @@ final class ResourceItem extends TreeItem<Resource> { case AGGREGATION: { if (resource instanceof UnstructuredAggregate) { result = ((UnstructuredAggregate) resource).getStructuredView(); + result = MergeStrategy.selectByTimeThenArea(null).update(result); } break; } diff --git a/storage/sis-storage/src/main/java/org/apache/sis/storage/aggregate/ConcatenatedGridCoverage.java b/storage/sis-storage/src/main/java/org/apache/sis/storage/aggregate/ConcatenatedGridCoverage.java index 31f799efcd..db43078431 100644 --- a/storage/sis-storage/src/main/java/org/apache/sis/storage/aggregate/ConcatenatedGridCoverage.java +++ b/storage/sis-storage/src/main/java/org/apache/sis/storage/aggregate/ConcatenatedGridCoverage.java @@ -196,7 +196,7 @@ final class ConcatenatedGridCoverage extends GridCoverage { } /** - * Returns {@code true} if the loading the coverage at the given index is deferred. + * Returns {@code true} if the loading of the coverage at the given index is deferred. * If {@code true}, then {@code slices[i]} shall be an instance of {@link GridCoverageResource}. * If {@code false}, then {@code slices[i]} shall be an instance of {@link GridCoverage}. */ diff --git a/storage/sis-storage/src/main/java/org/apache/sis/storage/aggregate/ConcatenatedGridResource.java b/storage/sis-storage/src/main/java/org/apache/sis/storage/aggregate/ConcatenatedGridResource.java index 903bc8f4a5..16398e76d2 100644 --- a/storage/sis-storage/src/main/java/org/apache/sis/storage/aggregate/ConcatenatedGridResource.java +++ b/storage/sis-storage/src/main/java/org/apache/sis/storage/aggregate/ConcatenatedGridResource.java @@ -82,12 +82,13 @@ final class ConcatenatedGridResource extends AbstractGridCoverageResource implem /** * The slices of this resource, in the same order than {@link GridSliceLocator#sliceLows}. * Each slice is not necessarily 1 cell tick; larger slices are accepted. + * This array shall be read-only. */ 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 + * This is a bit set packed as {@code int} 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. * @@ -98,10 +99,13 @@ final class ConcatenatedGridResource extends AbstractGridCoverageResource implem * In such case, we let the resource manages its own lazy loading.</li> * </ul> * + * This array shall be read-only. If changes are desired, a new array shall be created (copy-on-write). + * + * @see #isDeferred(int) * @see RasterLoadingStrategy#AT_READ_TIME * @see RasterLoadingStrategy#AT_RENDER_TIME */ - private final long[] deferredLoading; + private int[] deferredLoading; /** * The object for identifying indices in the {@link #slices} array. @@ -130,6 +134,7 @@ final class ConcatenatedGridResource extends AbstractGridCoverageResource implem /** * The resolutions, or {@code null} if not yet computed. Can be an empty array after computation. + * Shall be read-only after computation. * * @see #getResolutions() */ @@ -159,7 +164,7 @@ final class ConcatenatedGridResource extends AbstractGridCoverageResource implem this.slices = slices; this.locator = locator; this.strategy = strategy; - this.deferredLoading = new long[Numerics.ceilDiv(slices.length, Long.SIZE)]; + this.deferredLoading = new int[Numerics.ceilDiv(slices.length, Integer.SIZE)]; for (final SampleDimension sd : ranges) { if (sd.forConvertedValues(true) != sd) { isConverted = false; @@ -169,6 +174,30 @@ final class ConcatenatedGridResource extends AbstractGridCoverageResource implem isConverted = true; } + /** + * Creates a new resource with the same data than given resource but a different merge strategy. + * The two resources will share the same cache of loaded coverages. + * + * @param source the resource to copy. + * @param strategy the new merge strategy. + */ + ConcatenatedGridResource(final ConcatenatedGridResource source, final MergeStrategy strategy) { + super(source.listeners, false); + synchronized (source) { + name = source.name; + gridGeometry = source.gridGeometry; + sampleDimensions = source.sampleDimensions; + isConverted = source.isConverted; + slices = source.slices; + deferredLoading = source.deferredLoading; + locator = source.locator; + envelope = source.envelope; + envelopeIsEvaluated = source.envelopeIsEvaluated; + resolutions = source.resolutions; + } + this.strategy = strategy; + } + /** * Modifies the name of the resource. * This information is used for metadata. @@ -273,29 +302,23 @@ final class ConcatenatedGridResource extends AbstractGridCoverageResource implem * @throws DataStoreException if an error occurred while fetching data store configuration. */ @Override - public RasterLoadingStrategy getLoadingStrategy() throws DataStoreException { + public synchronized RasterLoadingStrategy getLoadingStrategy() throws DataStoreException { /* * 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) { + for (int i=0; i < slices.length; i++) { + final GridCoverageResource slice = slices[i]; 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; + s = isDeferred(i) ? 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 conservative; } @@ -309,25 +332,32 @@ final class ConcatenatedGridResource extends AbstractGridCoverageResource implem * @throws DataStoreException if an error occurred while setting data store configuration. */ @Override - public boolean setLoadingStrategy(final RasterLoadingStrategy strategy) throws DataStoreException { + public synchronized boolean setLoadingStrategy(final RasterLoadingStrategy strategy) throws DataStoreException { final boolean deferred = (strategy.ordinal() != 0); - Arrays.fill(deferredLoading, 0); + final int[] newValues = new int[deferredLoading.length]; boolean accepted = true; - int bitx = 0; - long mask = 1; - for (final GridCoverageResource slice : slices) { + for (int i=0; i < slices.length; i++) { + final GridCoverageResource slice = slices[i]; if (!slice.setLoadingStrategy(strategy)) { - if (deferred) deferredLoading[bitx] |= mask; + if (deferred) { + newValues[i >>> Numerics.INT_SHIFT] |= (1 << i); + } accepted = false; } - if ((mask <<= 1) == 0) { - mask=1; - bitx++; - } + } + if (!Arrays.equals(deferredLoading, newValues)) { + deferredLoading = newValues; } return accepted; } + /** + * Returns {@code true} if the loading of the coverage at the given index is deferred. + */ + private boolean isDeferred(final int i) { + return (deferredLoading[i >>> Numerics.INT_SHIFT] & (1 << i)) != 0; + } + /** * Loads a subset of the grid coverage represented by this resource. * @@ -361,16 +391,14 @@ final class ConcatenatedGridResource extends AbstractGridCoverageResource implem final Object[] coverages = new Object[count]; final GridGeometry[] geometries = new GridGeometry[count]; int[] deferred = null; - 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]; + final int j = lower + i; + final GridCoverageResource slice = slices[j]; if (slice instanceof MemoryGridResource) { final GridCoverage coverage = ((MemoryGridResource) slice).coverage; coverages [i] = coverage; geometries[i] = coverage.getGridGeometry(); - } else if ((deferredLoading[bitx] & mask) == 0) { + } else if (!isDeferred(j) || count <= 1) { final GridCoverage coverage = slice.read(domain, ranges); coverages [i] = coverage; geometries[i] = coverage.getGridGeometry(); @@ -382,18 +410,18 @@ final class ConcatenatedGridResource extends AbstractGridCoverageResource implem } deferred[i >>> Numerics.INT_SHIFT] |= (1 << i); } - if ((mask <<= 1) == 0) { - mask=1; - bitx++; - } } /* - * Following cast should never fail because the `mask = 0` trick ensures that we loaded the coverage. + * Following cast should never fail because the `count <= 1` check in above + * loop ensured that we loaded the coverage. * Otherwise (if more than one slice), create a concatenation of all slices. */ if (count == 1) { return (GridCoverage) coverages[0]; } + if (Arrays.equals(deferred, deferredLoading)) { + deferred = deferredLoading; // Slight memory saving for a common case. + } final GridGeometry union = locator.union(gridGeometry, Arrays.asList(geometries), GridGeometry::getExtent); return new ConcatenatedGridCoverage(this, union, coverages, lower, deferred, domain, ranges); } diff --git a/storage/sis-storage/src/main/java/org/apache/sis/storage/aggregate/GroupAggregate.java b/storage/sis-storage/src/main/java/org/apache/sis/storage/aggregate/GroupAggregate.java index d976fdaad8..1b7e9aa6eb 100644 --- a/storage/sis-storage/src/main/java/org/apache/sis/storage/aggregate/GroupAggregate.java +++ b/storage/sis-storage/src/main/java/org/apache/sis/storage/aggregate/GroupAggregate.java @@ -114,6 +114,26 @@ final class GroupAggregate extends AbstractResource implements Aggregate, Aggreg this.name = name; } + /** + * Returns an aggregate with the same data than this aggregate but a different merge strategy. + */ + final synchronized GroupAggregate update(final MergeStrategy strategy) { + boolean changed = false; + final GroupAggregate copy = new GroupAggregate(listeners, name, components.length); + for (int i=0; i < components.length; i++) { + final Resource component = components[i]; + changed |= ((copy.components[i] = strategy.update(component)) != component); + } + if (!changed) { + return this; + } + copy.componentsAreLeaves = componentsAreLeaves; + copy.envelope = envelope; + copy.envelopeIsEvaluated = envelopeIsEvaluated; + copy.sampleDimensions = sampleDimensions; + return copy; + } + /** * Sets all components of this aggregate to sub-aggregates, which are themselves initialized with the given filler. * This method may be invoked recursively if the sub-aggregates themselves have sub-sub-aggregates. diff --git a/storage/sis-storage/src/main/java/org/apache/sis/storage/aggregate/MergeStrategy.java b/storage/sis-storage/src/main/java/org/apache/sis/storage/aggregate/MergeStrategy.java index 82d7801cb1..14d4dd8c66 100644 --- a/storage/sis-storage/src/main/java/org/apache/sis/storage/aggregate/MergeStrategy.java +++ b/storage/sis-storage/src/main/java/org/apache/sis/storage/aggregate/MergeStrategy.java @@ -18,6 +18,7 @@ package org.apache.sis.storage.aggregate; import java.time.Instant; import java.time.Duration; +import org.apache.sis.storage.Resource; import org.apache.sis.coverage.grid.GridExtent; import org.apache.sis.coverage.grid.GridCoverage; import org.apache.sis.coverage.grid.GridGeometry; @@ -49,6 +50,14 @@ import org.apache.sis.internal.util.Strings; * @module */ public final class MergeStrategy { + /** + * Selects a single slice using criteria based first on temporal extent, then on geographic area. + * This default instance do not use any duration. + * + * @see #selectByTimeThenArea(Duration) + */ + private static final MergeStrategy SELECT_BY_TIME = new MergeStrategy(null); + /** * Temporal granularity of the time of interest, or {@code null} if none. * If non-null, intersections with TOI will be rounded to an integer amount of this granularity. @@ -105,11 +114,15 @@ public final class MergeStrategy { * * If two slices are still considered equal after all above criteria, then an arbitrary one is selected. * + * <h4>Limitations</h4> + * Current implementation does not check the vertical dimension. + * This check may be added in a future version. + * * @param timeGranularity the temporal granularity of the Time of Interest (TOI), or {@code null} if none. * @return a merge strategy for selecting a slice based on temporal criteria first. */ public static MergeStrategy selectByTimeThenArea(final Duration timeGranularity) { - return new MergeStrategy(timeGranularity); + return (timeGranularity != null) ? new MergeStrategy(timeGranularity) : SELECT_BY_TIME; } /** @@ -141,6 +154,27 @@ public final class MergeStrategy { return selector.best(); } + /** + * Updates the merge strategy of the specified resource. + * If the given resource is an instance created by {@link CoverageAggregator} and uses a different strategy, + * then a new resource using this merge strategy is returned. Otherwise the given resource is returned as-is. + * The returned resource will share the same resources and caches than the given resource. + * + * @param resource the resource for which to update the merge strategy, or {@code null}. + * @return resource with updated merge strategy, or {@code null} if the given resource was null. + */ + public Resource update(Resource resource) { + if (resource instanceof ConcatenatedGridResource) { + final ConcatenatedGridResource c = (ConcatenatedGridResource) resource; + if (!equals(c.strategy)) { + resource = new ConcatenatedGridResource(c, this); + } + } else if (resource instanceof GroupAggregate) { + resource = ((GroupAggregate) resource).update(this); + } + return resource; + } + /** * Returns a string representation of this strategy for debugging purposes. *