sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject [sis] 02/02: Target resolutions in GridGeometry.subgrid(Envelope, double...) should be in same units than the given envelope.
Date Sat, 29 Dec 2018 17:11:42 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

commit 34a9ce8f2aa8fdcc9d92bd6aef233ebef25028f0
Author: Martin Desruisseaux <martin.desruisseaux@geomatys.com>
AuthorDate: Sat Dec 29 17:01:35 2018 +0100

    Target resolutions in GridGeometry.subgrid(Envelope, double...) should be in same units
than the given envelope.
---
 .../org/apache/sis/coverage/grid/GridChange.java   |   2 +-
 .../org/apache/sis/coverage/grid/GridExtent.java   |   3 +-
 .../org/apache/sis/coverage/grid/GridGeometry.java | 107 ++---------
 .../sis/coverage/grid/SubgridCalculator.java       | 206 +++++++++++++++++++++
 .../apache/sis/coverage/grid/GridGeometryTest.java |  20 ++
 5 files changed, 245 insertions(+), 93 deletions(-)

diff --git a/core/sis-raster/src/main/java/org/apache/sis/coverage/grid/GridChange.java b/core/sis-raster/src/main/java/org/apache/sis/coverage/grid/GridChange.java
index 7c5faf1..f044f27 100644
--- a/core/sis-raster/src/main/java/org/apache/sis/coverage/grid/GridChange.java
+++ b/core/sis-raster/src/main/java/org/apache/sis/coverage/grid/GridChange.java
@@ -344,7 +344,7 @@ public class GridChange implements Serializable {
         double[] factors = null;
         Matrix toGiven = null;
         if (strides != null) {
-            // Validity of the strides values will be verified by GridExtent constructor
below.
+            // Validity of the strides values will be verified by GridExtent constructor
invoked below.
             final GridExtent unscaled = extent;
             final int dimension = extent.getDimension();
             for (int i = Math.min(dimension, strides.length); --i >= 0;) {
diff --git a/core/sis-raster/src/main/java/org/apache/sis/coverage/grid/GridExtent.java b/core/sis-raster/src/main/java/org/apache/sis/coverage/grid/GridExtent.java
index 0e17166..28d1190 100644
--- a/core/sis-raster/src/main/java/org/apache/sis/coverage/grid/GridExtent.java
+++ b/core/sis-raster/src/main/java/org/apache/sis/coverage/grid/GridExtent.java
@@ -213,8 +213,7 @@ public class GridExtent implements Serializable {
     /**
      * Creates a new grid extent with low and high values adjusted by dividing the {@linkplain
#getSize(int) size}
      * by the given strides. The policy of dividing the lower coordinates by the stride shall
be kept consistent
-     * with {@link GridGeometry#subgrid(Envelope, double...)} and {@link GridChange#getTargetGeometry(int...)}
-     * computation of grid to CRS.
+     * with {@link GridChange#getTargetGeometry(int...)} computation of grid to CRS.
      *
      * <div class="note"><b>Note:</b>
      * if a division does not produce an integer, then the size is rounded toward 0 (or toward
negative infinity since
diff --git a/core/sis-raster/src/main/java/org/apache/sis/coverage/grid/GridGeometry.java
b/core/sis-raster/src/main/java/org/apache/sis/coverage/grid/GridGeometry.java
index 2447b22..853c54e 100644
--- a/core/sis-raster/src/main/java/org/apache/sis/coverage/grid/GridGeometry.java
+++ b/core/sis-raster/src/main/java/org/apache/sis/coverage/grid/GridGeometry.java
@@ -30,11 +30,9 @@ import org.opengis.referencing.datum.PixelInCell;
 import org.opengis.referencing.operation.Matrix;
 import org.opengis.referencing.operation.MathTransform;
 import org.opengis.referencing.operation.TransformException;
-import org.opengis.referencing.operation.CoordinateOperation;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
 import org.opengis.referencing.cs.CoordinateSystemAxis;
 import org.opengis.referencing.cs.CoordinateSystem;
-import org.opengis.util.FactoryException;
 import org.apache.sis.math.MathFunctions;
 import org.apache.sis.geometry.Envelopes;
 import org.apache.sis.geometry.GeneralEnvelope;
@@ -43,7 +41,6 @@ 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.PassThroughTransform;
-import org.apache.sis.referencing.operation.transform.TransformSeparator;
 import org.apache.sis.internal.referencing.DirectPositionView;
 import org.apache.sis.internal.system.Modules;
 import org.apache.sis.internal.raster.Resources;
@@ -55,7 +52,6 @@ import org.apache.sis.util.resources.Errors;
 import org.apache.sis.util.logging.Logging;
 import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.util.CharSequences;
-import org.apache.sis.util.ArraysExt;
 import org.apache.sis.util.Classes;
 import org.apache.sis.util.Debug;
 import org.apache.sis.io.TableAppender;
@@ -601,42 +597,10 @@ public class GridGeometry implements Serializable {
         if (extent == null) {
             throw incomplete(EXTENT, Resources.Keys.UnspecifiedGridExtent);
         }
-        MathTransform gridToAOI = cornerToCRS;
-        if (gridToAOI == null) {
-            throw incomplete(GRID_TO_CRS, Resources.Keys.UnspecifiedTransform);
+        if (cornerToCRS == null) {
+            throw incomplete(GridGeometry.GRID_TO_CRS, Resources.Keys.UnspecifiedTransform);
         }
-        int[] modifiedDimensions = null;
-        try {
-            /*
-             * If the envelope CRS is different than the expected CRS, concatenate the envelope
transformation
-             * to the 'gridToCRS' transform.  We should not transform the envelope here -
only concatenate the
-             * transforms - because transforming envelopes twice add errors.
-             */
-            final CoordinateOperation operation = Envelopes.findOperation(envelope, areaOfInterest);
-            if (operation != null) {
-                gridToAOI = MathTransforms.concatenate(gridToAOI, operation.getMathTransform());
-            }
-            /*
-             * If the envelope dimensions does not encompass all grid dimensions, the envelope
is probably non-invertible.
-             * We need to reduce the number of grid dimensions in the transform for having
a one-to-one relationship.
-             */
-            final int modifiedDimensionCount = gridToAOI.getTargetDimensions();
-            ArgumentChecks.ensureDimensionMatches("areaOfInterest", modifiedDimensionCount,
areaOfInterest);
-            if (modifiedDimensionCount < gridToAOI.getSourceDimensions()) {
-                final TransformSeparator sep = new TransformSeparator(gridToAOI);
-                sep.setTrimSourceDimensions(true);
-                gridToAOI = sep.separate();
-                modifiedDimensions = sep.getSourceDimensions();
-                if (modifiedDimensions.length != modifiedDimensionCount) {
-                    throw new TransformException(Resources.format(Resources.Keys.CanNotMapToGridDimensions));
-                }
-            }
-        } catch (FactoryException e) {
-            throw new TransformException(Resources.format(Resources.Keys.CanNotMapToGridDimensions),
e);
-        }
-        final GridExtent sub = new GridExtent(Envelopes.transform(gridToAOI.inverse(), areaOfInterest),
-                GridRoundingMode.NEAREST, null, extent, modifiedDimensions);
-        return sub.equals(extent) ? extent : sub;
+        return new SubgridCalculator(this, cornerToCRS, areaOfInterest, null).extent;
     }
 
     /**
@@ -940,69 +904,32 @@ public class GridGeometry implements Serializable {
     /**
      * Returns a grid geometry over a sub-region of this grid geometry and optionally with
sub-sampling.
      * The given envelope can be expressed in any coordinate reference system (CRS) accepted
by {@link #getExtent(Envelope)}.
-     * The target resolution, if provided, shall be in the units of CRS axes, in same order.
+     * The target resolution, if provided, shall be in same units than the given envelope
with axes in the same order.
+     * If the length of {@code targetResolution} array is less than the number of dimensions
of {@code areaOfInterest},
+     * then no sub-sampling will be applied on the missing dimensions.
      *
      * @param  areaOfInterest    the desired spatiotemporal region in any CRS (transformations
will be applied as needed),
      *                           or {@code null} for not restricting the sub-grid to a sub-area.
-     * @param  targetResolution  the desired resolution in units of the CRS axes, or {@code
null} or an empty array
-     *                           if no sub-sampling is desired.
-     * @return a grid geometry over the specified sub-region of this grid geometry.
+     * @param  targetResolution  the desired resolution in the same units and order than
the axes of the given envelope,
+     *                           or {@code null} or an empty array if no sub-sampling is
desired.
+     * @return a grid geometry over the specified sub-region of this grid geometry with the
specified resolution.
      * @throws IncompleteGridGeometryException if this grid geometry has no extent or no
"grid to CRS" transform.
      * @throws TransformException if an error occurred while converting the envelope coordinates
to grid coordinates.
      *
      * @see #getExtent(Envelope)
      */
     public GridGeometry subgrid(final Envelope areaOfInterest, double... targetResolution)
throws TransformException {
-        GridExtent domain = extent;
-        if (areaOfInterest != null) {
-            domain = getExtent(areaOfInterest);
+        if (extent == null) {
+            throw incomplete(EXTENT, Resources.Keys.UnspecifiedGridExtent);
         }
-        MathTransform mt = null;
-        if (targetResolution != null && targetResolution.length != 0) {
-            /*
-             * Before to compute the strides, make sure we have all required information.
-             * One intent of following calls to getExtent() and getGridToCRS(…) is to get
-             * IncompleteGridGeometryException thrown if an information is missing.
-             */
-            if (domain == null) domain = getExtent();
-            final MathTransform gridToCRS = getGridToCRS(PixelInCell.CELL_CENTER);
-            /*
-             * Convert the target resolutions to sub-samplings as number of grid cells.
-             * The sub-sampling will be scale factors for the new "grid to CRS" transforms.
-             */
-            final int dimension = getDimension();
-            targetResolution = ArraysExt.resize(targetResolution, dimension);
-            Matrix m = gridToCRS.derivative(new DirectPositionView.Double(domain.getPointOfInterest()));
-            final double[] scales = Matrices.inverse(m).multiply(targetResolution);
-            /*
-             * Creates the matrix to pre-concatenate to "grid to CRS" transform. The (low
/si) term in translation
-             * below must be computed in the same way than the "low" coordinates in GridExtent(GridExtent,
int...)
-             * constructor.
-             */
-            m = Matrices.createIdentity(dimension + 1);
-            final int[] strides = new int[dimension];
-            Arrays.fill(strides, 1);
-            boolean isIdentity = true;
-            for (int i=0; i<dimension; i++) {
-                final double s = Math.floor(Math.abs(scales[i]));
-                if (s > 1) {                                                // Also for
skipping NaN values.
-                    final int  si  = (int) s;
-                    final long low = domain.getLow(i);
-                    m.setElement(i, i, s);
-                    m.setElement(i, dimension, low - (low / si) * s);       // (low / si)
must be consistent with GridExtent.
-                    strides[i] = si;
-                    isIdentity = false;
-                }
-            }
-            if (!isIdentity) {
-                mt = MathTransforms.linear(m);
-                domain = new GridExtent(domain, strides);
-            }
+        if (cornerToCRS == null) {
+            throw incomplete(GRID_TO_CRS, Resources.Keys.UnspecifiedTransform);
         }
-        if (mt == null && Objects.equals(domain, extent)) {
-            return this;
+        final SubgridCalculator sub = new SubgridCalculator(this, cornerToCRS, areaOfInterest,
targetResolution);
+        if (sub.toSubsampled != null || sub.extent != extent) {
+            return new GridGeometry(this, sub.extent, sub.toSubsampled);
         }
-        return new GridGeometry(this, domain, mt);
+        return this;
     }
 
     /**
diff --git a/core/sis-raster/src/main/java/org/apache/sis/coverage/grid/SubgridCalculator.java
b/core/sis-raster/src/main/java/org/apache/sis/coverage/grid/SubgridCalculator.java
new file mode 100644
index 0000000..60ab1b3
--- /dev/null
+++ b/core/sis-raster/src/main/java/org/apache/sis/coverage/grid/SubgridCalculator.java
@@ -0,0 +1,206 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.coverage.grid;
+
+import org.opengis.geometry.Envelope;
+import org.opengis.util.FactoryException;
+import org.opengis.referencing.operation.Matrix;
+import org.opengis.referencing.operation.MathTransform;
+import org.opengis.referencing.operation.TransformException;
+import org.opengis.referencing.operation.CoordinateOperation;
+import org.apache.sis.referencing.operation.transform.MathTransforms;
+import org.apache.sis.referencing.operation.transform.TransformSeparator;
+import org.apache.sis.referencing.operation.matrix.Matrices;
+import org.apache.sis.internal.referencing.DirectPositionView;
+import org.apache.sis.geometry.Envelopes;
+import org.apache.sis.geometry.GeneralEnvelope;
+import org.apache.sis.internal.raster.Resources;
+import org.apache.sis.util.ArgumentChecks;
+import org.apache.sis.util.ArraysExt;
+
+
+/**
+ * Helper class for computing the grid extent of a sub-area of a given grid geometry.
+ * This class provides the {@link MathTransform} converting source grid coordinates to target
grid coordinates,
+ * where the source is the given {@link GridGeometry} instance and the target is the sub-sampled
grid.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @version 1.0
+ * @since   1.0
+ * @module
+ */
+final class SubgridCalculator {
+    /**
+     * The sub-extent computed by the constructor.
+     */
+    GridExtent extent;
+
+    /**
+     * The conversion from the original grid to the sub-sampled grid, or {@code null} if
no sub-sampling is applied.
+     * This is computed by the constructor.
+     */
+    MathTransform toSubsampled;
+
+    /**
+     * Computes the sub-grid over the given area of interest with the given resolution.
+     * At least one of {@code areaOfInterest} and {@code resolution} shall be non-null.
+     * It is caller's responsibility to ensure that {@link GridGeometry#extent} is non-null.
+     *
+     * @param  grid            the enclosing grid geometry (mandatory).
+     * @param  cornerToCRS     the transform from cell corners to grid CRS (mandatory).
+     * @param  areaOfInterest  the desired spatiotemporal region in any CRS, or {@code null}
for the whole area.
+     * @param  resolution      the desired resolution in the same units and order than the
axes of the AOI envelope,
+     *                         or {@code null} or an empty array if no sub-sampling is desired.
+     * @throws TransformException if an error occurred while converting the envelope coordinates
to grid coordinates.
+     */
+    SubgridCalculator(final GridGeometry grid, MathTransform cornerToCRS, final Envelope
areaOfInterest, double[] resolution)
+            throws TransformException
+    {
+        /*
+         * List of grid dimensions that are modified by the 'cornerToCRS' transform, or null
for all dimensions.
+         * The length of this array is the number of dimensions of the given Area Of Interest
(AOI). Each value
+         * in this array is between 0 inclusive and 'extent.getDimension()' exclusive.
+         */
+        int[] modifiedDimensions = null;
+        try {
+            /*
+             * If the envelope CRS is different than the expected CRS, concatenate the envelope
transformation
+             * to the 'gridToCRS' transform.  We should not transform the envelope here -
only concatenate the
+             * transforms - because transforming envelopes twice would add errors.
+             */
+            final CoordinateOperation operation = Envelopes.findOperation(grid.envelope,
areaOfInterest);
+            if (operation != null) {
+                cornerToCRS = MathTransforms.concatenate(cornerToCRS, operation.getMathTransform());
+            }
+            /*
+             * If the envelope dimensions does not encompass all grid dimensions, the envelope
is probably non-invertible.
+             * We need to reduce the number of grid dimensions in the transform for having
a one-to-one relationship.
+             */
+            final int dimension = cornerToCRS.getTargetDimensions();
+            ArgumentChecks.ensureDimensionMatches("areaOfInterest", dimension, areaOfInterest);
+            if (dimension < cornerToCRS.getSourceDimensions()) {
+                final TransformSeparator sep = new TransformSeparator(cornerToCRS);
+                sep.setTrimSourceDimensions(true);
+                cornerToCRS = sep.separate();
+                modifiedDimensions = sep.getSourceDimensions();
+                if (modifiedDimensions.length != dimension) {
+                    throw new TransformException(Resources.format(Resources.Keys.CanNotMapToGridDimensions));
+                }
+            }
+        } catch (FactoryException e) {
+            throw new TransformException(Resources.format(Resources.Keys.CanNotMapToGridDimensions),
e);
+        }
+        /*
+         * Compute the sub-extent for the given Area Of Interest (AOI), ignoring for now
the sub-sampling.
+         * If no area of interest has been specified, or if the result is identical to the
original extent,
+         * then we will keep the reference to the original GridExtent (i.e. we share existing
instances).
+         */
+        extent = grid.extent;
+        final int dimension = extent.getDimension();
+        GeneralEnvelope indices = null;
+        if (areaOfInterest != null) {
+            indices = Envelopes.transform(cornerToCRS.inverse(), areaOfInterest);
+            setExtent(indices, extent, modifiedDimensions);
+        }
+        if (indices == null || indices.getDimension() != dimension) {
+            indices = new GeneralEnvelope(dimension);
+        }
+        for (int i=0; i<dimension; i++) {
+            indices.setRange(i, extent.getLow(i), extent.getHigh(i) + 1.0);
+        }
+        /*
+         * Convert the target resolutions to grid cell sub-samplings and adjust the extent
consequently.
+         * We perform this conversion by handling the resolution has a small translation
vector located
+         * at the point of interest, and converting it to a translation vector in grid coordinates.
The
+         * conversion is done by a multiplication with the "CRS to grid" derivative at that
point.
+         *
+         * The sub-sampling will be rounded in such a way that the difference in grid size
is less than
+         * one half of cell. Demonstration:
+         *
+         *    e = Math.getExponent(span)     →    2^e ≦ span
+         *    a = e+1                        →    2^a > span     →    1/2^a < 1/span
+         *   Δs = (s - round(s)) / 2^a
+         *   (s - round(s)) ≦ 0.5            →    Δs  ≦  0.5/2^a  <  0.5/span
+         *   Δs < 0.5/span                   →    Δs⋅span < 0.5 cell.
+         */
+        if (resolution != null && resolution.length != 0) {
+            resolution = ArraysExt.resize(resolution, cornerToCRS.getTargetDimensions());
+            Matrix m = cornerToCRS.derivative(new DirectPositionView.Double(getPointOfInterest(modifiedDimensions)));
+            resolution = Matrices.inverse(m).multiply(resolution);
+            boolean modified = false;
+            for (int k=0; k<resolution.length; k++) {
+                double s = Math.abs(resolution[k]);
+                if (s > 1) {                                // Also for skipping NaN values.
+                    final int i = (modifiedDimensions != null) ? modifiedDimensions[k] :
k;
+                    final int accuracy = Math.max(0, Math.getExponent(indices.getSpan(i)))
+ 1;         // Power of 2.
+                    s = Math.scalb(Math.rint(Math.scalb(s, accuracy)), -accuracy);
+                    indices.setRange(i, indices.getLower(i) / s, indices.getUpper(i) / s);
+                    modified = true;
+                }
+                resolution[k] = s;
+            }
+            /*
+             * If at least one sub-sampling is effective, build a scale from the old grid
coordinates to the new
+             * grid coordinates. If we had no rounding, the conversion would be only a scale.
But because of rounding,
+             * we need a small translation for the difference between the "real" coordinate
and the integer coordinate.
+             */
+            if (modified) {
+                final GridExtent unscaled = extent;
+                setExtent(indices, null, null);
+                m = Matrices.createIdentity(dimension + 1);
+                for (int k=0; k<resolution.length; k++) {
+                    final double s = resolution[k];
+                    if (s > 1) {                            // Also for skipping NaN values.
+                        final int i = (modifiedDimensions != null) ? modifiedDimensions[k]
: k;
+                        m.setElement(i, i, s);
+                        m.setElement(i, dimension, unscaled.getLow(i) - extent.getLow(i)
* s);
+                    }
+                }
+                toSubsampled = MathTransforms.linear(m);
+            }
+        }
+    }
+
+    /**
+     * Sets {@link #extent} to the given envelope, rounded to nearest integers.
+     *
+     * @param  indices             the envelope to use for setting the grid extent.
+     * @param  enclosing           the enclosing grid extent if a sub-sampling is not yet
applied, {@code null} otherwise.
+     * @param  modifiedDimensions  if {@code enclosing} is non-null, the grid dimensions
to set from the envelope.
+     */
+    private void setExtent(final GeneralEnvelope indices, final GridExtent enclosing, final
int[] modifiedDimensions) {
+        final GridExtent sub = new GridExtent(indices, GridRoundingMode.NEAREST, null, enclosing,
modifiedDimensions);
+        if (!sub.equals(extent)) {
+            extent = sub;
+        }
+    }
+
+    /**
+     * Returns the point of interest of current {@link #extent}.
+     */
+    private double[] getPointOfInterest(final int[] modifiedDimensions) {
+        final double[] pointOfInterest = extent.getPointOfInterest();
+        if (modifiedDimensions == null) {
+            return pointOfInterest;
+        }
+        final double[] filtered = new double[modifiedDimensions.length];
+        for (int i=0; i<filtered.length; i++) {
+            filtered[i] = pointOfInterest[modifiedDimensions[i]];
+        }
+        return filtered;
+    }
+}
diff --git a/core/sis-raster/src/test/java/org/apache/sis/coverage/grid/GridGeometryTest.java
b/core/sis-raster/src/test/java/org/apache/sis/coverage/grid/GridGeometryTest.java
index bb651e7..fd592b6 100644
--- a/core/sis-raster/src/test/java/org/apache/sis/coverage/grid/GridGeometryTest.java
+++ b/core/sis-raster/src/test/java/org/apache/sis/coverage/grid/GridGeometryTest.java
@@ -25,6 +25,7 @@ import org.apache.sis.referencing.operation.matrix.Matrix4;
 import org.apache.sis.referencing.operation.matrix.Matrices;
 import org.apache.sis.referencing.operation.transform.MathTransforms;
 import org.apache.sis.referencing.crs.HardCodedCRS;
+import org.apache.sis.geometry.DirectPosition2D;
 import org.apache.sis.geometry.GeneralEnvelope;
 import org.apache.sis.test.DependsOnMethod;
 import org.apache.sis.test.DependsOn;
@@ -347,5 +348,24 @@ public final strictfp class GridGeometryTest extends TestCase {
                   0, 1,  -90,
                   2, 0, -180,
                   0, 0,    1), MathTransforms.getMatrix(grid.getGridToCRS(PixelInCell.CELL_CORNER)),
STRICT);
+        /*
+         * A sub-region again but with a requested resolution which is not a divisor of the
actual resolution.
+         * It will force GridGeometry to adjust the translation term to compensate. We verify
that the adustment
+         * is correct by verifying that we still get the same envelope.
+         */
+        grid = grid.subgrid(envelope, 3, 2);
+        assertExtentEquals(new long[] {94, 13}, new long[] {95, 39}, grid.getExtent());
+        assertEnvelopeEquals(envelope, grid.getEnvelope(), STRICT);
+        MathTransform cornerToCRS = grid.getGridToCRS(PixelInCell.CELL_CORNER);
+        assertMatrixEquals("gridToCRS", new Matrix3(
+                  0, 3,  -89,
+                  2, 0, -180,
+                  0, 0,    1), MathTransforms.getMatrix(cornerToCRS), STRICT);
+
+        DirectPosition2D src = new DirectPosition2D();
+        DirectPosition2D tgt = new DirectPosition2D();
+        DirectPosition2D exp = new DirectPosition2D();
+        src.x = 94; src.y = 13; exp.x = -50; exp.y =  8; assertEquals("Lower corner", exp,
cornerToCRS.transform(src, tgt));
+        src.x = 96; src.y = 40; exp.x = +31; exp.y = 12; assertEquals("Upper corner", exp,
cornerToCRS.transform(src, tgt));
     }
 }


Mime
View raw message