This is an automated email from the ASF dual-hosted git repository.
asf-gitbox-commits 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 d56a95f9fa Status bar should rewrite coordinates with new accuracy
after zoom event.
d56a95f9fa is described below
commit d56a95f9fa6a71d76e95a419afa2c3c53d7231d0
Author: Martin Desruisseaux <[email protected]>
AuthorDate: Mon Apr 20 13:21:58 2026 +0200
Status bar should rewrite coordinates with new accuracy after zoom event.
---
.../main/org/apache/sis/gui/map/MapCanvas.java | 45 ++++++++---
.../main/org/apache/sis/gui/map/MapMenu.java | 1 +
.../sis/gui/map/RenderingCompletedEvent.java | 86 ++++++++++++++++++++++
.../main/org/apache/sis/gui/map/RenderingTask.java | 16 +++-
.../main/org/apache/sis/gui/map/StatusBar.java | 57 ++++++++------
.../org/apache/sis/gui/map/ValuesUnderCursor.java | 1 +
6 files changed, 169 insertions(+), 37 deletions(-)
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 0129f3d0e2..177e7ab95d 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
@@ -540,8 +540,8 @@ public abstract class MapCanvas extends PlanarCanvas {
protected StaticGraphics unique() {
final ObservableList<Node> siblings = floatingPane.getChildren();
for (int i = siblings.size(); --i >= 0;) {
- if (siblings.get(i) instanceof EvanescentPane pane) {
- final StaticGraphics other = pane.owner;
+ if (siblings.get(i) instanceof EvanescentPane graphics) {
+ final StaticGraphics other = graphics.owner;
if (other.getClass() == getClass()
&& Objects.equals(objectiveCRS, other.objectiveCRS)
&&
objectiveToDisplay.equals(other.objectiveToDisplay))
@@ -864,11 +864,17 @@ public abstract class MapCanvas extends PlanarCanvas {
/**
* Creates and registers a new handler for showing a contextual menu
in the enclosing canvas.
* It is caller responsibility to ensure that this method is invoked
only once.
+ * Callers shall invoke {@link #registerListeners()} after this
constructor.
*/
- @SuppressWarnings("this-escape")
MenuHandler(final ContextMenu menu) {
super(getDisplayCRS());
this.menu = menu;
+ }
+
+ /**
+ * Registers the listeners after construction.
+ */
+ final void registerListeners() {
fixedPane.setOnMousePressed (this);
fixedPane.setOnMouseReleased(this); // As recommended by
MouseEvent.isPopupTrigger().
}
@@ -1426,7 +1432,7 @@ public abstract class MapCanvas extends PlanarCanvas {
}
} catch (TransformException | RenderException ex) {
restoreCursorAfterPaint();
- isRendering.set(false);
+ fireRenderingCompletedEvent(null);
errorOccurred(ex);
return;
}
@@ -1460,10 +1466,10 @@ public abstract class MapCanvas extends PlanarCanvas {
isCursorChangeScheduled = true;
}
} else {
- if (!hasError) {
+ if (!hasError) { // Confirmation that the error message is not
actual anymore.
clearError();
}
- isRendering.set(false);
+ fireRenderingCompletedEvent(null);
restoreCursorAfterPaint();
}
}
@@ -1566,7 +1572,7 @@ public abstract class MapCanvas extends PlanarCanvas {
/*
* At this point the rendering is completed. If some error occurred,
report them.
*/
- isRendering.set(false);
+ fireRenderingCompletedEvent(changeInProgress);
final Throwable ex = task.getException();
if (ex != null) {
errorOccurred(ex);
@@ -1581,9 +1587,9 @@ public abstract class MapCanvas extends PlanarCanvas {
if (t != null) try {
afterRendering = null;
t.run();
- } catch (Exception e) {
+ } catch (RuntimeException e) {
// `runAfterRendering(…)` is the documented method providing this
feature.
- unexpectedException("runAfterRendering", e);
+ Logging.recoverableException(LOGGER, MapCanvas.class,
"runAfterRendering", e);
}
}
@@ -1680,6 +1686,18 @@ public abstract class MapCanvas extends PlanarCanvas {
}
}
+ /**
+ * Sets {@link #isRendering} to {@code false} and notifies the listeners
that the rendering is completed.
+ * The {@code change} argument is the value of {@link
RenderingTask#changeInProgress}, or {@code null} if
+ * the rendering failed.
+ *
+ * @param change the change in the transform between previous rendering
and new rendering, or {@code null}.
+ */
+ private void fireRenderingCompletedEvent(final Transform change) {
+ isRendering.set(false);
+ firePropertyChange(new RenderingCompletedEvent(this, change));
+ }
+
/**
* Returns a property telling whether a rendering is in progress. This
property become {@code true}
* when this {@code MapCanvas} is about to start a background thread for
performing a rendering, and
@@ -1800,8 +1818,13 @@ public abstract class MapCanvas extends PlanarCanvas {
afterRendering = () -> {
try {
before.run();
- } catch (Exception e) {
- unexpectedException("runAfterRendering", e);
+ } catch (RuntimeException e) {
+ try {
+ task.run();
+ } catch (RuntimeException s) {
+ e.addSuppressed(s);
+ }
+ throw e;
}
task.run();
};
diff --git
a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/map/MapMenu.java
b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/map/MapMenu.java
index 2c7010f7e7..9357ece73a 100644
--- a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/map/MapMenu.java
+++ b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/map/MapMenu.java
@@ -120,6 +120,7 @@ public class MapMenu extends ContextMenu {
defined |= mask;
if (menuHandler == null) {
menuHandler = canvas.new MenuHandler(this);
+ menuHandler.registerListeners();
}
return menuHandler;
}
diff --git
a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/map/RenderingCompletedEvent.java
b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/map/RenderingCompletedEvent.java
new file mode 100644
index 0000000000..4d074232c7
--- /dev/null
+++
b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/map/RenderingCompletedEvent.java
@@ -0,0 +1,86 @@
+/*
+ * 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.gui.map;
+
+import java.beans.PropertyChangeEvent;
+import javafx.scene.transform.Transform;
+import javafx.geometry.Point2D;
+
+
+/**
+ * Event sent when a rendering finished, either successfully or with errors.
+ *
+ * <h2><abbr>API</abbr> design note</h2>
+ * This event is redundant with {@link MapCanvas#renderingProperty()}, but
nevertheless added because we need
+ * to provide the {@linkplain #change} information, which is not easily
representable as an old a new value.
+ * Because of this redundancy, and because the class is not serializable, we
do not provide this event in the
+ * public <abbr>API</abbr> yet. We should try harder to retrofit in existing
<abbr>API</abbr>.
+ *
+ * @author Martin Desruisseaux (Geomatys)
+ *
+ * @see MapCanvas#renderingProperty()
+ * @see MapCanvas#runAfterRendering(Runnable)
+ */
+@SuppressWarnings("serial") // Not intended to be serialized.
+final class RenderingCompletedEvent extends PropertyChangeEvent {
+ /**
+ * Name of this property event.
+ */
+ static final String NAME = "isRendering";
+
+ /**
+ * The change in the "objective to display" transform between the previous
rendering and the new rendering.
+ * This is a snapshot of {@link MapCanvas#transform} at the time when
{@link RenderingTask} started its work.
+ * This field is {@code null} if the rendering was interrupted by an error
before completion.
+ *
+ * <p>If this field is non-null, then this change has already been
appended to the {@link MapCanvas}
+ * "objective to display" transform at the time when this {@code
RenderingCompletedEvent} is fired.
+ * Note however that the "objective to display" transform may also contain
additional changes
+ * if the user continued to navigate on the map (zooms and pans) after the
rendering.</p>
+ *
+ * @see RenderingTask#changeInProgress
+ */
+ private final Transform change;
+
+ /**
+ * Creates a new event for a change in the "objective to display"
transform between two renderings.
+ *
+ * @param source the canvas that fired the event.
+ * @param change the change in the transform between previous rendering
and new rendering, or {@code null}.
+ * @throws IllegalArgumentException if {@code source} is {@code null}.
+ */
+ public RenderingCompletedEvent(final MapCanvas source, final Transform
change) {
+ super(source, NAME, Boolean.TRUE, Boolean.FALSE);
+ this.change = change;
+ }
+
+ /**
+ * Transforms a display coordinate from the old rendering space to the new
rendering space.
+ * If the change specified at construction time was {@code null}, then
this method returns {@code null}.
+ *
+ * @param x the <var>x</var> coordinate in display units of the old
rendering.
+ * @param y the <var>y</var> coordinate in display units of the old
rendering.
+ * @return the (<var>x</var>, <var>y</var>) coordinates in display units
of the new rendering, or {@code null}.
+ */
+ public final Point2D updateDisplayCoordinates(final double x, final double
y) {
+ if (change != null) {
+ return change.transform(x, y);
+ } else {
+ return null;
+ }
+ }
+}
diff --git
a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/map/RenderingTask.java
b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/map/RenderingTask.java
index 6cb9a6a1f2..8e7d67c903 100644
---
a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/map/RenderingTask.java
+++
b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/map/RenderingTask.java
@@ -19,6 +19,7 @@ package org.apache.sis.gui.map;
import javafx.concurrent.Task;
import javafx.scene.transform.Affine;
import javafx.scene.transform.Transform;
+import javafx.scene.transform.Translate;
/**
@@ -45,13 +46,22 @@ abstract class RenderingTask<V> extends Task<V> {
}
/**
- * Takes a copy of the given transform.
+ * Takes a copy of the given transform. This method may use an
implementation simpler than {@link Affine},
+ * such as {@link Translate}, because the transform stored by this method
may become part of the transform
+ * chain of graphics such as {@link MapCanvas.StaticGraphics}.
*
* @param transform value of {@link MapCanvas#transform}.
*/
final void setChangeInProgress(final Affine transform) {
- // TODO: select a simpler transform implementation when possible.
- changeInProgress = new Affine(transform);
+ if (transform.getMxx() == 1 && transform.getMxy() == 0 &&
transform.getMxz() == 0 &&
+ transform.getMyx() == 0 && transform.getMyy() == 1 &&
transform.getMyz() == 0 &&
+ transform.getMzx() == 0 && transform.getMzy() == 0 &&
transform.getMzz() == 1)
+ {
+ // Pans are a very frequent operations.
+ changeInProgress = new Translate(transform.getTx(),
transform.getTy(), transform.getTz());
+ } else {
+ changeInProgress = new Affine(transform);
+ }
}
/**
diff --git
a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/map/StatusBar.java
b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/map/StatusBar.java
index 1336fc9505..61a9603443 100644
--- a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/map/StatusBar.java
+++ b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/map/StatusBar.java
@@ -632,10 +632,10 @@ public class StatusBar extends Widget implements
EventHandler<MouseEvent> {
lowestAccuracy.bind(canvas.positionalAccuracyProperty());
sampleValuesProvider.set(ValuesUnderCursor.create(canvas));
canvas.errorProperty().addListener((p,o,n) -> setRenderingError(n));
- canvas.renderingProperty().addListener((p,o,n) -> {
- if (!n) applyCanvasGeometry(); // Apply only after completion
of the background rendering task.
+ canvas.addPropertyChangeListener(RenderingCompletedEvent.NAME, (event)
-> {
+ displaySpaceChanged((RenderingCompletedEvent) event);
});
- applyCanvasGeometry();
+ displaySpaceChanged(null);
if (canvas.getObjectiveCRS() != null) {
registerMouseListeners();
} else {
@@ -676,22 +676,31 @@ public class StatusBar extends Widget implements
EventHandler<MouseEvent> {
}
/**
- * Invoked when {@link MapCanvas} completed its rendering. This method sets
- * {@link StatusBar#localToObjectiveCRS} to the inverse of {@link
MapCanvas#objectiveToDisplay}.
- * It assumes that even if the JavaFX local coordinates and {@link
#localToPositionCRS} transform
- * changed, the "real world" coordinates under the mouse cursor is still
the same. This assumption
- * should be true if this listener is notified as a result of zoom,
translation or rotation events.
+ * Invoked when {@link MapCanvas} completed its rendering, either
successfully or with an error.
+ * This method sets {@link #localToObjectiveCRS} to the inverse of {@link
MapCanvas#objectiveToDisplay}.
+ *
+ * The geospatial coordinates under the mouse cursor are usually unchanged
if this listener is notified as a
+ * result of zoom or pan. But the number of fraction digits used for
formatting the same coordinates may have
+ * changed, because the pixels on the screen may have a different
resolution after a zoom (or even, sometime,
+ * after a pan). Reformatting the geospatial coordinates requires the
JavaFX local coordinates of the mouse,
+ * which may have changed because of the change in the {@link
#localToPositionCRS} transform. We don't have
+ * an <abbr>API</abbr> for asking again the mouse coordinates, so the best
we can do is to try to reuse
+ * {@link #lastX} and {@link #lastY}.
+ *
+ * @param event contains the change in the display space of the {@link
MapCanvas}.
*/
- private void applyCanvasGeometry() {
+ private void displaySpaceChanged(final RenderingCompletedEvent event) {
+ final Point2D point = (event != null) ?
event.updateDisplayCoordinates(lastX, lastY) : null;
try {
apply(canvas.getGridGeometry());
- /*
- * Do not hide `position` since we assume that "real world"
coordinates are still valid.
- * Do not try to rewrite position neither since `lastX` and
`lastY` are not valid anymore.
- */
} catch (RenderException e) {
setRenderingError(e);
}
+ if (point != null) {
+ lastX = point.getX();
+ lastY = point.getY();
+ }
+ rewritePosition(null);
}
/**
@@ -765,11 +774,10 @@ public class StatusBar extends Widget implements
EventHandler<MouseEvent> {
/**
* Implementation of {@link #applyCanvasGeometry(GridGeometry)} without
changing {@link #position} visibility state.
- * Invoking this method usually invalidates the coordinates shown in this
status bar. The new coordinates cannot be
- * easily recomputed because the {@link #lastX} and {@link #lastY} values
may not be valid anymore, as a result of
- * possible changes in JavaFX local coordinate system. Consequently, the
coordinates should be temporarily hidden
- * until a new {@link MouseEvent} gives us the new local coordinates,
unless this method is invoked in a context
- * where we know that the "real world" coordinates should be the same even
if local coordinates changed.
+ * This method sets the {@link #lastX} and {@link #lastY} fields to NaN
because these coordinates become invalid as
+ * a consequence of the change in the {@link #localToPositionCRS}
transform. However, the {@linkplain #position} is
+ * not hidden because this method may be invoked in a context where we
know that the "real world" coordinates should
+ * be the same even if local coordinates changed.
*
* @param geometry geometry of the coverage shown in {@link MapCanvas},
or {@code null}.
*/
@@ -1023,8 +1031,8 @@ public class StatusBar extends Widget implements
EventHandler<MouseEvent> {
}
/**
- * Invoked after a new reference system has been set. This method rewrites
the coordinates
- * on the assumption that {@link #lastX} and {@link #lastY} are still
valid.
+ * Rewrites the coordinates on the assumption that {@link #lastX} and
{@link #lastY} are still valid.
+ * This method is invoked after a new reference system has been set or
after a navigation event.
*
* @param current the local coordinates used for current text, or {@code
null} if not valid.
*/
@@ -1326,10 +1334,13 @@ public class StatusBar extends Widget implements
EventHandler<MouseEvent> {
}
/**
- * Returns the coordinates given to the last call to {@link
#setLocalCoordinates(double, double)},
- * or an empty value if those coordinates are not visible.
+ * Returns the pixel coordinates from which the geospatial coordinates
have been computed. The returned
+ * point contains the coordinates given in the last call to {@link
#setLocalCoordinates(double, double)}.
+ * Note that the latter call may have been caused by a user an action such
as a mouse displacement,
+ * not necessarily an explicit call from the application code.
+ * If the coordinates are not currently shown in the status bar, then this
method returns an empty value.
*
- * @return the local coordinates currently shown in the status bar.
+ * @return the local coordinates which are the sources of the coordinates
currently shown in the status bar.
*/
public Optional<Point2D> getLocalCoordinates() {
if (isPositionVisible() && !Double.isNaN(lastX) &&
!Double.isNaN(lastY)) {
diff --git
a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/map/ValuesUnderCursor.java
b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/map/ValuesUnderCursor.java
index ace282bc0f..5c03b0b831 100644
---
a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/map/ValuesUnderCursor.java
+++
b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/map/ValuesUnderCursor.java
@@ -344,6 +344,7 @@ public abstract class ValuesUnderCursor {
* Invoked when an exception occurred while computing values.
*/
final void setError(final Throwable e) {
+ @SuppressWarnings("LocalVariableHidesMemberVariable")
final StatusBar owner = this.owner;
if (owner != null) {
owner.setSampleValues(owner.cause(e));