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 3164e8db34 When opening a large image in the JavaFX application, if there is no overview, zoom in the middle of the image as an initial view. We do that for avoiding to load too much data when the initial view is the whole image. 3164e8db34 is described below commit 3164e8db34c0631aef307e617ea4580dbab7d764 Author: Martin Desruisseaux <martin.desruisse...@geomatys.com> AuthorDate: Thu Feb 27 16:07:58 2025 +0100 When opening a large image in the JavaFX application, if there is no overview, zoom in the middle of the image as an initial view. We do that for avoiding to load too much data when the initial view is the whole image. --- .../coverage/MultiResolutionCoverageLoader.java | 2 +- .../org/apache/sis/util/privy/CollectionsExt.java | 18 ++++++ .../apache/sis/gui/coverage/CoverageCanvas.java | 68 ++++++++++++++++++---- .../gui/coverage/MultiResolutionImageLoader.java | 2 +- .../main/org/apache/sis/gui/map/MapCanvas.java | 6 +- .../main/org/apache/sis/gui/map/package-info.java | 2 +- 6 files changed, 80 insertions(+), 18 deletions(-) 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 2fda32ee32..ef1276110e 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 @@ -176,7 +176,7 @@ public class MultiResolutionCoverageLoader { * Build the arrays of resolutions from finest to coarsest. * The `base` array is cloned then updated to become the base of next level. */ - final double[][] resolutions = new double[numLevels][]; + final var resolutions = new double[numLevels][]; resolutions[0] = base; for (int j=1; j<numLevels; j++) { resolutions[j] = base = base.clone(); diff --git a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/privy/CollectionsExt.java b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/privy/CollectionsExt.java index d332b9b431..9d2068257a 100644 --- a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/privy/CollectionsExt.java +++ b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/privy/CollectionsExt.java @@ -139,6 +139,24 @@ public final class CollectionsExt extends Static { return null; } + /** + * Returns the last non-null element of the given list. + * + * @param <T> the type of elements contained in the list. + * @param list the list from which to get the last non-null element, or {@code null}. + * @return the last non-null element, or {@code null} if the given list is null or empty. + */ + public static <T> T lastNonNull(final List<T> collection) { + if (collection != null) { + int i = collection.size(); + while (--i >= 0) { + T e = collection.get(i); + if (e != null) return e; + } + } + return null; + } + /** * If the given iterable contains exactly one non-null element, returns that element. * Otherwise returns {@code null}. 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 bdf4c9a529..965841c4a6 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 @@ -58,6 +58,7 @@ import org.apache.sis.coverage.grid.GridCoverage; import org.apache.sis.coverage.grid.GridExtent; import org.apache.sis.coverage.grid.GridGeometry; import org.apache.sis.coverage.grid.PixelInCell; +import org.apache.sis.geometry.GeneralEnvelope; import org.apache.sis.geometry.Envelope2D; import org.apache.sis.geometry.Shapes2D; import org.apache.sis.image.Colorizer; @@ -77,6 +78,7 @@ import org.apache.sis.gui.internal.LogHandler; import org.apache.sis.util.ArraysExt; import org.apache.sis.util.Debug; import org.apache.sis.util.logging.Logging; +import org.apache.sis.util.privy.CollectionsExt; import org.apache.sis.io.TableAppender; import org.apache.sis.measure.Units; import static org.apache.sis.gui.internal.LogHandler.LOGGER; @@ -88,7 +90,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.4 + * @version 1.5 * * @see CoverageExplorer * @@ -103,6 +105,15 @@ public class CoverageCanvas extends MapCanvasAWT { */ private static final int OVERFLOW_SAFETY_MARGIN = 10_000_000; + /** + * Maximal surface or volume (in number of cells) of the data to load. + * If showing the full image would cause a larger amount of data to be loaded, + * then the widget will zoom on a smaller area by default. This is based on the + * assumption that data are tiled, and therefore zooming will reduce the amount + * of data to load. + */ + private static final int MAXIMAL_CELL_COUNT = 5000 * 5000; + /** * Whether to print debug information. If {@code true}, we use {@link System#out} instead of logging * because the log messages are intercepted and rerouted to the "logging" tab in the explorer widget. @@ -581,24 +592,57 @@ public class CoverageCanvas extends MapCanvasAWT { GridGeometry domain; final Long id = LogHandler.loadingStart(resource); try { + double[] scales; if (coverage != null) { domain = coverage.getGridGeometry(); ranges = coverage.getSampleDimensions(); + scales = null; } else { domain = resource.getGridGeometry(); ranges = resource.getSampleDimensions(); + scales = CollectionsExt.lastNonNull(resource.getResolutions()); } - /* - * The domain should never be null and should always be complete (including envelope). - * Nevertheless we try to be safe, since `setNewSource(…)` wants a complete geometry. - * So if the envelope is missing but the extent is present, then the missing part was - * the "grid to CRS" transform. We use an identity transform with a "display CRS". - */ - if (domain != null && !domain.isDefined(GridGeometry.ENVELOPE) && domain.isDefined(GridGeometry.EXTENT)) { - final GridExtent extent = domain.getExtent(); - final int dimension = extent.getDimension(); - domain = new GridGeometry(extent, PixelInCell.CELL_CORNER, MathTransforms.identity(dimension), - (dimension == BIDIMENSIONAL) ? CommonCRS.Engineering.DISPLAY.crs() : null); + if (domain != null) { + /* + * The domain should never be null and should always be complete (including envelope). + * Nevertheless we try to be safe, since `setNewSource(…)` wants a complete geometry. + * So if the envelope is missing but the extent is present, then the missing part was + * the "grid to CRS" transform. We use an identity transform with a "display CRS". + */ + if (!domain.isDefined(GridGeometry.ENVELOPE) && domain.isDefined(GridGeometry.EXTENT)) { + final GridExtent extent = domain.getExtent(); + final int dimension = extent.getDimension(); + domain = new GridGeometry(extent, PixelInCell.CELL_CORNER, MathTransforms.identity(dimension), + (dimension == BIDIMENSIONAL) ? CommonCRS.Engineering.DISPLAY.crs() : null); + } + /* + * Compute the maximum zoom out. Usually, we want to show the full image. + * But if the image has no pyramid, showing the full image may cause the + * loading of a large amount of data. We are better to limit the zoom to + * a small area. + */ + if (domain.isDefined(GridGeometry.ENVELOPE | GridGeometry.RESOLUTION)) { + if (scales == null) { + scales = domain.getResolution(true); + } + double ratio = MAXIMAL_CELL_COUNT; + 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. + } + if (ratio < 1) { + ratio = Math.pow(ratio, 1d / dimension); + final double out = (1 - ratio) / 2; // Fraction of bounds to take out on each side. + final var zoomArea = new GeneralEnvelope(bounds); + for (int i=0; i<dimension; i++) { + final double margin = zoomArea.getSpan(i) * out; + zoomArea.setRange(i, zoomArea.getLower(i) + margin, zoomArea.getUpper(i) - margin); + } + // Pretend that the data domain is smaller than reality. + domain = domain.derive().subgrid(zoomArea, null).build(); + } + } } } finally { LogHandler.loadingStop(id); diff --git a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/coverage/MultiResolutionImageLoader.java b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/coverage/MultiResolutionImageLoader.java index 4946b49614..2ca4bb5b28 100644 --- a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/coverage/MultiResolutionImageLoader.java +++ b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/coverage/MultiResolutionImageLoader.java @@ -69,7 +69,7 @@ final class MultiResolutionImageLoader extends MultiResolutionCoverageLoader { cached = CACHE.get(resource); } if (cached == null) { - final MultiResolutionImageLoader loader = new MultiResolutionImageLoader(resource); + final var loader = new MultiResolutionImageLoader(resource); synchronized (CACHE) { cached = CACHE.putIfAbsent(resource, loader); } diff --git a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/map/MapCanvas.java b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/map/MapCanvas.java index 2ee421ddc0..7e29d85e0b 100644 --- a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/map/MapCanvas.java +++ b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/map/MapCanvas.java @@ -131,7 +131,7 @@ import org.opengis.coordinate.MismatchedDimensionException; * </ol> * * @author Martin Desruisseaux (Geomatys) - * @version 1.4 + * @version 1.5 * @since 1.1 */ public abstract class MapCanvas extends PlanarCanvas { @@ -200,7 +200,7 @@ public abstract class MapCanvas extends PlanarCanvas { /** * The data bounds to use for computing the initial value of {@link #objectiveToDisplay}. - * We differ this recomputation until all parameters are known. + * We differ this computation until all parameters are known. * * @see #setObjectiveBounds(Envelope) * @see #invalidObjectiveToDisplay @@ -1195,7 +1195,7 @@ public abstract class MapCanvas extends PlanarCanvas { return; } invalidObjectiveToDisplay = false; - final GridExtent extent = new GridExtent(null, + final var extent = new GridExtent(null, new long[] {Math.round(target.getMinX()), Math.round(target.getMinY())}, new long[] {Math.round(target.getMaxX()), Math.round(target.getMaxY())}, false); /* diff --git a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/map/package-info.java b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/map/package-info.java index 43f51967bf..1f53783039 100644 --- a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/map/package-info.java +++ b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/map/package-info.java @@ -22,7 +22,7 @@ * {@link org.apache.sis.gui.map.MapCanvasAWT} is a specialization for painting the map using Java2D. * * @author Martin Desruisseaux (Geomatys) - * @version 1.4 + * @version 1.5 * @since 1.1 */ package org.apache.sis.gui.map;