Module org.apache.sis.gui
Package org.apache.sis.gui.map

# Class MapCanvas

Object
Canvas
PlanarCanvas
MapCanvas

All Implemented Interfaces:
  `Localized`

Direct Known Subclasses:
  `MapCanvasAWT`

public abstract class MapCanvas extends PlanarCanvas
A canvas for maps to be rendered on screen in a JavaFX application. The map may be an arbitrary JavaFX node, typically an `ImageView` or `Canvas`, which must be supplied by subclasses. This base class provides handlers for keyboard, mouse, track pad or touch screen events such as pans, zooms and rotations. The keyboard actions are:

Keyboard actions
Key | Action
--- | ---
⇨ | Move view to the right
⇦ | Move view to the left
⇧ | Move view to the top
⇩ | Move view to the bottom
⎇ + ⇨ | Rotate clockwise
⎇ + ⇦ | Rotate anticlockwise
Page down | Zoom in
Page up | Zoom out
Home | Reset
Ctrl + above | Above actions as a smaller translation, zoom or rotation

## Subclassing

Implementations need to add at least one JavaFX node in the `floatingPane` list of children. Map rendering involves the following steps:

1. `createRenderer()` is invoked in the JavaFX thread. That method shall take a snapshot of every information needed for performing the rendering in background.
2. `MapCanvas​.Renderer​.render()` is invoked in a background thread. That method creates or updates the nodes to show in this `MapCanvas` but without interacting with the canvas yet.
3. `MapCanvas​.Renderer​.commit(MapCanvas)` is invoked in the JavaFX thread. The nodes prepared by `render()` can be transferred to `floatingPane` in that method.

Since:
  1.1

- ## Property Summary

  Properties
  Type
  Property
  Description
  `final ReadOnlyObjectProperty<Throwable>`
  `error`
  Returns a property giving the exception or error that occurred during last rendering operation.
  `final ReadOnlyBooleanProperty`
  `rendering`
  Returns a property telling whether a rendering is in progress.

- ## Nested Class Summary

  Nested Classes
  Modifier and Type
  Class
  Description
  `protected static class`
  `MapCanvas​.Renderer`
  A snapshot of `MapCanvas` state to render as a map, together with rendering code.

- ## Field Summary

  Fields
  Modifier and Type
  Field
  Description
  `protected final StackPane`
  `fixedPane`
  The pane showing the map and other JavaFX nodes to keep at fixed position regardless pans, zooms or rotations applied on the map.
  `protected final Pane`
  `floatingPane`
  The pane showing the map and any other JavaFX nodes to scale and translate together with the map.

  ### Fields inherited from class PlanarCanvas

  `BIDIMENSIONAL, objectiveToDisplay`

  ### Fields inherited from class Canvas

  `DISPLAY_BOUNDS_PROPERTY, OBJECTIVE_CRS_PROPERTY, OBJECTIVE_TO_DISPLAY_PROPERTY, POINT_OF_INTEREST_PROPERTY`

- ## Constructor Summary

  Constructors
  Constructor
  Description
  `MapCanvas(Locale locale)`
  Creates a new canvas for JavaFX application.

