You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sis.apache.org by de...@apache.org on 2022/05/17 17:12:22 UTC

[sis] branch geoapi-4.0 updated (7d88d4c0f3 -> bc23143019)

This is an automated email from the ASF dual-hosted git repository.

desruisseaux pushed a change to branch geoapi-4.0
in repository https://gitbox.apache.org/repos/asf/sis.git


    from 7d88d4c0f3 Remove the generation of TOC fragment in each chapter. Will be replaced by better navigation pane.
     new 85769b9542 Avoid misleading error message for unsupported JPEG compression in GeoTIFF. Documentation update.
     new bc23143019 Add a `GridCoverageProcessor.convert(…)` method.

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .../sis/coverage/grid/ConvertedGridCoverage.java   | 23 +++++--
 .../org/apache/sis/coverage/grid/GridCoverage.java | 15 ++--
 .../apache/sis/coverage/grid/GridCoverage2D.java   |  2 +-
 .../sis/coverage/grid/GridCoverageProcessor.java   | 80 +++++++++++++++++++++-
 .../org/apache/sis/coverage/grid/package-info.java |  2 +-
 .../java/org/apache/sis/image/ImageProcessor.java  | 18 ++++-
 .../coverage/grid/ConvertedGridCoverageTest.java   | 47 +++++++++++--
 .../java/org/apache/sis/measure/NumberRange.java   | 36 +++++++++-
 .../java/org/apache/sis/measure/package-info.java  |  2 +-
 .../org/apache/sis/measure/NumberRangeTest.java    | 43 +++++++++++-
 storage/sis-earth-observation/pom.xml              |  2 +-
 .../sis/storage/geotiff/ImageFileDirectory.java    | 19 ++++-
 .../apache/sis/storage/geotiff/package-info.java   |  2 +-
 13 files changed, 258 insertions(+), 33 deletions(-)


[sis] 02/02: Add a `GridCoverageProcessor.convert(…)` method.

Posted by de...@apache.org.
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 bc2314301919790874400f91907cd8a9f3f05763
Author: Martin Desruisseaux <ma...@geomatys.com>
AuthorDate: Tue May 17 19:11:57 2022 +0200

    Add a `GridCoverageProcessor.convert(…)` method.
---
 .../sis/coverage/grid/ConvertedGridCoverage.java   | 23 +++++--
 .../org/apache/sis/coverage/grid/GridCoverage.java | 15 ++--
 .../apache/sis/coverage/grid/GridCoverage2D.java   |  2 +-
 .../sis/coverage/grid/GridCoverageProcessor.java   | 80 +++++++++++++++++++++-
 .../org/apache/sis/coverage/grid/package-info.java |  2 +-
 .../java/org/apache/sis/image/ImageProcessor.java  | 18 ++++-
 .../coverage/grid/ConvertedGridCoverageTest.java   | 47 +++++++++++--
 .../java/org/apache/sis/measure/NumberRange.java   | 36 +++++++++-
 .../java/org/apache/sis/measure/package-info.java  |  2 +-
 .../org/apache/sis/measure/NumberRangeTest.java    | 43 +++++++++++-
 10 files changed, 239 insertions(+), 29 deletions(-)

diff --git a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/ConvertedGridCoverage.java b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/ConvertedGridCoverage.java
index 1bfad3b115..e4dc6d2b3c 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/ConvertedGridCoverage.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/ConvertedGridCoverage.java
@@ -31,6 +31,7 @@ import org.apache.sis.coverage.SampleDimension;
 import org.apache.sis.measure.MeasurementRange;
 import org.apache.sis.measure.NumberRange;
 import org.apache.sis.image.DataType;
+import org.apache.sis.image.ImageProcessor;
 
 
 /**
@@ -47,7 +48,7 @@ import org.apache.sis.image.DataType;
  *
  * @author  Johann Sorel (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.1
+ * @version 1.3
  * @since   1.0
  * @module
  */
