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 <[email protected]> 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();} + }; + } }
