sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject [sis] branch geoapi-4.0 updated: First implementation of Canvas.setGridGeometry(…) - not yet tested.
Date Mon, 10 Feb 2020 18:56:49 GMT
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 5b1726a  First implementation of Canvas.setGridGeometry(…) - not yet tested.
5b1726a is described below

commit 5b1726a9d52eeea0f8aaaa903189ade182c7e3e6
Author: Martin Desruisseaux <martin.desruisseaux@geomatys.com>
AuthorDate: Mon Feb 10 19:56:11 2020 +0100

    First implementation of Canvas.setGridGeometry(…) - not yet tested.
---
 .../org/apache/sis/coverage/grid/GridExtent.java   |   7 +-
 .../java/org/apache/sis/internal/map/Canvas.java   | 110 ++++++++++++++++++++-
 .../org/apache/sis/internal/map/CanvasExtent.java  |   2 +-
 .../DefaultCoordinateOperationFactory.java         |   6 +-
 .../sis/referencing/operation/package-info.java    |   2 +-
 .../java/org/apache/sis/math/MathFunctions.java    |  24 +++++
 .../org/apache/sis/math/MathFunctionsTest.java     |  15 ++-
 7 files changed, 156 insertions(+), 10 deletions(-)

diff --git a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridExtent.java b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridExtent.java
index 6316ac4..261e4d6 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridExtent.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridExtent.java
@@ -52,6 +52,7 @@ import org.apache.sis.referencing.operation.matrix.Matrices;
 import org.apache.sis.referencing.operation.matrix.MatrixSIS;
 import org.apache.sis.referencing.operation.transform.MathTransforms;
 import org.apache.sis.referencing.operation.transform.TransformSeparator;
+import org.apache.sis.math.MathFunctions;
 import org.apache.sis.io.TableAppender;
 import org.apache.sis.util.ArraysExt;
 import org.apache.sis.util.iso.Types;
@@ -681,7 +682,11 @@ public class GridExtent implements GridEnvelope, Serializable {
         final int dimension = getDimension();
         final double[] center = new double[dimension];
         for (int i=0; i<dimension; i++) {
-            center[i] = ((double) coordinates[i] + (double) coordinates[i + dimension] +
1.0) * 0.5;
+            /*
+             * We want the average of (low + hi+1). However for the purpose of computing
an average, it does
+             * not matter if we add 1 to `low` or `hi`. So we add 1 to `low` because it should
not overflow.
+             */
+            center[i] = MathFunctions.average(Math.incrementExact(coordinates[i]), coordinates[i
+ dimension]);
         }
         return center;
     }
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 c01ae8d..1613ba3 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
@@ -31,6 +31,7 @@ import org.opengis.referencing.crs.EngineeringCRS;
 import org.opengis.referencing.operation.MathTransform;
 import org.opengis.referencing.operation.TransformException;
 import org.opengis.referencing.datum.PixelInCell;
+import org.opengis.coverage.CannotEvaluateException;
 import org.opengis.util.FactoryException;
 import org.apache.sis.util.Utilities;
 import org.apache.sis.util.Localized;
@@ -41,12 +42,15 @@ import org.apache.sis.geometry.GeneralEnvelope;
 import org.apache.sis.measure.Units;
 import org.apache.sis.referencing.CRS;
 import org.apache.sis.referencing.IdentifiedObjects;
+import org.apache.sis.referencing.operation.matrix.Matrices;
 import org.apache.sis.referencing.operation.transform.MathTransforms;
 import org.apache.sis.referencing.operation.transform.LinearTransform;
+import org.apache.sis.referencing.operation.transform.TransformSeparator;
 import org.apache.sis.referencing.operation.CoordinateOperationContext;
 import org.apache.sis.referencing.operation.DefaultCoordinateOperationFactory;
 import org.apache.sis.internal.referencing.CoordinateOperations;
 import org.apache.sis.internal.referencing.ReferencingUtilities;
+import org.apache.sis.coverage.grid.IncompleteGridGeometryException;
 import org.apache.sis.coverage.grid.GridGeometry;
 import org.apache.sis.coverage.grid.GridExtent;
 
