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
The following commit(s) were added to refs/heads/geoapi-4.0 by this push:
new a4c302f Defines the 4 fundamental properties of Canvas and document
their purpose and constraint.
a4c302f is described below
commit a4c302f4c7388d0937620d41e648628bde344733
Author: Martin Desruisseaux <[email protected]>
AuthorDate: Thu Feb 6 19:02:33 2020 +0100
Defines the 4 fundamental properties of Canvas and document their purpose
and constraint.
---
.../java/org/apache/sis/internal/map/Canvas.java | 231 ++++++++++++++++++---
.../java/org/apache/sis/internal/map/Canvas2D.java | 37 +++-
.../org/apache/sis/internal/map/GridCanvas.java | 17 +-
.../org/apache/sis/internal/map/Observable.java | 6 +
4 files changed, 256 insertions(+), 35 deletions(-)
diff --git
a/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/Canvas.java
b/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/Canvas.java
index 247da9b..8f5bbf6 100644
--- a/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/Canvas.java
+++ b/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/Canvas.java
@@ -17,10 +17,16 @@
package org.apache.sis.internal.map;
import java.util.Objects;
+import org.opengis.geometry.Envelope;
+import org.opengis.geometry.DirectPosition;
import org.opengis.geometry.MismatchedDimensionException;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
+import org.opengis.referencing.crs.EngineeringCRS;
import org.apache.sis.referencing.operation.transform.LinearTransform;
-import org.apache.sis.referencing.operation.transform.MathTransforms;
+import org.apache.sis.geometry.GeneralDirectPosition;
+import org.apache.sis.geometry.GeneralEnvelope;
+import org.apache.sis.geometry.ImmutableEnvelope;
+import org.apache.sis.measure.Units;
import org.apache.sis.util.ArgumentChecks;
@@ -30,13 +36,27 @@ import org.apache.sis.util.ArgumentChecks;
* of the display device (e.g. flat video monitor using Cartesian coordinate
system, or
* planetarium dome using spherical coordinate system).
*
- * <p>A newly constructed {@code Canvas} is initial empty. To make something
appears, at least one
+ * <p>A newly constructed {@code Canvas} is initially empty. To make something
appears, at least one
* {@link MapLayer} must be added. The visual content depends on the {@link
MapLayer} data and associated style.
* The contents are usually symbols, features or images, but some
implementations can also manage non-geographic
* elements like a map scale.</p>
*
- * <p>There is three {@linkplain CoordinateReferenceSystem coordinate
reference systems}
- * involved (at least conceptually) in rendering of geospatial data:</p>
+ * <p>In addition of the set of {@link MapLayer} to display,
+ * a {@code Canvas} manages four fundamental properties:</p>
+ * <ul>
+ * <li>The coordinate reference system to use for displaying data.</li>
+ * <li>The location of data to display in all dimensions, including the
dimensions
+ * not shown by the display device (for example time).</li>
+ * <li>The size of the display device, in units of the display coordinate
system.</li>
+ * <li>The conversion from the Coordinate Reference System to the display
coordinate system.</li>
+ * </ul>
+ *
+ * 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.
+ *
+ * <h2>Coordinate Reference Systems</h2>
+ * There is three {@linkplain CoordinateReferenceSystem Coordinate Reference
Systems}
+ * involved in the rendering of geospatial data:
*
* <ol class="verbose">
* <li>The <cite>data CRS</cite> is specific to the data to be displayed.
@@ -50,11 +70,36 @@ import org.apache.sis.util.ArgumentChecks;
* (often 2). Its domain of validity should be wide enough for
encompassing all data.
* The {@link org.apache.sis.referencing.CRS#suggestCommonTarget
CRS.suggestCommonTarget(…)}
* method may be helpful for choosing an objective CRS from a set of
data CRS.</li>
- * <li>The <cite>device CRS</cite> is the coordinate system of the display
device.
+ * <li>The <cite>display CRS</cite> is the coordinate system of the display
device.
* The conversion from <cite>objective CRS</cite> to <cite>display
CRS</cite> should
- * be an affine transform with a scale, a translation and optionally a
rotation.</li>
+ * 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.</li>
* </ol>
*
+ * <h2>Location of data to display</h2>
+ * In addition of above-cited Coordinate Reference Systems, a {@code Canvas}
contains also a point of interest.
+ * The point of interest is often, but not necessarily, at the center of
display area.
+ * It can be expressed in any CRS; it does not need to be the objective CRS or
the CRS of any data.
+ * However the point of interest CRS must have enough dimensions for being
convertible to the CRS of all data.
+ * In other words the number of dimensions of the point of interest is equal
or greater than the highest
+ * number of dimensions found in data. The point of interest is used not only
for defining which point to show
+ * in (typically) the center of the display area, but also for defining which
slice to select in all dimensions
+ * not shown by the display device.
+ *
+ * <div class="note"><b>Example:</b> if some data have
(<var>x</var>,<var>y</var>,<var>z</var>) dimensions and
+ * other data have (<var>x</var>,<var>y</var>,<var>t</var>) dimensions, then
the point of interest shall contain
+ * coordinate values for at least all of the
(<var>x</var>,<var>y</var>,<var>z</var>,<var>t</var>) 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 (<var>x</var>,<var>y</var>)
dimensions (horizontal plane),
+ * then the point of interest defines the <var>z</var> value (elevation or
depth) and the <var>t</var> value
+ * (date and time) of the slice to show.</div>
+ *
+ * <h2>Display device size</h2>
+ * 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 {@link #getDisplayBounds()} as an envelope
having the number of dimensions of
+ * the display device. The zoom level is given indirectly by the {@link
#getObjectiveToDisplay()} transform.
+ * The display device may have a wraparound axis, for example in the spherical
coordinate system of a planetarium.
+ *
* @author Johann Sorel (Geomatys)
* @author Martin Desruisseaux (Geomatys)
* @version 1.1
@@ -64,6 +109,8 @@ import org.apache.sis.util.ArgumentChecks;
public abstract class Canvas extends Observable {
/**
* The {@value} 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 {@link CoordinateReferenceSystem}.
*
* @see #getObjectiveCRS()
@@ -73,42 +120,109 @@ public abstract class Canvas extends Observable {
public static final String OBJECTIVE_CRS_PROPERTY = "objectiveCRS";
/**
- * The coordinate reference system to display.
- * If {@code null}, no transformation is applied on data CRS.
+ * The {@value} property name, used for notifications about changes in
point of interest.
+ * The point of interest defines the location to show typically (but not
necessarily) in
+ * the center of the display device. But it defines also the slice
coordinate values
+ * in all dimensions beyond the ones shown by the device.
+ * Associated values are instances of {@link DirectPosition}.
+ *
+ * @see #getPointOfInterest()
+ * @see #setPointOfInterest(DirectPosition)
+ * @see #addPropertyChangeListener(String, PropertyChangeListener)
+ */
+ public static final String POINT_OF_INTEREST_PROPERTY = "pointOfInterest";
+
+ /**
+ * The {@value} 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 {@link Envelope}.
+ *
+ * @see #getDisplayBounds()
+ * @see #setDisplayBounds(Envelope)
+ * @see #addPropertyChangeListener(String, PropertyChangeListener)
+ */
+ public static final String DISPLAY_BOUNDS_PROPERTY = "displayBounds";
+
+ /**
+ * The coordinate reference system in which to transform all data before
displaying.
+ * If {@code null}, then no transformation is applied and data coordinates
are used directly
+ * as display coordinates, regardless the data CRS (even if different data
use different CRS).
*
+ * @see #OBJECTIVE_CRS_PROPERTY
* @see #getObjectiveCRS()
*/
private CoordinateReferenceSystem objectiveCRS;
/**
- * The conversion (usually affine) from objective CRS to display
coordinate system.
- * The number of source dimensions shall be the number of dimensions of
{@link #objectiveCRS}.
- * The number of target dimensions shall be the number of dimensions of
the display device (usually 2).
- * This transform shall never be {@code null}.
+ * The point of interest to show typically (but not necessarily) in the
center of display area.
+ * Also used for selecting a slice in all supplemental dimensions.
+ * If {@code null}, then the 0 coordinate value is assumed in all
dimensions.
*
- * @see #getObjectiveToDisplay()
+ * @see #POINT_OF_INTEREST_PROPERTY
+ * @see #getPointOfInterest()
+ */
+ private GeneralDirectPosition pointOfInterest;
+
+ /**
+ * The size and location of the output device, modified in-place if the
size change.
+ * The CRS of this envelope is the display CRS.
+ *
+ * @see #DISPLAY_BOUNDS_PROPERTY
+ * @see #getDisplayBounds()
+ * @see #getDisplayCRS()
*/
- private LinearTransform objectiveToDisplay;
+ private final GeneralEnvelope displayBounds;
/**
- * Creates a new canvas for an output device having the specified number
of dimensions.
+ * Creates a new canvas for an output device using the given coordinate
reference system.
+ * The display CRS of a canvas can not 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 spherical coordinate system for planetarium domes).
+ * The axis units of measurement are typically (but not necessarily)
{@link Units#PIXEL}
+ * for Cartesian coordinate systems, with {@link Units#DEGREE} in polar,
cylindrical or
+ * spherical coordinate systems.
+ *
+ * @param displayCRS the coordinate system of the display device.
+ */
+ protected Canvas(final EngineeringCRS displayCRS) {
+ ArgumentChecks.ensureNonNull("displayCRS", displayCRS);
+ displayBounds = new GeneralEnvelope(displayCRS);
+ }
+
+ /**
+ * Returns the Coordinate Reference System of the display device.
+ * The axis units of measurement are typically (but not necessarily)
{@link Units#PIXEL}
+ * for Cartesian coordinate systems, with {@link 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).
+ *
+ * <div class="note"><b>Note:</b> 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.</div>
+ *
+ * @return the coordinate reference system of the display device.
*
- * @param dimension the number of dimensions of the objective CRS.
+ * @see #getObjectiveCRS()
+ * @see #getObjectiveToDisplay()
*/
- protected Canvas(final int dimension) {
- ArgumentChecks.ensureStrictlyPositive("dimension", dimension);
- objectiveToDisplay = MathTransforms.identity(dimension);
+ public final EngineeringCRS getDisplayCRS() {
+ return (EngineeringCRS) displayBounds.getCoordinateReferenceSystem();
}
/**
* Returns the Coordinate Reference System in which all data are
transformed before displaying.
- * The coordinate system of that CRS (Cartesian or spherical) should be
related to the display
- * device coordinate system with only a final scale, a translation and
optionally a rotation
- * to add.
+ * 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 to add.
+ *
+ * <p>This value may be {@code null} on newly created {@code Canvas},
before data are added and canvas
+ * is configured. It should not be {@code null} anymore once a {@code
Canvas} is ready for displaying.</p>
*
* @return the Coordinate Reference System in which to transform all data
before displaying.
*
* @see #OBJECTIVE_CRS_PROPERTY
+ * @see #getDisplayCRS()
+ * @see #getObjectiveToDisplay()
*/
public CoordinateReferenceSystem getObjectiveCRS() {
return objectiveCRS;
@@ -119,6 +233,10 @@ public abstract class Canvas extends Observable {
* If the given value is different than the previous value, then a change
event is sent to
* all listeners registered for the {@value #OBJECTIVE_CRS_PROPERTY}
property.
*
+ * <p>The domain of validity of the given CRS should be wide enough for
encompassing all data.
+ * The {@link org.apache.sis.referencing.CRS#suggestCommonTarget
CRS.suggestCommonTarget(…)}
+ * method may be helpful for choosing an objective CRS from a set of data
CRS.</p>
+ *
* @param newValue the new Coordinate Reference System in which to
transform all data before displaying.
* @throws NullPointerException if the given CRS is null.
* @throws MismatchedDimensionException if the given CRS does not have the
number of dimensions of the display device.
@@ -126,7 +244,7 @@ public abstract class Canvas extends Observable {
*/
public void setObjectiveCRS(final CoordinateReferenceSystem newValue)
throws RenderException {
ArgumentChecks.ensureNonNull(OBJECTIVE_CRS_PROPERTY, newValue);
- ArgumentChecks.ensureDimensionMatches(OBJECTIVE_CRS_PROPERTY,
objectiveToDisplay.getSourceDimensions(), newValue);
+ ArgumentChecks.ensureDimensionMatches(OBJECTIVE_CRS_PROPERTY,
getObjectiveToDisplay().getSourceDimensions(), newValue);
final CoordinateReferenceSystem oldValue = objectiveCRS;
if (!Objects.equals(oldValue, newValue)) {
objectiveCRS = newValue;
@@ -135,13 +253,68 @@ public abstract class Canvas extends Observable {
}
/**
- * Returns the conversion from objective CRS to display coordinate system,
usually as an affine transform.
- * The number of source and target dimensions is the number of dimensions
of the display device (usually 2).
- * This conversion will change every time that the user applies a zoom or
a translation on the viewed data.
+ * Returns the coordinates of the point to show typically (but not
necessarily) in the center of display area.
+ * This position may be expressed in any CRS, not necessarily the
{@linkplain #getObjectiveCRS() objective CRS}.
+ * The number of dimensions is equal or greater than the highest number of
dimensions found in data CRS.
+ * The coordinate values in dimensions beyond the {@linkplain
#getObjectiveCRS() objective CRS} dimensions
+ * specifies which slice to show (for example the depth of the horizontal
plane to display, or the date of
+ * the dynamic phenomenon to display). See {@linkplain Canvas class
javadoc} for more discussion.
*
- * @return conversion (usually affine) from objective CRS to display
coordinate system.
+ * <p>This value may be {@code null} on newly created {@code Canvas},
before data are added and canvas
+ * is configured. It should not be {@code null} anymore once a {@code
Canvas} is ready for displaying.</p>
+ *
+ * @return coordinates of the point to show typically (but not
necessarily) in the center of display area.
+ *
+ * @see #POINT_OF_INTEREST_PROPERTY
+ */
+ public DirectPosition getPointOfInterest() {
+ return (pointOfInterest != null) ? pointOfInterest.clone() : null;
+ }
+
+ /**
+ * Sets the coordinates of the point to show typically (but not
necessarily) in the center of display area.
+ * If the given value is different than the previous value, then a change
event
+ * is sent to all listeners registered for the {@value
#POINT_OF_INTEREST_PROPERTY} property.
+ *
+ * @param newValue the new coordinates of the point to show typically in
the center of display area.
+ * @throws NullPointerException if the given position is null.
+ * @throws RenderException if the point of interest can not be set to the
given value.
+ */
+ public void setPointOfInterest(final DirectPosition newValue) throws
RenderException {
+ ArgumentChecks.ensureNonNull(POINT_OF_INTEREST_PROPERTY, newValue);
+ final GeneralDirectPosition copy = new GeneralDirectPosition(newValue);
+ final GeneralDirectPosition oldValue = pointOfInterest;
+ if (!Objects.equals(oldValue, copy)) {
+ pointOfInterest = copy;
+ firePropertyChange(POINT_OF_INTEREST_PROPERTY, oldValue,
newValue); // Really `newValue`, not `copy`.
+ }
+ }
+
+ /**
+ * Returns the size and location of the display device.
+ *
+ * @return size and location of the display device.
*/
- public LinearTransform getObjectiveToDisplay() {
- return objectiveToDisplay;
+ public Envelope getDisplayBounds() {
+ return new ImmutableEnvelope(displayBounds);
+ }
+
+ public void setDisplayBounds(final Envelope newValue) {
+ ArgumentChecks.ensureNonNull(DISPLAY_BOUNDS_PROPERTY, newValue);
+ displayBounds.setEnvelope(newValue);
}
+
+ /**
+ * Returns the (usually affine) conversion from objective CRS to display
coordinate system.
+ * The number of source dimensions shall be the number of dimensions of
the {@linkplain #getObjectiveCRS() objective CRS}.
+ * The number of target dimensions shall be the number of dimensions of
the display device.
+ * That conversion will change every time that the user zooms or scrolls
on viewed data.
+ * This method shall never return {@code null}.
+ *
+ * @return conversion (usually affine) from objective CRS to display
coordinate system.
+ *
+ * @see #getObjectiveCRS()
+ * @see #getDisplayCRS()
+ */
+ public abstract LinearTransform getObjectiveToDisplay();
}
diff --git
a/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/Canvas2D.java
b/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/Canvas2D.java
index 50077f4..54aa379 100644
--- a/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/Canvas2D.java
+++ b/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/Canvas2D.java
@@ -16,9 +16,14 @@
*/
package org.apache.sis.internal.map;
+import java.awt.geom.AffineTransform;
+import org.apache.sis.internal.referencing.j2d.AffineTransform2D;
+import org.apache.sis.referencing.operation.transform.LinearTransform;
+
/**
* A canvas in which data are reduced to a two-dimensional slice before to be
displayed.
+ * This canvas assumes that the display device uses a Cartesian coordinate
system
*
* @author Johann Sorel (Geomatys)
* @author Martin Desruisseaux (Geomatys)
@@ -28,11 +33,39 @@ package org.apache.sis.internal.map;
*/
public abstract class Canvas2D extends Canvas {
/**
+ * The conversion from {@linkplain #getObjectiveCRS() objective CRS} to
the display coordinate system.
+ * This transform will be modified in-place when user applies zoom,
translation or rotation on the view area.
+ */
+ private final AffineTransform objectiveToDisplay;
+
+ /**
+ * An immutable snapshot of {@link #objectiveToDisplay}, created when
needed.
+ * This field is reset to {@code null} when {@link #objectiveToDisplay} is
modified.
+ *
+ * @see #getObjectiveToDisplay()
+ */
+ private AffineTransform2D conversionSnapshot;
+
+ /**
* Creates a new two-dimensional canvas.
*/
protected Canvas2D() {
- super(2);
+ super(null); // TODO
+ objectiveToDisplay = new AffineTransform();
}
- // TODO: methods to be provided.
+ /**
+ * Returns the conversion from objective CRS to display coordinate system.
+ * The number of source and target dimensions is always 2.
+ * That conversion will change every time that the user zooms or scrolls
on viewed data.
+ *
+ * @return conversion from objective CRS to display coordinate system.
+ */
+ @Override
+ public LinearTransform getObjectiveToDisplay() {
+ if (conversionSnapshot == null) {
+ conversionSnapshot = new AffineTransform2D(objectiveToDisplay);
+ }
+ return conversionSnapshot;
+ }
}
diff --git
a/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/GridCanvas.java
b/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/GridCanvas.java
index f4baca6..ace0fe3 100644
---
a/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/GridCanvas.java
+++
b/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/GridCanvas.java
@@ -22,6 +22,7 @@ import java.util.List;
import java.util.Map;
import org.apache.sis.coverage.grid.GridExtent;
import org.apache.sis.coverage.grid.GridGeometry;
+import org.apache.sis.geometry.Envelope2D;
import org.apache.sis.geometry.Envelopes;
import org.apache.sis.geometry.GeneralEnvelope;
import org.apache.sis.internal.referencing.j2d.AffineTransform2D;
@@ -56,11 +57,12 @@ import org.opengis.util.FactoryException;
*/
public abstract class GridCanvas extends Canvas2D {
/**
- * The operation method used by {@link #getDisplayCRS()}.
+ * @deprecated
* This is a temporary constant, as we will probably need to replace the
creation
* of a {@link DefaultDerivedCRS} by something else. After that
replacement, this
* constant will be removed.
*/
+ @Deprecated
private static final Affine DISPLAY_TO_OBJECTIVE_OPERATION = new Affine();
private GridGeometry gridGeometry = new GridGeometry(new GridExtent(360,
180), CRS.getDomainOfValidity(CommonCRS.WGS84.normalizedGeographic()));
@@ -122,7 +124,14 @@ public abstract class GridCanvas extends Canvas2D {
return gridGeometry2d.getCoordinateReferenceSystem();
}
- public final CoordinateReferenceSystem getDisplayCRS() {
+ /**
+ * @deprecated The display CRS is rarely needed. The information needed is
the "objectiveToDisplay" transform.
+ * The approach that consisted in wrapping that transform in a
new {@link DefaultDerivedCRS} after
+ * every zoom or translation was unnecessary and actually a
bad usage of derived CRS (those CRS are
+ * for another purpose.
+ */
+ @Deprecated
+ public final CoordinateReferenceSystem getDerivedCRS() {
/*
* TODO: will need a way to avoid the cast below. In my understanding,
DerivedCRS may not be the appropriate
* CRS to create after all, because in ISO 19111 a DerivedCRS is
more than just a base CRS with a math
@@ -203,7 +212,7 @@ public abstract class GridCanvas extends Canvas2D {
return proportion;
}
- public final Rectangle2D getDisplayBounds() {
+ public final Envelope2D getDisplayBounds() {
final GridGeometry gridGeometry = getGridGeometry();
final CoordinateReferenceSystem crs =
gridGeometry.getCoordinateReferenceSystem();
@@ -212,7 +221,7 @@ public abstract class GridCanvas extends Canvas2D {
//we are expecting axis index to be preserved from grid to crs
final GridExtent extent = gridGeometry.getExtent().reduce(idx, idx+1);
- final Rectangle2D.Double bounds = new Rectangle2D.Double();
+ final Envelope2D bounds = new Envelope2D();
bounds.x = extent.getLow(0);
bounds.y = extent.getLow(1);
bounds.width = extent.getSize(0);
diff --git
a/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/Observable.java
b/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/Observable.java
index 54436d7..99d7baa 100644
---
a/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/Observable.java
+++
b/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/Observable.java
@@ -31,6 +31,12 @@ import org.apache.sis.util.ArraysExt;
* This base class does not need to be public; its methods will appear as if
* they were defined directly in sub-classes.
*
+ * @todo Consider replacing the use of {@link PropertyChangeListener} by a new
listener interface
+ * defined in this package. The reason is because {@link
PropertyChangeListener} is defined
+ * in the {@code java.desktop} module, for which the future after 2026
is not clear at this
+ * state. It would also allow to add information not currently provided
to current listener,
+ * like the index of the element modified in a list.
+ *
* @author Martin Desruisseaux (Geomatys)
* @version 1.1
* @since 1.1