Author: desruisseaux
Date: Tue Mar  6 15:34:47 2018
New Revision: 1825995

URL: http://svn.apache.org/viewvc?rev=1825995&view=rev
Log:
Enable the use of Sentinel 1 localization grids in GeoTIFF reader.
https://issues.apache.org/jira/browse/SIS-407

Modified:
    
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/geometry/AbstractEnvelope.java
    
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/builder/LinearTransformBuilder.java
    
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/builder/LocalizationGridBuilder.java
    
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/MathTransforms.java
    
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/SpecializableTransform.java
    
sis/branches/JDK8/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/GridGeometry.java

Modified: 
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/geometry/AbstractEnvelope.java
URL: 
http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/geometry/AbstractEnvelope.java?rev=1825995&r1=1825994&r2=1825995&view=diff
==============================================================================
--- 
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/geometry/AbstractEnvelope.java
 [UTF-8] (original)
+++ 
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/geometry/AbstractEnvelope.java
 [UTF-8] Tue Mar  6 15:34:47 2018
@@ -695,6 +695,7 @@ public abstract class AbstractEnvelope e
 
     /**
      * Tests if a specified coordinate is inside the boundary of this envelope.
+     * Both lower and upper values of this envelope are considered inclusive.
      * If it least one ordinate value in the given point is {@link Double#NaN 
NaN},
      * then this method returns {@code false}.
      *
@@ -710,7 +711,7 @@ public abstract class AbstractEnvelope e
      *
      * @param  position  the point to text.
      * @return {@code true} if the specified coordinate is inside the boundary 
of this envelope; {@code false} otherwise.
-     * @throws MismatchedDimensionException if the specified point doesn't 
have the expected dimension.
+     * @throws MismatchedDimensionException if the specified point does not 
have the expected number of dimensions.
      * @throws AssertionError if assertions are enabled and the envelopes have 
mismatched CRS.
      */
     public boolean contains(final DirectPosition position) throws 