@@ -78,6 +79,11 @@ final class ConvertedGridCoverage extends GridCoverage {
      */
     private final DataType bandType;
 
+    /**
+     * The image processor to use for creating the tiles of converted values.
+     */
+    private final ImageProcessor processor;
+
     /**
      * Creates a new coverage with the same grid geometry than the given coverage but converted sample dimensions.
      *
@@ -85,20 +91,24 @@ final class ConvertedGridCoverage extends GridCoverage {
      * @param  range        the sample dimensions to assign to the converted grid coverage.
      * @param  converters   conversion from source to converted coverage, one transform per band.
      * @param  isConverted  whether this grid coverage is for converted or packed values.
+     * @param  processor    the image processor to use for creating the tiles of converted values.
      */
-    private ConvertedGridCoverage(final GridCoverage source, final List<SampleDimension> range,
-                                  final MathTransform1D[] converters, final boolean isConverted)
+    ConvertedGridCoverage(final GridCoverage source, final List<SampleDimension> range,
+                          final MathTransform1D[] converters, final boolean isConverted,
+                          final ImageProcessor processor)
     {
         super(source.getGridGeometry(), range);
         this.source      = source;
         this.converters  = converters;
         this.isConverted = isConverted;
         this.bandType    = getBandType(range, isConverted, source);
+        this.processor   = processor;
     }
 
     /**
      * Returns a coverage of converted values computed from a coverage of packed values, or conversely.
      * If the given coverage is already converted, then this method returns {@code coverage} unchanged.
+     * This method is used for {@link GridCoverage#forConvertedValues(boolean)} default implementation.
      *
      * @param  source     the coverage containing values to convert.
      * @param  converted  {@code true} for a coverage containing converted values,
@@ -110,7 +120,10 @@ final class ConvertedGridCoverage extends GridCoverage {
         final List<SampleDimension> sources = source.getSampleDimensions();
         final List<SampleDimension> targets = new ArrayList<>(sources.size());
         final MathTransform1D[]  converters = converters(sources, targets, converted);
-        return (converters != null) ? new ConvertedGridCoverage(source, targets, converters, converted) : source;
+        if (converters == null) {
+            return source;
+        }
+        return new ConvertedGridCoverage(source, targets, converters, converted, Lazy.PROCESSOR);
     }
 
     /**
@@ -280,7 +293,7 @@ final class ConvertedGridCoverage extends GridCoverage {
          * That image should never be null. But if an implementation wants to do so, respect that.
          */
         if (image != null) {
-            image = convert(image, bandType, converters);
+            image = convert(image, bandType, converters, processor);
         }
         return image;
     }
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 2d1ade3610..c4f7115e92 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
@@ -56,18 +56,18 @@ import org.opengis.coverage.CannotEvaluateException;
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
  * @author  Johann Sorel (Geomatys)
