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 b071e11ed38be47343db9ffacf3ae136c7abdf5d
Author: Martin Desruisseaux <martin.desruisse...@geomatys.com>
AuthorDate: Sun Aug 14 15:09:46 2022 +0200

    When debugging isoline generations using `StepsViewer`, use different 
colors for polylines at different stages.
---
 .../sis/internal/feature/j2d/PathBuilder.java      | 13 +++-
 .../sis/internal/processing/isoline/Isolines.java  | 23 +++---
 .../processing/isoline/PolylineBuffer.java         | 10 +--
 .../internal/processing/isoline/PolylineStage.java | 82 ++++++++++++++++++++++
 .../sis/internal/processing/isoline/Tracer.java    | 12 ++--
 .../internal/processing/isoline/StepsViewer.java   | 73 ++++++++++++++-----
 6 files changed, 174 insertions(+), 39 deletions(-)

diff --git 
a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/j2d/PathBuilder.java
 
b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/j2d/PathBuilder.java
index 3977234d43..f4ae2b28b5 100644
--- 
a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/j2d/PathBuilder.java
+++ 
b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/j2d/PathBuilder.java
@@ -221,7 +221,7 @@ public class PathBuilder {
      * The {@link #createPolyline(boolean)} method should be invoked before 
this method
      * for making sure that there are no pending polylines.
      *
-     * @return the polyline, polygon or collector of polylines.
+     * @return the polyline, polygon or collection of polylines.
      *         May be {@code null} if no polyline or polygon has been created.
      */
     public final Shape build() {
@@ -232,6 +232,17 @@ public class PathBuilder {
         }
     }
 
+    /**
+     * Returns a snapshot of currently added polylines or polygons without 
modifying the state of this builder.
+     * It is safe to continue building the shape and invoke this method again 
later for progressive rendering.
+     *
+     * @return the polyline, polygon or collection of polylines added so far.
+     *         May be {@code null} if no polyline or polygon has been created.
+     */
+    public final Shape snapshot() {
+        return build();
+    }
+
     /**
      * Returns a string representation of the polyline under construction for 
debugging purposes.
      * Current implementation formats only the first and last points, and 
tells how many points are between.
diff --git 
a/core/sis-feature/src/main/java/org/apache/sis/internal/processing/isoline/Isolines.java
 
b/core/sis-feature/src/main/java/org/apache/sis/internal/processing/isoline/Isolines.java
index a1e22aa3fe..abb8792332 100644
--- 
a/core/sis-feature/src/main/java/org/apache/sis/internal/processing/isoline/Isolines.java
+++ 
b/core/sis-feature/src/main/java/org/apache/sis/internal/processing/isoline/Isolines.java
@@ -19,6 +19,8 @@ package org.apache.sis.internal.processing.isoline;
 import java.util.AbstractList;
 import java.util.Arrays;
 import java.util.List;
+import java.util.Map;
+import java.util.EnumMap;
 import java.util.TreeMap;
 import java.util.NavigableMap;
 import java.util.function.BiConsumer;
@@ -49,7 +51,7 @@ import static 
org.apache.sis.internal.processing.isoline.Tracer.LOWER_RIGHT;
  *
  * @author  Johann Sorel (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.1
+ * @version 1.3
  *
  * @see <a href="https://en.wikipedia.org/wiki/Marching_squares";>Marching 
squares on Wikipedia</a>
  *
@@ -69,7 +71,7 @@ public final class Isolines {
      * by step.
      */
     @Debug
-    private static final BiConsumer<String,Path2D> LISTENER = null;
+    private static final BiConsumer<String,Isolines> LISTENER = null;
 
     /**
      * Creates an initially empty set of isolines for the given levels. The 
given {@code values}
@@ -399,8 +401,7 @@ abort:  while (iterator.next()) {
                 if (LISTENER != null) {
                     final int y = tracer.y;
                     final int h = iterator.getDomain().height;
-                    LISTENER.accept(String.format("After row %d of %d 
(%3.1f%%)", y, h, 100f*y/h),
-                                    isolines[b].toRawPath());
+                    LISTENER.accept(String.format("After row %d of %d 
(%3.1f%%)", y, h, 100f*y/h), isolines[b]);
                 }
             }
             tracer.x = 0;
@@ -414,7 +415,7 @@ abort:  while (iterator.next()) {
                 level.finish();
             }
             if (LISTENER != null) {
-                LISTENER.accept("Finished band " + b, isolines[b].toRawPath());
+                LISTENER.accept("Finished band " + b, isolines[b]);
             }
         }
         return isolines;
@@ -526,18 +527,18 @@ abort:  while (iterator.next()) {
     }
 
     /**
-     * Appends the pixel coordinates of all level to the given path, for 
debugging purposes only.
+     * Returns the pixel coordinates of all level, for debugging purposes only.
      * The {@link #gridToCRS} transform is <em>not</em> applied by this method.
      * For avoiding confusing behavior, that transform should be null.
      *
-     * @param  appendTo  where to append the coordinates.
+     * @return the pixel coordinates.
      */
     @Debug
-    private Path2D toRawPath() {
-        final Path2D path = new Path2D.Float();
+    final Map<PolylineStage,Path2D> toRawPath() {
+        final Map<PolylineStage,Path2D> appendTo = new 
EnumMap<>(PolylineStage.class);
         for (final Tracer.Level level : levels) {
-            level.toRawPath(path);
+            level.toRawPath(appendTo);
         }
-        return path;
+        return appendTo;
     }
 }
diff --git 
a/core/sis-feature/src/main/java/org/apache/sis/internal/processing/isoline/PolylineBuffer.java
 
b/core/sis-feature/src/main/java/org/apache/sis/internal/processing/isoline/PolylineBuffer.java
index 5da2768203..be7b42e4f2 100644
--- 
a/core/sis-feature/src/main/java/org/apache/sis/internal/processing/isoline/PolylineBuffer.java
+++ 
b/core/sis-feature/src/main/java/org/apache/sis/internal/processing/isoline/PolylineBuffer.java
@@ -16,6 +16,7 @@
  */
 package org.apache.sis.internal.processing.isoline;
 
+import java.util.Map;
 import java.util.Arrays;
 import java.awt.geom.Path2D;
 import org.apache.sis.internal.feature.j2d.PathBuilder;
@@ -200,15 +201,16 @@ final class PolylineBuffer {
      *
      * @param  appendTo  where to append the coordinates.
      *
-     * @see Tracer.Level#toRawPath(Path2D)
+     * @see Tracer.Level#toRawPath(Map)
      */
     @Debug
-    final void toRawPath(final Path2D appendTo) {
+    final void toRawPath(final Map<PolylineStage,Path2D> appendTo) {
         int i = 0;
         if (i < size) {
-            appendTo.moveTo(coordinates[i++], coordinates[i++]);
+            final Path2D p = PolylineStage.BUFFER.destination(appendTo);
+            p.moveTo(coordinates[i++], coordinates[i++]);
             while (i < size) {
-                appendTo.lineTo(coordinates[i++], coordinates[i++]);
+                p.lineTo(coordinates[i++], coordinates[i++]);
             }
         }
     }
diff --git 
a/core/sis-feature/src/main/java/org/apache/sis/internal/processing/isoline/PolylineStage.java
 
b/core/sis-feature/src/main/java/org/apache/sis/internal/processing/isoline/PolylineStage.java
new file mode 100644
index 0000000000..7ad1733809
--- /dev/null
+++ 
b/core/sis-feature/src/main/java/org/apache/sis/internal/processing/isoline/PolylineStage.java
@@ -0,0 +1,82 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.internal.processing.isoline;
+
+import java.util.Map;
+import java.awt.Shape;
+import java.awt.geom.Path2D;
+import org.apache.sis.util.Debug;
+
+
+/**
+ * Tells at which stage are the polylines represented by a Java2D {@link 
Shape}.
+ * A set of polylines way still be under construction in {@link PolylineBuffer}
+ * during iteration over pixel values, or the polylines may have been 
classified
+ * as incomplete after iteration over a row, or the polylines may be final 
result.
+ *
+ * <p>This is used only for debugging purposes because end users should see 
only the final result.
+ * This information allows {@code StepsViewer} (in test package) to use 
different colors for different stages.</p>
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @version 1.3
+ * @since   1.3
+ * @module
+ */
+@Debug
+enum PolylineStage {
+    /**
+     * The polylines are under construction in various {@link PolylineBuffer} 
instances.
+     * This is the first stage, which happens during iteration over pixel 
values.
+     */
+    BUFFER,
+
+    /**
+     * The polylines are no longer in the buffers filled by the iteration over 
pixel values,
+     * but are still incomplete. It happens when, after finishing iteration 
over a row, some
+     * polylines will not be continued by iteration on the next row and those 
polylines have
+     * not yet been closed as polygons. Those polyline fragments are moved to 
a "pending" list,
+     * as they may be closed later after more polylines fragments become 
available.
+     */
+    FRAGMENT,
+
+    /**
+     * The polylines are final result to be show to user.
+     */
+    FINAL;
+
+    /**
+     * Returns the destination where to write polylines for this stage.
+     *
+     * @param  appendTo  map of path for different stages.
+     * @return the path to use for writing polylines at this stage.
+     */
+    final Path2D destination(final Map<PolylineStage,Path2D> appendTo) {
+        return appendTo.computeIfAbsent(this, (k) -> new Path2D.Float());
+    }
+
+    /**
+     * Adds polylines to the specified map.
+     *
+     * @param  appendTo   where to append the polylines.
+     * @param  polylines  the polylines to append to the map, or {@code null} 
if none.
+     */
+    final void add(final Map<PolylineStage,Path2D> appendTo, final Shape 
polylines) {
+        if (polylines != null) {
+            destination(appendTo).append(polylines, false);
+        }
+    }
+}
diff --git 
a/core/sis-feature/src/main/java/org/apache/sis/internal/processing/isoline/Tracer.java
 
b/core/sis-feature/src/main/java/org/apache/sis/internal/processing/isoline/Tracer.java
index 5bfbbac58d..d6e3e6cfc6 100644
--- 
a/core/sis-feature/src/main/java/org/apache/sis/internal/processing/isoline/Tracer.java
+++ 
b/core/sis-feature/src/main/java/org/apache/sis/internal/processing/isoline/Tracer.java
@@ -222,15 +222,20 @@ final class Tracer {
 
         /**
          * Builder of isolines as a Java2D shape, created when first needed.
-         * The {@link PolylineBuffer} coordinates are copied in this path when 
a geometry is closed.
+         * The {@link PolylineBuffer} coordinates are copied in this path when 
a geometry is closed
+         * and transformed using {@link #gridToCRS}. This is almost final 
result; the only difference
+         * compared to {@link #shape} is that the coordinates are not yet 
wrapped in a {@link Shape}.
          *
          * @see #writeTo(Joiner, PolylineBuffer[], boolean)
+         * @see PolylineStage#FINAL
          */
         private Joiner path;
 
         /**
          * The isolines as a Java2D shape, created by {@link #finish()}.
          * This is the shape to be returned to user for this level after we 
finished to process all cells.
+         *
+         * @see PolylineStage#FINAL
          */
         Shape shape;
 
@@ -688,9 +693,8 @@ final class Tracer {
          * @see Isolines#toRawPath()
          */
         @Debug
-        final void toRawPath(final Path2D appendTo) {
-            final Shape s = (path != null) ? path.build() : shape;
-            if (s != null) appendTo.append(s, false);
+        final void toRawPath(final Map<PolylineStage,Path2D> appendTo) {
+            PolylineStage.FINAL.add(appendTo, (path != null) ? path.snapshot() 
: shape);
             polylineOnLeft.toRawPath(appendTo);
             for (final PolylineBuffer p : polylinesOnTop) {
                 if (p != null) p.toRawPath(appendTo);
diff --git 
a/core/sis-feature/src/test/java/org/apache/sis/internal/processing/isoline/StepsViewer.java
 
b/core/sis-feature/src/test/java/org/apache/sis/internal/processing/isoline/StepsViewer.java
index 0618d21a10..4f540210bf 100644
--- 
a/core/sis-feature/src/test/java/org/apache/sis/internal/processing/isoline/StepsViewer.java
+++ 
b/core/sis-feature/src/test/java/org/apache/sis/internal/processing/isoline/StepsViewer.java
@@ -16,6 +16,8 @@
  */
 package org.apache.sis.internal.processing.isoline;
 
+import java.util.Map;
+import java.util.EnumMap;
 import java.awt.Shape;
 import java.awt.Color;
 import java.awt.Graphics;
@@ -58,7 +60,7 @@ import static org.junit.Assert.*;
  * @module
  */
 @SuppressWarnings("serial")
-public final class StepsViewer extends JComponent implements 
BiConsumer<String,Path2D>, ChangeListener, ActionListener {
+public final class StepsViewer extends JComponent implements 
BiConsumer<String,Isolines>, ChangeListener, ActionListener {
     /**
      * Sets the component to be notified after each row of isolines generated 
from the rendered image.
      * The body of this method is commented-out because {@link 
Isolines#LISTENER} is private and final.
@@ -116,7 +118,13 @@ public final class StepsViewer extends JComponent 
implements BiConsumer<String,P
     /**
      * The isolines to show.
      */
-    private Path2D isolines;
+    private final Map<PolylineStage,Path2D> isolines;
+
+    /**
+     * The colors to associate to the isoline for each stage.
+     * Array indices are {@link PolylineStage#ordinal()} values.
+     */
+    private final Color[] stageColors;
 
     /**
      * Bounds of {@link #isolines}, slightly expanded for making easier to see.
@@ -136,6 +144,10 @@ public final class StepsViewer extends JComponent 
implements BiConsumer<String,P
      */
     @SuppressWarnings("ThisEscapedInObjectConstruction")
     private StepsViewer(final RenderedImage data, final Container pane) {
+        isolines    = new EnumMap<>(PolylineStage.class);
+        stageColors = new Color[] {Color.YELLOW, Color.CYAN, Color.GRAY};
+        setBackground(Color.BLACK);
+        setOpaque(true);
         final double scaleX = (CANVAS_WIDTH  - 2*PADDING) / (double) 
data.getWidth();
         final double scaleY = (CANVAS_HEIGHT - 2*PADDING) / (double) 
data.getHeight();
         sourceToCanvas = new AffineTransform2D(
@@ -186,7 +198,6 @@ public final class StepsViewer extends JComponent 
implements BiConsumer<String,P
         for (final Shape shape : iso.polylines().values()) {
             path.append(shape, false);
         }
-        viewer.accept("Final result", path);
     }
 
     /**
@@ -196,15 +207,18 @@ public final class StepsViewer extends JComponent 
implements BiConsumer<String,P
     protected void paintComponent(final Graphics g) {
         super.paintComponent(g);
         final Graphics2D gh = (Graphics2D) g;
+        gh.setColor(getBackground());
+        gh.fillRect(0, 0, getWidth(), getHeight());
         if (bounds != null) {
             gh.setStroke(new BasicStroke(2));
             gh.setColor(Color.RED);
             gh.draw(bounds);
         }
-        if (isolines != null) {
-            gh.setStroke(new BasicStroke(1));
-            gh.setColor(Color.BLUE);
-            gh.draw(isolines);
+        for (final Map.Entry<PolylineStage,Path2D> entry : 
isolines.entrySet()) {
+            final int stage = entry.getKey().ordinal();
+            gh.setStroke(new BasicStroke(stageColors.length - stage));
+            gh.setColor(stageColors[stage]);
+            gh.draw(entry.getValue());
         }
     }
 
@@ -246,27 +260,48 @@ public final class StepsViewer extends JComponent 
implements BiConsumer<String,P
      * Invoked after a row has been processed during the isoline generation.
      * This is invoked from the main thread (<strong>not</strong> the Swing 
thread).
      *
-     * @param  title   description of current state.
-     * @param  update  new isolines to show.
+     * @param  title      description of current state.
+     * @param  generator  new generator of isolines.
      */
     @Override
-    public void accept(final String title, final Path2D update) {
-        update.transform(sourceToCanvas);
-        final Rectangle b = update.getBounds();
-        b.x      -= PADDING;
-        b.y      -= PADDING;
-        b.width  += PADDING * 2;
-        b.height += PADDING * 2;
+    public void accept(final String title, final Isolines generator) {
+        final Map<PolylineStage, Path2D> paths = generator.toRawPath();
+        for (final Map.Entry<PolylineStage,Path2D> entry : paths.entrySet()) {
+            entry.getValue().transform(sourceToCanvas);
+        }
         try {
             final CountDownLatch c = new CountDownLatch(1);
             EventQueue.invokeLater(() -> {
-                if (isolines != null && equal(isolines.getPathIterator(null), 
update.getPathIterator(null))) {
+                Rectangle b = null;
+                boolean unchanged = true;
+                for (final PolylineStage stage : PolylineStage.values()) {
+                    final Path2D current = isolines.get(stage);
+                    final Path2D update  = paths.get(stage);
+                    if (unchanged && current != update && !(current != null && 
update != null &&
+                            equal(current.getPathIterator(null), 
update.getPathIterator(null))))
+                    {
+                        unchanged = false;
+                    }
+                    if (update == null) {
+                        isolines.remove(stage);
+                    } else {
+                        isolines.put(stage, update);
+                        if (stage == PolylineStage.BUFFER) {
+                            b = update.getBounds();
+                            b.x      -= PADDING;
+                            b.y      -= PADDING;
+                            b.width  += PADDING * 2;
+                            b.height += PADDING * 2;
+                            bounds = b;
+                        }
+                    }
+                }
+                bounds = b;
+                if (unchanged) {
                     stepTitle.setText(title + " (no change)");
                     c.countDown();
                 } else {
                     stepTitle.setText(title);
-                    isolines = update;
-                    bounds = b;
                     repaint();
                     assertNull(blocker);
                     if (next.getModel().isPressed()) {

Reply via email to