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 327b2df53b3e6074e93f85426024d0b82fb6d575
Author: Martin Desruisseaux <martin.desruisse...@geomatys.com>
AuthorDate: Sat Feb 15 00:43:28 2020 +0100

    Refactor a bit DatumShiftGridGroup for computing sub-grid coordinates in a 
more direct way than using AffineTransform.
    This change allows us to fix a bug, where the translation that we got was 
not adjusted for the different size of the cell.
---
 .../internal/referencing/j2d/MosaicCalculator.java |   4 +-
 .../apache/sis/internal/referencing/j2d/Tile.java  |  99 ++----------
 .../referencing/provider/DatumShiftGridFile.java   |   8 +-
 .../referencing/provider/DatumShiftGridGroup.java  | 179 ++++++++++++++-------
 4 files changed, 134 insertions(+), 156 deletions(-)

diff --git 
a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/j2d/MosaicCalculator.java
 
b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/j2d/MosaicCalculator.java
index b0654cd..e1f4445 100644
--- 
a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/j2d/MosaicCalculator.java
+++ 
b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/j2d/MosaicCalculator.java
@@ -511,8 +511,6 @@ public class MosaicCalculator {
      */
     @Override
     public String toString() {
-        final List<Tile> tiles = new ArrayList<>(this.tiles.values());
-        Collections.sort(tiles);
-        return Tile.toString(tiles, 400);
+        return Tile.toString(tiles.values(), 400);
     }
 }
diff --git 
a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/j2d/Tile.java
 
b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/j2d/Tile.java
index ec755eb..847fb42 100644
--- 
a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/j2d/Tile.java
+++ 
b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/j2d/Tile.java
@@ -104,7 +104,7 @@ import org.apache.sis.io.TableAppender;
  * @since   1.1
  * @module
  */