- ## Method Summary

  Modifier and Type
  Method
  Description
  `final void`
  `addPropertyChangeListener(String propertyName, PropertyChangeListener listener)`
  Registers a listener for the property of the given name.
  `protected void`
  `clear()`
  Removes map content and clears all properties of this canvas.
  `protected final void`
  `clearError()`
  Clears the error message in status bar.
  `protected abstract MapCanvas​.Renderer`
  `createRenderer()`
  Invoked in JavaFX thread for creating a renderer to be executed in a background thread.
  `protected void`
  `errorOccurred(Throwable ex)`
  Sets the error property to the given value.
  `final ReadOnlyObjectProperty<Throwable>`
  `errorProperty()`
  Returns a property giving the exception or error that occurred during last rendering operation.
  `protected void`
  `firePropertyChange(PropertyChangeEvent event)`
  Notifies all registered listeners that a property changed its value.
  `protected void`
  `firePropertyChange(String propertyName, Object oldValue, Object newValue)`
  Notifies all registered listeners that a property of the given name changed its value.
  `Envelope`
  `getObjectiveBounds()`
  Returns the data bounds to use for computing the initial "objective to display" transform.
  `protected final boolean`
  `hasPropertyChangeListener(String propertyName)`
  Returns `true` if the given property has at least one listener.
  `protected void`
  `initialize(GridGeometry visibleArea)`
  Sets the objective bounds and/or the zoom level and objective CRS to use for the initial view of data.
  `final void`
  `removePropertyChangeListener(String propertyName, PropertyChangeListener listener)`
  Unregisters a property listener.
  `final ReadOnlyBooleanProperty`
  `renderingProperty()`
  Returns a property telling whether a rendering is in progress.
  `final void`
  `requestRepaint()`
  Requests the map to be rendered again, possibly with new data.
  `void`
  `reset()`
  Resets the map view to its default zoom level and default position with no rotation.
  `protected boolean`
  `runAfterRendering(Runnable task)`
  Registers a task to execute after the background thread finished its current rendering task.
  `void`
  `setObjectiveBounds(Envelope visibleArea)`
  Sets the data bounds to use for computing the initial value of `PlanarCanvas​.objectiveToDisplay`.
  `void`
  `setObjectiveToDisplay(LinearTransform newValue)`
  Sets the conversion from objective CRS to display coordinate system.
  `String`
  `toString()`
  Returns a string representation of this canvas for debugging purposes.
  `void`
  `transformDisplayCoordinates(AffineTransform after)`
  Updates the objective to display transform with the given transform in pixel coordinates.
  `void`
  `transformObjectiveCoordinates(AffineTransform before)`
  Updates the objective to display transform with the given transform in objective coordinates.

  ### Methods inherited from class PlanarCanvas

  `getDisplayBounds`

  ### Methods inherited from class Canvas

  `getDisplayCRS, getGeographicArea, getGridGeometry, getLocale, getObjectiveCRS, getObjectiveToDisplay, getPointOfInterest, getSpatialResolution, setDisplayBounds, setGridGeometry, setObjectiveCRS, setPointOfInterest`

  ### Methods inherited from class Object

  `clone, equals, finalize, getClass, hashCode, notify, notifyAll, wait, wait, wait`

- ## Property Details

  - ### rendering

    public final ReadOnlyBooleanProperty renderingProperty
    Returns a property telling whether a rendering is in progress. This property become `true` when this `MapCanvas` is about to start a background thread for performing a rendering, and is reset to `false` after this `MapCanvas` has been updated with new rendering result.
    See Also:
      - `renderingProperty()`

  - ### error

    public final ReadOnlyObjectProperty<Throwable> errorProperty
    Returns a property giving the exception or error that occurred during last rendering operation. The property value is reset to `null` when a rendering operation completed successfully.
    See Also:
      - `errorProperty()`

- ## Field Details

  - ### floatingPane

    protected final Pane floatingPane
    The pane showing the map and any other JavaFX nodes to scale and translate together with the map. This pane is initially empty; subclasses should add nodes (canvas, images, shapes, texts, etc.) into the `Pane​.getChildren()` list. All children must specify their coordinates in units relative to the pane (absolute layout). Those coordinates can be computed from real world coordinates by `PlanarCanvas​.objectiveToDisplay`.

    This pane contains an `Affine` transform which is updated by user gestures such as pans, zooms or rotations. Visual positions of all children move together in response to user's gesture, thus giving an appearance of pane floating around. Changes in `floatingPane` affine transform are temporary; they are applied for producing immediate visual feedback while the map is recomputed in a background thread. Once calculation is completed and the content of this pane has been updated, the `floatingPane` `Affine` transform is reset to identity.

  - ### fixedPane

    protected final StackPane fixedPane
    The pane showing the map and other JavaFX nodes to keep at fixed position regardless pans, zooms or rotations applied on the map. This pane contains at least the floatingPane (which itself contains the map), but more children (shapes, texts, controls, etc.) can be added by subclasses into the `Pane​.getChildren()` list.

- ## Constructor Details

  - ### MapCanvas

    public MapCanvas(Locale locale)
    Creates a new canvas for JavaFX application.
    Parameters:
      `locale` - the locale to use for labels and some messages, or `null` for default.

