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 <[email protected]>
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