MismatchedDimensionException {

Modified: 
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/builder/LinearTransformBuilder.java
URL: 
http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/builder/LinearTransformBuilder.java?rev=1825995&r1=1825994&r2=1825995&view=diff
==============================================================================
--- 
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/builder/LinearTransformBuilder.java
 [UTF-8] (original)
+++ 
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/builder/LinearTransformBuilder.java
 [UTF-8] Tue Mar  6 15:34:47 2018
@@ -20,11 +20,13 @@ import java.util.Map;
 import java.util.Arrays;
 import java.io.IOException;
 import org.opengis.util.FactoryException;
+import org.opengis.geometry.Envelope;
 import org.opengis.geometry.DirectPosition;
 import org.opengis.geometry.MismatchedDimensionException;
 import org.opengis.geometry.coordinate.Position;
 import org.opengis.referencing.operation.Matrix;
 import org.opengis.referencing.operation.MathTransformFactory;
+import org.apache.sis.geometry.GeneralEnvelope;
 import org.apache.sis.io.TableAppender;
 import org.apache.sis.math.Line;
 import org.apache.sis.math.Plane;
@@ -339,6 +341,64 @@ search: for (int j=0; j<numPoints; j++)
     }
 
     /**
+     * Returns the envelope of source points. The lower and upper values are 
inclusive.
+     *
+     * @return the envelope of source points.
+     * @throws IllegalStateException if the source points are not yet known.
+     *
+     * @since 1.0
+     */
+    public Envelope getSourceEnvelope() {
+        if (gridSize != null) {
+            final int dim = gridSize.length;
+            final GeneralEnvelope envelope = new GeneralEnvelope(dim);
+            for (int i=0; i <dim; i++) {
+                envelope.setRange(i, 0, gridSize[i] - 1);
+            }
+            return envelope;
+        } else {
+            return envelope(sources);
+        }
+    }
+
+    /**
+     * Returns the envelope of target points. The lower and upper values are 
inclusive.
+     *
+     * @return the envelope of target points.
+     * @throws IllegalStateException if the target points are not yet known.
+     *
+     * @since 1.0
+     */
+    public Envelope getTargetEnvelope() {
+        return envelope(targets);
+    }
+
+    /**
+     * Implementation of {@link #getSourceEnvelope()} and {@link 
#getTargetEnvelope()}.
+     */
+    private static Envelope envelope(final double[][] points) {
+        if (points == null) {
+            throw new IllegalStateException(noData());
+        }
+        final int dim = points.length;
+        final GeneralEnvelope envelope = new GeneralEnvelope(dim);
+        for (int i=0; i <dim; i++) {
+            final double[] data = points[i];
+            double lower = Double.POSITIVE_INFINITY;
+            double upper = Double.NEGATIVE_INFINITY;
+            for (final double value : data) {
+                if (value < lower) lower = value;
+                if (value > upper) upper = value;
+            }
+            if (lower > upper) {
+                lower = upper = Double.NaN;
+            }
+            envelope.setRange(i, lower, upper);
+        }
+        return envelope;
+    }
+
+    /**
      * Returns the direct position of the given position, or {@code null} if 
none.
      */
     private static DirectPosition position(final Position p) {
@@ -569,7 +629,7 @@ search: for (int j=0; j<numPoints; j++)
             final double[][] sources = this.sources;                    // 
Protect from changes.
             final double[][] targets = this.targets;
             if (targets == null) {
-                throw new FactoryException(noData());
+                throw new InvalidGeodeticParameterException(noData());
             }
             final int sourceDim = (sources != null) ? sources.length : 
gridSize.length;
             final int targetDim = targets.length;

Modified: 
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/builder/LocalizationGridBuilder.java
URL: 
http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/builder/LocalizationGridBuilder.java?rev=1825995&r1=1825994&r2=1825995&view=diff
==============================================================================
--- 
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/builder/LocalizationGridBuilder.java
 [UTF-8] (original)
+++ 
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/builder/LocalizationGridBuilder.java
 [UTF-8] Tue Mar  6 15:34:47 2018
@@ -17,6 +17,7 @@
 package org.apache.sis.referencing.operation.builder;
 
 import org.opengis.util.FactoryException;
+import org.opengis.geometry.Envelope;
 import org.opengis.geometry.MismatchedDimensionException;
 import org.opengis.referencing.operation.Matrix;
 import org.opengis.referencing.operation.MathTransform;
@@ -31,6 +32,7 @@ import org.apache.sis.referencing.operat
 import org.apache.sis.referencing.datum.DatumShiftGrid;
 import org.apache.sis.internal.referencing.Resources;
 import org.apache.sis.geometry.DirectPosition2D;
+import org.apache.sis.geometry.Envelopes;
 import org.apache.sis.internal.util.Numerics;
 import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.measure.NumberRange;
@@ -314,6 +316,25 @@ public class LocalizationGridBuilder ext
     }
 
     /**
+     * Returns the envelope of source coordinates. This is the envelope of the 
grid domain
+     * (i.e. the ranges of valid {@code gridX} and {@code gridY} argument 
values in calls
+     * to {@code get/setControlPoint(…)} methods) transformed by the inverse of
+     * {@linkplain #getSourceToGrid() source to grid} transform.
+     * The lower and upper values are inclusive.
+     *
+     * @return the envelope of grid points.
+     * @throws IllegalStateException if the grid points are not yet known.
+     * @throws TransformException if the envelope can not be calculated.
+     *
+     * @see LinearTransformBuilder#getSourceEnvelope()
+     *
+     * @since 1.0
+     */
+    public Envelope getSourceEnvelope() throws TransformException {
+        return Envelopes.transform(sourceToGrid.inverse(), 
linear.getSourceEnvelope());
+    }
+
+    /**
      * Creates a transform from the source points to the target points.
      * This method assumes that source points are precise and all uncertainty 
is in the target points.
      * If this transform is close enough to an affine transform, then an 
instance of {@link LinearTransform} is returned.

Modified: 
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/MathTransforms.java
URL: 
http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/MathTransforms.java?rev=1825995&r1=1825994&r2=1825995&view=diff
==============================================================================
--- 
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/MathTransforms.java
 [UTF-8] (original)
+++ 
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/MathTransforms.java
 [UTF-8] Tue Mar  6 15:34:47 2018
@@ -184,9 +184,9 @@ public final class MathTransforms extend
      * Creates a transform defined as one transform applied globally except in 
sub-areas where more accurate
      * transforms are available. Such constructs appear in some datum shift 
files. The result of transforming
      * a point by the returned {@code MathTransform} is as if iterating over 
all given {@link Envelope}s in
-     * no particular order, find the smallest one containing the point to 
transform, then use the associated
-     * {@link MathTransform} for transforming the point. If the point is not 
found in any envelope,
-     * then the global transform is applied.
+     * no particular order, find the smallest one containing the point to 
transform (envelope border considered
+     * inclusive), then use the associated {@link MathTransform} for 
transforming the point.
+     * If the point is not found in any envelope, then the global transform is 
applied.
      *
      * <p>The following constraints apply:</p>
      * <ul>

Modified: 
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/SpecializableTransform.java
URL: 
http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/SpecializableTransform.java?rev=1825995&r1=1825994&r2=1825995&view=diff
==============================================================================
--- 
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/SpecializableTransform.java
 [UTF-8] (original)
+++ 
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/SpecializableTransform.java
 [UTF-8] Tue Mar  6 15:34:47 2018
@@ -42,6 +42,7 @@ import org.apache.sis.util.ArraysExt;
 /**
  * A transform having sub-areas where more accurate transforms can be used.
  * The global transform must be a reasonable approximation of the specialized 
transforms.
+ * The lower and upper values of given envelopes are inclusive.
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @version 1.0
@@ -277,7 +278,7 @@ next:   for (final Map.Entry<Envelope,Ma
                 }
             }
             for (int i=0; i<n; i++) {
-                if (area.intersects(areas[n])) {
+                if (area.intersects(areas[i])) {
                     // Pending implementation of R-Tree in Apache SIS.
                     throw new IllegalArgumentException("Current implementation 
does not accept overlapping envelopes.");
                 }

Modified: 
sis/branches/JDK8/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/GridGeometry.java
URL: 
http://svn.apache.org/viewvc/sis/branches/JDK8/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/GridGeometry.java?rev=1825995&r1=1825994&r2=1825995&view=diff
==============================================================================
--- 
sis/branches/JDK8/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/GridGeometry.java
 [UTF-8] (original)
+++ 
sis/branches/JDK8/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/GridGeometry.java
 [UTF-8] Tue Mar  6 15:34:47 2018
@@ -18,9 +18,12 @@ package org.apache.sis.storage.geotiff;
 
 import java.util.Arrays;
 import java.util.Set;
+import java.util.Map;
 import java.util.HashSet;
+import java.util.LinkedHashMap;
 import java.util.Collection;
 import java.util.Collections;
+import org.opengis.geometry.Envelope;
 import org.opengis.util.FactoryException;
 import org.opengis.metadata.quality.DataQuality;
 import org.opengis.metadata.spatial.GeolocationInformation;
@@ -28,6 +31,7 @@ import org.opengis.referencing.operation
 import org.opengis.referencing.operation.TransformException;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
 import org.apache.sis.referencing.operation.matrix.MatrixSIS;
+import org.apache.sis.referencing.operation.transform.MathTransforms;
 import org.apache.sis.referencing.operation.transform.LinearTransform;
 import org.apache.sis.referencing.operation.builder.LocalizationGridBuilder;
 import org.apache.sis.referencing.operation.AbstractCoordinateOperation;
@@ -93,16 +97,22 @@ final class GridGeometry extends Abstrac
     GridGeometry(final String name, final CoordinateReferenceSystem crs, final 
Vector modelTiePoints)
             throws FactoryException, TransformException
     {
-        super(Collections.singletonMap(NAME_KEY, name), null, crs, null, 
localizationGrid(modelTiePoints));
+        super(Collections.singletonMap(NAME_KEY, name), null, crs, null, 
localizationGrid(modelTiePoints, null));
     }
 
     /**
      * Builds a localization grid from the given GeoTIFF tie points.
      * This method may invoke itself recursively.
+     *
+     * @param  modelTiePoints  the model tie points read from GeoTIFF file.
+     * @param  addTo           if non-null, add the transform result to this 
map.
      */