- * @version 1.2
+ * @version 1.3
  * @since   1.0
  * @module
  */
 public abstract class GridCoverage extends BandedCoverage {
     /**
-     * The processor to use for {@link #convert(RenderedImage, DataType, MathTransform1D[])} operations.
+     * The processor to use in calls to {@link #convert(RenderedImage, DataType, MathTransform1D[], ImageProcessor)}.
      * Wrapped in a class for lazy instantiation.
      */
-    private static final class Lazy {
+    static final class Lazy {
         private Lazy() {}
-        private static final ImageProcessor PROCESSOR = new ImageProcessor();
+        static final ImageProcessor PROCESSOR = new ImageProcessor();
     }
 
     /**
@@ -276,9 +276,12 @@ public abstract class GridCoverage extends BandedCoverage {
      * @param  source      the image for which to convert sample values.
      * @param  bandType    the type of data in the bands resulting from conversion of given image.
      * @param  converters  the transfer functions to apply on each band of the source image.
+     * @param  processor   the processor to use for creating the tiles of converted values.
      * @return the image which compute converted values from the given source.
      */
-    final RenderedImage convert(final RenderedImage source, final DataType bandType, final MathTransform1D[] converters) {
+    final RenderedImage convert(final RenderedImage source, final DataType bandType,
+            final MathTransform1D[] converters, final ImageProcessor processor)
+    {
         final int visibleBand = Math.max(0, ImageUtilities.getVisibleBand(source));
         final Colorizer colorizer = new Colorizer(Colorizer.GRAYSCALE);
         final ColorModel colors;
@@ -289,7 +292,7 @@ public abstract class GridCoverage extends BandedCoverage {
         } else {
             colors = Colorizer.NULL_COLOR_MODEL;
         }
-        return Lazy.PROCESSOR.convert(source, getRanges(), converters, bandType, colors);
+        return processor.convert(source, getRanges(), converters, bandType, colors);
     }
 
     /**
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 aa80d6a0e8..b6bf54843b 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
@@ -147,7 +147,7 @@ public class GridCoverage2D extends GridCoverage {
     {
         super(source.gridGeometry, range);
         final DataType bandType = ConvertedGridCoverage.getBandType(range, isConverted, source);
-        data           = convert(source.data, bandType, converters);
+        data           = convert(source.data, bandType, converters, Lazy.PROCESSOR);
         gridToImageX   = source.gridToImageX;
         gridToImageY   = source.gridToImageY;
         xDimension     = source.xDimension;
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
index 01b2ecab9d..0e855321e1 100644
--- 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
@@ -16,12 +16,20 @@
  */
 package org.apache.sis.coverage.grid;
 
-import java.awt.Shape;
+import java.util.List;
 import java.util.Objects;
+import java.util.function.Function;
+import java.awt.Shape;
+import java.awt.Rectangle;
+import java.awt.image.ColorModel;
 import java.awt.image.RenderedImage;
 import javax.measure.Quantity;
 import org.opengis.util.FactoryException;
+import org.opengis.referencing.operation.MathTransform1D;
 import org.opengis.referencing.operation.TransformException;
+import org.apache.sis.coverage.RegionOfInterest;
+import org.apache.sis.coverage.SampleDimension;
+import org.apache.sis.image.DataType;
 import org.apache.sis.image.ImageProcessor;
 import org.apache.sis.image.Interpolation;
 import org.apache.sis.util.ArgumentChecks;
@@ -29,7 +37,8 @@ import org.apache.sis.util.logging.Logging;
 import org.apache.sis.util.collection.WeakHashSet;
 import org.apache.sis.internal.system.Modules;
 import org.apache.sis.internal.util.FinalFieldSetter;
-import org.apache.sis.coverage.RegionOfInterest;
+import org.apache.sis.internal.util.UnmodifiableArrayList;
+import org.apache.sis.measure.NumberRange;
 
 import static java.util.logging.Logger.getLogger;
 
@@ -41,7 +50,7 @@ import static java.util.logging.Logger.getLogger;
  * {@code GridCoverageProcessor} is safe for concurrent use in multi-threading environment.
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.2
+ * @version 1.3
  *
  * @see org.apache.sis.image.ImageProcessor
  *
@@ -175,6 +184,8 @@ public class GridCoverageProcessor implements Cloneable {
      * @return a coverage with mask applied.
      * @throws TransformException if ROI coordinates can not be transformed to grid coordinates.
      *
+     * @see ImageProcessor#mask(RenderedImage, Shape, boolean)
+     *
      * @since 1.2
      */
     public GridCoverage mask(final GridCoverage source, final RegionOfInterest mask, final boolean maskInside)
@@ -188,6 +199,67 @@ public class GridCoverageProcessor implements Cloneable {
         return new GridCoverage2D(source, data);
     }
 
+    /**
+     * Returns a coverage with sample values converted by the given functions.
+     * The number of sample dimensions in the returned coverage is the length of the {@code converters} array,
+     * which must be greater than 0 and not greater than the number of sample dimensions in the source coverage.
+     * If the {@code converters} array length is less than the number of source sample dimensions,
+     * then all sample dimensions at index ≥ {@code converters.length} will be ignored.
+     *
+     * <h4>Sample dimensions customization</h4>
+     * By default, this method creates new sample dimensions with the same names and categories than in the
+     * previous coverage, but with {@linkplain org.apache.sis.coverage.Category#getSampleRange() sample ranges}
+     * converted using the given converters and with {@linkplain SampleDimension#getUnits() units of measurement}
+     * omitted. This behavior can be modified by specifying a non-null {@code sampleDimensionModifier} function.
+     * If non-null, that function will be invoked with, as input, a pre-configured sample dimension builder.
+     * The {@code sampleDimensionModifier} function can {@linkplain SampleDimension.Builder#setName(CharSequence)
+     * change the sample dimension name} or {@linkplain SampleDimension.Builder#categories() rebuild the categories}.
+     *
+     * <h4>Result relationship with source</h4>
+     * If the source coverage is backed by a {@link java.awt.image.WritableRenderedImage},
+     * then changes in the source coverage are reflected in the returned coverage and conversely.
+     *
+     * @param  source      the coverage for which to convert sample values.
+     * @param  converters  the transfer functions to apply on each sample dimension of the source coverage.
+     * @param  sampleDimensionModifier  a callback for modifying the {@link SampleDimension.Builder} default
+     *         configuration for each sample dimension of the target coverage, or {@code null} if none.
+     * @return the coverage which computes converted values from the given source.
+     *
+     * @see ImageProcessor#convert(RenderedImage, NumberRange<?>[], MathTransform1D[], DataType, ColorModel)
+     *
+     * @since 1.3
+     */
+    public GridCoverage convert(final GridCoverage source, MathTransform1D[] converters,
+            Function<SampleDimension.Builder, SampleDimension> sampleDimensionModifier)
+    {
+        ArgumentChecks.ensureNonNull("source",     source);
+        ArgumentChecks.ensureNonNull("converters", converters);
+        final List<SampleDimension> sourceBands = source.getSampleDimensions();
+        ArgumentChecks.ensureSizeBetween("converters", 1, sourceBands.size(), converters.length);
+        final SampleDimension[] targetBands = new SampleDimension[converters.length];
+        final SampleDimension.Builder builder = new SampleDimension.Builder();
+        if (sampleDimensionModifier == null) {
+            sampleDimensionModifier = SampleDimension.Builder::build;
+        }
+        for (int i=0; i < converters.length; i++) {
+            final MathTransform1D converter = converters[i];
+            ArgumentChecks.ensureNonNullElement("converters", i, converter);
+            final SampleDimension band = sourceBands.get(i);
+            band.getBackground().ifPresent(builder::setBackground);
+            band.getCategories().forEach((category) -> {
+                if (category.isQuantitative()) {
+                    // Unit is assumed different as a result of conversion.
+                    builder.addQuantitative(category.getName(), category.getSampleRange(), converter, null);
+                } else {
+                    builder.addQualitative(category.getName(), category.getSampleRange());
+                }
+            });
+            targetBands[i] = sampleDimensionModifier.apply(builder.setName(band.getName())).forConvertedValues(true);
+            builder.clear();
+        }
+        return new ConvertedGridCoverage(source, UnmodifiableArrayList.wrap(targetBands), converters, true, unique(imageProcessor));
+    }
+
     /**
      * 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.
@@ -221,6 +293,8 @@ public class GridCoverageProcessor implements Cloneable {
      * @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.
+     *
+     * @see ImageProcessor#resample(RenderedImage, Rectangle, MathTransform)
      */
     public GridCoverage resample(GridCoverage source, final GridGeometry target) throws TransformException {
         ArgumentChecks.ensureNonNull("source", source);
diff --git a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/package-info.java b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/package-info.java
index 8ced2567b2..c97ab867d7 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/package-info.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/package-info.java
@@ -41,7 +41,7 @@
  * @author  Martin Desruisseaux (Geomatys)
  * @author  Johann Sorel (Geomatys)
  * @author  Alexis Manin (Geomatys)
- * @version 1.2
+ * @version 1.3
  * @since   1.0
  * @module
  */
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 5bd5a02fa0..ef4923f5d7 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
@@ -56,6 +56,12 @@ import org.apache.sis.internal.feature.Resources;
 import org.apache.sis.measure.NumberRange;
 import org.apache.sis.measure.Units;
 
+// For javadoc
+import org.apache.sis.coverage.RegionOfInterest;
+import org.apache.sis.coverage.grid.GridCoverage;
+import org.apache.sis.coverage.grid.GridGeometry;
+import org.apache.sis.coverage.grid.GridCoverageProcessor;
+
 
 /**
  * A predefined set of operations on images as convenience methods.
@@ -822,6 +828,8 @@ public class ImageProcessor implements Cloneable {
      * @param  maskInside  {@code true} for masking pixels inside the shape, or {@code false} for masking outside.
      * @return an image with mask applied.
      *
+     * @see GridCoverageProcessor#mask(GridCoverage, RegionOfInterest, boolean)
+     *
      * @since 1.2
      */
     public RenderedImage mask(final RenderedImage source, final Shape mask, final boolean maskInside) {
@@ -863,15 +871,17 @@ public class ImageProcessor implements Cloneable {
      * </ul>
      *
      * <h4>Result relationship with source</h4>
-     * Changes in the source image are reflected in the returned images
+     * Changes in the source image are reflected in the returned image
      * if the source image notifies {@linkplain java.awt.image.TileObserver tile observers}.
      *
      * @param  source        the image for which to convert sample values.
      * @param  sourceRanges  approximate ranges of values for each band in source image, or {@code null} if unknown.
      * @param  converters    the transfer functions to apply on each band of the source image.
-     * @param  targetType    the type of image resulting from conversions.
+     * @param  targetType    the type of data in the image resulting from conversions.
      * @param  colorModel    color model of resulting image, or {@code null}.
-     * @return the image which compute converted values from the given source.
+     * @return the image which computes converted values from the given source.
+     *
+     * @see GridCoverageProcessor#convert(GridCoverage, MathTransform1D[], Function)
      */
     public RenderedImage convert(final RenderedImage source, final NumberRange<?>[] sourceRanges,
                 MathTransform1D[] converters, final DataType targetType, final ColorModel colorModel)
@@ -935,6 +945,8 @@ public class ImageProcessor implements Cloneable {
      *                   Updated by this method if {@link Resizing#EXPAND} policy is applied.
      * @param  toSource  conversion of pixel coordinates from resampled image to {@code source} image.
      * @return resampled image (may be {@code source}).
+     *
+     * @see GridCoverageProcessor#resample(GridCoverage, GridGeometry)
      */
     public RenderedImage resample(RenderedImage source, final Rectangle bounds, MathTransform toSource) {
         ArgumentChecks.ensureNonNull("source",   source);
diff --git a/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/ConvertedGridCoverageTest.java b/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/ConvertedGridCoverageTest.java
index 91092969f1..8d1597fdc4 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/ConvertedGridCoverageTest.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/ConvertedGridCoverageTest.java
@@ -19,15 +19,19 @@ package org.apache.sis.coverage.grid;
 import java.util.Collections;
 import java.awt.image.DataBuffer;
 import org.opengis.referencing.datum.PixelInCell;
-import org.apache.sis.coverage.SampleDimension;
+import org.opengis.referencing.operation.MathTransform1D;
+import org.apache.sis.referencing.operation.transform.MathTransforms;
 import org.apache.sis.internal.referencing.j2d.AffineTransform2D;
+import org.apache.sis.coverage.SampleDimension;
 import org.apache.sis.math.MathFunctions;
+import org.apache.sis.measure.NumberRange;
 import org.apache.sis.measure.Units;
 import org.apache.sis.referencing.crs.HardCodedCRS;
 import org.apache.sis.test.TestCase;
 import org.junit.Test;
 
 import static org.apache.sis.test.FeatureAssert.*;
+import static org.apache.sis.test.TestUtilities.getSingleton;
 
 
 /**
@@ -35,17 +39,16 @@ import static org.apache.sis.test.FeatureAssert.*;
  *
  * @author  Johann Sorel (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.1
+ * @version 1.3
  * @since   1.1
  * @module
  */
 public final strictfp class ConvertedGridCoverageTest extends TestCase {
     /**
-     * Tests forward conversion from packed values to "geophysics" values.
-     * Test includes a conversion of an integer value to {@link Float#NaN}.
+     * Creates a test coverage backed by an image of 2 pixels
+     * on a single row with sample values (-1, 3).
      */
-    @Test
-    public void testForward() {
+    private static BufferedGridCoverage coverage() {
         /*
          * A sample dimension with an identity transfer function
          * except for value -1 which will be mapped to NaN.
@@ -56,7 +59,6 @@ public final strictfp class ConvertedGridCoverageTest extends TestCase {
                 .setName("data")
                 .build();
         /*
-         * Creates an image of 2 pixels on a single row with sample values (-1, 3).
          * The "grid to CRS" transform does not matter for this test.
          */
         final GridGeometry grid = new GridGeometry(new GridExtent(2, 1), PixelInCell.CELL_CENTER,
@@ -67,6 +69,16 @@ public final strictfp class ConvertedGridCoverageTest extends TestCase {
 
         coverage.data.setElem(0, -1);
         coverage.data.setElem(1,  3);
+        return coverage;
+    }
+
+    /**
+     * Tests forward conversion from packed values to "geophysics" values.
+     * Test includes a conversion of an integer value to {@link Float#NaN}.
+     */
+    @Test
+    public void testForward() {
+        final BufferedGridCoverage coverage = coverage();
         /*
          * Verify values before and after conversion.
          */
@@ -79,4 +91,25 @@ public final strictfp class ConvertedGridCoverageTest extends TestCase {
             {nan, 3}
         });
     }
+
+    /**
+     * Tests the creation of a converted grid coverage through {@link GridCoverageProcessor}.
+     */
+    @Test
+    public void testProcessor() {
+        final GridCoverageProcessor processor = new GridCoverageProcessor();
+        final GridCoverage source = coverage();
+        final GridCoverage target = processor.convert(source, new MathTransform1D[] {
+            (MathTransform1D) MathTransforms.linear(10, 100)
+        }, null);
+        assertSame(target, target.forConvertedValues(true));
+        assertSame(source, target.forConvertedValues(false));
+        assertValuesEqual(target.render(null), 0, new double[][] {
+            {90, 130}      // {-1, 3} × 10 + 100
+        });
+        final SampleDimension band = getSingleton(target.getSampleDimensions());
+        final NumberRange<?> range = band.getSampleRange().get();
+        assertEquals(100, range.getMinDouble(), STRICT);
+        assertEquals(200, range.getMaxDouble(), STRICT);
+    }
 }
diff --git a/core/sis-utility/src/main/java/org/apache/sis/measure/NumberRange.java b/core/sis-utility/src/main/java/org/apache/sis/measure/NumberRange.java
index 99cce2dadf..6c55339228 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/measure/NumberRange.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/measure/NumberRange.java
@@ -22,6 +22,8 @@ import org.apache.sis.util.resources.Errors;
 import org.apache.sis.internal.util.Numerics;
 import org.apache.sis.math.MathFunctions;
 import org.apache.sis.util.collection.WeakHashSet;
+import org.opengis.referencing.operation.MathTransform1D;
+import org.opengis.referencing.operation.TransformException;
 
 
 /**
@@ -54,6 +56,7 @@ import org.apache.sis.util.collection.WeakHashSet;
  * <ul>
  *   <li>Convenience {@code create(…)} static methods for every numeric primitive types.</li>
  *   <li>{@link #castTo(Class)} for casting the range values to an other type.</li>
+ *   <li>{@link #transform(MathTransform1D)} for applying an arbitrary conversion.</li>
  * </ul>
  *
  * <h2>Relationship with standards</h2>
@@ -80,7 +83,7 @@ import org.apache.sis.util.collection.WeakHashSet;
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
  * @author  Jody Garnett (for parameterized type inspiration)
- * @version 1.2
+ * @version 1.3
  *
  * @param <E>  the type of range elements as a subclass of {@link Number}.
  *
@@ -848,4 +851,35 @@ public class NumberRange<E extends Number & Comparable<? super E>> extends Range
         final Class type = Numbers.widestClass(elementType, range.elementType);
         return (NumberRange[]) castTo(type).subtract(convertAndCast(range, type));
     }
+
+    /**
+     * Returns this range converted using the given converter.
+     *
+     * @param  converter  the converter to apply.
+     * @return the converted range, or {@code this} if the result is the same as this range.
+     * @throws TransformException if an error occurred during the conversion.
+     *
+     * @since 1.3
+     */
+    public NumberRange<?> transform(final MathTransform1D converter) throws TransformException {
+        final double lower = getMinDouble();
+        final double upper = getMaxDouble();
+        final double min   = converter.transform(lower);
+        final double max   = converter.transform(upper);
+        /*
+         * Use `doubleToLongBits` instead of `doubleToLongRawBits` for preserving the NaN values
+         * used by the original range. Different NaN values may be used for different types of
+         * "no data" values we usually want to keep them unchanged by the converter.
+         */
+        if (Double.doubleToLongBits(min) != Double.doubleToLongBits(lower) ||
+            Double.doubleToLongBits(max) != Double.doubleToLongBits(upper))
+        {
+            if (min > max) {
+                return create(max, isMaxIncluded, min, isMinIncluded);
+            } else {
+                return create(min, isMinIncluded, max, isMaxIncluded);
+            }
+        }
+        return this;
+    }
 }
diff --git a/core/sis-utility/src/main/java/org/apache/sis/measure/package-info.java b/core/sis-utility/src/main/java/org/apache/sis/measure/package-info.java
index d0aaf2a6e3..7a00e23470 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/measure/package-info.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/measure/package-info.java
@@ -97,7 +97,7 @@
  *
  * @author  Martin Desruisseaux (MPO, IRD, Geomatys)
  * @author  Alexis Manin (Geomatys)
- * @version 1.2
+ * @version 1.3
  * @since   0.3
  * @module
  */
diff --git a/core/sis-utility/src/test/java/org/apache/sis/measure/NumberRangeTest.java b/core/sis-utility/src/test/java/org/apache/sis/measure/NumberRangeTest.java
index b5a35e46e3..d13ac4e16a 100644
--- a/core/sis-utility/src/test/java/org/apache/sis/measure/NumberRangeTest.java
+++ b/core/sis-utility/src/test/java/org/apache/sis/measure/NumberRangeTest.java
@@ -16,19 +16,23 @@
  */
 package org.apache.sis.measure;
 
+import org.opengis.referencing.operation.MathTransform1D;
 import org.apache.sis.math.MathFunctions;
 import org.junit.Test;
 import org.apache.sis.test.TestCase;
 import org.apache.sis.test.DependsOn;
 
 import static org.junit.Assert.*;
+import org.opengis.geometry.DirectPosition;
+import org.opengis.referencing.operation.Matrix;
+import org.opengis.referencing.operation.TransformException;
 
 
 /**
  * Tests the {@link NumberRange} class.
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
- * @version 1.2
+ * @version 1.3
  * @since   0.3
  * @module
  */
@@ -155,4 +159,41 @@ public final strictfp class NumberRangeTest extends TestCase {
         final NumberRange<Short> range = new NumberRange<>(Short.class, values);
         assertEquals(NumberRange.create((short) 4, true, (short) 8, false), range);
     }
+
+    /**
+     * Tests {@link NumberRange#transform(MathTransform1D)}.
+     *
+     * @throws TransformException should never happen.
+     */
+    @Test
+    public void testTransform() throws TransformException {
+        final NumberRange<Integer> range = new NumberRange<>(Integer.class, -5, true, 18, false);
+        assertSame(range, range.transform(scale(1)));
+        assertEquals(new NumberRange<>(Double.class, -10d, true, 36d, false), range.transform(scale(2)));
+        assertEquals(new NumberRange<>(Double.class, -36d, false, 10d, true), range.transform(scale(-2)));
+    }
+
+    /**
+     * Returns a mock transform which applies the given multiplication factor.
+     *
+     * @param  factor  the scale factor.
+     * @return a transform applying the given scale factor.
+     */
+    private static MathTransform1D scale(final double factor) {
+        return new MathTransform1D() {
+            @Override public int     getSourceDimensions()    {return 1;}
+            @Override public int     getTargetDimensions()    {return 1;}
+            @Override public boolean isIdentity()             {return factor == 1;}
+            @Override public double  transform (double value) {return factor * value;}
+            @Override public double  derivative(double value) {throw new UnsupportedOperationException();}
+            @Override public MathTransform1D inverse()        {throw new UnsupportedOperationException();}
+            @Override public DirectPosition transform(DirectPosition ptSrc, DirectPosition ptDst) {throw new UnsupportedOperationException();}
+            @Override public void transform(double[] srcPts, int srcOff, double[] dstPts, int dstOff, int numPts) {throw new UnsupportedOperationException();}
+            @Override public void transform(float [] srcPts, int srcOff, float [] dstPts, int dstOff, int numPts) {throw new UnsupportedOperationException();}
+            @Override public void transform(float [] srcPts, int srcOff, double[] dstPts, int dstOff, int numPts) {throw new UnsupportedOperationException();}
+            @Override public void transform(double[] srcPts, int srcOff, float [] dstPts, int dstOff, int numPts) {throw new UnsupportedOperationException();}
+            @Override public Matrix derivative(DirectPosition point) {throw new UnsupportedOperationException();}
+            @Override public String toWKT() {throw new UnsupportedOperationException();}
+        };
+    }
 }


[sis] 01/02: Avoid misleading error message for unsupported JPEG compression in GeoTIFF. Documentation update.

Posted by de...@apache.org.
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 85769b95422b47d58977deeca18b9c33b94e97fa
Author: Martin Desruisseaux <ma...@geomatys.com>
AuthorDate: Tue May 17 15:28:16 2022 +0200

    Avoid misleading error message for unsupported JPEG compression in GeoTIFF.
    Documentation update.
---
 storage/sis-earth-observation/pom.xml                 |  2 +-
 .../sis/storage/geotiff/ImageFileDirectory.java       | 19 +++++++++++++++++--
 .../org/apache/sis/storage/geotiff/package-info.java  |  2 +-
 3 files changed, 19 insertions(+), 4 deletions(-)

diff --git a/storage/sis-earth-observation/pom.xml b/storage/sis-earth-observation/pom.xml
index f224bf9a0d..a3c9b828da 100644
--- a/storage/sis-earth-observation/pom.xml
+++ b/storage/sis-earth-observation/pom.xml
@@ -39,7 +39,7 @@
   <artifactId>sis-earth-observation</artifactId>
   <name>Apache SIS Earth Observation storage</name>
   <description>
-    Landsat and MODIS metadata files to ISO 19115 metadata.
+    Read a directory of Landsat files as a single resource.
   </description>
 
 
diff --git a/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/ImageFileDirectory.java b/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/ImageFileDirectory.java
index f0b9a2cb78..23eee30271 100644
--- a/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/ImageFileDirectory.java
+++ b/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/ImageFileDirectory.java
@@ -72,7 +72,7 @@ import org.apache.sis.image.DataType;
  * @author  Johann Sorel (Geomatys)
  * @author  Thi Phuong Hao Nguyen (VNSC)
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.2
+ * @version 1.3
  *
  * @see <a href="http://www.awaresystems.be/imaging/tiff/tifftags.html">TIFF Tag Reference</a>
  *
@@ -83,8 +83,9 @@ final class ImageFileDirectory extends DataCube {
     /**
      * Possible value for the {@link #tileTagFamily} field. That field tells whether image tiling
      * was specified using the {@code Tile*} family of TIFF tags or the {@code Strip*} family.
+     * JPEG was also used to have its own set of tags.
      */
-    private static final byte TILE = 1, STRIP = 2;
+    private static final byte TILE = 1, STRIP = 2, JPEG=3;
 
     /**
      * Possible value for {@link #sampleFormat} specifying how to interpret each data sample in a pixel.
@@ -609,6 +610,19 @@ final class ImageFileDirectory extends DataCube {
                 tileByteCounts = type.readVector(input(), count);
                 break;
             }
+            /*
+             * Legacy tags for JPEG formats, to be also interpreted as a tile.
+             */
+            case Tags.JPEGInterchangeFormat: {
+                setTileTagFamily(JPEG);
+                tileOffsets = type.readVector(input(), count);
+                break;
+            }
+            case Tags.JPEGInterchangeFormatLength: {
+                setTileTagFamily(JPEG);
+                tileByteCounts = type.readVector(input(), count);
+                break;
+            }
 
             ////////////////////////////////////////////////////////////////////////////////////////////////
             ////                                                                                        ////
@@ -1195,6 +1209,7 @@ final class ImageFileDirectory extends DataCube {
         if (imageHeight < 0) throw missingTag(Tags.ImageLength);
         final short offsetsTag, byteCountsTag;
         switch (tileTagFamily) {
+            case JPEG:                      // Handled as strips.
             case STRIP: {
                 if (tileWidth  < 0) tileWidth  = Math.toIntExact(imageWidth);
                 if (tileHeight < 0) tileHeight = Math.toIntExact(imageHeight);
diff --git a/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/package-info.java b/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/package-info.java
index 208d60ac3d..129ebd3913 100644
--- a/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/package-info.java
+++ b/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/package-info.java
@@ -32,7 +32,7 @@
  * @author  Thi Phuong Hao Nguyen (VNSC)
  * @author  Minh Chinh Vu (VNSC)
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.2
+ * @version 1.3
  * @since   0.8
  * @module
  */