-public class Tile implements Comparable<Tile>, Serializable {
+public class Tile implements Serializable {
     /**
      * For cross-version compatibility during serialization.
      */
@@ -506,88 +506,13 @@ public class Tile implements Comparable<Tile>, 
Serializable {
         return 0;
     }
 
-    /**
-     * Compares two tiles for optimal order in sequential reads. Default 
implementation sorts by
-     * increasing {@linkplain #getImageIndex image index}. This ordering 
allows efficient access
-     * for tiles that are stored sequentially in a file.
-     *
-     * <p>For tiles having the same image index, additional criterion are used 
like increasing
-     * subsampling, increasing <var>y</var> then increasing <var>x</var> 
coordinates.
-     * But the actual set of additional criterion may change in any future 
version.
-     *
-     * @param  other  the tile to compare with.
-     * @return -1 if this tile should be read before {@code other},
-     *         +1 if it should be read after or 0 if equal.
-     */
-    @Override
-    public final int compareTo(final Tile other) {
-        int c = getImageIndex() - other.getImageIndex();
-        if (c == 0) {
-            /*
-             * From this point it does not matter much for disk access. But we 
continue to
-             * define criterions for consistency with `equals(Object)` method. 
We compare
-             * subsampling first because it may be undefined while it is 
needed for (x,y)
-             * ordering. Undefined subsampling will be ordered first (this is 
arbitrary).
-             */
-            final int sy =  this.ySubsampling;
-            final int oy = other.ySubsampling;
-            c = sy - oy;
-            if (c == 0) {
-                final int sx =  this.xSubsampling;
-                final int ox = other.xSubsampling;
-                c = sx - ox;
-                if (c == 0) {
-                    c = (y * sy) - (other.y * oy);
-                    if (c == 0) {
-                        c = (x * sx) - (other.x * ox);
-                    }
-                }
-            }
-        }
-        return c;
-    }
-
-    /**
-     * Compares this tile with the specified one for equality. Two tiles are 
considered equal
-     * if they are of the same class and have the same {@linkplain 
#getRegion() region} and
-     * same {@linkplain #getSubsampling() subsampling}. Subclasses should 
override if there
-     * is more properties to compare such as image format and index.
-     *
-     * @param  object  the object to compare with.
-     * @return {@code true} if both objects are equal.
+    /*
+     * Intentionally no implementation for `equals()` and `hashCode()`. Tile 
is an "almost immutable" class
+     * which can still be modified (only once) by MocaicCalculator, or by read 
operations during `getSize()`
+     * or `getRegion()` execution. This causes confusing behavior when used in 
an HashMap. We are better to
+     * rely on system identity. For example `DatumShiftGridGroup` rely on the 
capability to locate Tiles in
+     * HashMap before and after they have been processed by `MosaicCalculator`.
      */
-    @Override
-    public boolean equals(final Object object) {
-        if (object == this) {
-            return true;
-        }
-        if (object != null && object.getClass() == getClass()) {
-            final Tile that = (Tile) object;
-            if (this.x == that.x  &&  this.y == that.y &&
-                this.xSubsampling == that.xSubsampling &&
-                this.ySubsampling == that.ySubsampling)
-            {
-                /*
-                 * Compares width and height only if they are defined in both 
tiles.  We do not
-                 * invoke `getRegion()` because it may be expensive and 
useless anyway: If both
-                 * tiles have the same image reader, image index and input, 
then logically they
-                 * must have the same size - invoking `getRegion()` would read 
exactly the same
-                 * image twice.
-                 */
-                return (width  == 0 || that.width  == 0 || width  == 
that.width) &&
-                       (height == 0 || that.height == 0 || height == 
that.height);
-            }
-        }
-        return false;
-    }
-
-    /**
-     * Returns a hash code value for this tile.
-     */
-    @Override
-    public int hashCode() {
-        return x + 37*y;
-    }
 
     /**
      * Returns a string representation of this tile for debugging purposes.
@@ -632,9 +557,8 @@ public class Tile implements Comparable<Tile>, Serializable 
{
     }
 
     /**
-     * Returns a string representation of a collection of tiles. The tiles are 
formatted in a
-     * table in iteration order. Tip: consider sorting the tiles before to 
invoke this method;
-     * tiles are {@link Comparable} for this purpose.
+     * Returns a string representation of a collection of tiles.
+     * The tiles are formatted in a table in iteration order.
      *
      * <p>This method is not public because it can consume a large amount of 
memory (the underlying
      * {@link StringBuffer} can be quite large). Users are encouraged to use 
the method expecting a
@@ -658,9 +582,8 @@ public class Tile implements Comparable<Tile>, Serializable 
{
     }
 
     /**
-     * Formats a collection of tiles in a table. The tiles are appended in 
iteration order.
-     * Tip: consider sorting the tiles before to invoke this method;
-     * tiles are {@link Comparable} for this purpose.
+     * Formats a collection of tiles in a table.
+     * The tiles are appended in iteration order.
      *
      * @param tiles    the tiles to format in a table.
      * @param out      where to write the table.
diff --git 
a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/DatumShiftGridFile.java
 
b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/DatumShiftGridFile.java
index 1826544..c9c3794 100644
--- 
a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/DatumShiftGridFile.java
+++ 
b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/DatumShiftGridFile.java
@@ -71,11 +71,11 @@ abstract class DatumShiftGridFile<C extends Quantity<C>, T 
extends Quantity<T>>
     static final Cache<Object, DatumShiftGridFile<?,?>> CACHE = new 
Cache<Object, DatumShiftGridFile<?,?>>(4, 32*1024, true) {
         @Override protected int cost(final DatumShiftGridFile<?,?> grid) {
             int p = 1;
-            for (final Object array : grid.getData()) {
-                if (array instanceof DatumShiftGridFile<?,?>) {
-                    p += cost((DatumShiftGridFile<?,?>) array);
+            for (final Object data : grid.getData()) {
+                if (data instanceof DatumShiftGridFile<?,?>) {
+                    p += cost((DatumShiftGridFile<?,?>) data);          // 
When `grid` is a DatumShiftGridGroup.
                 } else {
-                    p *= Array.getLength(array);
+                    p *= Array.getLength(data);                         // 
short[], float[] or double[].
                 }
             }
             return p;
diff --git 
a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/DatumShiftGridGroup.java
 
b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/DatumShiftGridGroup.java
index d9b5d32..e634375 100644
--- 
a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/DatumShiftGridGroup.java
+++ 
b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/DatumShiftGridGroup.java
@@ -20,12 +20,14 @@ import java.util.Map;
 import java.util.List;
 import java.nio.file.Path;
 import java.io.IOException;
+import java.awt.Dimension;
 import java.awt.Rectangle;
-import java.awt.geom.Point2D;
 import java.awt.geom.AffineTransform;
+import java.util.LinkedHashMap;
 import javax.measure.Quantity;
 import org.opengis.util.FactoryException;
 import org.opengis.referencing.operation.NoninvertibleTransformException;
+import org.apache.sis.referencing.operation.transform.InterpolatedTransform;
 import org.apache.sis.internal.referencing.j2d.AffineTransform2D;
 import org.apache.sis.internal.referencing.j2d.MosaicCalculator;
 import org.apache.sis.internal.referencing.j2d.Tile;
@@ -35,10 +37,8 @@ import org.apache.sis.internal.util.CollectionsExt;
 
 /**
  * A group of datum shift grids. This is used when a NTv2 file contains more 
than one grid with no common parent.
- * This class creates a synthetic parent with an affine transform 
approximating all grids. The affine transform is
- * close to identity transform. Its main purpose is to locate a grid during 
inverse transforms, before refinements
- * using the real grids.  So a "best match" transform (for example estimated 
using least squares method) would not
- * be useful because the differences would be small compared to grid cell 
sizes.
+ * This class creates a synthetic parent which always delegate its work to a 
child (as opposed to more classical
+ * trees where the parent can do some work if no child can).
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @version 1.1
@@ -47,66 +47,79 @@ import org.apache.sis.internal.util.CollectionsExt;
  */
 final class DatumShiftGridGroup<C extends Quantity<C>, T extends Quantity<T>> 
extends DatumShiftGridFile<C,T> {
     /**
-     * For each {@code subgrids[i]}, {@code regions[i]} is the range of 
indices valid of that grid.
-     *
-     * @todo We should replace by an R-Tree. For now we assume that the array 
is small enough.
+     * The bounds of a sub-grid, together with the subsampling level compared 
to the grid having the finest resolution.
+     * All values in this class are integers, but nevertheless stored as 
{@code double} for avoiding to cast them every
+     * time {@link #interpolateInCell(double, double, double[])} is executed.
      */
-    private final Rectangle[] regions;
+    private static final class Region {
+        /** Grid bounds in units of the grid having finest resolution. */
+        private final double xmin, xmax, ymin, ymax;
+
+        /** Subsampling compared to the grid having finest resolution. */
+        private final double sx, sy;
+
+        /** Creates a new instance from the given {@link MosaicCalculator} 
result. */
+        Region(final Tile tile) throws IOException {
+            final Rectangle r = tile.getAbsoluteRegion();
+            final Dimension s = tile.getSubsampling();
+            xmin = r.getMinX();
+            xmax = r.getMaxX();
+            ymin = r.getMinY();
+            ymax = r.getMaxY();
+            sx   = s.width;
+            sy   = s.height;
+        }
+
+        /** Tests whether the given coordinates are included in this region. */
+        final boolean contains(final double x, final double y) {
+            return x >= xmin && x <= xmax && y >= ymin && y <= ymax;
+        }
+
+        /** Converts a coordinate from the parent grid to this grid. */
+        final double x(final double p) {return (p - xmin) / sx;}
+        final double y(final double p) {return (p - ymin) / sy;}
+
+        /** Returns the subsampling (compared to the grid having finest 
resolution) in the specified dimension. */
+        final double relativeCellSize(final int dim) {
+            switch (dim) {
+                case 0:  return sx;
+                case 1:  return sy;
+                default: throw new IndexOutOfBoundsException();
+            }
+        }
+    }
 
     /**
-     * Converts indices from this grid to indices in sub-grids.
+     * For each {@code subgrids[i]}, {@code regions[i]} is the range of 
indices valid of that grid.
+     * This array will be used only as a fallback if the {@code MathTransform} 
has not been able to
+     * find the sub-grid itself. Since it should be rarely used, we do not 
bother using a R-Tree.
      */
-    private final AffineTransform[] toSubGrids;
+    private final Region[] regions;
 
     /**
      * Creates a new group for the given list of sub-grids. That list shall 
contain at least 2 elements.
      * The first sub-grid is taken as a template for setting parameter values 
such as filename (all list
      * elements should declare the same filename parameters, so the selected 
element should not matter).
      *
-     * @param grids      sub-grids with their indices range. The array is 
declared as {@code Tile[]}
-     *                   because this is the type returned by {@link 
MosaicCalculator#tiles()}, but
-     *                   each element shall be an instance of {@link Region}.
-     * @param gridToCRS  conversion from grid indices to "real world" 
coordinates.
-     * @param nx         number of cells along the <var>x</var> axis in the 
grid.
-     * @param ny         number of cells along the <var>y</var> axis in the 
grid.
+     * @param  tiles      the tiles computed by {@link MosaicCalculator}.
+     * @param  grids      sub-grids associated to tiles computed by {@link 
MosaicCalculator}.
+     * @param  gridToCRS  conversion from grid indices to "real world" 
coordinates.
+     * @param  nx         number of cells along the <var>x</var> axis in the 
grid.
+     * @param  ny         number of cells along the <var>y</var> axis in the 
grid.
      * @throws IOException declared because {@link Tile#getRegion()} declares 
it, but should not happen.
      */
     @SuppressWarnings({"rawtypes", "unchecked"})
-    private DatumShiftGridGroup(final Tile[] grids, final AffineTransform2D 
gridToCRS, final int nx, final int ny)
+    private DatumShiftGridGroup(final Tile[] tiles, final 
Map<Tile,DatumShiftGridFile<C,T>> grids,
+            final AffineTransform2D gridToCRS, final int nx, final int ny)
             throws IOException, NoninvertibleTransformException
     {
-        super((DatumShiftGridFile<C,T>) ((Region) grids[0]).grid, 
gridToCRS.inverse(), nx, ny);
-        subgrids   = new DatumShiftGridFile[grids.length];
-        regions    = new Rectangle[grids.length];
-        toSubGrids = new AffineTransform[grids.length];
-        for (int i=0; i<grids.length; i++) {
-            final Region r = (Region) grids[i];
-            final AffineTransform tr = new AffineTransform(gridToCRS);
-            tr.preConcatenate((AffineTransform) r.grid.getCoordinateToGrid());
-            subgrids  [i] = (DatumShiftGridFile<C,T>) r.grid;
-            regions   [i] = r.getAbsoluteRegion();
-            toSubGrids[i] = tr;
-        }
-    }
-
-    /**
-     * A sub-grid wrapped with information about the region where it applies.
-     * The region is expressed as indices in a larger grid. That larger grid
-     * is what {@link MosaicCalculator} will try to infer.
-     */
-    @SuppressWarnings("serial")
-    private static final class Region extends Tile {
-        /** The wrapped sub-grid. */
-        final DatumShiftGridFile<?,?> grid;
-
-        /**
-         * Creates a new wrapper for the given sub-grid.
-         *
-         * @param size  value of {@link DatumShiftGridFile#getGridSize()}.
-         */
-        Region(final DatumShiftGridFile<?,?> grid, final int[] size) throws 
NoninvertibleTransformException {
-            super(new Rectangle(size[0], size[1]), (AffineTransform) 
grid.getCoordinateToGrid().inverse());
-            this.grid = grid;
+        super(grids.get(tiles[0]), gridToCRS.inverse(), nx, ny);
+        final int n = grids.size();
+        regions  = new Region[n];
+        subgrids = new DatumShiftGridFile[n];
+        for (int i=0; i<n; i++) {
+            regions [i] = new Region(tiles[i]);
+            subgrids[i] = grids.get(tiles[i]);
         }
     }
 
@@ -124,8 +137,16 @@ final class DatumShiftGridGroup<C extends Quantity<C>, T 
extends Quantity<T>> ex
             throws IOException, FactoryException, 
NoninvertibleTransformException
     {
         final MosaicCalculator mosaic = new MosaicCalculator(null);
+        final Map<Tile,DatumShiftGridFile<C,T>> grids = new LinkedHashMap<>();
         for (final DatumShiftGridFile<C,T> grid : subgrids) {
-            mosaic.add(new Region(grid, grid.getGridSize()));
+            final int[] size = grid.getGridSize();
+            final Tile tile = new Tile(new Rectangle(size[0], size[1]),
+                    (AffineTransform) grid.getCoordinateToGrid().inverse());
+            if (mosaic.add(tile)) {                                     // 
Should never be false, but check anyway.
+                if (grids.put(tile, grid) != null) {
+                    throw new AssertionError(tile);                     // 
Should never happen (paranoiac check).
+                }
+            }
         }
         final Map.Entry<Tile,Tile[]> result = 
CollectionsExt.singletonOrNull(mosaic.tiles().entrySet());
         if (result == null) {
@@ -133,7 +154,7 @@ final class DatumShiftGridGroup<C extends Quantity<C>, T 
extends Quantity<T>> ex
         }
         final Tile global = result.getKey();
         final Rectangle r = global.getRegion();
-        return new DatumShiftGridGroup<>(result.getValue(), 
global.getGridToCRS(), r.width, r.height);
+        return new DatumShiftGridGroup<>(result.getValue(), grids, 
global.getGridToCRS(), r.width, r.height);
     }
 
     /**
@@ -142,9 +163,8 @@ final class DatumShiftGridGroup<C extends Quantity<C>, T 
extends Quantity<T>> ex
      */
     private DatumShiftGridGroup(final DatumShiftGridGroup<C,T> other, final 
DatumShiftGridFile<C,T>[] data) {
         super(other);
-        subgrids   = data;
-        regions    = other.regions;
-        toSubGrids = other.toSubGrids;
+        subgrids = data;
+        regions  = other.regions;
     }
 
     /**
@@ -182,6 +202,9 @@ final class DatumShiftGridGroup<C extends Quantity<C>, T 
extends Quantity<T>> ex
 
     /**
      * Returns the translation stored at the given two-dimensional grid 
indices for the given dimension.
+     * This method is defined for consistency with {@link 
#interpolateInCell(double, double, double[])}
+     * but should never be invoked. The {@link InterpolatedTransform} class 
will rather invoke the
+     * {@code interpolateInCell} method for efficiency.
      *
      * @param  dim    the dimension of the translation vector component to get.
      * @param  gridX  the grid index on the <var>x</var> axis, from 0 
inclusive to {@code gridSize[0]} exclusive.
@@ -191,15 +214,49 @@ final class DatumShiftGridGroup<C extends Quantity<C>, T 
extends Quantity<T>> ex
     @Override
     public double getCellValue(final int dim, final int gridX, final int 
gridY) {
         for (int i=0; i<regions.length; i++) {
-            final Rectangle r = regions[i];
+            final Region r = regions[i];
             if (r.contains(gridX, gridY)) {
-                Point2D pt = new Point2D.Double(gridX, gridY);
-                pt = toSubGrids[i].transform(pt, pt);
-                return subgrids[i].getCellValue(dim,
-                        Math.toIntExact(Math.round(pt.getX())),
-                        Math.toIntExact(Math.round(pt.getY())));
+                double shift = subgrids[i].getCellValue(dim,
+                        Math.toIntExact(Math.round(r.x(gridX))),
+                        Math.toIntExact(Math.round(r.y(gridY))));
+                /*
+                 * If the translations have been divided by the cell size, we 
may need to compensate.
+                 * The size of the cells of the grid used below may be bigger 
than the cells of this
+                 * pseudo-grid.
+                 */
+                if (isCellValueRatio()) {
+                    shift *= r.relativeCellSize(dim);
+                }
+                return shift;
             }
         }
         throw new IndexOutOfBoundsException();
     }
+
+    /**
+     * Interpolates the translation to apply for the given two-dimensional 
grid indices. The result is stored
+     * in the given {@code vector} array. This method is invoked only as a 
fallback if the transform has not
+     * been able to use directly one of the child transforms. Consequently 
this implementation does not need
+     * to be very fast.
+     *
+     * @param  gridX   first grid coordinate of the point for which to get the 
translation.
+     * @param  gridY   second grid coordinate of the point for which to get 
the translation.
+     * @param  vector  a pre-allocated array where to write the translation 
vector.
+     */
+    @Override
+    public void interpolateInCell(final double gridX, final double gridY, 
final double[] vector) {
+        for (int i=0; i<regions.length; i++) {
+            final Region r = regions[i];
+            if (r.contains(gridX, gridY)) {
+                subgrids[i].interpolateInCell(r.x(gridX), r.y(gridY), vector);
+                if (isCellValueRatio()) {
+                    for (int dim=0; dim < INTERPOLATED_DIMENSIONS; dim++) {
+                        vector[dim] *= r.relativeCellSize(dim);
+                    }
+                }
+                return;
+            }
+        }
+        super.interpolateInCell(gridX, gridY, vector);
+    }
 }

Reply via email to