sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject [sis] branch geoapi-4.0 updated: Initial draft of a GridCoverage "resample" operation. This starting point process only 2D coverages with a complete target grid geometry.
Date Fri, 27 Mar 2020 13:37:54 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


The following commit(s) were added to refs/heads/geoapi-4.0 by this push:
     new cbbfb4e  Initial draft of a GridCoverage "resample" operation. This starting point
process only 2D coverages with a complete target grid geometry.
cbbfb4e is described below

commit cbbfb4e6ce012eccfb6856d2df41f4e5bf67aefc
Author: Martin Desruisseaux <martin.desruisseaux@geomatys.com>
AuthorDate: Fri Mar 27 14:36:57 2020 +0100

    Initial draft of a GridCoverage "resample" operation. This starting point process only
2D coverages with a complete target grid geometry.
---
 .../org/apache/sis/coverage/grid/GridCoverage.java |  11 +
 .../apache/sis/coverage/grid/GridCoverage2D.java   |  22 ++
 .../sis/coverage/grid/GridCoverageProcessor.java   | 124 +++++++++++
 .../org/apache/sis/coverage/grid/GridExtent.java   |  19 +-
 .../sis/coverage/grid/ResampledGridCoverage.java   | 243 +++++++++++++++++++++
 .../java/org/apache/sis/image/ImageProcessor.java  |   5 +-
 6 files changed, 422 insertions(+), 2 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 997845e..06a859d 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
