Module org.apache.sis.portrayal
Package org.apache.sis.portrayal

# Class CanvasFollower

Object
CanvasFollower

All Implemented Interfaces:
  `PropertyChangeListener`, `EventListener`, `Disposable`

Direct Known Subclasses:
  `GestureFollower`

public class CanvasFollower extends Object implements PropertyChangeListener, Disposable
A listener of displacements in a source canvas which can reproduce the same displacement in a target canvas. For example if a translation of 100 meters is applied in a source canvas, the same translation (in meters) can be applied in the target canvas. This class does automatically the necessary conversions for taking in account the differences in zoom levels and map projections. For example, a translation of 10 pixels in one canvas may map to a translation of 20 pixels in the other canvas for reproducing the same "real world" translation.

## Listeners

`CanvasFollower` listeners need to be registered explicitly by a call to the `initialize()` method. The `dispose()` convenience method is provided for unregistering all those listeners. The listeners registered by this class implement an unidirectional binding: changes in source are applied on target, but not the converse.

## Multi-threading

This class is not thread-safe. All events should be processed in the same thread.

Since:
  1.3

- ## Field Summary

  Fields
  Modifier and Type
  Field
  Description
  `protected final PlanarCanvas`
  `source`
  The canvas which is the source of zoom, translation or rotation events.
  `protected final PlanarCanvas`
  `target`
  The canvas on which to apply the change of zoom, translation or rotation.

- ## Constructor Summary

  Constructors
  Constructor
  Description
  `CanvasFollower(PlanarCanvas source, PlanarCanvas target)`
  Creates a new listener for synchronizing "objective to display" transform changes between the specified canvas.

- ## Method Summary

  Modifier and Type
  Method
  Description
  `void`
  `dispose()`
  Removes all listeners documented in the `initialize()` method.
  `protected boolean`
  `filter(TransformChangeEvent event)`
  Returns `true` if this listener should replicate the following changes on the target canvas.
  `Optional<MathTransform2D>`
  `getDisplayTransform()`
  Returns the transform from source display coordinates to target display coordinates.
  `boolean`
  `getFollowRealWorld()`
  Returns whether this instance is following changes in "real world" coordinates.
  `Optional<Point2D>`
  `getSourceDisplayPOI()`
  Returns the display coordinates of the Point Of Interest (POI) in source canvas.
  `DirectPosition`
  `getSourceObjectivePOI()`
  Returns the objective coordinates of the Point Of Interest (POI) in source canvas.
  `void`
  `initialize()`
  Registers all listeners needed by this object.
  `boolean`
  `isDisabled()`
  Returns `true` if this object stopped to replicate changes from source canvas to target canvas.
  `void`
  `propertyChange(PropertyChangeEvent event)`
  Invoked when the objective CRS, zoom, translation or rotation changed on a map that we are tracking.
  `void`
  `setDisabled(boolean stop)`
  Sets whether to stop to replicate changes from source canvas to target canvas.
  `void`
  `setFollowRealWorld(boolean real)`
  Sets whether this instance should following changes in "real world" coordinates.
  `protected void`
  `transformDisplayCoordinates(TransformChangeEvent event, AffineTransform after)`
  Invoked by `propertyChange(PropertyChangeEvent)` for updating the transform of the target canvas in display units (typically pixels).
  `protected void`
  `transformedSource(TransformChangeEvent event)`
  Invoked after the source "objective to display" transform has been updated.
  `protected void`
  `transformedTarget(TransformChangeEvent event)`
  Invoked after the target "objective to display" transform has been updated.
  `protected void`
  `transformObjectiveCoordinates(TransformChangeEvent event, AffineTransform before)`
  Invoked by `propertyChange(PropertyChangeEvent)` for updating the transform of the target canvas in units of the objective CRS.

  ### Methods inherited from class Object

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

- ## Field Details

  - ### source

    protected final PlanarCanvas source
    The canvas which is the source of zoom, translation or rotation events.

  - ### target

    protected final PlanarCanvas target
    The canvas on which to apply the change of zoom, translation or rotation.

