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 640019466211ac9af699bff4486d95b6dd10b013 Author: Martin Desruisseaux <martin.desruisse...@geomatys.com> AuthorDate: Tue Sep 20 13:05:59 2022 +0200 If a slice does not intersect the requested extent, try other slice candidates. It may happen if the "best" slice was selected using only temporal creteria but the geographic area of that particular slice does not intersect. --- .../sis/coverage/grid/DisjointExtentException.java | 6 +- .../aggregate/ConcatenatedGridCoverage.java | 80 ++++++++++++++++------ .../sis/storage/aggregate/GridSliceLocator.java | 2 +- .../sis/storage/aggregate/MergeStrategy.java | 14 ++-- 4 files changed, 73 insertions(+), 29 deletions(-) diff --git a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/DisjointExtentException.java b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/DisjointExtentException.java index 1aa54b6a0b..aebaa23bd9 100644 --- a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/DisjointExtentException.java +++ b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/DisjointExtentException.java @@ -25,7 +25,7 @@ import org.apache.sis.internal.feature.Resources; * * @author Johann Sorel (Geomatys) * @author Martin Desruisseaux (Geomatys) - * @version 1.1 + * @version 1.3 * @since 1.0 * @module */ @@ -81,8 +81,10 @@ public class DisjointExtentException extends IllegalGridGeometryException { * @param source extent of the source. * @param request extent of a slice requested by user. * @param dim index of the dimension having an invalid value. + * + * @since 1.3 */ - DisjointExtentException(final GridExtent source, final GridExtent request, final int dim) { + public DisjointExtentException(final GridExtent source, final GridExtent request, final int dim) { this(source.getAxisIdentification(dim, dim), source .getLow(dim), source .getHigh(dim), request.getLow(dim), request.getHigh(dim)); 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 db43078431..b3672cffa7 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 @@ -18,6 +18,7 @@ package org.apache.sis.storage.aggregate; import java.util.List; import java.util.ArrayList; +import java.util.logging.Logger; import java.awt.image.RenderedImage; import org.apache.sis.coverage.SampleDimension; import org.apache.sis.coverage.grid.GridExtent; @@ -28,8 +29,10 @@ import org.apache.sis.coverage.grid.DisjointExtentException; import org.apache.sis.storage.GridCoverageResource; import org.apache.sis.storage.DataStoreException; import org.apache.sis.internal.storage.Resources; +import org.apache.sis.internal.system.Modules; import org.apache.sis.internal.util.Numerics; import org.apache.sis.util.collection.Cache; +import org.apache.sis.util.logging.Logging; // Branch-dependent imports import org.opengis.coverage.CannotEvaluateException; @@ -242,6 +245,8 @@ final class ConcatenatedGridCoverage extends GridCoverage { /** * Returns a two-dimensional slice of grid data as a rendered image. + * Invoking this method may cause the loading of data from {@link ConcatenatedGridResource}. + * Most recently used slices are cached for future invocations of this method. * * @param extent a subspace of this grid coverage extent where all dimensions except two have a size of 1 cell. * @return the grid slice as a rendered image. Image location is relative to {@code sliceExtent}. @@ -255,11 +260,10 @@ final class ConcatenatedGridCoverage extends GridCoverage { } else { extent = gridGeometry.getExtent(); } + final GridGeometry request; // The geographic area and temporal extent requested by user. + final GridGeometry[] candidates; // Grid geometry of all slices that intersect the request. final int count = upper - lower; - if (count != 1) { - if (count == 0) { - throw new DisjointExtentException(); - } + if (count > 1) { if (strategy == null) { /* * Can not infer a slice. If the user specified a single slice but that slice @@ -278,35 +282,71 @@ final class ConcatenatedGridCoverage extends GridCoverage { throw new SubspaceNotSpecifiedException(Resources.format(message, arguments)); } /* - * Select a slice using the user-specified merge strategy. - * Current implementation does only a selection; a future version may allow real merges. + * Prepare a list of slice candidates. Later in this method, a single slice will be selected + * among those candidates using the user-specified merge strategy. Elements in `candidates` + * array will become null if that candidate did not worked and we want to look again among + * remaining candidates. */ - final GridGeometry[] geometries = new GridGeometry[count]; try { + request = new GridGeometry(getGridGeometry(), extent, null); + candidates = new GridGeometry[count]; for (int i=0; i<count; i++) { final int j = lower + i; final Object slice = slices[j]; - geometries[i] = isDeferred(j) ? ((GridCoverageResource) slice).getGridGeometry() + candidates[i] = isDeferred(j) ? ((GridCoverageResource) slice).getGridGeometry() : ((GridCoverage) slice).getGridGeometry(); } - lower += strategy.apply(new GridGeometry(getGridGeometry(), extent, null), geometries); } catch (DataStoreException | TransformException e) { throw new CannotEvaluateException(Resources.format(Resources.Keys.CanNotSelectSlice), e); } + } else { + request = null; + candidates = null; } /* - * Argument have been validated and slice has been located. - * If the coverage has not already been loaded, load it now. + * The following loop should be executed exactly once. However it may happen that the "best" slice + * actually does not intersect the requested extent, for example because the merge strategy looked + * only for temporal intersection and did not saw that the geographic extents do not intersect. */ - final GridCoverage coverage; - final Object slice = slices[lower]; - if (!isDeferred(lower)) { - coverage = (GridCoverage) slice; // Should never fail. - } else try { - coverage = loader.getOrLoad(lower, (GridCoverageResource) slice).forConvertedValues(isConverted); - } catch (DataStoreException e) { - throw new CannotEvaluateException(Resources.format(Resources.Keys.CanNotReadSlice_1, lower + startAt), e); + DisjointExtentException failure = null; + if (count > 0) do { + int index = lower; + if (candidates != null) { + final Integer n = strategy.apply(request, candidates); + if (n == null) break; + candidates[n] = null; + index += n; + } + final Object slice = slices[index]; + final GridCoverage coverage; + if (!isDeferred(index)) { + coverage = (GridCoverage) slice; // This cast should never fail. + } else try { + coverage = loader.getOrLoad(index, (GridCoverageResource) slice).forConvertedValues(isConverted); + } catch (DataStoreException e) { + throw new CannotEvaluateException(Resources.format(Resources.Keys.CanNotReadSlice_1, index + startAt), e); + } + /* + * At this point, coverage of the "best" slice has been fetched from the cache or read from resource. + * Delegate the rendering to that coverage, after converting the extent from this grid coverage space + * to the slice coordinate space. If the coverage said that the converted extent does not intersect, + * try the "next best" slice until we succeed or until we exhausted the candidate list. + */ + try { + final RenderedImage image = coverage.render(locator.toSliceExtent(extent, index)); + if (failure != null) { + Logging.ignorableException(Logger.getLogger(Modules.STORAGE), + ConcatenatedGridCoverage.class, "render", failure); + } + return image; + } catch (DisjointExtentException e) { + if (failure == null) failure = e; + else failure.addSuppressed(e); + } + } while (candidates != null); + if (failure == null) { + failure = new DisjointExtentException(gridGeometry.getExtent(), extent, locator.searchDimension); } - return coverage.render(locator.toSliceExtent(extent, lower)); + throw failure; } } diff --git a/storage/sis-storage/src/main/java/org/apache/sis/storage/aggregate/GridSliceLocator.java b/storage/sis-storage/src/main/java/org/apache/sis/storage/aggregate/GridSliceLocator.java index 786c940fd6..1aaa45d040 100644 --- a/storage/sis-storage/src/main/java/org/apache/sis/storage/aggregate/GridSliceLocator.java +++ b/storage/sis-storage/src/main/java/org/apache/sis/storage/aggregate/GridSliceLocator.java @@ -49,7 +49,7 @@ final class GridSliceLocator { * * @see GroupByTransform#findConcatenatedDimensions() */ - private final int searchDimension; + final int searchDimension; /** * Lows grid coordinates of each slice (inclusive) in the search dimension. 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 6c80c7d835..00fcc2b4fa 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 @@ -131,7 +131,7 @@ public final class MergeStrategy { * A future version may allow real merge operations. * * @param request the geographic area and temporal extent requested by user. - * @param candidates grid geometry of all slices that intersect the request. + * @param candidates grid geometry of all slices that intersect the request. Null elements are ignored. * @return index of best slice according the heuristic rules of this {@code MergeStrategy}. */ final Integer apply(final GridGeometry request, final GridGeometry[] candidates) { @@ -145,11 +145,13 @@ public final class MergeStrategy { } for (int i=0; i < candidates.length; i++) { final GridGeometry candidate = candidates[i]; - final Instant[] t = candidate.getTemporalExtent(); - final int n = t.length; - selector.evaluate(candidate.getGeographicExtent().orElse(null), - (n == 0) ? null : t[0], - (n == 0) ? null : t[n-1], i); + if (candidate != null) { + final Instant[] t = candidate.getTemporalExtent(); + final int n = t.length; + selector.evaluate(candidate.getGeographicExtent().orElse(null), + (n == 0) ? null : t[0], + (n == 0) ? null : t[n-1], i); + } } return selector.best(); }