@@ -113,6 +113,17 @@ public abstract class GridCoverage {
     }
 
     /**
+     * Constructs a new grid coverage with the same sample dimensions than the given source.
+     *
+     * @param  source  the source from which to copy the sample dimensions.
+     * @param  domain  the grid extent, CRS and conversion from cell indices to CRS.
+     */
+    GridCoverage(final GridCoverage source, final GridGeometry domain) {
+        gridGeometry = domain;
+        sampleDimensions = source.sampleDimensions;
+    }
+
+    /**
      * Returns the coordinate reference system to which the values in grid domain are referenced.
      * This is the CRS used when accessing a coverage with the {@code evaluate(…)} methods.
      * This coordinate reference system is usually different than the coordinate system of
the grid.
diff --git a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverage2D.java
b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverage2D.java
index 0f3e7c0..4889928 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverage2D.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverage2D.java
@@ -157,6 +157,28 @@ public class GridCoverage2D extends GridCoverage {
     }
 
     /**
+     * Creates a new grid coverage for the resampling of specified source coverage.
+     *
+     * @param  source       the coverage containing source values.
+     * @param  domain       the grid extent, CRS and conversion from cell indices to CRS.
+     * @param  extent       the {@code domain.getExtent()} value.
+     * @param  data         the sample values as a {@link RenderedImage}, with one band for
each sample dimension.
+     * @param  xDimension   index of extent dimension for image <var>x</var>
coordinates.
+     * @param  yDimension   index of extent dimension for image <var>y</var>
coordinates.
+     */
+    GridCoverage2D(final GridCoverage source, final GridGeometry domain, final GridExtent
extent,
+                   final RenderedImage data, final int xDimension, final int yDimension)
+    {
+        super(source, domain);
+        this.data       = data;
+        this.xDimension = xDimension;
+        this.yDimension = yDimension;
+        gridToImageX    = subtractExact(data.getMinX(), extent.getLow(xDimension));
+        gridToImageY    = subtractExact(data.getMinY(), extent.getLow(yDimension));
+        gridGeometry2D  = new AtomicReference<>();
+    }
+
+    /**
      * Constructs a grid coverage using the specified domain, range and data. If the given
domain does not
      * have an extent, then a default {@link GridExtent} will be computed from given image.
Otherwise the
      * {@linkplain RenderedImage#getWidth() image width} and {@linkplain RenderedImage#getHeight()
height}
diff --git a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverageProcessor.java
b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverageProcessor.java
new file mode 100644
index 0000000..e63f974
--- /dev/null
+++ b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverageProcessor.java
@@ -0,0 +1,124 @@
+/*
+ * 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.util.FactoryException;
+import org.opengis.referencing.operation.TransformException;
+import org.apache.sis.image.Interpolation;
+import org.apache.sis.util.ArgumentChecks;
+import org.apache.sis.util.logging.Logging;
+import org.apache.sis.internal.system.Modules;
+
+
+/**
+ * A predefined set of operations on grid coverages as convenience methods.
+ *
+ * <h2>Thread-safety</h2>
+ * {@code GridCoverageProcessor} is thread-safe if its configuration is not modified after
construction.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @version 1.1
+ *
+ * @see org.apache.sis.image.ImageProcessor
+ *
+ * @since 1.1
+ * @module
+ */
+public class GridCoverageProcessor {
+    /**
+     * The interpolation method to use for resampling operations.
+     */
+    private Interpolation interpolation;
+
+    /**
+     * Creates a new set of grid coverage operations with default configuration.
+     */
+    public GridCoverageProcessor() {
+        interpolation = Interpolation.BILINEAR;
+    }
+
+    /**
+     * Returns the interpolation method to use for resampling operations.
+     *
+     * @return interpolation method to use in resampling operations.
+     */
+    public Interpolation getInterpolation() {
+        return interpolation;
+    }
+
+    /**
+     * Sets the interpolation method to use for resampling operations.
+     *
+     * @param  method  interpolation method to use in resampling operations.
+     */
+    public void setInterpolation(final Interpolation method) {
+        ArgumentChecks.ensureNonNull("method", method);
+        interpolation = method;
+    }
+
+    /**
+     * Creates a new coverage with a different grid extent, resolution or coordinate reference
system.
+     * The desired properties are specified by the {@link GridGeometry} argument, which may
be incomplete.
+     * The missing grid geometry components are completed as below:
+     *
+     * <table class="sis">
+     *   <caption>Default values for undefined grid geometry components</caption>
+     *   <tr>
+     *     <th>Component</th>
+     *     <th>Default value</th>
+     *   </tr><tr>
+     *     <td>{@linkplain GridGeometry#getExtent() Grid extent}</td>
+     *     <td>A default size preserving resolution at source
+     *       {@linkplain GridExtent#getPointOfInterest() point of interest}.</td>
+     *   </tr><tr>
+     *     <td>{@linkplain GridGeometry#getGridToCRS Grid to CRS transform}</td>
+     *     <td>Whatever it takes for fitting data inside the supplied extent.</td>
+     *   </tr><tr>
+     *     <td>{@linkplain GridGeometry#getCoordinateReferenceSystem() Coordinate reference
system}</td>
+     *     <td>Same as source coverage.</td>
+     *   </tr>
+     * </table>
+     *
+     * The interpolation method can be specified by {@link #setInterpolation(Interpolation)}.
+     *
+     * @param  source  the grid coverage to resample.
+     * @param  target  the desired geometry of returned grid coverage. May be incomplete.
+     * @return a grid coverage with the characteristics specified in the given grid geometry.
+     * @throws IncompleteGridGeometryException if the source grid geometry is missing an
information.
+     *         It may be the source CRS, the source extent, <i>etc.</i> depending
on context.
+     * @throws TransformException if some coordinates can not be transformed to the specified
target.
+     */
+    public GridCoverage resample(final GridCoverage source, GridGeometry target) throws TransformException
{
+        ArgumentChecks.ensureNonNull("source", source);
+        ArgumentChecks.ensureNonNull("target", target);
+        try {
+            return ResampledGridCoverage.create(source, target, interpolation);
+        } catch (FactoryException e) {
+            throw new TransformException(e);
+        }
+    }
+
+    /**
+     * Invoked when an ignorable exception occurred.
+     *
+     * @param  caller  the method where the exception occurred.
+     * @param  ex      the ignorable exception.
+     */
+    static void recoverableException(final String caller, final Exception ex) {
+        Logging.recoverableException(Logging.getLogger(Modules.RASTER), GridCoverageProcessor.class,
caller, ex);
+    }
+}
diff --git a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridExtent.java b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridExtent.java
index db09c1e..21247d5 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridExtent.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridExtent.java
@@ -157,7 +157,7 @@ public class GridExtent implements GridEnvelope, Serializable {
      *         greater than the corresponding coordinate value in the high part.
      */
     private void validateCoordinates() throws IllegalArgumentException {
-        final int dimension = coordinates.length >>> 1;
+        final int dimension = getDimension();
         for (int i=0; i<dimension; i++) {
             final long lower = coordinates[i];
             final long upper = coordinates[i + dimension];
@@ -565,6 +565,23 @@ public class GridExtent implements GridEnvelope, Serializable {
     }
 
     /**
+     * Returns the number of dimensions where this grid extent has a size greater than 1.
+     * This is a value between 0 and {@link #getDimension()} inclusive.
+     *
+     * @return the number of dimensions where this grid extent has a size greater than 1.
+     *
+     * @see #getSubspaceDimensions(int)
+     */
+    final int getSubDimension() {
+        int n = 0;
+        final int dimension = getDimension();
+        for (int i=0; i<dimension; i++) {
+            if (coordinates[i] != coordinates[i + dimension]) n++;
+        }
+        return n;
+    }
+
+    /**
      * Returns {@code true} if all low coordinates are zero.
      * This is a very common case since many grids start their cell numbering at zero.
      *
diff --git a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/ResampledGridCoverage.java
b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/ResampledGridCoverage.java
new file mode 100644
index 0000000..68a1f40
--- /dev/null
+++ b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/ResampledGridCoverage.java
@@ -0,0 +1,243 @@
+/*
+ * 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 java.util.List;
+import java.util.Optional;
+import java.awt.Rectangle;
+import java.awt.image.RenderedImage;
+import org.opengis.util.FactoryException;
+import org.opengis.coverage.CannotEvaluateException;
+import org.opengis.referencing.datum.PixelInCell;
+import org.opengis.referencing.crs.CoordinateReferenceSystem;
+import org.opengis.referencing.operation.CoordinateOperation;
+import org.opengis.referencing.operation.TransformException;
+import org.opengis.referencing.operation.MathTransform;
+import org.apache.sis.geometry.Envelopes;
+import org.apache.sis.image.Interpolation;
+import org.apache.sis.image.ResampledImage;
+import org.apache.sis.coverage.SampleDimension;
+import org.apache.sis.internal.coverage.j2d.ImageUtilities;
+import org.apache.sis.metadata.iso.extent.DefaultGeographicBoundingBox;
+import org.apache.sis.referencing.operation.transform.MathTransforms;
+import org.apache.sis.referencing.CRS;
+
+import static java.lang.Math.toIntExact;
+import static java.lang.Math.negateExact;
+import static java.lang.Math.subtractExact;
+
+
+/**
+ * A multi-dimensional grid coverage where each two-dimensional slice is the resampling
+ * of data from another grid coverage. This class is used when the resampling can not be
+ * stored in a {@link GridCoverage2D}.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @version 1.1
+ * @since   1.1
+ * @module
+ */
+final class ResampledGridCoverage extends GridCoverage {
+    /**
+     * The {@value} constant for identifying code specific to the two-dimensional case.
+     */
+    private static final int BIDIMENSIONAL = 2;
+
+    /**
+     * The coverage to resample.
+     */
+    private final GridCoverage source;
+
+    /**
+     * The transform from cell coordinates in this coverage to cell coordinates in {@linkplain
#source} coverage.
+     * Note that an offset may exist between cell coordinates and pixel coordinates, so some
translations may need
+     * to be concatenated with this transform on an image-by-image basis.
+     */
+    private final MathTransform toSource;
+
+    /**
+     * The interpolation method to use for resampling images.
+     */
+    private final Interpolation interpolation;
+
+    /**
+     * Indices of extent dimensions corresponding to image <var>x</var> and <var>y</var>
coordinates.
+     * Typical values are 0 for {@code xDimension} and 1 for {@code yDimension}, but different
values
+     * are allowed. This class select the dimensions having largest size.
+     */
+    private final int xDimension, yDimension;
+
+    /**
+     * Creates a new grid coverage which will be the resampling of the given source.
+     *
+     * @param  source         the coverage to resample.
+     * @param  domain         the grid extent, CRS and conversion from cell indices to CRS.
+     * @param  toSource       transform from cell coordinates in this coverage to source
coverage.
+     * @param  interpolation  the interpolation method to use for resampling images.
+     */
+    private ResampledGridCoverage(final GridCoverage source, final GridGeometry domain,
+                               final MathTransform toSource, final Interpolation interpolation)
+    {
+        super(source, domain);
+        this.source        = source;
+        this.toSource      = toSource;
+        this.interpolation = interpolation;
+        final GridExtent extent = domain.getExtent();
+        long size1 = 0; int idx1 = 0;
+        long size2 = 0; int idx2 = 1;
+        final int dimension = extent.getDimension();
+        for (int i=0; i<dimension; i++) {
+            final long size = extent.getSize(i);
+            if (size > size1) {
+                size2 = size1; idx2 = idx1;
+                size1 = size;  idx1 = i;
+            } else if (size > size2) {
+                size2 = size;  idx2 = i;
+            }
+        }
+        if (idx1 < idx2) {          // Keep (x,y) dimensions in the order they appear.
+            xDimension = idx1;
+            yDimension = idx2;
+        } else {
+            xDimension = idx2;
+            yDimension = idx1;
+        }
+    }
+
+    /**
+     * If this coverage can be represented as a {@link GridCoverage2D} instance,
+     * returns such instance. Otherwise returns {@code this}.
+     */
+    private GridCoverage specialize() {
+        final GridExtent extent = gridGeometry.getExtent();
+        if (extent.getSubDimension() > BIDIMENSIONAL) {
+            return this;
+        }
+        return new GridCoverage2D(source, gridGeometry, extent, render(null), xDimension,
yDimension);
+    }
+
+    /**
+     * Implementation of {@link GridCoverageProcessor#resample(GridCoverage, GridGeometry)}.
+     *
+     * @param  source  the grid coverage to resample.
+     * @param  target  the desired geometry of returned grid coverage. May be incomplete.
+     * @return a grid coverage with the characteristics specified in the given grid geometry.
+     * @throws IncompleteGridGeometryException if the source grid geometry is missing an
information.
+     * @throws TransformException if some coordinates can not be transformed to the specified
target.
+     */
+    static GridCoverage create(final GridCoverage source, GridGeometry target, final Interpolation
interpolation)
+            throws FactoryException, TransformException
+    {
+        /*
+         * Get the coordinate operation from source CRS to target CRS. It may be the identity
operation,
+         * or null only if there is not enough information for determining the operation.
We try to take
+         * envelopes in account because the operation choice may depend on the geographic
area.
+         */
+        CoordinateOperation changeOfCRS = null;
+        final GridGeometry sourceGG = source.getGridGeometry();
+        if (sourceGG.isDefined(GridGeometry.ENVELOPE) && target.isDefined(GridGeometry.ENVELOPE))
{
+            changeOfCRS = Envelopes.findOperation(sourceGG.getEnvelope(), target.getEnvelope());
+        }
+        if (changeOfCRS == null) try {
+            final CoordinateReferenceSystem sourceCRS = source.getCoordinateReferenceSystem();
+            final CoordinateReferenceSystem targetCRS = target.isDefined(GridGeometry.CRS)
?
+                                                        target.getCoordinateReferenceSystem()
: sourceCRS;
+            DefaultGeographicBoundingBox areaOfInterest = null;
+            if (sourceGG.isDefined(GridGeometry.ENVELOPE)) {
+                areaOfInterest = new DefaultGeographicBoundingBox();
+                areaOfInterest.setBounds(sourceGG.getEnvelope());
+            }
+            changeOfCRS = CRS.findOperation(sourceCRS, targetCRS, areaOfInterest);
+        } catch (IncompleteGridGeometryException e) {
+            // Happen if the source GridCoverage does not define a CRS.
+            GridCoverageProcessor.recoverableException("resample", e);
+        }
+        /*
+         * Compute the transform from source pixels to target CRS (to be completed to target
pixels later).
+         * The following line may throw IncompleteGridGeometryException, which is desired
because if that
+         * transform is missing, we can not continue (we have no way to guess it).
+         */
+        MathTransform toTarget = sourceGG.getGridToCRS(PixelInCell.CELL_CENTER);
+        if (changeOfCRS != null) {
+            toTarget = MathTransforms.concatenate(toTarget, changeOfCRS.getMathTransform());
+        }
+        /*
+         * Compute the transform from target grid to target CRS. This transform may be unspecified,
+         * in which case we need to compute a default transform trying to preserve resolution
at the
+         * point of interest.
+         */
+        MathTransform toSource = null;
+        if (target.isDefined(GridGeometry.GRID_TO_CRS)) {
+            toSource = target.getGridToCRS(PixelInCell.CELL_CENTER);
+        } else {
+            throw new UnsupportedOperationException("Automatic computation of gridToCRS not
yet implemented.");
+            // TODO: complete the target GridGeometry.
+        }
+        if (sourceGG.equals(target)) {
+            return source;
+        }
+        /*
+         * Complete the "target to source" transform.
+         */
+        toSource = MathTransforms.concatenate(toSource, toTarget.inverse());
+        return new ResampledGridCoverage(source, target, toSource, interpolation).specialize();
+    }
+
+    /**
+     * Returns a two-dimensional slice of resampled grid data as a rendered image.
+     */
+    @Override
+    public RenderedImage render(final GridExtent sliceExtent) {
+        if (sliceExtent != null) {
+            throw new CannotEvaluateException("Slice extent not yet supported.");
+        }
+        final RenderedImage image        = source.render(null);           // TODO: compute
slice.
+        final GridExtent    sourceExtent = source.getGridGeometry().getExtent();
+        final GridExtent    targetExtent = gridGeometry.getExtent();
+        final Rectangle     bounds       = new Rectangle(toIntExact(targetExtent.getSize(xDimension)),
+                                                         toIntExact(targetExtent.getSize(yDimension)));
+        /*
+         * `this.toSource` is a transform from source cell coordinates to target cell coordinates.
+         * We need a transform from source pixel coordinates to target pixel coordinates
(in images).
+         * An offset may exist between cell coordinates and pixel coordinates.
+         */
+        final MathTransform pixelsToTransform = MathTransforms.translation(
+                subtractExact(sourceExtent.getLow(xDimension), image.getMinX()),
+                subtractExact(sourceExtent.getLow(yDimension), image.getMinY()));
+
+        final MathTransform transformToPixels = MathTransforms.translation(
+                negateExact(targetExtent.getLow(xDimension)),
+                negateExact(targetExtent.getLow(yDimension)));
+
+        final MathTransform toImage = MathTransforms.concatenate(pixelsToTransform, toSource,
transformToPixels);
+        /*
+         * Get fill values from background values declared for each band, if any.
+         * If no background value is declared, default is 0 for integer data or
+         * NaN for floating point values.
+         */
+        final Number[] fillValues = new Number[ImageUtilities.getNumBands(image)];
+        final List<SampleDimension> bands = getSampleDimensions();
+        for (int i=Math.min(bands.size(), fillValues.length); --i >= 0;) {
+            final SampleDimension band = bands.get(i);
+            final Optional<Number> bg = band.getBackground();
+            if (bg.isPresent()) {
+                fillValues[i] = bg.get();
+            }
+        }
+        return new ResampledImage(bounds, toImage, image, interpolation, fillValues);
+    }
+}
diff --git a/core/sis-feature/src/main/java/org/apache/sis/image/ImageProcessor.java b/core/sis-feature/src/main/java/org/apache/sis/image/ImageProcessor.java
index 4a24e6b..bec2a66 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/image/ImageProcessor.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/image/ImageProcessor.java
@@ -65,7 +65,10 @@ import org.apache.sis.internal.coverage.j2d.ImageUtilities;
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @version 1.1
- * @since   1.1
+ *
+ * @see org.apache.sis.coverage.grid.GridCoverageProcessor
+ *
+ * @since 1.1
  * @module
  */
 public class ImageProcessor {


Mime
View raw message