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 cfd13790296f4501a6c5e1f27ef17112cd965e24 Author: Martin Desruisseaux <[email protected]> AuthorDate: Sat Jan 23 00:34:47 2021 +0100 Enable parallel execution of isoline computation in JavaFX viewer. --- .../apache/sis/gui/coverage/CoverageCanvas.java | 15 ++++--- .../apache/sis/gui/coverage/IsolineRenderer.java | 51 ++++++++++++++++++---- .../org/apache/sis/gui/coverage/RenderingData.java | 11 +++-- 3 files changed, 59 insertions(+), 18 deletions(-) diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/CoverageCanvas.java b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/CoverageCanvas.java index c2ae8f6..949c2ae 100644 --- a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/CoverageCanvas.java +++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/CoverageCanvas.java @@ -20,6 +20,7 @@ import java.util.Map; import java.util.EnumMap; import java.util.List; import java.util.Locale; +import java.util.concurrent.Future; import java.util.function.Function; import java.io.IOException; import java.awt.Graphics2D; @@ -67,6 +68,7 @@ import org.apache.sis.gui.map.MapCanvas; import org.apache.sis.gui.map.MapCanvasAWT; import org.apache.sis.gui.map.StatusBar; import org.apache.sis.portrayal.RenderException; +import org.apache.sis.internal.processing.image.Isolines; import org.apache.sis.internal.gui.BackgroundThreads; import org.apache.sis.internal.gui.GUIUtilities; import org.apache.sis.internal.gui.LogHandler; @@ -657,7 +659,7 @@ public class CoverageCanvas extends MapCanvasAWT { */ @Override @SuppressWarnings("PointlessBitwiseExpression") - protected void render() throws TransformException { + protected void render() throws Exception { final Long id = LogHandler.loadingStart(originator); try { /* @@ -697,12 +699,15 @@ public class CoverageCanvas extends MapCanvasAWT { trace("render(): resampling result:%n\t%s", resampledImage); } } - prefetchedImage = data.prefetch(resampledImage, resampledToDisplay, displayBounds); /* - * Create isolines if requested. + * Launch isolines creation if requested. We do this operation before `prefetch(…)` + * because it will be executed in background threads while we process the coverage. + * We can not invoke it sooner because it needs some `resampleAndConvert(…)` results. */ - if (isolines != null) { - data.complete(isolines); + final Future<Isolines[]> newIsolines = data.generate(isolines); + prefetchedImage = data.prefetch(resampledImage, resampledToDisplay, displayBounds); + if (newIsolines != null) { + IsolineRenderer.complete(isolines, newIsolines); } } finally { LogHandler.loadingStop(id); diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/IsolineRenderer.java b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/IsolineRenderer.java index 12116c6..ebb3e83 100644 --- a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/IsolineRenderer.java +++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/IsolineRenderer.java @@ -22,6 +22,8 @@ import java.util.List; import java.util.HashMap; import java.util.HashSet; import java.util.Arrays; +import java.util.concurrent.Future; +import java.util.concurrent.ExecutionException; import java.awt.Shape; import java.awt.Color; import java.awt.Graphics2D; @@ -265,11 +267,11 @@ final class IsolineRenderer { } /** - * Prepares a list of isolines to draw for all bands, initially populated with shapes that are already available. + * Prepares a list of isolines to draw for each bands, initially populated with shapes that are already available. * This method shall be invoked in JavaFX thread for having consistent information. The snapshots returned by this * method will be completed and used in a background thread. * - * @return snapshots of information about isolines in all bands, or {@code null} if none. + * @return snapshots of information about isolines in each bands, or {@code null} if none. */ final Snapshot[] prepare() { assert Platform.isFxApplicationThread(); @@ -287,17 +289,18 @@ final class IsolineRenderer { /** * Continues isoline preparation by computing the missing Java2D shapes. * This method shall be invoked in a background thread. After this call, + * {@link #complete(Snapshot[], Future)} needs to be invoked before * isolines can be painted with {@link Snapshot#paint(Graphics2D, Rectangle2D)}. * * @param snapshots value of {@link #prepare()}. Shall not be {@code null}. * @param data the source of data. Used only if there is new isolines to compute. * @param gridToCRS transform from pixel coordinates to geometry coordinates, or {@code null} if none. * Integer source coordinates are located at pixel centers. - * @return the {@code snapshots} array, potentially with less elements. + * @return result of isolines generation, or {@code null} if there is no isoline to compute. * @throws TransformException if an interpolated point can not be transformed using the given transform. */ @SuppressWarnings("UseOfSystemOutOrSystemErr") // Used only for debugging. - static Snapshot[] complete(final Snapshot[] snapshots, final RenderedImage data, final MathTransform gridToCRS) + static Future<Isolines[]> generate(final Snapshot[] snapshots, final RenderedImage data, final MathTransform gridToCRS) throws TransformException { assert !Platform.isFxApplicationThread(); @@ -331,12 +334,31 @@ final class IsolineRenderer { System.out.printf("\tFor band %d: %s%n", i, Arrays.toString(levels[i])); } } - final Isolines[] isolines = Isolines.generate(data, levels, gridToCRS); - for (int i=0; i<numViews; i++) { - snapshots[i].complete(isolines[i]); - } + return Isolines.parallelGenerate(data, levels, gridToCRS); + } + return null; + } + + /** + * Waits for completion of isolines generation if not already finished, then stores the result. + * The {@code isolines} argument is the {@link #generate(Snapshot[], RenderedImage, MathTransform)} + * return value. Caller shall verify that {@code snapshots} is non-null. + * + * @param snapshots where to store the result of isoline computations. + * @param newIsolines the result of isolines generation, or {@code null} if none. + */ + static void complete(final Snapshot[] snapshots, final Future<Isolines[]> newIsolines) + throws ExecutionException, InterruptedException + { + final Isolines[] isolines = newIsolines.get(); + final int n = Math.min(snapshots.length, isolines.length); + int i; + for (i=0; i<n; i++) { + snapshots[i].complete(isolines[i]); + } + while (i < snapshots.length) { // Clear remaining snapshots if any. + snapshots[i++].clear(); } - return ArraysExt.resize(snapshots, numViews); } /** @@ -403,6 +425,17 @@ final class IsolineRenderer { } /** + * Removes all isolines. + */ + private void clear() { + isolines.clear(); + missingLevels.clear(); + newIsolines = null; + Arrays.fill(shapes, null); + count = 0; + } + + /** * Adds an isoline level. This method shall be invoked in JavaFX thread. * * @param value the level value. diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/RenderingData.java b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/RenderingData.java index 7dfb293..6e08f03 100644 --- a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/RenderingData.java +++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/RenderingData.java @@ -19,6 +19,7 @@ package org.apache.sis.gui.coverage; import java.util.Map; import java.util.HashMap; import java.util.List; +import java.util.concurrent.Future; import java.io.IOException; import java.io.UncheckedIOException; import java.awt.Graphics2D; @@ -48,6 +49,7 @@ import org.apache.sis.image.ImageProcessor; import org.apache.sis.internal.coverage.j2d.ColorModelType; import org.apache.sis.internal.coverage.j2d.ImageUtilities; import org.apache.sis.internal.referencing.WraparoundApplicator; +import org.apache.sis.internal.processing.image.Isolines; import org.apache.sis.internal.system.Modules; import org.apache.sis.io.TableAppender; import org.apache.sis.math.Statistics; @@ -560,14 +562,15 @@ final class RenderingData implements Cloneable { * This method shall be invoked in a background thread after image rendering has been completed (because this * method uses some image computation results). * - * @param isolines value of {@link IsolineRenderer#prepare()}. Shall not be {@code null}. - * @return the {@code isolines} array, potentially with less elements. + * @param isolines value of {@link IsolineRenderer#prepare()}, or {@code null} if none. + * @return result of isolines generation, or {@code null} if there is no isoline to compute. * @throws TransformException if an interpolated point can not be transformed using the given transform. */ - final IsolineRenderer.Snapshot[] complete(final IsolineRenderer.Snapshot[] isolines) throws TransformException { + final Future<Isolines[]> generate(final IsolineRenderer.Snapshot[] isolines) throws TransformException { + if (isolines == null) return null; final MathTransform centerToObjective = PixelTranslation.translate( cornerToObjective, PixelInCell.CELL_CORNER, PixelInCell.CELL_CENTER); - return IsolineRenderer.complete(isolines, data, centerToObjective); + return IsolineRenderer.generate(isolines, data, centerToObjective); } /**
