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 242b57a25f7b5215f93ca911f411c8487fa183d0 Author: Martin Desruisseaux <martin.desruisse...@geomatys.com> AuthorDate: Sat Nov 25 16:02:18 2023 +0100 Add convenience method for adding a vertical and temporal dimensions to a grid coverage. --- .../sis/coverage/grid/DimensionAppender.java | 60 +++++++--- .../sis/coverage/grid/DimensionalityReduction.java | 15 ++- .../sis/coverage/grid/GridCoverageProcessor.java | 58 +++++++++ .../org/apache/sis/coverage/grid/GridExtent.java | 25 ++++ .../sis/coverage/grid/DimensionAppenderTest.java | 131 +++++++++++++++++++++ .../coverage/grid/DimensionalityReductionTest.java | 4 +- .../main/org/apache/sis/referencing/CommonCRS.java | 11 ++ .../main/org/apache/sis/util/ArraysExt.java | 19 +++ .../org/apache/sis/util/internal/Numerics.java | 14 +++ 9 files changed, 317 insertions(+), 20 deletions(-) diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/DimensionAppender.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/DimensionAppender.java index 4deb4f4a1c..d3fac44fe9 100644 --- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/DimensionAppender.java +++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/DimensionAppender.java @@ -21,7 +21,6 @@ import org.opengis.util.FactoryException; import org.apache.sis.image.DataType; import org.apache.sis.util.ArraysExt; import org.apache.sis.util.ArgumentChecks; -import org.apache.sis.util.Workaround; import org.apache.sis.util.internal.Numerics; import org.apache.sis.feature.internal.Resources; import org.apache.sis.coverage.SubspaceNotSpecifiedException; @@ -50,12 +49,12 @@ final class DimensionAppender extends GridCoverage { /** * Creates a new dimension appender for the given grid coverage. - * The grid extent of {@code dimToAdd} shall have a grid size of one cell in all dimensions. + * This constructor does not verify the grid geometry validity. + * It is caller's responsibility to verify that the size is 1 cell. * * @param source the source grid coverage for which to append extra dimensions. * @param dimToAdd the dimensions to add to the source grid coverage. * @throws FactoryException if the compound CRS cannot be created. - * @throws IllegalGridGeometryException if a dimension has more than one grid cell. * @throws IllegalArgumentException if the concatenation results in duplicated * {@linkplain GridExtent#getAxisType(int) grid axis types}. */ @@ -63,22 +62,32 @@ final class DimensionAppender extends GridCoverage { super(source, new GridGeometry(source.getGridGeometry(), dimToAdd)); this.source = source; this.dimToAdd = dimToAdd; + } + + /** + * Creates a grid coverage augmented with the given dimensions. + * The grid extent of {@code dimToAdd} shall have a grid size of one cell in all dimensions. + * + * @param source the source grid coverage for which to append extra dimensions. + * @param dimToAdd the dimensions to add to the source grid coverage. + * @throws FactoryException if the compound CRS cannot be created. + * @throws IllegalGridGeometryException if a dimension has more than one grid cell. + * @throws IllegalArgumentException if the concatenation results in duplicated + * {@linkplain GridExtent#getAxisType(int) grid axis types}. + */ + static GridCoverage create(GridCoverage source, GridGeometry dimToAdd) throws FactoryException { final GridExtent extent = dimToAdd.getExtent(); - for (int i = extent.getDimension(); --i >= 0;) { - final long size = extent.getSize(i); + int i = extent.getDimension(); + if (i == 0) { + return source; + } + do { + final long size = extent.getSize(--i); if (size != 1) { throw new IllegalGridGeometryException(Resources.format(Resources.Keys.NotASlice_2, extent.getAxisIdentification(i,i), size)); } - } - } - - /** - * Work around for RFE #4093999 in Sun's bug database - * ("Relax constraint on placement of this()/super() call in constructors"). - */ - @Workaround(library="JDK", version="1.7") - static DimensionAppender create(GridCoverage source, GridGeometry dimToAdd) throws FactoryException { + } while (i != 0); if (source instanceof DimensionAppender) { final var a = (DimensionAppender) source; dimToAdd = new GridGeometry(a.dimToAdd, dimToAdd); @@ -87,6 +96,29 @@ final class DimensionAppender extends GridCoverage { return new DimensionAppender(source, dimToAdd); } + /** + * Returns a grid coverage with a subset of the grid dimensions, or {@code null} if not possible by this method. + * + * @param gridAxesToPass the grid dimensions to keep. Indices must be in strictly increasing order. + * @return a grid coverage with the specified dimensions, or {@code null}. + * @throws FactoryException if the compound CRS cannot be created. + */ + final GridCoverage selectDimensions(final int[] gridAxesToPass) throws FactoryException { + final int sourceDim = source.getGridGeometry().getDimension(); + final int dimension = gridAxesToPass.length; + if (dimension < sourceDim || gridAxesToPass[0] != 0 || gridAxesToPass[sourceDim - 1] != sourceDim - 1) { + return null; + } + if (dimension == sourceDim) { + return source; + } + final int[] selected = new int[dimension - sourceDim]; + for (int i=sourceDim; i<dimension; i++) { + selected[i - sourceDim] = gridAxesToPass[i] - sourceDim; + } + return create(source, dimToAdd.selectDimensions(selected)); + } + /** * Returns the data type identifying the primitive type used for storing sample values in each band. */ diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/DimensionalityReduction.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/DimensionalityReduction.java index 03bca8b31f..71a07187a7 100644 --- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/DimensionalityReduction.java +++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/DimensionalityReduction.java @@ -68,7 +68,7 @@ import org.opengis.coverage.PointOutsideCoverageException; * * @author Alexis Manin (Geomatys) * @author Martin Desruisseaux (Geomatys) - * @version 1.4 + * @version 1.5 * @since 1.4 */ public class DimensionalityReduction implements UnaryOperator<GridCoverage>, Serializable { @@ -171,7 +171,7 @@ public class DimensionalityReduction implements UnaryOperator<GridCoverage>, Ser * @param source the grid geometry on which to select a subset of its grid dimensions. * @param gridAxes bitmask of indices of source grid dimensions to keep in the reduced grid. * Will be modified by this constructor for internal purpose. - * @param factory the factory to use for creating new math transforms, or {@code null} if none. + * @param factory the factory to use for creating new math transforms, or {@code null} for the default. * @throws FactoryException if the dimensions to keep cannot be separated from the dimensions to omit. */ protected DimensionalityReduction(final GridGeometry source, final BitSet gridAxes, final MathTransformFactory factory) @@ -262,7 +262,7 @@ public class DimensionalityReduction implements UnaryOperator<GridCoverage>, Ser * @param gridAxesToRemove the dimensions on which to operate. * @param bitset same as {@link gridAxesToRemove} but as a bit set (for efficiency). * @param anchor whether to compute the transform for pixel corner or pixel center. - * @param factory the factory to use for creating new math transforms, or {@code null} if none. + * @param factory the factory to use for creating new math transforms, or {@code null} for the default. */ private MathTransform filterGridToCRS(final int[] gridAxesToRemove, final BitSet bitset, final PixelInCell anchor, final MathTransformFactory factory) throws FactoryException @@ -734,7 +734,14 @@ public class DimensionalityReduction implements UnaryOperator<GridCoverage>, Ser public GridCoverage apply(final GridCoverage source) { ArgumentChecks.ensureNonNull("source", source); ensureSameAxes(sourceGeometry.extent, source.getGridGeometry().extent); - return isIdentity() ? source : new ReducedGridCoverage(source, this); + if (isIdentity()) return source; + if (source instanceof DimensionAppender) try { + GridCoverage c = ((DimensionAppender) source).selectDimensions(gridAxesToPass); + if (c != null) return c; + } catch (FactoryException e) { + throw new IllegalGridGeometryException(Resources.format(Resources.Keys.NonSeparableReducedDimensions, e)); + } + return new ReducedGridCoverage(source, this); } /** diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridCoverageProcessor.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridCoverageProcessor.java index d61f6a4b18..d7194127b1 100644 --- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridCoverageProcessor.java +++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridCoverageProcessor.java @@ -21,6 +21,8 @@ import java.util.Set; import java.util.EnumSet; import java.util.Objects; import java.util.function.Function; +import java.time.Instant; +import java.time.Duration; import java.lang.reflect.Field; import java.lang.reflect.InaccessibleObjectException; import java.awt.Shape; @@ -28,6 +30,7 @@ import java.awt.Rectangle; import java.awt.image.RenderedImage; import javax.measure.Quantity; import org.opengis.util.FactoryException; +import org.opengis.metadata.spatial.DimensionNameType; import org.opengis.referencing.datum.PixelInCell; import org.opengis.referencing.crs.CoordinateReferenceSystem; import org.opengis.referencing.operation.MathTransform; @@ -44,9 +47,13 @@ import org.apache.sis.image.ImageProcessor; import org.apache.sis.image.Interpolation; import org.apache.sis.coverage.internal.SampleDimensions; import org.apache.sis.coverage.internal.MultiSourceArgument; +import org.apache.sis.referencing.CommonCRS; +import org.apache.sis.referencing.crs.DefaultTemporalCRS; +import org.apache.sis.referencing.operation.transform.MathTransforms; import org.apache.sis.util.ArgumentChecks; import org.apache.sis.util.logging.Logging; import org.apache.sis.util.collection.WeakHashSet; +import org.apache.sis.util.internal.Numerics; import org.apache.sis.util.internal.UnmodifiableArrayList; import org.apache.sis.measure.NumberRange; @@ -645,6 +652,57 @@ public class GridCoverageProcessor implements Cloneable { } } + /** + * Appends a single grid dimension after the dimensions of the given source coverage. + * This method is typically invoked for adding a vertical axis to a two-dimensional coverage. + * The default implementation delegates to {@link #appendDimensions(GridCoverage, GridGeometry)}. + * + * @param source the source on which to append a dimension. + * @param lower lower coordinate value of the slice, in units of the CRS. + * @param span size of the slice, in units of the CRS. + * @param crs coordinate reference system of the slice, or {@code null} if unknown. + * @return a coverage with the specified dimension added. + * @throws IllegalGridGeometryException if the compound CRS or compound extent cannot be created. + * + * @since 1.5 + */ + public GridCoverage appendDimension(final GridCoverage source, double lower, final double span, final CoordinateReferenceSystem crs) { + /* + * Choose a cell index such as the translation term in the matrix will be as close as possible to zero. + * Reducing the magnitude of additions with IEEE 754 arithmetic can help to reduce rounding errors. + * It also has the desirable side-effect to increase the chances that slices share the same + * "grid to CRS" transform. + */ + final long index = Numerics.roundAndClamp(lower / span); + final long[] indices = new long[] {index}; + final GridExtent extent = new GridExtent(GridExtent.typeFromAxes(crs, 1), indices, indices, true); + final MathTransform gridToCRS = MathTransforms.linear(span, Math.fma(index, -span, lower)); + return appendDimensions(source, new GridGeometry(extent, PixelInCell.CELL_CORNER, gridToCRS, crs)); + } + + /** + * Appends a temporal grid dimension after the dimensions of the given source coverage. + * The default implementation delegates to {@link #appendDimensions(GridCoverage, GridGeometry)}. + * + * @param source the source on which to append a temporal dimension. + * @param lower start time of the slice. + * @param span duration of the slice. + * @return a coverage with the specified temporal dimension added. + * @throws IllegalGridGeometryException if the compound CRS or compound extent cannot be created. + * + * @since 1.5 + */ + public GridCoverage appendDimension(final GridCoverage source, final Instant lower, final Duration span) { + final DefaultTemporalCRS crs = DefaultTemporalCRS.castOrCopy(CommonCRS.Temporal.TRUNCATED_JULIAN.crs()); + double scale = crs.toValue(span); + double offset = crs.toValue(lower); + long index = Numerics.roundAndClamp(offset / scale); // See comment in above method. + offset = crs.toValue(lower.minus(span.multipliedBy(index))); + final GridExtent extent = new GridExtent(DimensionNameType.TIME, index, index, true); + final MathTransform gridToCRS = MathTransforms.linear(scale, offset); + return appendDimensions(source, new GridGeometry(extent, PixelInCell.CELL_CORNER, gridToCRS, crs)); + } + /** * Automatically reduces a grid coverage dimensionality by removing all grid axes with an extent size of 1. * Axes in the reduced grid coverage will be in the same order than in the source coverage. diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridExtent.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridExtent.java index 62d68bb38b..d996db4595 100644 --- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridExtent.java +++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridExtent.java @@ -300,6 +300,31 @@ public class GridExtent implements GridEnvelope, LenientComparable, Serializable } } + /** + * Constructs a one-dimensional grid extent set to the specified coordinates. + * This convenience constructor does the same work than the constructor for the + * {@linkplain #GridExtent(org.opengis.metadata.spatial.DimensionNameType[], long[], long[], boolean) general case}. + * It is provided as a convenience for {@linkplain #GridExtent(GridExtent, GridExtent) appending a single dimension} + * to an existing grid. + * + * @param axisType the type of the grid axis, or {@code null} if unspecified. + * @param low the valid minimum grid coordinate, always inclusive. + * @param high the valid maximum grid coordinate, inclusive or exclusive depending on the next argument. + * @param isHighIncluded {@code true} if the {@code high} value is inclusive (as in ISO 19123 specification), + * or {@code false} if it is exclusive (as in Java2D usage). + * @throws IllegalArgumentException if {@code low} is greater than {@code high}. + * + * @since 1.5 + */ + public GridExtent(final DimensionNameType axisType, final long low, long high, final boolean isHighIncluded) { + if (!isHighIncluded) { + high = Math.decrementExact(high); + } + coordinates = new long[] {low, high}; + types = validateAxisTypes(new DimensionNameType[] {axisType}); + validateCoordinates(); + } + /** * Constructs a new grid extent set to the specified coordinates. * The given arrays contain a minimum (inclusive) and maximum value for each dimension of the grid coverage. diff --git a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/coverage/grid/DimensionAppenderTest.java b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/coverage/grid/DimensionAppenderTest.java new file mode 100644 index 0000000000..b3e40a1eec --- /dev/null +++ b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/coverage/grid/DimensionAppenderTest.java @@ -0,0 +1,131 @@ +/* + * 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.time.Instant; +import java.time.Duration; +import java.awt.image.BufferedImage; +import org.opengis.referencing.datum.PixelInCell; +import org.opengis.referencing.operation.Matrix; +import org.opengis.referencing.operation.MathTransform; +import org.opengis.referencing.operation.TransformException; +import org.apache.sis.referencing.operation.matrix.Matrix3; +import org.apache.sis.referencing.operation.matrix.Matrix4; +import org.apache.sis.referencing.operation.matrix.Matrices; +import org.apache.sis.referencing.operation.transform.MathTransforms; +import org.apache.sis.util.ArraysExt; + +// Test dependencies +import org.junit.Test; +import static org.junit.jupiter.api.Assertions.*; +import org.apache.sis.test.TestCase; +import org.apache.sis.referencing.crs.HardCodedCRS; + +// Specific to the geoapi-3.1 and geoapi-4.0 branches: +import static org.opengis.test.Assert.assertMatrixEquals; + + +/** + * Tests {@link DimensionAppender}. This is partially the converse of {@link DimensionalityReductionTest}. + * + * @author Martin Desruisseaux (Geomatys) + */ +public final class DimensionAppenderTest extends TestCase { + /** + * Creates a new test case. + */ + public DimensionAppenderTest() { + } + + /** + * Returns a grid coverage to use as a starting point. + * + * @param width image width in pixels. + * @param height image height in pixels. + */ + private static GridCoverage initial(final int width, final int height) { + var extent = new GridExtent(width, height); + var gridToCRS = new Matrix3( + 4, 0, 100, + 0, 3, -20, + 0, 0, 1); + + var gg = new GridGeometry(extent, PixelInCell.CELL_CORNER, MathTransforms.linear(gridToCRS), HardCodedCRS.WGS84); + return new GridCoverage2D(gg, null, new BufferedImage(width, height, BufferedImage.TYPE_BYTE_BINARY)); + } + + /** + * Asserts that the grid geometry of the given coverage has the expected properties. + * + * @param actual the coverage for which to verify the grid geometry. + * @param gridToCRS expected "grid to CRS" transform as a matrix. + * @param gridIndices expected lower grid coordinate values. + */ + private static void assertGridGeometryEquals(final GridCoverage actual, final Matrix gridToCRS, final long... gridIndices) { + final GridGeometry gg = actual.getGridGeometry(); + assertMatrixEquals("gridToCRS", gridToCRS, MathTransforms.getMatrix(gg.getGridToCRS(PixelInCell.CELL_CORNER)), STRICT); + assertArrayEquals(gridIndices, gg.getExtent().getLow().getCoordinateValues()); + } + + /** + * Verifies that the conversion of lower grid coordinates to CRS produces the expected values. + * + * @param actual the coverage for which to verify the coordinate conversion. + * @param expected expected coordinates in units of the CRS. + * @throws TransformException if the conversion failed. + */ + private static void verifyTransformLower(final GridCoverage actual, final double... expected) throws TransformException { + final GridGeometry gg = actual.getGridGeometry(); + final MathTransform gridToCRS = gg.getGridToCRS(PixelInCell.CELL_CORNER); + final double[] coordinates = ArraysExt.copyAsDoubles(gg.getExtent().getLow().getCoordinateValues()); + gridToCRS.transform(coordinates, 0, coordinates, 0, 1); + assertArrayEquals(expected, coordinates); + } + + /** + * Tests the {@link GridCoverageProcessor} convenience methods. + * + * @throws TransformException if the coordinate conversion failed. + */ + @Test + public void testUsingProcessor() throws TransformException { + final var coverage2D = initial(16, 8); + final var processor = new GridCoverageProcessor(); + final var coverage3D = processor.appendDimension(coverage2D, 260, 5, HardCodedCRS.GRAVITY_RELATED_HEIGHT); + assertSame(coverage2D, processor.selectGridDimensions(coverage3D, 0, 1)); + verifyTransformLower(coverage3D, 100, -20, 260); + assertGridGeometryEquals(coverage3D, new Matrix4( + 4, 0, 0, 100, + 0, 3, 0, -20, + 0, 0, 5, 0, + 0, 0, 0, 1), 0, 0, 52); + + final var coverage4D = processor.appendDimension(coverage3D, Instant.parse("2022-06-18T00:00:00Z"), Duration.parse("P21D")); + assertSame(coverage2D, processor.selectGridDimensions(coverage3D, 0, 1)); + verifyTransformLower(coverage4D, 100, -20, 260, 19748); + assertGridGeometryEquals(coverage4D, Matrices.create(5, 5, new double[] { + 4, 0, 0, 0, 100, + 0, 3, 0, 0, -20, + 0, 0, 5, 0, 0, + 0, 0, 0, 21, 8, + 0, 0, 0, 0, 1}), 0, 0, 52, 940); + + // Easy way to check that the correct dimensions were selected. + verifyTransformLower(processor.selectGridDimensions(coverage4D, 0, 1, 2), 100, -20, 260); + verifyTransformLower(processor.selectGridDimensions(coverage4D, 0, 1, 3), 100, -20, 19748); + } +} diff --git a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/coverage/grid/DimensionalityReductionTest.java b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/coverage/grid/DimensionalityReductionTest.java index 03f123a749..2751ffd1a8 100644 --- a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/coverage/grid/DimensionalityReductionTest.java +++ b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/coverage/grid/DimensionalityReductionTest.java @@ -31,7 +31,7 @@ import org.apache.sis.util.Utilities; // Test dependencies import org.junit.Test; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; import org.apache.sis.test.TestCase; import org.apache.sis.referencing.crs.HardCodedCRS; @@ -127,7 +127,7 @@ public final class DimensionalityReductionTest extends TestCase { * @param target expected reduced coordinates. */ private static void testPosition(final DimensionalityReduction reduction, double[] source, double[] target) { - assertArrayEquals(target, reduction.apply(new DirectPositionView.Double(source)).getCoordinate(), STRICT); + assertArrayEquals(target, reduction.apply(new DirectPositionView.Double(source)).getCoordinate()); } /** diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/CommonCRS.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/CommonCRS.java index eb78f22360..56979288ba 100644 --- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/CommonCRS.java +++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/CommonCRS.java @@ -1535,6 +1535,8 @@ public enum CommonCRS { * to the proleptic Gregorian calendar for every dates. For parsing and formatting of Julian days, * the {@link java.text.SimpleDateFormat} class is closer to the common practice (but not ISO 8601 * compliant).</p> + * + * @see <a href="https://en.wikipedia.org/wiki/Julian_day">Julian day on Wikipedia</a> */ JULIAN(Vocabulary.Keys.Julian, -2440588L * MILLISECONDS_PER_DAY + MILLISECONDS_PER_DAY/2, "JulianDate", true), @@ -1543,6 +1545,9 @@ public enum CommonCRS { * Time measured as days since November 17, 1858 at 00:00 UTC. * A <cite>Modified Julian day</cite> (MJD) is defined relative to * <cite>Julian day</cite> (JD) as {@code MJD = JD − 2400000.5}. + * This variant was introduced by the Smithsonian Astrophysical Observatory (Massachusetts) in 1955. + * + * @see <a href="https://en.wikipedia.org/wiki/Julian_day">Julian day on Wikipedia</a> */ MODIFIED_JULIAN(Vocabulary.Keys.ModifiedJulian, -40587L * MILLISECONDS_PER_DAY, "ModifiedJulianDate", false), @@ -1552,6 +1557,9 @@ public enum CommonCRS { * This epoch was introduced by NASA for the space program. * A <cite>Truncated Julian day</cite> (TJD) is defined relative to * <cite>Julian day</cite> (JD) as {@code TJD = JD − 2440000.5}. + * This variant was introduced by National Aeronautics and Space Administration (NASA) in 1979. + * + * @see <a href="https://en.wikipedia.org/wiki/Julian_day">Julian day on Wikipedia</a> */ TRUNCATED_JULIAN(Vocabulary.Keys.TruncatedJulian, -587L * MILLISECONDS_PER_DAY, "TruncatedJulianDate", true), @@ -1560,6 +1568,9 @@ public enum CommonCRS { * Time measured as days since December 31, 1899 at 12:00 UTC. * A <cite>Dublin Julian day</cite> (DJD) is defined relative to * <cite>Julian day</cite> (JD) as {@code DJD = JD − 2415020}. + * This variant was introduced by the International Astronomical Union (IAU) in 1955. + * + * @see <a href="https://en.wikipedia.org/wiki/Julian_day">Julian day on Wikipedia</a> */ DUBLIN_JULIAN(Vocabulary.Keys.DublinJulian, -25568L * MILLISECONDS_PER_DAY + MILLISECONDS_PER_DAY/2, "DublinJulian", false), diff --git a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/ArraysExt.java b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/ArraysExt.java index 87ccac229d..f6af6788a1 100644 --- a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/ArraysExt.java +++ b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/ArraysExt.java @@ -1914,6 +1914,25 @@ public final class ArraysExt extends Static { return result; } + /** + * Returns a copy of the given array where each value has been casted to the {@code double} type. + * This method does not verify if the casts would cause data loss. + * + * @param data the array to copy, or {@code null}. + * @return a copy of the given array with values casted to the {@code double} type, + * or {@code null} if the given array was null. + * + * @since 1.5 + */ + public static double[] copyAsDoubles(final long[] data) { + if (data == null) return null; + final double[] result = new double[data.length]; + for (int i=0; i<data.length; i++) { + result[i] = data[i]; + } + return result; + } + /** * Returns a copy of the given array where each value has been casted to the {@code float} type. * This method does not verify if the casts would cause data loss. diff --git a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/internal/Numerics.java b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/internal/Numerics.java index f7f6f62111..b8d3b055d3 100644 --- a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/internal/Numerics.java +++ b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/internal/Numerics.java @@ -132,6 +132,8 @@ public final class Numerics extends Static { /** * Maximal integer value which is convertible to {@code double} type without lost of precision digits. + * + * @see #clampForDouble(long) */ public static final long MAX_INTEGER_CONVERTIBLE_TO_DOUBLE = 1L << DOUBLE_PRECISION; @@ -267,6 +269,18 @@ public final class Numerics extends Static { return (result < x) ? Long.MAX_VALUE : Long.MIN_VALUE; } + /** + * Returns the value rounded to nearest integer and clamped to a range + * that can be converted to {@code double} without precision lost. + * + * @param value the value to round and clamp. + * @return the value clamped to the range convertible to {@code double} without precision lost. + */ + public static long roundAndClamp(final double value) { + return Math.max(-MAX_INTEGER_CONVERTIBLE_TO_DOUBLE, + Math.min(+MAX_INTEGER_CONVERTIBLE_TO_DOUBLE, Math.round(value))); + } + /** * Returns the given value clamped to the range on 32 bits integer. *