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

# Class Canvas

Object
Canvas

All Implemented Interfaces:
  `Localized`

Direct Known Subclasses:
  `PlanarCanvas`

public class Canvas extends Object implements Localized
Common abstraction for implementations that manage the display and user manipulation of spatial graphic elements. This base class makes no assumption about the geometry of the display device (e.g. flat video monitor using Cartesian coordinate system, or planetarium dome using spherical coordinate system).

This `Canvas` base class does not draw anything by itself. Subclasses are responsible for drawing graphic elements. The visual contents are usually geographic located symbols, features or images, but some implementations can also manage non-geographic elements like a map scale.

A `Canvas` manages four fundamental properties:

- The coordinate reference system to use for displaying data.
- The location of data to display in all dimensions, including the dimensions not shown by the display device (for example time).
- The size of the display device, in units of the display coordinate system (typically pixels).
- The conversion from the Coordinate Reference System to the display coordinate system.

Those properties are explained in more details below. Other information, for example the geographic bounding box of the data shown on screen, are inferred from above properties.

## Coordinate Reference Systems

There is three Coordinate Reference Systems involved in the rendering of geospatial data:

1. The data CRS is specific to the data to be displayed. It may be anything convertible to the objective CRS. Different graphic elements may use different data CRS, potentially with a different number of dimensions.
2. The objective CRS is the common CRS in which all data are converted before to be displayed. If the objective CRS involves a map projection, it determines the deformation of shapes that user will see on the display device. The objective CRS should have the same number of dimensions than the display device (often 2). Its domain of validity should be wide enough for encompassing all data. The `CRS​.suggestCommonTarget(…)` method may be helpful for choosing an objective CRS from a set of data CRS.
3. The display CRS is the coordinate system of the display device. The conversion from objective CRS to display CRS should be an affine transform with a scale, a translation and optionally a rotation. This conversion changes every time that the user zooms or scrolls on viewed data.

## Location of data to display

In addition of above-cited Coordinate Reference Systems, a `Canvas` also contains a point of interest. The point of interest is often, but not necessarily, at the center of display area. It defines the position where resolutions will be computed, and the position to keep fixed when scales and rotations are applied.

The point of interest can be expressed in any CRS; it does not need to be the objective CRS or the CRS of any data. However, the CRS of that point must have enough dimensions for being convertible to the CRS of all data. This rule implies that the number of dimensions of the point of interest is equal or greater than the highest number of dimensions found in data. The purpose is not only to specify which point to show in (typically) the center of the display area, but also to specify which slice to select in all dimensions not shown by the display device.

### Example

If some data have (x,y,z) dimensions and other data have (x,y,t) dimensions, then the point of interest shall contain coordinate values for at least all of the (x,y,z,t) dimensions (i.e. it must be 4-dimensional, even if all data in this example are 3-dimensional). If the display device is a two-dimensional screen showing map in the (x,y) dimensions (horizontal plane), then the point of interest defines the z value (elevation or depth) and the t value (date and time) of the slice to show.

## Display device size

The geographic extent of data to be rendered is constrained by the zoom level and the display device size. The display size is given by `getDisplayBounds()` as an envelope having the number of dimensions of the display device. The display bounds is usually given in pixel units, but other units such as `Units​.POINT` are also authorized. The zoom level is given indirectly by the `getObjectiveToDisplay()` transform. The display device may have a wraparound axis, for example in the spherical coordinate system of a planetarium.

## Multi-threading

`Canvas` is not thread-safe. Synchronization, if desired, must be done by the caller. Another common strategy is to interact with `Canvas` from a single thread, for example the Swing or JavaFX event queue.

Since:
  1.1

- ## Field Summary

  Fields
  Modifier and Type
  Field
  Description
  `static final String`
  `DISPLAY_BOUNDS_PROPERTY`
  The "displayBounds" property name, used for notifications about changes in bounds of display device.
  `static final String`
  `OBJECTIVE_CRS_PROPERTY`
  The "objectiveCRS" property name, used for notifications about changes in objective CRS.
  `static final String`
  `OBJECTIVE_TO_DISPLAY_PROPERTY`
  The "objectiveToDisplay" property name, used for notifications about changes in objective to display conversion.
  `static final String`
  `POINT_OF_INTEREST_PROPERTY`
  The "pointOfInterest" property name, used for notifications about changes in point of interest.