@@ -195,6 +199,19 @@ public class Canvas extends Observable implements Localized {
     public static final String POINT_OF_INTEREST_PROPERTY = "pointOfInterest";
 
     /**
+     * The {@value} property name, used for notifications about changes in grid geometry.
+     * The grid geometry is a synthetic property computed from other properties when requested.
+     * The computed grid geometry may change every time that a {@value #OBJECTIVE_CRS_PROPERTY},
+     * {@value #OBJECTIVE_TO_DISPLAY_PROPERTY}, {@value #DISPLAY_BOUNDS_PROPERTY} or
+     * {@value #POINT_OF_INTEREST_PROPERTY} property is changed, but a {@value} change event
+     * is send only when {@link #setGridGeometry(GridGeometry)} is explicitly invoked.
+     *
+     * @see #getGridGeometry()
+     * @see #setGridGeometry(GridGeometry)
+     */
+    public static final String GRID_GEOMETRY_PROPERTY = "gridGeometry";
+
+    /**
      * 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).
@@ -634,6 +651,7 @@ public class Canvas extends Observable implements Localized {
             throw new IllegalArgumentException(errors().getString(Errors.Keys.EmptyProperty_1,
DISPLAY_BOUNDS_PROPERTY));
         }
         if (!oldValue.equals(displayBounds)) {
+            gridGeometry = null;
             firePropertyChange(DISPLAY_BOUNDS_PROPERTY, oldValue, newValue);    // Do not
publish reference to `displayBounds`.
         }
     }
@@ -745,6 +763,8 @@ public class Canvas extends Observable implements Localized {
      *
      * @return a grid geometry encapsulating canvas properties, including supplemental dimensions
if possible.
      * @throws RenderException if the grid geometry can not be computed.
+     *
+     * @see #GRID_GEOMETRY_PROPERTY
      */
     public GridGeometry getGridGeometry() throws RenderException {
         if (gridGeometry == null) try {
@@ -785,7 +805,9 @@ public class Canvas extends Observable implements Localized {
              * coordinate values in supplemental dimensions. Those coordinate values will
be stored in the
              * translation terms of the `gridToCRS` matrix.
              */
-            final LinearTransform objectiveToDisplay = getObjectiveToDisplay();         //
Should never be null.
+            if (objectiveToDisplay == null) {
+                objectiveToDisplay = updateObjectiveToDisplay();
+            }
             LinearTransform gridToCRS = objectiveToDisplay.inverse();
             if (supplementalDimensions != 0) {
                 gridToCRS = CanvasExtent.createGridToCRS(gridToCRS.getMatrix(), pointOfInterest,
supplementalDimensions);
@@ -807,13 +829,93 @@ public class Canvas extends Observable implements Localized {
             }
             gridGeometry = new GridGeometry(extent, PixelInCell.CELL_CORNER, gridToCRS, augmentedObjectiveCRS);
         } catch (FactoryException | TransformException e) {
-            throw new RenderException(errors().getString(Errors.Keys.CanNotCompute_1, "gridGeometry"),
e);
+            throw new RenderException(errors().getString(Errors.Keys.CanNotCompute_1, GRID_GEOMETRY_PROPERTY),
e);
         }
         return gridGeometry;
     }
 
-    public void setGridGeometry(final GridGeometry geometry) throws RenderException {
-        // TODO
+    /**
+     * 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 {@code Canvas} properties. If the given value is different than the previous value,
then
+     * change events are sent to all listeners registered for the {@value #GRID_GEOMETRY_PROPERTY}
+     * property, with also potential change events for {@value #OBJECTIVE_CRS_PROPERTY},
+     * {@value #OBJECTIVE_TO_DISPLAY_PROPERTY}, {@value #DISPLAY_BOUNDS_PROPERTY} and
+     * {@value #POINT_OF_INTEREST_PROPERTY} properties.
+     *
+     * @param  newValue  the grid geometry from which to get new canvas properties.
+     * @throws RenderException if the given grid geometry can not be converted to canvas
properties.
+     */
+    public void setGridGeometry(final GridGeometry newValue) throws RenderException {
+        ArgumentChecks.ensureNonNull(GRID_GEOMETRY_PROPERTY, newValue);
+        if (!newValue.equals(gridGeometry)) try {
+            /*
+             * Do not test grid.isDefined(…) — we consider all elements as mandatory
for this method.
+             * First, get the dimensions to show in the canvas by searching dimensions having
a span
+             * larger than 1 grid cell. Those spans will become the sizes of display bounds.
+             *
+             * Result of this block: DISPLAY_BOUNDS_PROPERTY: newBounds
+             */
+            final GridExtent extent = newValue.getExtent();
+            final int[] displayDimensions = extent.getSubspaceDimensions(getDisplayDimensions());
+            final GeneralEnvelope newBounds = new GeneralEnvelope(getDisplayCRS());
+            for (int i=0; i<displayDimensions.length; i++) {
+                final int s = displayDimensions[i];
+                newBounds.setRange(i, extent.getLow(s), Math.incrementExact(extent.getHigh(s)));
+            }
+            /*
+             * Computes the point of interest in the Coordinate Reference System (CRS) of
the given grid geometry.
+             * This point will also contain the coordinates in supplemental dimensions (if
any), such as vertical
+             * and temporal positions of the slice shown in this canvas. Those supplemental
coordinates should be
+             * computed in cell centers. This suggests that we should use PixelInCell.CELL_CENTER
transform, but
+             * actually the coordinates returned by `extent.getPointOfInterest()` for [x
… x] ranges (span of 1,
+             * as required for supplemental dimensions) already includes a 0.5 fraction digit.
+             *
+             * Result of this block: POINT_OF_INTEREST_PROPERTY: newPOI
+             */
+            final MathTransform gridToCRS = newValue.getGridToCRS(PixelInCell.CELL_CORNER);
+            final CoordinateReferenceSystem crs = newValue.getCoordinateReferenceSystem();
+            final GeneralDirectPosition newPOI = new GeneralDirectPosition(crs);
+            gridToCRS.transform(extent.getPointOfInterest(), 0, newPOI.coordinates, 0, 1);
+            /*
+             * Get the CRS component in the dimensions shown by this canvas.
+             *
+             * Result of this block: OBJECTIVE_CRS_PROPERTY:        newObjectiveCRS
+             *                       OBJECTIVE_TO_DISPLAY_PROPERTY: newObjToDisplay
+             */
+            final TransformSeparator analyzer = new TransformSeparator(gridToCRS, coordinateOperationFactory.getMathTransformFactory());
+            analyzer.addSourceDimensions(displayDimensions);
+            final LinearTransform           newObjToDisplay     = MathTransforms.tangent(analyzer.separate().inverse(),
newPOI);
+            final int[]                     objectiveDimensions = analyzer.getTargetDimensions();
+            final CoordinateReferenceSystem newObjectiveCRS     = CRS.reduce(crs, objectiveDimensions);
+            final MathTransform             dimensionSelect     = MathTransforms.linear(
+                    Matrices.createDimensionSelect(newPOI.getDimension(), objectiveDimensions));
+            /*
+             * Set internal fields only after we successfully computed everything, in order
to have a
+             * "all or nothing" behavior. Notify listeners only after all properties have
been updated.
+             */
+            final GeneralEnvelope           oldBounds       = new GeneralEnvelope(displayBounds);
+            final DirectPosition            oldPOI          = pointOfInterest;
+            final LinearTransform           oldObjToDisplay = objectiveToDisplay;
+            final CoordinateReferenceSystem oldObjectiveCRS = objectiveCRS;
+            final GridGeometry              oldGrid         = gridGeometry;
+
+            displayBounds.setEnvelope(newBounds);
+            pointOfInterest       = newPOI;
+            objectiveToDisplay    = newObjToDisplay;
+            objectiveCRS          = newObjectiveCRS;
+            multidimToObjective   = dimensionSelect;
+            augmentedObjectiveCRS = null;               // Will be recomputed when first
needed.
+            axisTypes             = null;
+            gridGeometry          = newValue;
+            if (!newBounds      .equals(oldBounds))       firePropertyChange(DISPLAY_BOUNDS_PROPERTY,
      oldBounds,       newBounds);
+            if (!newObjectiveCRS.equals(oldObjectiveCRS)) firePropertyChange(OBJECTIVE_CRS_PROPERTY,
       oldObjectiveCRS, newObjectiveCRS);
+            if (!newObjToDisplay.equals(oldObjToDisplay)) firePropertyChange(OBJECTIVE_TO_DISPLAY_PROPERTY,
oldObjToDisplay, newObjToDisplay);
+            if (!newPOI         .equals(oldPOI))          firePropertyChange(POINT_OF_INTEREST_PROPERTY,
   oldPOI,          newPOI);
+            /* Unconditional notification. */             firePropertyChange(GRID_GEOMETRY_PROPERTY,
       oldGrid,         newValue);
+        } catch (IncompleteGridGeometryException | CannotEvaluateException | FactoryException
| TransformException e) {
+            throw new RenderException(errors().getString(Errors.Keys.CanNotSetPropertyValue_1,
GRID_GEOMETRY_PROPERTY), e);
+        }
     }
 
     public Optional<GeographicBoundingBox> getGeographicArea() {
diff --git a/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/CanvasExtent.java
b/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/CanvasExtent.java
index cba3ae9..85f35cc 100644
--- a/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/CanvasExtent.java
+++ b/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/CanvasExtent.java
@@ -207,7 +207,7 @@ final class CanvasExtent extends GridExtent {
      * Those types are only a help for debugging purpose, by providing more information
      * to the developers. They should not be used for any "real" work.
      *
-     * @param  crs               the coordinate reference system to use for inferring axis
types.
+     * @param  crs  the coordinate reference system to use for inferring axis types.
      * @param  displayDimension  number of dimensions managed by the {@link Canvas}.
      * @return suggested axis types. Never null, but contains null elements.
      *
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/DefaultCoordinateOperationFactory.java
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/DefaultCoordinateOperationFactory.java
index d1e6807..298e6df 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/DefaultCoordinateOperationFactory.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/DefaultCoordinateOperationFactory.java
@@ -86,7 +86,7 @@ import org.apache.sis.util.Utilities;
  * The second approach is the most frequently used.
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
- * @version 1.0
+ * @version 1.1
  * @since   0.6
  * @module
  */
@@ -252,8 +252,10 @@ public class DefaultCoordinateOperationFactory extends AbstractFactory
implement
      * instances.
      *
      * @return the underlying math transform factory.
+     *
+     * @since 1.1
      */
-    final MathTransformFactory getMathTransformFactory() {
+    public final MathTransformFactory getMathTransformFactory() {
         MathTransformFactory factory = mtFactory;
         if (factory == null) {
             mtFactory = factory = DefaultFactories.forBuildin(MathTransformFactory.class);
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/package-info.java
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/package-info.java
index d63d296..5eea1bb 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/package-info.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/package-info.java
@@ -91,7 +91,7 @@
  * for example by specifying the area of interest.
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
- * @version 1.0
+ * @version 1.1
  * @since   0.6
  * @module
  */
diff --git a/core/sis-utility/src/main/java/org/apache/sis/math/MathFunctions.java b/core/sis-utility/src/main/java/org/apache/sis/math/MathFunctions.java
index fa69922..4075ca2 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/math/MathFunctions.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/math/MathFunctions.java
@@ -182,6 +182,30 @@ public final class MathFunctions extends Static {
     }
 
     /**
+     * Computes the averages of two signed integers without overflow. The calculation is
performed with
+     * {@code long} arithmetic before to convert the result to the {@code double} floating
point number.
+     * This function may be more accurate than the classical (x+y)/2 formula when <var>x</var>
and/or
+     * <var>y</var> are very large, because it will avoid the lost of last digits
before averaging.
+     * If exactly one of <var>x</var> and <var>y</var> is odd, the
result will contain the 0.5 fraction digit.
+     *
+     * <div class="note"><b>Source:</b> this function is adapted from
+     * <a href="http://aggregate.org/MAGIC/#Average%20of%20Integers">The Aggregate
Magic Algorithms</a>
+     * from University of Kentucky.</div>
+     *
+     * @param  x  the first value to average.
+     * @param  y  the second value to average.
+     * @return average of given values without integer overflow.
+     *
+     * @since 1.1
+     */
+    public static double average(final long x, final long y) {
+        final long xor = (x ^ y);
+        double c = (x & y) + (xor >> 1);      // Really need >> 1, not /2
(they differ with negative numbers).
+        if ((xor & 1) != 0) c += 0.5;
+        return c;
+    }
+
+    /**
      * Truncates the given value toward zero. Invoking this method is equivalent to invoking
      * {@link Math#floor(double)} if the value is positive, or {@link Math#ceil(double)}
if
      * the value is negative.
diff --git a/core/sis-utility/src/test/java/org/apache/sis/math/MathFunctionsTest.java b/core/sis-utility/src/test/java/org/apache/sis/math/MathFunctionsTest.java
index b899114..6b253d1 100644
--- a/core/sis-utility/src/test/java/org/apache/sis/math/MathFunctionsTest.java
+++ b/core/sis-utility/src/test/java/org/apache/sis/math/MathFunctionsTest.java
@@ -35,7 +35,7 @@ import static org.apache.sis.internal.util.Numerics.SIGNIFICAND_SIZE;
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
  * @author  Johann Sorel (Geomatys)
- * @version 1.0
+ * @version 1.1
  * @since   0.3
  * @module
  */
@@ -69,6 +69,19 @@ public final strictfp class MathFunctionsTest extends TestCase {
     }
 
     /**
+     * Tests {@link MathFunctions#average(long, long)}.
+     */
+    @Test
+    public void testAverage() {
+        final Random random = TestUtilities.createRandomNumberGenerator();
+        for (int i=0; i<100; i++) {
+            final long x = random.nextInt(200000) - 100000;
+            final long y = random.nextInt(200000) - 100000;
+            assertEquals((x + y) * 0.5, average(x, y), STRICT);
+        }
+    }
+
+    /**
      * Tests {@link MathFunctions#truncate(double)}.
      */
     @Test


Mime
View raw message