-    private static MathTransform localizationGrid(final Vector modelTiePoints) 
throws FactoryException, TransformException {
+    private static MathTransform localizationGrid(final Vector modelTiePoints, 
final Map<Envelope,MathTransform> addTo)
+            throws FactoryException, TransformException
+    {
         final int size = modelTiePoints.size();
         final int n = size / RECORD_LENGTH;
+        if (n == 0) return null;
         final Vector x = modelTiePoints.subSampling(0, RECORD_LENGTH, n);
         final Vector y = modelTiePoints.subSampling(1, RECORD_LENGTH, n);
         try {
@@ -119,47 +129,94 @@ final class GridGeometry extends Abstrac
                                      modelTiePoints.doubleValue(i+4));
             }
             grid.setDesiredPrecision(PRECISION);
-            return grid.create(null);
+            final MathTransform tr = grid.create(null);
+            if (addTo != null && addTo.put(grid.getSourceEnvelope(), tr) != 
null) {
+                throw new FactoryException();       // Should never happen. If 
it does, we have a bug in our algorithm.
+            }
+            return tr;
         } catch (ArithmeticException | FactoryException e) {
             /*
              * May happen when the model tie points are not distributed on a 
regular grid.
              * For example Sentinel 1 images may have tie points spaced by 
1320 pixels on the X axis,
              * except the very last point which is only 1302 pixels after the 
previous one. We try to
              * handle such grids by splitting them in two parts: one grid for 
the columns where points
-             * are spaced by 1320 pixels and one grid for the last column.
+             * are spaced by 1320 pixels and one grid for the last column. 
Such splitting needs to be
+             * done horizontally and vertically, which result in four grids:
+             *
+             *    ┌──────────────────┬───┐
+             *    │                  │   │
+             *    │         0        │ 1 │
+             *    │                  │   │
+             *    ├──────────────────┼───┤ splitY
+             *    │         2        │ 3 │
+             *    └──────────────────┴───┘
+             *                    splitX
              */
             final Set<Double> uniques = new HashSet<>(100);
-            final double lastX = threshold(x, uniques);
-            final double lastY = threshold(y, uniques);
-            if (!Double.isNaN(lastX) || !Double.isNaN(lastY)) {
-                int[] indicesGrid1 = new int[size];
-                int[] indicesGrid2 = new int[size];
-                int sizeGrid1 = 0;
-                int sizeGrid2 = 0;
-                for (int i=0; i<size;) {
-                    final int[] indicesGrid;
-                    int sizeGrid;
-                    final int s;
-                    if (modelTiePoints.doubleValue(i) > lastX || 
modelTiePoints.doubleValue(i+1) > lastY) {
-                        indicesGrid = indicesGrid2;
-                        sizeGrid    = sizeGrid2;
-                        s = sizeGrid2 += RECORD_LENGTH;
-                    } else {
-                        indicesGrid = indicesGrid1;
-                        sizeGrid    = sizeGrid1;
-                        s = sizeGrid1 += RECORD_LENGTH;
+            final double splitX = threshold(x, uniques);
+            final double splitY = threshold(y, uniques);
+            if (Double.isNaN(splitX) && Double.isNaN(splitY)) {
+                throw e;                                            // Can not 
do better. Report the failure.
+            }
+            final int[][] indices = new int[4][size];
+            final int[]   lengths = new int[4];
+            for (int i=0; i<size;) {
+                final double px = modelTiePoints.doubleValue(i  );
+                final double py = modelTiePoints.doubleValue(i+1);
+                int part = 0;                                       // Number 
of the part where to add current point.
+                if (px > splitX) part  = 1;                         // Point 
will be added to part #1 or #3.
+                if (py > splitY) part |= 2;                         // Point 
will be added to part #2 or #3.
+                int parts = 1 << part;                              // Bitmask 
of the parts where to add the point.
+                if (px == splitX) parts |= 1 << (part | 1);         // Add 
also the point to part #1 or #3.
+                if (py == splitY) parts |= 1 << (part | 2);         // Add 
also the point to part #2 or #3.
+                if (parts == 0b0111) {
+                    parts = 0b1111;                                 // Add 
also the point to part #3.
+                    assert px == splitX && py == splitY;
+                }
+                final int upper = i + RECORD_LENGTH;
+                do {
+                    part = Integer.numberOfTrailingZeros(parts);
+                    @SuppressWarnings("MismatchedReadAndWriteOfArray")
+                    final int[] tileIndices = indices[part];
+                    int k = lengths[part];
+                    for (int j=i; j<upper; j++) {
+                        tileIndices[k++] = j;
                     }
-                    while (sizeGrid < s) indicesGrid[sizeGrid++] = i++;
+                    lengths[part] = k;
+                } while ((parts &= ~(1 << part)) != 0);            // Clear 
the bit of the part we processed.
+                i = upper;
+            }
+            /*
+             * At this point, we finished to collect indices of the points to 
use for parts #0, 1, 2 and 3.
+             * Verify that each part has less points than the initial vector 
(otherwise it would be a bug),
+             * and identify which part is the biggest one. This is usually 
part #0.
+             */
+            int maxLength   = 0;
+            int largestPart = 0;
+            for (int i=0; i<indices.length; i++) {
+                final int length = lengths[i];
+                if (length >= size) throw e;                        // Safety 
against infinite recursivity.
+                indices[i] = Arrays.copyOf(indices[i], length);
+                if (length > maxLength) {
+                    maxLength = length;
+                    largestPart = i;
                 }
-                if (sizeGrid1 < size && sizeGrid2 < size) {       // Paranoiac 
check against never-ending recursivity.
-                    indicesGrid1 = Arrays.copyOf(indicesGrid1, sizeGrid1);
-                    indicesGrid2 = Arrays.copyOf(indicesGrid2, sizeGrid2);
-                    final MathTransform grid1 = 
localizationGrid(modelTiePoints.pick(indicesGrid1));
-                    final MathTransform grid2 = 
localizationGrid(modelTiePoints.pick(indicesGrid2));
-                    // TODO: pending completing of SIS-408.
+            }
+            /*
+             * The biggest part will define the global transform. All other 
parts will define a specialization
+             * valid only in a sub-area. Put those information in a map for 
MathTransforms.specialize(…).
+             */
+            MathTransform global = null;
+            final Map<Envelope,MathTransform> specialization = new 
LinkedHashMap<>(4);
+            for (int i=0; i<indices.length; i++) {
+                final Vector sub = modelTiePoints.pick(indices[i]);
+                if (i == largestPart) {
+                    global = localizationGrid(sub, null);
+                } else {
+                    localizationGrid(sub, specialization);
                 }
             }
-            throw e;
+            return MathTransforms.specialize(global, specialization);
         }
     }
 


Reply via email to