- ## Method Details

  - ### initialize

    protected void initialize(GridGeometry visibleArea)
    Sets the objective bounds and/or the zoom level and objective CRS to use for the initial view of data. The `visibleArea` CRS defines the initial objective CRS of this canvas. The `visibleArea` envelope defines the (usually constant) objective bounds of this canvas. In addition if `visibleArea` contains a grid to CRS transform, its inverse will define the initial objective to display transform (which in turn defines the initial viewed area and zoom level).

    This method should be invoked only when new data have been loaded, or when the caller wants to discard any zoom or translation and reset the view to the given bounds. This method does not cause new repaint event; `requestRepaint()` must be invoked by the caller if desired.

    Parameters:
      `visibleArea` - bounding box, objective CRS and or initial zoom level, or `null` if unknown (in which case an identity transform will be set).
    Throws:
      `MismatchedDimensionException` - if the given grid geometry is not two-dimensional.
    Since:
      1.3
    See Also:
      - `setObjectiveBounds(Envelope)`
      - `Canvas​.getGridGeometry()`

  - ### reset

    public void reset()
    Resets the map view to its default zoom level and default position with no rotation. Contrarily to `clear()`, this method does not remove the map content.

  - ### getObjectiveBounds

    public Envelope getObjectiveBounds()
    Returns the data bounds to use for computing the initial "objective to display" transform. This is the value specified by the last call to `setObjectiveBounds(Envelope)`. The coordinate reference system of the returned envelope defines also the CRS which is restored when the `reset()` method is invoked.
    Returns:
      the data bounds to use for computing the initial "objective to display" transform, or `null` if unspecified.
    Since:
      1.3

  - ### setObjectiveBounds

    public void setObjectiveBounds(Envelope visibleArea)
    Sets the data bounds to use for computing the initial value of `PlanarCanvas​.objectiveToDisplay`. Invoking this method also sets the initial objective CRS of this canvas to the CRS of given envelope.

    This method should be invoked only when new data have been loaded, or when the caller wants to discard any zoom or translation and reset the view to the given bounds. This method does not cause new repaint event; `requestRepaint()` must be invoked by the caller if desired.

    Parameters:
      `visibleArea` - bounding box in (new) objective CRS of the initial area to show, or `null` if unknown (in which case an identity transform will be set).
    Throws:
      `MismatchedDimensionException` - if the given envelope is not two-dimensional.
    See Also:
      - `Canvas​.setObjectiveCRS(CoordinateReferenceSystem, DirectPosition)`

  - ### setObjectiveToDisplay

    public void setObjectiveToDisplay(LinearTransform newValue) throws RenderException
    Sets the conversion from objective CRS to display coordinate system. Invoking this method has the effect of changing the viewed area, the zoom level or the rotation of the map. Caller needs to invoke `requestRepaint()` after this method call (this is not done automatically).
    Overrides:
      `setObjectiveToDisplay` in class `Canvas`
    Parameters:
      `newValue` - the new objective to display conversion.
    Throws:
      `IllegalArgumentException` - if given the transform does not have the expected number of dimensions or is not affine.
      `RenderException` - if the objective to display transform cannot be set to the given value for another reason.

  - ### transformObjectiveCoordinates

    public void transformObjectiveCoordinates(AffineTransform before)
    Updates the objective to display transform with the given transform in objective coordinates. This method must be invoked in the JavaFX thread. The visual is updated immediately by transforming the current image, then a more accurate image is prepared in a background thread.

    #### Transform events

    This method fires immediately an "objectiveToDisplay" event with `TransformChangeEvent​.Reason​.INTERIM`. This event does not yet reflect the state of the objective to display transform. At some arbitrary time in the future, another "objectiveToDisplay" event will occur (still in JavaFX thread) with `TransformChangeEvent​.Reason​.DISPLAY_NAVIGATION` (really display, not objective). That event will consolidate all `INTERIM` events that happened since the last non-interim event.
    Overrides:
      `transformObjectiveCoordinates` in class `PlanarCanvas`
    Parameters:
      `before` - coordinate conversion to apply before the current objective to display transform.
    Since:
      1.3
    See Also:
      - `TransformChangeEvent​.getObjectiveChange()`

  - ### transformDisplayCoordinates

    public void transformDisplayCoordinates(AffineTransform after)
    Updates the objective to display transform with the given transform in pixel coordinates. This method must be invoked in the JavaFX thread. The visual is updated immediately by transforming the current image, then a more accurate image is prepared in a background thread.

    #### Transform events

    This method fires immediately an "objectiveToDisplay" event with `TransformChangeEvent​.Reason​.INTERIM`. This event does not yet reflect the state of the objective to display transform. At some arbitrary time in the future, another "objectiveToDisplay" event will occur (still in JavaFX thread) with `TransformChangeEvent​.Reason​.DISPLAY_NAVIGATION`. That event will consolidate all `INTERIM` events that happened since the last non-interim event.
    Overrides:
      `transformDisplayCoordinates` in class `PlanarCanvas`
    Parameters:
      `after` - coordinate conversion to apply after the current objective to display transform.
    Since:
      1.3
    See Also:
      - `TransformChangeEvent​.getDisplayChange()`

  - ### createRenderer

    protected abstract MapCanvas.Renderer createRenderer()
    Invoked in JavaFX thread for creating a renderer to be executed in a background thread. Subclasses shall copy in this method all `MapCanvas` properties that the background thread will need for performing the rendering process.
    Returns:
      rendering process to be executed in background thread, or `null` if there is nothing to paint.

  - ### requestRepaint

    public final void requestRepaint()
    Requests the map to be rendered again, possibly with new data. Invoking this method does not necessarily causes the repaint process to start immediately. The request will be queued and executed at an arbitrary (short) time later.

  - ### renderingProperty

    public final ReadOnlyBooleanProperty renderingProperty()
    Returns a property telling whether a rendering is in progress. This property become `true` when this `MapCanvas` is about to start a background thread for performing a rendering, and is reset to `false` after this `MapCanvas` has been updated with new rendering result.
    Returns:
      a property telling whether a rendering is in progress.

  - ### errorProperty

    public final ReadOnlyObjectProperty<Throwable> errorProperty()
    Returns a property giving the exception or error that occurred during last rendering operation. The property value is reset to `null` when a rendering operation completed successfully.
    Returns:
      a property giving the exception or error that occurred during last rendering operation.

  - ### clearError

    protected final void clearError()
    Clears the error message in status bar.

  - ### errorOccurred

    protected void errorOccurred(Throwable ex)
    Sets the error property to the given value. This method is provided for subclasses that perform processing outside the `MapCanvas​.Renderer`. It does not need to be invoked if the error occurred during the rendering process.

    If the error property already has a value, then the new error will be to the current error as a suppressed exception. The error property is cleared when a rendering operation completed successfully.

    Parameters:
      `ex` - the exception that occurred (cannot be null).

  - ### runAfterRendering

    protected boolean runAfterRendering(Runnable task)
    Registers a task to execute after the background thread finished its current rendering task. This method shall be invoked in JavaFX thread. If there is a rendering in progress at the time this method is invoked, then the given task is queued for execution in JavaFX thread after the rendering finished. Otherwise the given task is executed immediately.

    Exceptions are propagated if the given task has been executed immediately, or logged if execution has been deferred.

    This method is useful for subclasses when modifying the `MapCanvas` state during a rendering process may cause inconsistent state.

    Parameters:
      `task` - the task to execute.
    Returns:
      `true` if the task has been executed immediately, or `false` if it has been queued for later execution.
    Since:
      1.2
    See Also:
      - `renderingProperty()`
      - `Platform​.runLater(Runnable)`

  - ### clear

    protected void clear()
    Removes map content and clears all properties of this canvas.

    #### Usage

    Overriding methods in subclasses should invoke `super​.clear()`. Other methods should generally not invoke this method directly, and use the following code instead:

        runAfterRendering(this::clear);

    See Also:
      - `reset()`
      - `runAfterRendering(Runnable)`

  - ### toString

    public String toString()
    Returns a string representation of this canvas for debugging purposes. This string spans multiple lines.
    Overrides:
      `toString` in class `Object`
    Returns:
      debug string (may change in any future version).
    Since:
      1.3

  - ### addPropertyChangeListener

    public final void addPropertyChangeListener(String propertyName, PropertyChangeListener listener)
    Registers a listener for the property of the given name. The listener will be notified every time that the property of the given name got a new value. If the same listener is registered twice for the same property, then it will be notified twice (this method does not perform duplication checks).
    Parameters:
      `propertyName` - name of the property to listen (should be one of the `*_PROPERTY` constants).
      `listener` - property listener to register.

  - ### removePropertyChangeListener

    public final void removePropertyChangeListener(String propertyName, PropertyChangeListener listener)
    Unregisters a property listener. The given `propertyName` should be the name used during listener registration. If the specified listener is not registered for the named property, then nothing happen. If the listener has been registered twice, then only one registration is removed (one registration will remain).
    Parameters:
      `propertyName` - name of the listened property.
      `listener` - property listener to unregister.

  - ### hasPropertyChangeListener

    protected final boolean hasPropertyChangeListener(String propertyName)
    Returns `true` if the given property has at least one listener.
    Parameters:
      `propertyName` - name of the property to test.
    Returns:
      `true` if the given property has at least one listener.

  - ### firePropertyChange

    protected void firePropertyChange(String propertyName, Object oldValue, Object newValue)
    Notifies all registered listeners that a property of the given name changed its value. The change event source will be `this`. It is caller responsibility to verify that the old and new values are different (this method does not check for equality).
    Parameters:
      `propertyName` - name of the property that changed its value.
      `oldValue` - the old property value (may be `null`).
      `newValue` - the new property value (may be `null`).
    See Also:
      - `PropertyChangeEvent`
      - `PropertyChangeListener`

  - ### firePropertyChange

    protected void firePropertyChange(PropertyChangeEvent event)
    Notifies all registered listeners that a property changed its value. It is caller responsibility to verify that the event source and property name are valid.
    Parameters:
      `event` - the event to forward. Cannot be null.
    See Also:
      - `PropertyChangeEvent`
      - `PropertyChangeListener`