- ## Constructor Summary

  Constructors
  Modifier
  Constructor
  Description
  `protected`
  `Canvas(EngineeringCRS displayCRS, Locale locale)`
  Creates a new canvas for a display device using the given coordinate reference system.

- ## 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`
  `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`
  `getDisplayBounds()`
  Returns the size and location of the display device.
  `final EngineeringCRS`
  `getDisplayCRS()`
  Returns the Coordinate Reference System of the display device.
  `Optional<GeographicBoundingBox>`
  `getGeographicArea()`
  Returns the geographic bounding box encompassing the area shown on the display device.
  `GridGeometry`
  `getGridGeometry()`
  Returns canvas properties (CRS, display bounds, conversion) encapsulated in a grid geometry.
  `Locale`
  `getLocale()`
  Returns the locale used for texts or for producing some error messages.
  `CoordinateReferenceSystem`
  `getObjectiveCRS()`
  Returns the Coordinate Reference System in which all data are transformed before displaying.
  `LinearTransform`
  `getObjectiveToDisplay()`
  Returns the (usually affine) conversion from objective CRS to display coordinate system.
  `DirectPosition`
  `getPointOfInterest(boolean objective)`
  Returns the coordinates of a point considered representative of the data.
  `OptionalDouble`
  `getSpatialResolution()`
  Returns an estimation of the resolution (in metres) at the point of interest.
  `protected final boolean`
  `hasPropertyChangeListener(String propertyName)`
  Returns `true` if the given property has at least one listener.
  `final void`
  `removePropertyChangeListener(String propertyName, PropertyChangeListener listener)`
  Unregisters a property listener.
  `void`
  `setDisplayBounds(Envelope newValue)`
  Sets the size and location of the display device.
  `void`
  `setGridGeometry(GridGeometry newValue)`
  Sets canvas properties from the given grid geometry.
  `void`
  `setObjectiveCRS(CoordinateReferenceSystem newValue, DirectPosition anchor)`
  Sets the Coordinate Reference System in which all data are transformed before displaying.
  `void`
  `setObjectiveToDisplay(LinearTransform newValue)`
  Sets the conversion from objective CRS to display coordinate system.
  `void`
  `setPointOfInterest(DirectPosition newValue)`
  Sets the coordinates of a representative point inside the data bounding box.

  ### Methods inherited from class Object

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

- ## Field Details

  - ### OBJECTIVE_CRS_PROPERTY

    public static final String OBJECTIVE_CRS_PROPERTY
    The "objectiveCRS" property name, used for notifications about changes in objective CRS. The objective CRS is the Coordinate Reference System in which all data are transformed before displaying. Its number of dimension is the determined by the display device (two for flat screens). Associated values are instances of `CoordinateReferenceSystem`.
    See Also:
      - `getObjectiveCRS()`
      - `setObjectiveCRS(CoordinateReferenceSystem, DirectPosition)`
      - `addPropertyChangeListener(String, PropertyChangeListener)`
      - Constant Field Values

  - ### OBJECTIVE_TO_DISPLAY_PROPERTY

    public static final String OBJECTIVE_TO_DISPLAY_PROPERTY
    The "objectiveToDisplay" property name, used for notifications about changes in objective to display conversion. This conversion maps coordinates in the objective CRS to coordinates in the display CRS. Associated values are instances of `LinearTransform`. The event class is the `TransformChangeEvent` specialization.
    See Also:
      - `getObjectiveToDisplay()`
      - `setObjectiveToDisplay(LinearTransform)`
      - `addPropertyChangeListener(String, PropertyChangeListener)`
      - Constant Field Values

  - ### DISPLAY_BOUNDS_PROPERTY

    public static final String DISPLAY_BOUNDS_PROPERTY
    The "displayBounds" property name, used for notifications about changes in bounds of display device. It may be for example changes in the size of the window were data are shown. Associated values are instances of `Envelope`.
    See Also:
      - `getDisplayBounds()`
      - `setDisplayBounds(Envelope)`
      - `addPropertyChangeListener(String, PropertyChangeListener)`
      - Constant Field Values

  - ### POINT_OF_INTEREST_PROPERTY

    public static final String POINT_OF_INTEREST_PROPERTY
    The "pointOfInterest" property name, used for notifications about changes in point of interest. The point of interest defines the location of a representative point, typically (but not necessarily) in the center of the data bounding box. It defines also the slice coordinate values in all dimensions beyond the ones shown by the device. Associated values are instances of `DirectPosition`.
    See Also:
      - `getPointOfInterest(boolean)`
      - `setPointOfInterest(DirectPosition)`
      - `addPropertyChangeListener(String, PropertyChangeListener)`
      - Constant Field Values

