sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From jso...@apache.org
Subject [sis] branch geoapi-4.0 updated: Coverage : add evaluate method on GridCoverage class
Date Fri, 15 Nov 2019 15:40:19 GMT
This is an automated email from the ASF dual-hosted git repository.

jsorel 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 173ab3a  Coverage : add evaluate method on GridCoverage class
173ab3a is described below

commit 173ab3a77b565e5dc6296b58054ff63321c4848d
Author: jsorel <johann.sorel@geomatys.com>
AuthorDate: Fri Nov 15 16:37:30 2019 +0100

    Coverage : add evaluate method on GridCoverage class
---
 .../org/apache/sis/coverage/grid/GridCoverage.java | 91 +++++++++++++++++++++-
 .../java/org/apache/sis/image/PixelIterator.java   | 28 ++++++-
 .../internal/coverage/ConvertedGridCoverage.java   |  7 +-
 .../sis/internal/coverage/GridCoverage2D.java      | 72 +++--------------
 .../coverage/BufferedGridCoverageTest.java         | 26 +++++++
 .../sis/internal/coverage/GridCoverage2DTest.java  | 26 +++++++
 6 files changed, 182 insertions(+), 68 deletions(-)

diff --git a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverage.java
b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverage.java
index 4e1005e..6da4620 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverage.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverage.java
@@ -20,11 +20,19 @@ import java.util.List;
 import java.util.Collection;
 import java.util.Locale;
 import java.awt.image.RenderedImage;
+import org.opengis.coverage.PointOutsideCoverageException;
 import org.opengis.geometry.DirectPosition;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
+import org.opengis.referencing.datum.PixelInCell;
+import org.opengis.referencing.operation.MathTransform;
+import org.opengis.referencing.operation.TransformException;
+import org.opengis.util.FactoryException;
 import org.apache.sis.internal.util.UnmodifiableArrayList;
 import org.apache.sis.coverage.SampleDimension;
 import org.apache.sis.coverage.SubspaceNotSpecifiedException;
+import org.apache.sis.image.PixelIterator;
+import org.apache.sis.referencing.CRS;
+import org.apache.sis.referencing.operation.transform.MathTransforms;
 import org.apache.sis.util.collection.DefaultTreeTable;
 import org.apache.sis.util.collection.TableColumn;
 import org.apache.sis.util.collection.TreeTable;
@@ -45,7 +53,7 @@ import org.opengis.coverage.CannotEvaluateException;
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
  * @author  Johann Sorel (Geomatys)
- * @version 1.0
+ * @version 2.0
  * @since   1.0
  * @module
  */