- ## Constructor Details

  - ### CanvasFollower

    public CanvasFollower(PlanarCanvas source, PlanarCanvas target)
    Creates a new listener for synchronizing "objective to display" transform changes between the specified canvas. This is a unidirectional binding: changes in source are applied on target, but not the converse.

    Caller needs to register listeners by a call to the `initialize()` method. This is not done automatically by this constructor for allowing users to control when to start listening to changes.

    Parameters:
      `source` - the canvas which is the source of zoom, pan or rotation events.
      `target` - the canvas on which to apply the changes of zoom, pan or rotation.

- ## Method Details

  - ### initialize

    public void initialize()
    Registers all listeners needed by this object. This method must be invoked at least once after construction, but not necessarily immediately after (it is okay to defer until first needed). The default implementation registers the following listeners:

        source.addPropertyChangeListener(PlanarCanvas.OBJECTIVE_CRS_PROPERTY, this);
        target.addPropertyChangeListener(PlanarCanvas.OBJECTIVE_CRS_PROPERTY, this);
        source.addPropertyChangeListener(PlanarCanvas.OBJECTIVE_TO_DISPLAY_PROPERTY, this);
        target.addPropertyChangeListener(PlanarCanvas.OBJECTIVE_TO_DISPLAY_PROPERTY, this);

    This method is idempotent (it is okay to invoke it twice).
    See Also:
      - `dispose()`

  - ### isDisabled

    public boolean isDisabled()
    Returns `true` if this object stopped to replicate changes from source canvas to target canvas. If `true`, this object continues to listen to changes in order to keep its state consistent, but does not replicate those changes on the target canvas.

    A non-initialized object is considered disabled.

    Returns:
      whether this object stopped to replicate changes from source canvas to target canvas.

  - ### setDisabled

    public void setDisabled(boolean stop)
    Sets whether to stop to replicate changes from source canvas to target canvas. It does not stop this object to listen to events, because it is necessary for keeping its state consistent.
    Parameters:
      `stop` - `true` for stopping to replicate changes from source canvas to target canvas.

  - ### getFollowRealWorld

    public boolean getFollowRealWorld()
    Returns whether this instance is following changes in "real world" coordinates. If `true` (the default value), then changes applied on the source canvas and converted into changes to apply on the `target` canvas in such a way that the two canvas got the same translations in real world units. It may result in a different amount of pixels is the two canvas have different zoom level, or a different direction if a canvas is rotated relatively to the other canvas.
    Returns:
      whether this instance is following changes in "real world" coordinates.

  - ### setFollowRealWorld

    public void setFollowRealWorld(boolean real)
    Sets whether this instance should following changes in "real world" coordinates. The default value is `true`. If this is set to `false`, then the same changes in pixel coordinates will be applied on canvas regardless the difference in rotation or zoom level.
    Parameters:
      `real` - whether this instance should following changes in "real world" coordinates.

  - ### getSourceObjectivePOI

    public DirectPosition getSourceObjectivePOI()
    Returns the objective coordinates of the Point Of Interest (POI) in source canvas. This information is used when the source and target canvases do not use the same CRS. Changes in "real world" coordinates on the target canvas are guaranteed to reflect the changes in "real world" coordinates of the source canvas at that location only. At all other locations, the "real world" coordinate changes may differ because of map projection deformations.

    The default implementation computes the value from `getSourceDisplayPOI()` if present, or fallback on `source​.getPointOfInterest(true)` otherwise. Subclasses can override this method for using a different point of interest.

    The CRS associated to the position shall be `Canvas​.getObjectiveCRS()`. For performance reason, this is not verified by this `CanvasFollower` class.

    Returns:
      objective coordinates in source canvas where displacements, zooms and rotations applied on the source canvas should be mirrored exactly on the target canvas.
    See Also:
      - `Canvas​.getPointOfInterest(boolean)`

  - ### getSourceDisplayPOI

    public Optional<Point2D> getSourceDisplayPOI()
    Returns the display coordinates of the Point Of Interest (POI) in source canvas. This method provides the same information than `getSourceObjectivePOI()`, but in units that are more convenient for expressing the location of mouse cursor for example.

    The default implementation returns an empty value.

    Returns:
      display coordinates in source canvas where displacements, zooms and rotations applied on the source canvas should be mirrored exactly on the target canvas.

  - ### getDisplayTransform

    public Optional<MathTransform2D> getDisplayTransform()
    Returns the transform from source display coordinates to target display coordinates. This transform may change every time that a zoom; translation or rotation is applied on at least one canvas. The transform may be absent if an error prevent to compute it, for example is no coordinate operation has been found between the objective CRS of the source and target canvases.
    Returns:
      transform from source display coordinates to target display coordinates.

  - ### propertyChange

    public void propertyChange(PropertyChangeEvent event)
    Invoked when the objective CRS, zoom, translation or rotation changed on a map that we are tracking. If the event is an instance of `TransformChangeEvent`, then this method applies the same change on the target canvas.

    This method delegates part of its work to the following methods, which can be overridden for altering the changes:

    - `transformObjectiveCoordinates(TransformChangeEvent, AffineTransform)` if following real world coordinates.
    - `transformDisplayCoordinates(TransformChangeEvent, AffineTransform)` if following pixel coordinates instead of real world.
    - `transformedSource(TransformChangeEvent)` after the change has been applied on source.
    - `transformedTarget(TransformChangeEvent)` after the change has been applied on target.
    Specified by:
      `propertyChange` in interface `PropertyChangeListener`
    Parameters:
      `event` - a change in the canvas that this listener is tracking.

  - ### filter

    protected boolean filter(TransformChangeEvent event)
    Returns `true` if this listener should replicate the following changes on the target canvas. The default implementation returns `true` if the transform reason is `TransformChangeEvent​.Reason​.OBJECTIVE_NAVIGATION` or `TransformChangeEvent​.Reason​.DISPLAY_NAVIGATION`.
    Parameters:
      `event` - a transform change event that occurred on the source canvas.
    Returns:
      whether to replicate that change on the target canvas.

  - ### transformObjectiveCoordinates

    protected void transformObjectiveCoordinates(TransformChangeEvent event, AffineTransform before)
    Invoked by `propertyChange(PropertyChangeEvent)` for updating the transform of the target canvas in units of the objective CRS. The target canvas is updated by this method as if the given transform was applied before its current objective to display transform.

    The default implementation delegates to `PlanarCanvas​.transformObjectiveCoordinates(AffineTransform)`. Subclasses can override if they need to transform additional data.

    Parameters:
      `event` - the change in the source canvas.
      `before` - the change to apply on the target canvas, in unit of objective CRS.
    See Also:
      - `PlanarCanvas​.transformObjectiveCoordinates(AffineTransform)`

  - ### transformDisplayCoordinates

    protected void transformDisplayCoordinates(TransformChangeEvent event, AffineTransform after)
    Invoked by `propertyChange(PropertyChangeEvent)` for updating the transform of the target canvas in display units (typically pixels). The target canvas is updated by this method as if the given transform was applied after its current objective to display transform.

    The default implementation delegates to `PlanarCanvas​.transformDisplayCoordinates(AffineTransform)`. Subclasses can override if they need to transform additional data.

    Parameters:
      `event` - the change in the source canvas.
      `after` - the change to apply on the target canvas, in display units (typically pixels).
    See Also:
      - `PlanarCanvas​.transformDisplayCoordinates(AffineTransform)`

  - ### transformedSource

    protected void transformedSource(TransformChangeEvent event)
    Invoked after the source "objective to display" transform has been updated. This method is invoked automatically by `propertyChange(PropertyChangeEvent)`. The default implementation does nothing. Subclasses can override if they need to transform additional data.
    Parameters:
      `event` - the change which has been applied on the source canvas.

  - ### transformedTarget

    protected void transformedTarget(TransformChangeEvent event)
    Invoked after the target "objective to display" transform has been updated. This method is invoked automatically by `propertyChange(PropertyChangeEvent)`. The default implementation does nothing. Subclasses can override if they need to transform additional data.
    Parameters:
      `event` - the change which has been applied on the target canvas.

  - ### dispose

    public void dispose()
    Removes all listeners documented in the `initialize()` method. This method should be invoked when `CanvasFollower` is no longer needed, in order to avoid memory leak.
    Specified by:
      `dispose` in interface `Disposable`
    See Also:
      - `initialize()`