- ## Constructor Details

  - ### Canvas

    protected Canvas(EngineeringCRS displayCRS, Locale locale)
    Creates a new canvas for a display device using the given coordinate reference system. The display CRS of a canvas cannot be changed after construction. Its coordinate system depends on the display device shape (for example a two-dimensional Cartesian coordinate system for flat screens, or a polar or spherical coordinate system for planetarium domes). The axis units of measurement are typically (but not necessarily) `Units​.PIXEL` for Cartesian coordinate systems, with `Units​.DEGREE` in polar, cylindrical or spherical coordinate systems.
    Parameters:
      `displayCRS` - the coordinate system of the display device.
      `locale` - the locale to use for labels and some messages, or `null` for default.

- ## Method Details

  - ### getLocale

    public Locale getLocale()
    Returns the locale used for texts or for producing some error messages. May be `null` if no locale has been specified, in which case the system default should be used.
    Specified by:
      `getLocale` in interface `Localized`
    Returns:
      the locale for messages, or `null` if not explicitly defined.

  - ### getDisplayCRS

    public final EngineeringCRS getDisplayCRS()
    Returns the Coordinate Reference System of the display device. The axis units of measurement are typically (but not necessarily) `Units​.PIXEL` for Cartesian coordinate systems, with `Units​.DEGREE` in polar, cylindrical or spherical coordinate systems. The coordinate system may have a wraparound axis for some "exotic" display devices (e.g. planetarium dome).

    Note that the `CRS​.findOperation(…)` static method can generally not handle this display CRS. To apply coordinate operations on display coordinates, `getObjectiveToDisplay()` transform must be inverted and used.

    #### Usage note

    Invoking this method is rarely needed. It is sufficient to said that a display CRS exists at least conceptually, and that we define a conversion from the objective CRS to that display CRS. This method may be useful when the subclasses may be something else than `PlanarCanvas`, in which case the caller may want more information about the geometry of the display device.
    Returns:
      the Coordinate Reference System of the display device.
    See Also:
      - `getObjectiveCRS()`
      - `getObjectiveToDisplay()`

  - ### getObjectiveCRS

    public CoordinateReferenceSystem getObjectiveCRS()
    Returns the Coordinate Reference System in which all data are transformed before displaying. After conversion to this CRS, coordinates should be related to the display device coordinates with only a final scale, a translation and optionally a rotation remaining to apply.

    This value may be `null` on newly created `Canvas`, before data are added and canvas is configured. It should not be `null` anymore once a `Canvas` is ready for displaying.

    Returns:
      the Coordinate Reference System in which to transform all data before displaying.
    See Also:
      - `OBJECTIVE_CRS_PROPERTY`
      - `getDisplayCRS()`
      - `getObjectiveToDisplay()`

  - ### setObjectiveCRS

    public void setObjectiveCRS(CoordinateReferenceSystem newValue, DirectPosition anchor) throws RenderException
    Sets the Coordinate Reference System in which all data are transformed before displaying. The new CRS must be compatible with the previous CRS, i.e. a coordinate operation between the two CRSs shall exist. If this is not the case (e.g. for rendering completely new data), use `setGridGeometry(GridGeometry)` instead.

    The given CRS should have a domain of validity wide enough for encompassing all data (the `CRS​.suggestCommonTarget(…)` method may be helpful for choosing an objective CRS from a set of data CRS). If the given value is different than the previous value, then a change event is sent to all listeners registered for the "objectiveCRS" property.

    If the transform between old and new CRS is not identity, then this method recomputes the objective to display conversion in a way preserving the display coordinates of the given anchor, together with the scales and orientations of features in close neighborhood of that point. This calculation may cause "objectiveToDisplay" property change event with the `TransformChangeEvent​.Reason​.CRS_CHANGE` reason to be sent to listeners. That event is sent after the above-cited "objectiveCRS" event (note that "pointOfInterest" stay unchanged). All those change events are sent only after all property values have been updated to their new values.

    Parameters:
      `newValue` - the new Coordinate Reference System in which to transform all data before displaying.
      `anchor` - the point to keep at fixed display coordinates, expressed in any compatible CRS. If `null`, defaults to point of interest. If non-null, the anchor must be associated to a CRS.
    Throws:
      `NullPointerException` - if the given CRS is null.
      `MismatchedDimensionException` - if the given CRS does not have the number of dimensions of the display device.
      `RenderException` - if the objective CRS cannot be set to the given value for another reason.

  - ### getObjectiveToDisplay

    public LinearTransform getObjectiveToDisplay()
    Returns the (usually affine) conversion from objective CRS to display coordinate system. The source coordinates shall be in the CRS given by `getObjectiveCRS()` and the converted coordinates will be in the CRS given by `getDisplayCRS()`.

    The objective to display conversion changes every time that user zooms or scrolls on viewed data. However, the transform returned by this method is a snapshot taken at the time this method is invoked; subsequent changes in the objective to display conversion are not reflected in the returned transform.

    Returns:
      snapshot of the (usually affine) conversion from objective CRS to display coordinate system (never `null`).
    See Also:
      - `OBJECTIVE_CRS_PROPERTY`
      - `getObjectiveCRS()`
      - `getDisplayCRS()`

  - ### setObjectiveToDisplay

    public void setObjectiveToDisplay(LinearTransform newValue) throws RenderException
    Sets the conversion from objective CRS to display coordinate system. If the given value is different than the previous value, then a change event is sent to all listeners registered for the "objectiveToDisplay" property. The event reason is `TransformChangeEvent​.Reason​.ASSIGNMENT`.

    Invoking this method has the effect of changing the viewed area, the zoom level or the rotation of the map. It does not update the "pointOfInterest" property however. The point of interest may move outside the view area as a result of this method call.

    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.

  - ### getDisplayBounds

    public Envelope getDisplayBounds()
    Returns the size and location of the display device. The unit of measurement is typically (but not necessarily) pixels. The coordinate values are often integers, but this is not mandatory. The coordinate reference system is given by `getDisplayCRS()`.

    This value may be `null` on newly created `Canvas`, before data are added and canvas is configured. It should not be `null` anymore once a `Canvas` is ready for displaying.

    Returns:
      size and location of the display device.
    See Also:
      - `DISPLAY_BOUNDS_PROPERTY`
      - `getGeographicArea()`

  - ### setDisplayBounds

    public void setDisplayBounds(Envelope newValue) throws RenderException
    Sets the size and location of the display device. The envelope CRS shall be either the display CRS or unspecified, in which case the display CRS is assumed. Unit of measurement is typically (but not necessarily) `Units​.PIXEL`. If the given value is different than the previous value, then a change event is sent to all listeners registered for the "displayBounds" property.
    Parameters:
      `newValue` - the new display bounds.
    Throws:
      `IllegalArgumentException` - if the given envelope does not have the expected CRS or number of dimensions.
      `RenderException` - if the display bounds cannot be set to the given value for another reason.

  - ### getPointOfInterest

    public DirectPosition getPointOfInterest(boolean objective)
    Returns the coordinates of a point considered representative of the data. This is typically (but not necessarily) the center of data bounding box. This point is used for example as the default location where to compute resolution (the resolution may vary at each pixel because of map projection deformations). This position may become outside the viewing area after zooms or translations have been applied.

    The coordinates can be given in their original CRS or in the objective CRS. If `objective` is `false`, then the returned position can be expressed in any CRS convertible to data or objective CRS. If that CRS has more dimensions than the objective CRS, then the supplemental dimensions specify which slice to show (for example the depth of the horizontal plane to display, or the date of the dynamic phenomenon to display. See class javadoc for more discussion.) If `objective` is `true`, then the position is transformed to the objective CRS.

    This value is initially `null`. A value should be specified either by invoking `setPointOfInterest(DirectPosition)` or `setGridGeometry(GridGeometry)`.

    Parameters:
      `objective` - whether to return a position transformed to objective CRS.
    Returns:
      coordinates of a representative point, or `null` if unspecified.
    See Also:
      - `POINT_OF_INTEREST_PROPERTY`

  - ### setPointOfInterest

    public void setPointOfInterest(DirectPosition newValue) throws RenderException
    Sets the coordinates of a representative point inside the data bounding box. If the given value is different than the previous value, then a change event is sent to all listeners registered for the "pointOfInterest" property.
    Parameters:
      `newValue` - the new coordinates of a representative point.
    Throws:
      `NullPointerException` - if the given position is null.
      `IllegalArgumentException` - if the given position does not have a CRS.
      `RenderException` - if the point of interest cannot be set to the given value.

  - ### getGridGeometry

    public GridGeometry getGridGeometry() throws RenderException
    Returns canvas properties (CRS, display bounds, conversion) encapsulated in a grid geometry. This is a convenience method for interoperability with grid coverage API. If `setGridGeometry(GridGeometry)` has been invoked with a non-null value and no other `Canvas` property changed since that method call, then this method returns that value. Otherwise this method computes a grid geometry as described below.

    The set of `GridGeometry` dimensions includes all the dimensions of the objective CRS, augmented with all (if possible) or some supplemental dimensions found in the point of interest. For example if the canvas manages only (x,y) coordinates but the point of interest includes also a t coordinate, then a third dimension (which we call the supplemental dimension) for t is added to the CRS, `GridExtent` and "grid to CRS" transform of the returned grid geometry.

    Canvas properties → grid geometry properties
    Grid geometry element | Display dimensions | Supplemental dimensions
    --- | --- | ---
    `GridGeometry.getCoordinateReferenceSystem()` | `getObjectiveCRS()`. | Some of `getPointOfInterest(false)​.getCoordinateReferenceSystem()`
    `GridGeometry.getExtent()` | `getDisplayBounds()` rounded to enclosing (floor and ceil) integers | [0 … 0]
    `GridGeometry.getGridToCRS(PixelInCell)` | Inverse of `getObjectiveToDisplay()` | Some point of interest coordinates as translation terms

    The `GridGeometry​.getGridToCRS(PixelInCell)` transform built by this method is always a `LinearTransform`. This linearity implies that the grid geometry CRS cannot be the Point Of Interest (POI) CRS, unless conversion from POI CRS to objective CRS is linear.
    Returns:
      a grid geometry encapsulating canvas properties, including supplemental dimensions if possible.
    Throws:
      `RenderException` - if the grid geometry cannot be computed.

  - ### setGridGeometry

    public void setGridGeometry(GridGeometry newValue) throws RenderException
    Sets canvas properties from the given grid geometry. This convenience method converts the coordinate reference system, "grid to CRS" transform and extent of the given grid geometry to `Canvas` properties. If the given value is different than the previous value, then change events are sent to all listeners registered for the "displayBounds", "objectiveCRS", "objectiveToDisplay" (with `TransformChangeEvent​.Reason​.GRID_GEOMETRY_CHANGE` reason), and/or "pointOfInterest" properties, in that order.

    The value given to this method will be returned by `getGridGeometry()` as long as none of above cited properties is changed. If one of those properties changes (for example if the user zooms or pans the map), then a new grid geometry will be computed. There is no guarantee that the recomputed grid geometry will be similar to the grid geometry specified to this method. For example, the `GridExtent` in supplemental dimensions may be different.

    Parameters:
      `newValue` - the grid geometry from which to get new canvas properties.
    Throws:
      `RenderException` - if the given grid geometry cannot be converted to canvas properties.

  - ### getGeographicArea

    public Optional<GeographicBoundingBox> getGeographicArea() throws RenderException
    Returns the geographic bounding box encompassing the area shown on the display device. If the objective CRS is not convertible to a geographic CRS, then this method returns an empty value.
    Returns:
      geographic bounding box encompassing the viewed area.
    Throws:
      `RenderException` - in an error occurred while computing the geographic area.
    See Also:
      - `getDisplayBounds()`

  - ### getSpatialResolution

    public OptionalDouble getSpatialResolution() throws RenderException
    Returns an estimation of the resolution (in metres) at the point of interest. If the objective CRS is not convertible to a geographic CRS, then this method returns an empty value.
    Returns:
      estimation of the resolution in metres at current point of interest.
    Throws:
      `RenderException` - in an error occurred while computing the resolution.

  - ### 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`