@@ -226,6 +234,38 @@ public abstract class GridCoverage {
     public abstract RenderedImage render(GridExtent sliceExtent) throws CannotEvaluateException;
 
     /**
+     * Returns a sequence of double values for a given point in the coverage. A value for
each
+     * {@linkplain SampleDimension sample dimension} is included in the sequence. The default
+     * interpolation type used when accessing grid values for points which fall between grid
cells
+     * is nearest neighbor.
+     * The CRS of the point may be in any coordinate reference system.
+     * If the CRS of the point is undefined, it is assumed to be the same as the coverage.
+     *
+     * @param  coord The coordinate point where to evaluate.
+     * @param  dest An array in which to store values, or {@code null} to create a new array.
+     * @return The {@code dest} array, or a newly created array if {@code dest} was null.
+     * @throws PointOutsideCoverageException if the evaluation failed because the input point
+     *         has invalid coordinates.
+     * @throws CannotEvaluateException if the values can't be computed at the specified coordinate
+     *         for an other reason. It may be thrown if the coverage data type can't be converted
+     *         to {@code double} by an identity or widening conversion. Subclasses may relax
this
+     *         constraint if appropriate.
+     */
+    public double[] evaluate(DirectPosition coord, double[] dest) throws CannotEvaluateException
{
+        try {
+            coord = toGridCoord(coord);
+            final long[] coordl = toLongExact(coord);
+            final GridExtent subExtent = new GridExtent(null, coordl, coordl, true);
+            final RenderedImage image = render(subExtent);
+            final PixelIterator ite = PixelIterator.create(image);
+            ite.moveTo(0, 0);
+            return ite.getPixel(dest);
+        } catch (FactoryException | TransformException ex) {
+            throw new CannotEvaluateException(ex.getMessage(), ex);
+        }
+    }
+
+    /**
      * Returns a string representation of this grid coverage for debugging purpose.
      * The returned string is implementation dependent and may change in any future version.
      * Current implementation is equivalent to the following, where {@code <default flags>}
@@ -269,4 +309,53 @@ public abstract class GridCoverage {
         branch.newChild().setValue(column, SampleDimension.toString(locale, sampleDimensions));
         return tree;
     }
+
+    /**
+     * Converts the specified point to grid coordinate.
+     *
+     * @param point point to transform to grid coordinate
+     * @return point in grid coordinate
+     * @throws org.opengis.util.FactoryException if creating transformation fails
+     * @throws org.opengis.referencing.operation.TransformException if transformation fails
+     */
+    protected DirectPosition toGridCoord(final DirectPosition point)
+            throws FactoryException, TransformException
+    {
+        final CoordinateReferenceSystem sourceCRS = point.getCoordinateReferenceSystem();
+        MathTransform trs = getGridGeometry().getGridToCRS(PixelInCell.CELL_CENTER).inverse();
+        if (sourceCRS != null) {
+            MathTransform toCrs = CRS.findOperation(sourceCRS, getCoordinateReferenceSystem(),
null).getMathTransform();
+            if (!toCrs.isIdentity()) {
+                trs = MathTransforms.concatenate(toCrs, trs);
+            }
+        }
+        return trs.transform(point, null);
+    }
+
+    /**
+     * Converts given grid coordinate to long values and ensure coordinate
+     * is inside grid geometry extent.
+     *
+     * @param position in grid coordinate
+     * @return position as long type in grid coordinate
+     * @throws PointOutsideCoverageException
+     */
+    protected long[] toLongExact(DirectPosition position) throws PointOutsideCoverageException
{
+        final long[] coord = new long[position.getDimension()];
+        final GridExtent extent = getGridGeometry().getExtent();
+        final long[] low = extent.getLow().getCoordinateValues();
+        final long[] high = extent.getHigh().getCoordinateValues();
+
+        for (int i = 0; i < coord.length; i++) {
+            final double dv = position.getOrdinate(i);
+            if (!Double.isFinite(dv)) {
+                throw new PointOutsideCoverageException("Position outside coverage, axis
" + i + " value " + dv);
+            }
+            coord[i] = Math.round(dv);
+            if (coord[i] < low[i] || coord[i] > high[i]) {
+                throw new PointOutsideCoverageException("Position outside coverage, axis
" + i + " value " + coord[i]);
+            }
+        }
+        return coord;
+    }
 }
diff --git a/core/sis-feature/src/main/java/org/apache/sis/image/PixelIterator.java b/core/sis-feature/src/main/java/org/apache/sis/image/PixelIterator.java
index 4235a5d..a0be7ec 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/image/PixelIterator.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/image/PixelIterator.java
@@ -22,6 +22,7 @@ import java.nio.Buffer;
 import java.awt.Point;
 import java.awt.Dimension;
 import java.awt.Rectangle;
+import java.awt.image.BufferedImage;
 import java.awt.image.DataBuffer;
 import java.awt.image.Raster;
 import java.awt.image.RenderedImage;
@@ -163,8 +164,31 @@ public abstract class PixelIterator {
         numBands        = data.getSampleModel().getNumBands();
         tileWidth       = data.getTileWidth();
         tileHeight      = data.getTileHeight();
-        tileGridXOffset = data.getTileGridXOffset();
-        tileGridYOffset = data.getTileGridYOffset();
+
+        if (data instanceof BufferedImage) {
+            /**
+             * BufferedImage.getSubImage produces an image which raster has a SampleModelTranslateX
+             * which concatenate the offset requested in getSubImage.
+             * This means each tile raster may have a unique offset and that
+             * we can't compute the real tile pixels bounds until we have read the tile raster.
+             *
+             * This PixelIterator assume each tile has a deterministic position in the image
+             * which allows to fetch the exact tiles when required.
+             *
+             * To compensate the raster offset we cheat on the tileGridOffset in this special
case.
+             * We can apply this trick only because we assume BufferedImage is the only implementation
+             * to behave this way and because a BufferedImage only has one raster tile.
+             */
+            final Raster rasterTemplate = data.getTile(data.getMinTileX(), data.getMinTileY());
+            final int innerRasterTranslationX = rasterTemplate.getSampleModelTranslateX()
- data.getMinX();
+            final int innerRasterTranslationY = rasterTemplate.getSampleModelTranslateY()
- data.getMinY();
+            tileGridXOffset = data.getTileGridXOffset() - innerRasterTranslationX;
+            tileGridYOffset = data.getTileGridYOffset() - innerRasterTranslationY;
+        } else {
+            tileGridXOffset = data.getTileGridXOffset();
+            tileGridYOffset = data.getTileGridYOffset();
+        }
+
         bounds          = intersection(data.getMinX(), data.getMinY(), data.getWidth(), data.getHeight(),
subArea, window);
         lowerX          = bounds.x;
         lowerY          = bounds.y;
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/ConvertedGridCoverage.java
b/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/ConvertedGridCoverage.java
index 60d6f2b..5fed1e8 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/ConvertedGridCoverage.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/ConvertedGridCoverage.java
@@ -16,6 +16,7 @@
  */
 package org.apache.sis.internal.coverage;
 
+import java.awt.Rectangle;
 import java.awt.image.BufferedImage;
 import java.awt.image.ColorModel;
 import java.awt.image.DataBuffer;
@@ -125,14 +126,16 @@ final class ConvertedGridCoverage extends GridCoverage {
             return render;
         }
         final Raster raster;
-        if (render.getNumXTiles() == 1 && render.getNumYTiles() == 1) {
+        if (render.getNumXTiles() == 1 && render.getNumYTiles() == 1 && render.getTileGridXOffset()
== 0 && render.getTileGridYOffset() == 0) {
             raster = render.getTile(render.getMinTileX(), render.getMinTileY());
         } else {
             /*
              * This fallback is very inefficient since it copies all data in one big raster.
              * We will replace this class by tiles management in a future Apache SIS version.
+             *
+             * Note : we need to specify the Rectangle to reset raster location at 0,0
              */
-            raster = render.getData();
+            raster = render.getData(new Rectangle(render.getMinX(), render.getMinY(), render.getWidth(),
render.getHeight()));
         }
         final SampleModel baseSm = raster.getSampleModel();
         final DataBuffer dataBuffer = raster.getDataBuffer();
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/GridCoverage2D.java
b/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/GridCoverage2D.java
index b2b9bf8..247baaf 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/GridCoverage2D.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/GridCoverage2D.java
@@ -16,7 +16,6 @@
  */
 package org.apache.sis.internal.coverage;
 
-import java.awt.Rectangle;
 import java.awt.image.BufferedImage;
 import java.awt.image.RenderedImage;
 import java.util.Collection;
@@ -26,14 +25,10 @@ import org.apache.sis.coverage.grid.GridExtent;
 import org.apache.sis.coverage.grid.GridGeometry;
 import org.apache.sis.internal.image.TranslatedRenderedImage;
 import org.apache.sis.referencing.CRS;
-import org.apache.sis.referencing.operation.transform.MathTransforms;
 import org.apache.sis.util.ArgumentChecks;
 import org.opengis.coverage.CannotEvaluateException;
-import org.opengis.coverage.PointOutsideCoverageException;
 import org.opengis.geometry.DirectPosition;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
-import org.opengis.referencing.datum.PixelInCell;
-import org.opengis.referencing.operation.MathTransform;
 import org.opengis.referencing.operation.TransformException;
 import org.opengis.util.FactoryException;
 
@@ -60,8 +55,7 @@ public final class GridCoverage2D extends GridCoverage {
 
     /**
      * The given RenderedImage may not start at 0,0, so does the gridExtent of the grid geometry.
-     * Image minX/MinY coordinate is expected to be located grid extent lower corner.
-     *
+     * Image 0/0 coordinate is expected to match grid extent lower corner.
      *
      * @param grid  the grid extent, CRS and conversion from cell indices to CRS.
      * @param bands sample dimensions for each image band.
@@ -84,7 +78,6 @@ public final class GridCoverage2D extends GridCoverage {
         if (image.getHeight()!= extent.getSize(imageAxes[1])) {
             throw new IllegalArgumentException("Image height " + image.getHeight()+ "does
not match grid extent height "+ extent.getSize(imageAxes[1]));
         }
-
     }
 
     /**
@@ -136,70 +129,23 @@ public final class GridCoverage2D extends GridCoverage {
         }
     }
 
+    /**
+     * {@inheritDoc }
+     */
+    @Override
     public double[] evaluate(DirectPosition position, double[] buffer) throws CannotEvaluateException
{
-
         try {
             position = toGridCoord(position);
-
-            int x = 0;
-            int y = 0;
-            for (int i = 0, n = position.getDimension(); i < n; i++) {
-                final double dv = position.getOrdinate(i);
-                if (Double.isFinite(dv)) {
-                    throw new PointOutsideCoverageException("Position outside coverage, axis
" + i + " value " + dv);
-                }
-
-                final int v = Math.toIntExact(Math.round(dv));
-                if (i == imageAxes[0]) {
-                    x = v;
-                } else if (i == imageAxes[1]) {
-                    y = v;
-                } else if (v != 0) {
-                    //coverage is a slice, all other indices must be zero, otherwise we are
outside coverage
-                    throw new PointOutsideCoverageException("Position outside coverage, axis
" + i + " value " + v);
-                }
-            }
-
-            if (getBounds().contains(x,y)) {
-                return image.getTile(XToTileX(x), YToTileY(y)).getPixel(x, y, buffer);
-            }
-            throw new PointOutsideCoverageException("");
+            long[] coord = toLongExact(position);
+            int x = Math.toIntExact(Math.round(coord[imageAxes[0]]));
+            int y = Math.toIntExact(Math.round(coord[imageAxes[1]]));
+            return image.getTile(XToTileX(x), YToTileY(y)).getPixel(x, y, buffer);
         } catch (FactoryException | TransformException ex) {
             throw new CannotEvaluateException(ex.getMessage(), ex);
         }
     }
 
     /**
-     * Converts the specified point to grid coordinate.
-     *
-     * @param point point to transform to grid coordinate
-     * @return point in grid coordinate
-     * @throws org.opengis.util.FactoryException if creating transformation fails
-     * @throws org.opengis.referencing.operation.TransformException if transformation fails
-     */
-    protected DirectPosition toGridCoord(final DirectPosition point)
-            throws FactoryException, TransformException
-    {
-        final CoordinateReferenceSystem sourceCRS = point.getCoordinateReferenceSystem();
-        final MathTransform trs;
-        if (sourceCRS != null) {
-            MathTransform toCrs = CRS.findOperation(sourceCRS, getCoordinateReferenceSystem(),
null).getMathTransform();
-            trs = MathTransforms.concatenate(toCrs, getGridGeometry().getGridToCRS(PixelInCell.CELL_CENTER).inverse());
-        } else {
-            trs = getGridGeometry().getGridToCRS(PixelInCell.CELL_CENTER);
-        }
-        return trs.transform(point, null);
-    }
-
-    /**
-     * Utility method to convert image bounds as {@link java.awt.Rectangle}.
-     * @return {@link java.awt.Rectangle} bounds.
-     */
-    private Rectangle getBounds() {
-        return new Rectangle(image.getMinX(), image.getMinY(), image.getWidth(), image.getHeight());
-    }
-
-    /**
      * Converts a pixel's X coordinate into a horizontal tile index.
      * @param x pixel x coordinate
      * @return tile x coordinate
diff --git a/core/sis-feature/src/test/java/org/apache/sis/internal/coverage/BufferedGridCoverageTest.java
b/core/sis-feature/src/test/java/org/apache/sis/internal/coverage/BufferedGridCoverageTest.java
index 786b803..d151ed0 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/internal/coverage/BufferedGridCoverageTest.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/internal/coverage/BufferedGridCoverageTest.java
@@ -25,6 +25,7 @@ import org.apache.sis.coverage.SampleDimension;
 import org.apache.sis.coverage.grid.GridCoverage;
 import org.apache.sis.coverage.grid.GridExtent;
 import org.apache.sis.coverage.grid.GridGeometry;
+import org.apache.sis.geometry.DirectPosition2D;
 import org.apache.sis.measure.NumberRange;
 import org.apache.sis.measure.Units;
 import org.apache.sis.referencing.crs.HardCodedCRS;
@@ -34,6 +35,7 @@ import org.opengis.referencing.datum.PixelInCell;
 import org.opengis.referencing.operation.MathTransform1D;
 import org.junit.Assert;
 import org.junit.Test;
+import org.opengis.coverage.PointOutsideCoverageException;
 
 
 /**
@@ -100,6 +102,30 @@ public class BufferedGridCoverageTest extends TestCase {
             { -60, -195},
             {-216, -380}
         });
+
+        /**
+         * Test evaluation
+         */
+        Assert.assertArrayEquals(new double[]{ 70.0}, coverage.evaluate(new DirectPosition2D(0,
0), null), STRICT);
+        Assert.assertArrayEquals(new double[]{  2.5}, coverage.evaluate(new DirectPosition2D(1,
0), null), STRICT);
+        Assert.assertArrayEquals(new double[]{- 8.0}, coverage.evaluate(new DirectPosition2D(0,
1), null), STRICT);
+        Assert.assertArrayEquals(new double[]{-90.0}, coverage.evaluate(new DirectPosition2D(1,
1), null), STRICT);
+        //test nearest neighor rounding
+        Assert.assertArrayEquals(new double[]{70.0}, coverage.evaluate(new DirectPosition2D(-0.499,
-0.499), null), STRICT);
+        Assert.assertArrayEquals(new double[]{70.0}, coverage.evaluate(new DirectPosition2D(
0.499,  0.499), null), STRICT);
+        //test out of coverage
+        try {
+            coverage.evaluate(new DirectPosition2D(-0.51, 0), null);
+            Assert.fail("Point ouside coverage evalue must fail");
+        } catch (PointOutsideCoverageException ex) {
+            //ok
+        }
+        try {
+            coverage.evaluate(new DirectPosition2D(1.51, 0), null);
+            Assert.fail("Point ouside coverage evalue must fail");
+        } catch (PointOutsideCoverageException ex) {
+            //ok
+        }
     }
 
     /**
diff --git a/core/sis-feature/src/test/java/org/apache/sis/internal/coverage/GridCoverage2DTest.java
b/core/sis-feature/src/test/java/org/apache/sis/internal/coverage/GridCoverage2DTest.java
index d1de1eb..073a100 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/internal/coverage/GridCoverage2DTest.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/internal/coverage/GridCoverage2DTest.java
@@ -31,6 +31,7 @@ import org.apache.sis.coverage.SampleDimension;
 import org.apache.sis.coverage.grid.GridCoverage;
 import org.apache.sis.coverage.grid.GridExtent;
 import org.apache.sis.coverage.grid.GridGeometry;
+import org.apache.sis.geometry.DirectPosition2D;
 import org.apache.sis.measure.NumberRange;
 import org.apache.sis.measure.Units;
 import org.apache.sis.referencing.crs.HardCodedCRS;
@@ -38,6 +39,7 @@ import org.apache.sis.referencing.operation.transform.MathTransforms;
 import org.apache.sis.test.TestCase;
 import org.junit.Assert;
 import org.junit.Test;
+import org.opengis.coverage.PointOutsideCoverageException;
 import org.opengis.referencing.datum.PixelInCell;
 import org.opengis.referencing.operation.MathTransform1D;
 import org.opengis.util.FactoryException;
@@ -110,6 +112,30 @@ public class GridCoverage2DTest extends TestCase {
             { -60, -195},
             {-216, -380}
         });
+
+        /**
+         * Test evaluation
+         */
+        Assert.assertArrayEquals(new double[]{ 70.0}, coverage.evaluate(new DirectPosition2D(0,
0), null), STRICT);
+        Assert.assertArrayEquals(new double[]{  2.5}, coverage.evaluate(new DirectPosition2D(1,
0), null), STRICT);
+        Assert.assertArrayEquals(new double[]{- 8.0}, coverage.evaluate(new DirectPosition2D(0,
1), null), STRICT);
+        Assert.assertArrayEquals(new double[]{-90.0}, coverage.evaluate(new DirectPosition2D(1,
1), null), STRICT);
+        //test nearest neighor rounding
+        Assert.assertArrayEquals(new double[]{70.0}, coverage.evaluate(new DirectPosition2D(-0.499,
-0.499), null), STRICT);
+        Assert.assertArrayEquals(new double[]{70.0}, coverage.evaluate(new DirectPosition2D(
0.499,  0.499), null), STRICT);
+        //test out of coverage
+        try {
+            coverage.evaluate(new DirectPosition2D(-0.51, 0), null);
+            Assert.fail("Point ouside coverage evalue must fail");
+        } catch (PointOutsideCoverageException ex) {
+            //ok
+        }
+        try {
+            coverage.evaluate(new DirectPosition2D(1.51, 0), null);
+            Assert.fail("Point ouside coverage evalue must fail");
+        } catch (PointOutsideCoverageException ex) {
+            //ok
+        }
     }
 
     /**


Mime
View raw message