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 ed2e54e6eca21fab58b021bd2a21ac6a2125c7c9 Author: Martin Desruisseaux <[email protected]> AuthorDate: Thu Jan 3 16:49:33 2019 +0100 Add GridGeometry.reduce(lower, upper) method. --- .../metadata/EllipsoidalHeightCombiner.java | 9 +- .../org/apache/sis/coverage/grid/GridExtent.java | 18 ++- .../org/apache/sis/coverage/grid/GridGeometry.java | 164 ++++++++++++++++++--- .../apache/sis/coverage/grid/GridExtentTest.java | 6 +- .../apache/sis/coverage/grid/GridGeometryTest.java | 18 +-- .../apache/sis/internal/referencing/Resources.java | 5 + .../sis/internal/referencing/Resources.properties | 1 + .../internal/referencing/Resources_fr.properties | 1 + .../main/java/org/apache/sis/referencing/CRS.java | 89 +++++++++++ .../java/org/apache/sis/referencing/CommonCRS.java | 49 +++--- .../referencing/EllipsoidalHeightSeparator.java | 136 +++++++++++++++++ .../org/apache/sis/referencing/cs/AbstractCS.java | 2 + .../sis/referencing/cs/CoordinateSystems.java | 4 +- .../sis/referencing/cs/DefaultCartesianCS.java | 1 + .../sis/referencing/cs/DefaultEllipsoidalCS.java | 2 +- .../java/org/apache/sis/referencing/CRSTest.java | 39 ++++- .../apache/sis/referencing/crs/HardCodedCRS.java | 8 +- 17 files changed, 490 insertions(+), 62 deletions(-) diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/EllipsoidalHeightCombiner.java b/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/EllipsoidalHeightCombiner.java index 827826c..d6d4a7e 100644 --- a/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/EllipsoidalHeightCombiner.java +++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/EllipsoidalHeightCombiner.java @@ -40,12 +40,15 @@ import org.apache.sis.util.ArraysExt; /** - * A class in charges of combining two-dimensional geographic or projected CRS with an ellipsoidal height - * into a three-dimensional CRS. + * A class in charges of combining two-dimensional geographic or projected CRS with an ellipsoidal height into a + * three-dimensional CRS. This is the converse of {@link org.apache.sis.referencing.EllipsoidalHeightSeparator}. * * @author Martin Desruisseaux (Geomatys) * @version 0.8 - * @since 0.8 + * + * @see org.apache.sis.referencing.EllipsoidalHeightSeparator + * + * @since 0.8 * @module */ public class EllipsoidalHeightCombiner { diff --git a/core/sis-raster/src/main/java/org/apache/sis/coverage/grid/GridExtent.java b/core/sis-raster/src/main/java/org/apache/sis/coverage/grid/GridExtent.java index b1a1f9b..4c6318e 100644 --- a/core/sis-raster/src/main/java/org/apache/sis/coverage/grid/GridExtent.java +++ b/core/sis-raster/src/main/java/org/apache/sis/coverage/grid/GridExtent.java @@ -751,17 +751,22 @@ public class GridExtent implements Serializable { } /** - * Returns a new grid envelope that encompass only some dimensions of this grid envelope. + * Returns a grid envelope that encompass only some dimensions of this grid envelope. * This method copies this grid envelope into a new grid envelope, beginning at dimension * {@code lower} and extending to dimension {@code upper-1} inclusive. Thus the dimension * of the sub grid envelope is {@code upper - lower}. * + * <p>This method performs a <cite>dimensionality reduction</cite> and can be used as the + * converse of {@link #append(DimensionNameType, long, long, boolean)}.</p> + * * @param lower the first dimension to copy, inclusive. * @param upper the last dimension to copy, exclusive. * @return the sub-envelope, or {@code this} if [{@code lower} … {@code upper}] is [0 … {@link #getDimension() dimension}]. * @throws IndexOutOfBoundsException if an index is out of bounds. + * + * @see GridGeometry#reduce(int, int) */ - public GridExtent subExtent(final int lower, final int upper) { + public GridExtent reduce(final int lower, final int upper) { final int dimension = getDimension(); ArgumentChecks.ensureValidIndexRange(dimension, lower, upper); final int newDim = upper - lower; @@ -791,6 +796,9 @@ public class GridExtent implements Serializable { * which implies that accurate representation of the same envelope may require fractional cells on some * grid borders.</div> * + * This method does not reduce the number of dimensions of the grid extent. + * For dimensionality reduction, see {@link #reduce(int, int)}. + * * @param strides the strides. Length shall be equal to the number of dimension and all values shall be greater than zero. * @return the sub-sampled extent, or {@code this} is sub-sampling results in the same extent. * @throws IllegalArgumentException if a stride is not greater than zero. @@ -824,7 +832,11 @@ public class GridExtent implements Serializable { * Creates a new grid extent which represent a slice of this grid at the given point. * The given point may have less dimensions than this grid extent, in which case the * dimensions must be specified in the {@code modifiedDimensions} array. Coordinates - * of the given point will be rounded to nearest integer. + * in the given point will be rounded to nearest integer. This method does not reduce + * the number of dimensions of the grid extent. + * + * <p>This method does not reduce the number of dimensions of the grid extent. + * For dimensionality reduction, see {@link #reduce(int, int)}.</p> * * @param slicePoint where to take a slice. NaN values are handled as if their dimensions were absent. * @param modifiedDimensions mapping from {@code slicePoint} dimensions to this {@code GridExtent} dimensions, diff --git a/core/sis-raster/src/main/java/org/apache/sis/coverage/grid/GridGeometry.java b/core/sis-raster/src/main/java/org/apache/sis/coverage/grid/GridGeometry.java index a376d04..3d4c2cc 100644 --- a/core/sis-raster/src/main/java/org/apache/sis/coverage/grid/GridGeometry.java +++ b/core/sis-raster/src/main/java/org/apache/sis/coverage/grid/GridGeometry.java @@ -23,6 +23,7 @@ import java.io.Serializable; import java.io.IOException; import java.io.UncheckedIOException; import java.awt.image.RenderedImage; // For javadoc only. +import org.opengis.util.FactoryException; import org.opengis.metadata.Identifier; import org.opengis.geometry.Envelope; import org.opengis.geometry.DirectPosition; @@ -41,6 +42,7 @@ import org.apache.sis.geometry.ImmutableEnvelope; import org.apache.sis.referencing.IdentifiedObjects; import org.apache.sis.referencing.operation.matrix.Matrices; import org.apache.sis.referencing.operation.transform.MathTransforms; +import org.apache.sis.referencing.operation.transform.TransformSeparator; import org.apache.sis.referencing.operation.transform.PassThroughTransform; import org.apache.sis.internal.referencing.DirectPositionView; import org.apache.sis.internal.system.Modules; @@ -53,6 +55,7 @@ import org.apache.sis.util.resources.Errors; import org.apache.sis.util.logging.Logging; import org.apache.sis.util.ArgumentChecks; import org.apache.sis.util.CharSequences; +import org.apache.sis.util.ArraysExt; import org.apache.sis.util.Classes; import org.apache.sis.util.Debug; import org.apache.sis.io.TableAppender; @@ -181,6 +184,7 @@ public class GridGeometry implements Serializable { /** * An <em>estimation</em> of the grid resolution, in units of the CRS axes. * Computed from {@link #gridToCRS}, eventually together with {@link #extent}. + * May be {@code null} if unknown. * * @see #RESOLUTION * @see #getResolution(boolean) @@ -216,7 +220,7 @@ public class GridGeometry implements Serializable { /** * Creates a new grid geometry derived from the given grid geometry with a new extent and a modified transform. * This constructor is used for creating a grid geometry over a subregion (for example with the grid extent - * computed by {@link #getExtent(Envelope)}) or grid geometry for a sub-sampled raster. + * computed by {@link #subExtent(Envelope)}) or grid geometry for a sub-sampled raster. * * <p>If {@code toOther} is non-null, it should be a transform from the given {@code extent} coordinates to the * {@code other} grid coordinates. That transform should be merely a {@linkplain MathTransforms#scale(double...) @@ -235,7 +239,7 @@ public class GridGeometry implements Serializable { * @throws NullPointerException if {@code extent} is {@code null} and the other grid geometry contains no other information. * @throws TransformException if the math transform can not compute the geospatial envelope from the grid extent. * - * @see #getExtent(Envelope) + * @see #subExtent(Envelope) * @see #subgrid(Envelope, double...) */ GridGeometry(final GridGeometry other, final GridExtent extent, final MathTransform toOther) throws TransformException { @@ -476,6 +480,68 @@ public class GridGeometry implements Serializable { } /** + * Creates a new grid geometry over the specified range of dimensions of the given grid geometry. + * + * @param other the grid geometry to copy. + * @param lower the first dimension to copy, inclusive. + * @param upper the last dimension to copy, exclusive. + * @throws FactoryException if an error occurred while separating the "grid to CRS" transform. + * + * @see #reduce(int, int) + */ + private GridGeometry(final GridGeometry other, final int lower, final int upper) throws FactoryException { + extent = (other.extent != null) ? other.extent.reduce(lower, upper) : null; + final int n = upper - lower; + final int[] dimensions; + if (other.gridToCRS != null) { + TransformSeparator sep = new TransformSeparator(other.gridToCRS); + sep.addSourceDimensionRange(lower, upper); + gridToCRS = sep.separate(); + dimensions = sep.getTargetDimensions(); + assert dimensions.length == n : Arrays.toString(dimensions); + + sep = new TransformSeparator(other.cornerToCRS); + sep.addSourceDimensionRange(lower, upper); + sep.addTargetDimensions(dimensions); + cornerToCRS = sep.separate(); + assert Arrays.equals(sep.getSourceDimensions(), dimensions) : Arrays.toString(dimensions); + } else { + gridToCRS = null; + cornerToCRS = null; + dimensions = ArraysExt.sequence(lower, upper); + } + final ImmutableEnvelope env = other.envelope; + if (env != null) { + CoordinateReferenceSystem crs = env.getCoordinateReferenceSystem(); + crs = org.apache.sis.referencing.CRS.reduce(crs, dimensions); + final double[] min = new double[n]; + final double[] max = new double[n]; + for (int i=0; i<n; i++) { + final int j = dimensions[i]; + min[i] = env.getLower(j); + max[i] = env.getUpper(j); + } + envelope = new ImmutableEnvelope(min, max, crs); + } else { + envelope = null; + } + long nonLinears = 0; + double[] resolution = other.resolution; + if (resolution != null) { + resolution = new double[n]; + } + for (int i=0; i<n; i++) { + final int j = dimensions[i]; + if (resolution != null) { + resolution[i] = other.resolution[j]; + } + nonLinears |= ((other.nonLinears >>> j) & 1L) << i; + } + this.resolution = resolution; + this.nonLinears = nonLinears; + } + + /** * Returns the number of dimensions of the <em>grid</em>. This is typically the same * than the number of {@linkplain #getEnvelope() envelope} dimensions or the number of * {@linkplain #getCoordinateReferenceSystem() coordinate reference system} dimensions, @@ -584,6 +650,9 @@ public class GridGeometry implements Serializable { * <p>If the envelope CRS is not specified, then it is assumed the same than the CRS of this grid geometry. * In such case the envelope needs to contain all dimensions.</p> * + * <p>This method does not reduce the number of dimensions of this grid geometry. + * For dimensionality reduction, see {@link #reduce(int, int)}.</p> + * * @param areaOfInterest the desired spatiotemporal region in any CRS (transformations will be applied as needed). * @return a grid extent of the same dimension than the grid geometry which intersects the given area of interest. * @throws IncompleteGridGeometryException if this grid geometry has no extent or no "grid to CRS" transform. @@ -591,18 +660,37 @@ public class GridGeometry implements Serializable { * * @see #subgrid(Envelope, double...) */ - public GridExtent getExtent(final Envelope areaOfInterest) throws IncompleteGridGeometryException, TransformException { + public GridExtent subExtent(final Envelope areaOfInterest) throws IncompleteGridGeometryException, TransformException { ArgumentChecks.ensureNonNull("areaOfInterest", areaOfInterest); - if (extent == null) { - throw incomplete(EXTENT, Resources.Keys.UnspecifiedGridExtent); - } - if (cornerToCRS == null) { - throw incomplete(GridGeometry.GRID_TO_CRS, Resources.Keys.UnspecifiedTransform); - } + requireGridToCRS(); return new SubgridCalculator(this, cornerToCRS, areaOfInterest, null).extent; } /** + * Returns the coordinate range of a slice of this grid geometry at the given point. + * The given position can be expressed in any coordinate reference system (CRS). + * The position should not define a coordinate for all dimensions, otherwise the slice would degenerate + * to a single point. Dimensions can be left unspecified either by assigning to {@code slicePoint} a CRS + * without those dimensions, or by assigning the NaN value to some coordinates. + * See {@link #slice(DirectPosition)} for examples. + * + * <p>This method does not reduce the number of dimensions of this grid geometry. + * For dimensionality reduction, see {@link #reduce(int, int)}.</p> + * + * @param slicePoint the coordinates where to get a slice. + * @return a slice of the grid extent at the given slice point. + * @throws TransformException if an error occurred while converting the point coordinates to grid coordinates. + * @throws PointOutsideCoverageException if the given point is outside the grid extent. + * + * @see #slice(DirectPosition) + */ + public GridExtent subExtent(final DirectPosition slicePoint) throws TransformException { + ArgumentChecks.ensureNonNull("slicePoint", slicePoint); + requireGridToCRS(); + return new SubgridCalculator(this, cornerToCRS, slicePoint).extent; + } + + /** * Returns the conversion from grid coordinates to "real world" coordinates. * The conversion is often an affine transform, but not necessarily. * Conversions from cell indices to geospatial coordinates can be performed for example as below: @@ -872,6 +960,19 @@ public class GridGeometry implements Serializable { } /** + * Verifies that this grid geometry defines an {@linkplain #extent} and a {@link #cornerToCRS} transform. + * They are the information required for mapping the grid to a spatiotemporal envelope. + */ + private void requireGridToCRS() throws IncompleteGridGeometryException { + if (extent == null) { + throw incomplete(EXTENT, Resources.Keys.UnspecifiedGridExtent); + } + if (cornerToCRS == null) { + throw incomplete(GRID_TO_CRS, Resources.Keys.UnspecifiedTransform); + } + } + + /** * Returns {@code true} if all the parameters specified by the argument are set. * If this method returns {@code true}, then invoking the corresponding getter * methods will not throw {@link IncompleteGridGeometryException}. @@ -911,6 +1012,9 @@ public class GridGeometry implements Serializable { * If the length of {@code targetResolution} array is less than the number of dimensions of {@code areaOfInterest}, * then no sub-sampling will be applied on the missing dimensions. * + * <p>This method does not reduce the number of dimensions of this grid geometry. + * For dimensionality reduction, see {@link #reduce(int, int)}.</p> + * * @param areaOfInterest the desired spatiotemporal region in any CRS (transformations will be applied as needed), * or {@code null} for not restricting the sub-grid to a sub-area. * @param targetResolution the desired resolution in the same units and order than the axes of the given envelope, @@ -919,16 +1023,11 @@ public class GridGeometry implements Serializable { * @throws IncompleteGridGeometryException if this grid geometry has no extent or no "grid to CRS" transform. * @throws TransformException if an error occurred while converting the envelope coordinates to grid coordinates. * - * @see #getExtent(Envelope) + * @see #subExtent(Envelope) * @see GridExtent#subsample(int[]) */ public GridGeometry subgrid(final Envelope areaOfInterest, double... targetResolution) throws TransformException { - if (extent == null) { - throw incomplete(EXTENT, Resources.Keys.UnspecifiedGridExtent); - } - if (cornerToCRS == null) { - throw incomplete(GRID_TO_CRS, Resources.Keys.UnspecifiedTransform); - } + requireGridToCRS(); final SubgridCalculator sub = new SubgridCalculator(this, cornerToCRS, areaOfInterest, targetResolution); if (sub.toSubsampled != null || sub.extent != extent) { return new GridGeometry(this, sub.extent, sub.toSubsampled); @@ -939,7 +1038,7 @@ public class GridGeometry implements Serializable { /** * Returns a grid geometry for a slice at the given point. * The given position can be expressed in any coordinate reference system (CRS). - * The position should not define a coordinate for all dimensions, otherwise the sub-grid would degenerate + * The position should not define a coordinate for all dimensions, otherwise the slice would degenerate * to a single point. Dimensions can be left unspecified either by assigning to {@code slicePoint} a CRS * without those dimensions, or by assigning the NaN value to some coordinates. * @@ -954,18 +1053,49 @@ public class GridGeometry implements Serializable { * of the grid geometry CRS.</li> * </ul></div> * + * This method does not reduce the number of dimensions of this grid geometry. + * For dimensionality reduction, see {@link #reduce(int, int)}. + * * @param slicePoint the coordinates where to get a slice. * @return a slice of this grid geometry at the given slice point. May be {@code this}. * @throws TransformException if an error occurred while converting the point coordinates to grid coordinates. * @throws PointOutsideCoverageException if the given point is outside the grid extent. + * + * @see #subExtent(DirectPosition) */ public GridGeometry slice(final DirectPosition slicePoint) throws TransformException { ArgumentChecks.ensureNonNull("slicePoint", slicePoint); + requireGridToCRS(); final GridExtent slice = new SubgridCalculator(this, cornerToCRS, slicePoint).extent; return (slice != extent) ? new GridGeometry(this, slice, null) : this; } /** + * Returns a grid geometry that encompass only some dimensions of this grid geometry. + * This method copies this grid geometry into a new grid geometry, beginning at dimension + * {@code lower} and extending to dimension {@code upper-1} inclusive. Thus the dimension + * of the sub grid geometry is {@code upper - lower}. + * + * <p>This method performs a <cite>dimensionality reduction</cite>.</p> + * + * @param lower the first grid dimension to copy, inclusive. + * @param upper the last grid dimension to copy, exclusive. + * @return the sub grid geometry, or {@code this} if [{@code lower} … {@code upper}] is [0 … {@link #getDimension() dimension}]. + * @throws IndexOutOfBoundsException if an index is out of bounds. + * + * @see GridExtent#reduce(int, int) + */ + public GridGeometry reduce(final int lower, final int upper) { + if (lower == 0 && upper == getDimension()) { + return this; + } else try { + return new GridGeometry(this, lower, upper); + } catch (FactoryException e) { + throw new RuntimeException(e); // TODO + } + } + + /** * Returns a hash value for this grid geometry. This value need not remain * consistent between different implementations of the same class. */ diff --git a/core/sis-raster/src/test/java/org/apache/sis/coverage/grid/GridExtentTest.java b/core/sis-raster/src/test/java/org/apache/sis/coverage/grid/GridExtentTest.java index b1ad427..01bfeff 100644 --- a/core/sis-raster/src/test/java/org/apache/sis/coverage/grid/GridExtentTest.java +++ b/core/sis-raster/src/test/java/org/apache/sis/coverage/grid/GridExtentTest.java @@ -122,12 +122,12 @@ public final strictfp class GridExtentTest extends TestCase { } /** - * Tests {@link GridExtent#subExtent(int, int)}. + * Tests {@link GridExtent#reduce(int, int)}. */ @Test - public void testSubExtent() { + public void testReduce() { GridExtent extent = create3D(); - extent = extent.subExtent(0, 2); + extent = extent.reduce(0, 2); assertEquals("dimension", 2, extent.getDimension()); assertExtentEquals(extent, 0, 100, 499); assertExtentEquals(extent, 1, 200, 799); diff --git a/core/sis-raster/src/test/java/org/apache/sis/coverage/grid/GridGeometryTest.java b/core/sis-raster/src/test/java/org/apache/sis/coverage/grid/GridGeometryTest.java index 3c503bc..f484946 100644 --- a/core/sis-raster/src/test/java/org/apache/sis/coverage/grid/GridGeometryTest.java +++ b/core/sis-raster/src/test/java/org/apache/sis/coverage/grid/GridGeometryTest.java @@ -248,13 +248,13 @@ public final strictfp class GridGeometryTest extends TestCase { } /** - * Tests {@link GridGeometry#getExtent(Envelope)}. + * Tests {@link GridGeometry#subExtent(Envelope)}. * * @throws TransformException if an error occurred while using the "grid to CRS" transform. */ @Test @DependsOnMethod("testFromGeospatialEnvelope") - public void testGetExtent() throws TransformException { + public void testSubExtent() throws TransformException { GeneralEnvelope envelope = new GeneralEnvelope(HardCodedCRS.WGS84_3D); envelope.setRange(0, -80, 120); envelope.setRange(1, -12, 21); @@ -276,17 +276,17 @@ public final strictfp class GridGeometryTest extends TestCase { envelope.setRange(0, -70.001, +80.002); envelope.setRange(1, 4.997, 15.003); assertExtentEquals(new long[] {370, 40, 4}, - new long[] {389, 339, 10}, grid.getExtent(envelope)); + new long[] {389, 339, 10}, grid.subExtent(envelope)); } /** - * Tests {@link GridGeometry#getExtent(Envelope)} with a non-linear "grid to CRS" transform. + * Tests {@link GridGeometry#subExtent(Envelope)} with a non-linear "grid to CRS" transform. * * @throws TransformException if an error occurred while using the "grid to CRS" transform. */ @Test - @DependsOnMethod({"testNonLinear", "testGetExtent"}) - public void testGetExtentNonLinear() throws TransformException { + @DependsOnMethod({"testNonLinear", "testSubExtent"}) + public void testSubExtentNonLinear() throws TransformException { final GridExtent extent = new GridExtent( new DimensionNameType[] { DimensionNameType.COLUMN, @@ -304,7 +304,7 @@ public final strictfp class GridGeometryTest extends TestCase { final MathTransform gridToCRS = MathTransforms.concatenate(linear, MathTransforms.passThrough(1, latitude, 1)); final GridGeometry grid = new GridGeometry(extent, PixelInCell.CELL_CENTER, gridToCRS, HardCodedCRS.WGS84_3D); /* - * Following tests is similar to the one executed in testGetExtent(). Expected values are only + * Following tests is similar to the one executed in testSubExtent(). Expected values are only * anti-regression values, except the vertical range which is expected to cover all cells. The * main purpose of this test is to verify that TransformSeparator has been able to extract the * two-dimensional transform despite its non-linear component. @@ -312,7 +312,7 @@ public final strictfp class GridGeometryTest extends TestCase { final GeneralEnvelope envelope = new GeneralEnvelope(HardCodedCRS.WGS84); envelope.setRange(0, -70.001, +80.002); envelope.setRange(1, -4.997, 15.003); - final GridExtent actual = grid.getExtent(envelope); + final GridExtent actual = grid.subExtent(envelope); assertEquals(extent.getAxisType(0), actual.getAxisType(0)); assertExtentEquals(new long[] { 56, 69, 2}, new long[] {130, 73, 4}, actual); @@ -324,7 +324,7 @@ public final strictfp class GridGeometryTest extends TestCase { * @throws TransformException if an error occurred during computation. */ @Test - @DependsOnMethod({"testFromGeospatialEnvelope", "testGetExtent"}) + @DependsOnMethod({"testFromGeospatialEnvelope", "testSubExtent"}) public void testSubgrid() throws TransformException { final GeneralEnvelope envelope = new GeneralEnvelope(HardCodedCRS.WGS84_φλ); envelope.setRange(0, -70, +80); diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/Resources.java b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/Resources.java index bb81dae..cf0e8bb 100644 --- a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/Resources.java +++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/Resources.java @@ -113,6 +113,11 @@ public final class Resources extends IndexedResourceBundle { public static final short CanNotParseCombinedReference_2 = 78; /** + * Can not separate the “{0}” coordinate reference system into sub-components. + */ + public static final short CanNotSeparateCRS_1 = 84; + + /** * Target dimension {0} depends on excluded source dimensions. */ public static final short CanNotSeparateTargetDimension_1 = 7; diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/Resources.properties b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/Resources.properties index 35b4be2..0e26dad 100644 --- a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/Resources.properties +++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/Resources.properties @@ -51,6 +51,7 @@ CanNotInferGridSizeFromValues_1 = Can not infer a grid size from the given val CanNotInstantiateGeodeticObject_1 = Can not instantiate geodetic object for \u201c{0}\u201d. CanNotMapAxisToDirection_1 = Can not map an axis from the specified coordinate system to the \u201c{0}\u201d direction. CanNotParseCombinedReference_2 = Can not parse component {1} in the combined {0,choice,0#URN|1#URL}. +CanNotSeparateCRS_1 = Can not separate the \u201c{0}\u201d coordinate reference system into sub-components. CanNotSeparateTransform_3 = Can not separate the transform because result would have {2} {0,choice,0#source|1#target} dimension{2,choice,1#|2#s} instead of {1}. CanNotSeparateTargetDimension_1 = Target dimension {0} depends on excluded source dimensions. CanNotTransformEnvelopeToGeodetic = Can not transform envelope to a geodetic reference system. diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/Resources_fr.properties b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/Resources_fr.properties index 1181bcc..3e25983 100644 --- a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/Resources_fr.properties +++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/Resources_fr.properties @@ -55,6 +55,7 @@ CanNotFindCommonCRS = Ne peut pas trouver un syst\u00e8me de r\u00 CanNotInferGridSizeFromValues_1 = Ne peut pas inf\u00e9rer une taille de grille \u00e0 partir des valeurs donn\u00e9es dans la plage {0}. CanNotInstantiateGeodeticObject_1 = Ne peut pas cr\u00e9er l\u2019objet g\u00e9od\u00e9tique pour \u00ab\u202f{0}\u202f\u00bb. CanNotMapAxisToDirection_1 = Aucun axe du syst\u00e8me de coordonn\u00e9es sp\u00e9cifi\u00e9 n\u2019a pu \u00eatre associ\u00e9 \u00e0 la direction \u00ab\u202f{0}\u202f\u00bb. +CanNotSeparateCRS_1 = Ne peut pas s\u00e9parer le syst\u00e8me de r\u00e9f\u00e9rence \u00ab\u202f{0}\u202f\u00bb en sous-composantes. CanNotSeparateTransform_3 = Ne peut pas s\u00e9parer la transformation parce-que le r\u00e9sultat aurait {2} dimension{2,choice,1#|2#s} en {0,choice,0#entr\u00e9|1#sortie} au lieu de {1}. CanNotSeparateTargetDimension_1 = La dimension de destination {0} d\u00e9pend de dimensions sources qui ont \u00e9t\u00e9 exclues. CanNotParseCombinedReference_2 = Ne peut pas d\u00e9coder la composante {1} dans l\u2019{0,choice,0#URN|1#URL} combin\u00e9. diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/CRS.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/CRS.java index 62ea42e..8218765 100644 --- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/CRS.java +++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/CRS.java @@ -43,6 +43,8 @@ import org.opengis.referencing.crs.ProjectedCRS; import org.opengis.referencing.crs.TemporalCRS; import org.opengis.referencing.crs.VerticalCRS; import org.opengis.referencing.crs.EngineeringCRS; +import org.opengis.referencing.datum.Datum; +import org.opengis.referencing.datum.GeodeticDatum; import org.opengis.referencing.operation.Conversion; import org.opengis.referencing.operation.CoordinateOperationFactory; import org.opengis.referencing.operation.OperationNotFoundException; @@ -62,9 +64,11 @@ import org.apache.sis.internal.referencing.PositionalAccuracyConstant; import org.apache.sis.internal.referencing.CoordinateOperations; import org.apache.sis.internal.referencing.ReferencingUtilities; import org.apache.sis.internal.referencing.DefinitionVerifier; +import org.apache.sis.internal.referencing.Resources; import org.apache.sis.internal.system.DefaultFactories; import org.apache.sis.internal.system.Modules; import org.apache.sis.internal.system.Loggers; +import org.apache.sis.internal.util.Numerics; import org.apache.sis.referencing.cs.AxisFilter; import org.apache.sis.referencing.cs.CoordinateSystems; import org.apache.sis.referencing.cs.DefaultVerticalCS; @@ -1259,6 +1263,7 @@ public final class CRS extends Static { ArgumentChecks.ensureValidIndexRange(dimension, lower, upper); check: while (lower != 0 || upper != dimension) { if (crs instanceof CompoundCRS) { + // We need nested CompoundCRS (if any) below, not a flattened list of SingleCRS. final List<CoordinateReferenceSystem> components = ((CompoundCRS) crs).getComponents(); final int size = components.size(); for (int i=0; i<size; i++) { @@ -1282,6 +1287,90 @@ check: while (lower != 0 || upper != dimension) { } /** + * Gets or creates a coordinate reference system with a subset of the dimensions of the given CRS. + * This method can be used for dimensionality reduction. + * + * @param crs the CRS to reduce the dimensionality, or {@code null} if none. + * @param dimensions the dimensions to retain. The dimensions will be taken in increasing order, ignoring duplicated values. + * @return a coordinate reference system for the given dimensions. May be the given {@code crs}, which may be {@code null}. + * @throws IllegalArgumentException if the given array is empty or if the array contains invalid indices. + * @throws FactoryException if the geodetic factory failed to create a compound CRS. + * + * @since 1.0 + */ + public static CoordinateReferenceSystem reduce(final CoordinateReferenceSystem crs, final int... dimensions) throws FactoryException { + ArgumentChecks.ensureNonNull("dimensions", dimensions); + if (crs == null) { + return null; + } + final int dimension = ReferencingUtilities.getDimension(crs); + if (dimension > Long.SIZE) { + throw new IllegalArgumentException(Errors.format(Errors.Keys.ExcessiveNumberOfDimensions_1, dimension)); + } + long selected = 0; + for (final int d : dimensions) { + if (d < 0 || d >= dimension) { + throw new IndexOutOfBoundsException(Errors.format(Errors.Keys.IndexOutOfBounds_1, d)); + } + selected |= (1L << d); + } + if (selected == 0) { + throw new IllegalArgumentException(Errors.format(Errors.Keys.EmptyArgument_1, "dimensions")); + } + final List<CoordinateReferenceSystem> components = new ArrayList<>(Long.bitCount(selected)); + reduce(0, crs, dimension, selected, components); + return compound(components.toArray(new CoordinateReferenceSystem[components.size()])); + } + + /** + * Adds the components of reduced CRS into the given list. + * This method may invoke itself recursively for walking through compound CRS. + * + * @param previous number of dimensions of previous CRS. + * @param crs the CRS for which to select components. + * @param dimension number of dimensions of {@code crs}. + * @param selected bitmask of dimensions to select. + * @param addTo where to add CRS components. + * @return new bitmask after removal of dimensions of the components added to {@code addTo}. + */ + private static long reduce(int previous, final CoordinateReferenceSystem crs, int dimension, long selected, + final List<CoordinateReferenceSystem> addTo) throws FactoryException + { + final long current = (Numerics.bitmask(dimension) - 1) << previous; + final long intersect = selected & current; + if (intersect != 0) { + if (intersect == current) { + addTo.add(crs); + selected &= ~current; + } else if (crs instanceof CompoundCRS) { + for (final CoordinateReferenceSystem component : ((CompoundCRS) crs).getComponents()) { + dimension = ReferencingUtilities.getDimension(component); + selected = reduce(previous, component, dimension, selected, addTo); + if ((selected & current) == 0) break; // Stop if it would be useless to continue. + previous += dimension; + } + } else if (dimension == 3 && crs instanceof SingleCRS) { + final Datum datum = ((SingleCRS) crs).getDatum(); + if (datum instanceof GeodeticDatum) { + final boolean isVertical = Long.bitCount(intersect) == 1; // Presumed for now, verified later. + final int verticalDimension = Long.numberOfTrailingZeros((isVertical ? intersect : ~intersect) >>> previous); + final CoordinateSystemAxis verticalAxis = crs.getCoordinateSystem().getAxis(verticalDimension); + if (AxisDirections.isVertical(verticalAxis.getDirection())) try { + addTo.add(new EllipsoidalHeightSeparator((GeodeticDatum) datum).separate((SingleCRS) crs, isVertical)); + selected &= ~current; + } catch (IllegalArgumentException | ClassCastException e) { + throw new FactoryException(Resources.format(Resources.Keys.CanNotSeparateCRS_1, crs.getName())); + } + } + } + } + if ((selected & current) != 0) { + throw new FactoryException(Resources.format(Resources.Keys.CanNotSeparateCRS_1, crs.getName())); + } + return selected; + } + + /** * Returns the Greenwich longitude of the prime meridian of the given CRS in degrees. * If the prime meridian uses an other unit than degrees, then the value will be converted. * diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/CommonCRS.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/CommonCRS.java index 220e7e3..d2f44f1 100644 --- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/CommonCRS.java +++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/CommonCRS.java @@ -479,32 +479,41 @@ public enum CommonCRS { } final Datum datum = single.getDatum(); if (datum instanceof GeodeticDatum) { - /* - * First, try to search using only the EPSG code. This approach avoid initializing unneeded - * geodetic objects (such initializations are costly if they require connection to the EPSG - * database). - */ - int epsg = 0; - final Identifier identifier = IdentifiedObjects.getIdentifier(datum, Citations.EPSG); - if (identifier != null) { - final String code = identifier.getCode(); - if (code != null) try { - epsg = Integer.parseInt(code); - } catch (NumberFormatException e) { - Logging.recoverableException(Logging.getLogger(Modules.REFERENCING), CommonCRS.class, "forDatum", e); - } - } - for (final CommonCRS c : values()) { - if ((epsg != 0) ? c.datum == epsg : Utilities.equalsIgnoreMetadata(c.datum(), datum)) { - return c; - } - } + final CommonCRS c = forDatum((GeodeticDatum) datum); + if (c != null) return c; } throw new IllegalArgumentException(Errors.format( Errors.Keys.UnsupportedDatum_1, IdentifiedObjects.getName(datum, null))); } /** + * Returns the {@code CommonCRS} enumeration value for the given datum, or {@code null} if none. + */ + static CommonCRS forDatum(final GeodeticDatum datum) { + /* + * First, try to search using only the EPSG code. This approach avoid initializing unneeded + * geodetic objects (such initializations are costly if they require connection to the EPSG + * database). + */ + int epsg = 0; + final Identifier identifier = IdentifiedObjects.getIdentifier(datum, Citations.EPSG); + if (identifier != null) { + final String code = identifier.getCode(); + if (code != null) try { + epsg = Integer.parseInt(code); + } catch (NumberFormatException e) { + Logging.recoverableException(Logging.getLogger(Modules.REFERENCING), CommonCRS.class, "forDatum", e); + } + } + for (final CommonCRS c : values()) { + if ((epsg != 0) ? c.datum == epsg : Utilities.equalsIgnoreMetadata(c.datum(), datum)) { + return c; + } + } + return null; + } + + /** * Returns the default two-dimensional normalized geographic CRS. * The CRS returned by this method has the following properties: * diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/EllipsoidalHeightSeparator.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/EllipsoidalHeightSeparator.java new file mode 100644 index 0000000..c2d4ded --- /dev/null +++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/EllipsoidalHeightSeparator.java @@ -0,0 +1,136 @@ +/* + * 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.referencing; + +import java.util.Map; +import java.util.Collections; +import org.opengis.util.FactoryException; +import org.opengis.referencing.cs.VerticalCS; +import org.opengis.referencing.cs.CartesianCS; +import org.opengis.referencing.cs.EllipsoidalCS; +import org.opengis.referencing.crs.CRSFactory; +import org.opengis.referencing.crs.SingleCRS; +import org.opengis.referencing.crs.VerticalCRS; +import org.opengis.referencing.crs.ProjectedCRS; +import org.opengis.referencing.crs.GeographicCRS; +import org.opengis.referencing.cs.CoordinateSystem; +import org.opengis.referencing.cs.CoordinateSystemAxis; +import org.opengis.referencing.datum.GeodeticDatum; +import org.opengis.referencing.operation.Conversion; +import org.apache.sis.internal.system.DefaultFactories; +import org.apache.sis.internal.metadata.AxisDirections; +import org.apache.sis.internal.referencing.ReferencingUtilities; +import org.apache.sis.referencing.cs.CoordinateSystems; +import org.apache.sis.referencing.cs.AxisFilter; +import org.apache.sis.util.Utilities; + + +/** + * Helper class for separating the ellipsoidal height from the horizontal part of a CRS. + * This is the converse of {@link org.apache.sis.internal.metadata.EllipsoidalHeightCombiner}. + * + * @author Martin Desruisseaux (Geomatys) + * @version 1.0 + * + * @see org.apache.sis.internal.metadata.EllipsoidalHeightCombiner + * + * @since 1.0 + * @module + */ +final class EllipsoidalHeightSeparator implements AxisFilter { + /** + * The value of {@link SingleCRS#getDatum()}. + */ + private final GeodeticDatum datum; + + /** + * Whether to extract the vertical component ({@code true}) or the horizontal component ({@code false}). + */ + private boolean vertical; + + /** + * Creates a new separator for a CRS having the given datum. + */ + EllipsoidalHeightSeparator(final GeodeticDatum datum) { + this.datum = datum; + } + + /** + * Returns {@code true} if the given axis shall be included in the new coordinate system. + */ + @Override + public boolean accept(final CoordinateSystemAxis axis) { + return AxisDirections.isVertical(axis.getDirection()) == vertical; + } + + /** + * The factory to use for creating new coordinate reference system. + */ + private static CRSFactory factory() { + return DefaultFactories.forBuildin(CRSFactory.class); + } + + /** + * Returns properties with the name of the given CRS. + */ + private static Map<String,?> properties(final SingleCRS component) { + return Collections.singletonMap(SingleCRS.NAME_KEY, component.getName()); + } + + /** + * Extracts the horizontal or vertical component of the coordinate reference system. + * + * @param crs the coordinate reference system from which to extract the horizontal or vertical component. + * @param vertical whether to extract the vertical component ({@code true}) or the horizontal component ({@code false}). + * @return the requested component. + * @throws IllegalArgumentException if the specified coordinate system can not be filtered. + * It may be because the coordinate system would contain an illegal number of axes, + * or because an axis would have an unexpected direction or unexpected unit of measurement. + * @throws ClassCastException if a coordinate system is not of the expected type. + */ + SingleCRS separate(final SingleCRS crs, final boolean vertical) throws FactoryException { + this.vertical = vertical; + final CoordinateSystem cs = CoordinateSystems.replaceAxes(crs.getCoordinateSystem(), this); + if (vertical) { + VerticalCRS component = CommonCRS.Vertical.ELLIPSOIDAL.crs(); + if (!Utilities.equalsIgnoreMetadata(component.getCoordinateSystem(), cs)) { + component = factory().createVerticalCRS(properties(component), component.getDatum(), (VerticalCS) cs); + } + return component; + } + if (crs instanceof GeographicCRS) { + final CommonCRS ref = CommonCRS.WGS84; + if (Utilities.equalsIgnoreMetadata(ref.geographic().getCoordinateSystem(), cs)) { + final CommonCRS c = CommonCRS.forDatum(datum); + if (c != null) return c.geographic(); + } else if (Utilities.equalsIgnoreMetadata(ref.normalizedGeographic().getCoordinateSystem(), cs)) { + final CommonCRS c = CommonCRS.forDatum(datum); + if (c != null) return c.normalizedGeographic(); + } + return factory().createGeographicCRS(properties(crs), datum, (EllipsoidalCS) cs); + } + if (crs instanceof ProjectedCRS) { + GeographicCRS baseCRS = ((ProjectedCRS) crs).getBaseCRS(); + if (ReferencingUtilities.getDimension(baseCRS) != 2) { + baseCRS = (GeographicCRS) separate(baseCRS, false); + } + Conversion projection = ((ProjectedCRS) crs).getConversionFromBase(); + return factory().createProjectedCRS(properties(crs), baseCRS, projection, (CartesianCS) cs); + } + throw new IllegalArgumentException(); + } +} diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/AbstractCS.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/AbstractCS.java index 4115099..20ab633 100644 --- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/AbstractCS.java +++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/AbstractCS.java @@ -379,6 +379,8 @@ public class AbstractCS extends AbstractIdentifiedObject implements CoordinateSy * * @param axes the set of axes to give to the new coordinate system. * @return a new coordinate system of the same type than {@code this}, but using the given axes. + * @throws IllegalArgumentException if {@code axes} contains an unexpected number of axes, + * or if an axis has an unexpected direction or unexpected unit of measurement. */ AbstractCS createForAxes(final Map<String,?> properties, final CoordinateSystemAxis[] axes) { return new AbstractCS(properties, axes); diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/CoordinateSystems.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/CoordinateSystems.java index 03fb76a..b8a00a3 100644 --- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/CoordinateSystems.java +++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/CoordinateSystems.java @@ -381,7 +381,9 @@ public final class CoordinateSystems extends Static { * @param filter the modifications to apply on coordinate system axes. * @return the modified coordinate system as a new instance, * or {@code cs} if the given coordinate system was null or does not need any change. - * @throws IllegalArgumentException if the specified coordinate system can not be normalized. + * @throws IllegalArgumentException if the specified coordinate system can not be filtered. + * It may be because the coordinate system would contain an illegal number of axes, + * or because an axis would have an unexpected direction or unexpected unit of measurement. * * @see AxesConvention#NORMALIZED * diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DefaultCartesianCS.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DefaultCartesianCS.java index f4f4896..7590365 100644 --- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DefaultCartesianCS.java +++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DefaultCartesianCS.java @@ -236,6 +236,7 @@ public class DefaultCartesianCS extends DefaultAffineCS implements CartesianCS { @Override final AbstractCS createForAxes(final Map<String,?> properties, final CoordinateSystemAxis[] axes) { switch (axes.length) { + case 1: return new DefaultVerticalCS(properties, axes); case 2: // Fall through case 3: return new DefaultCartesianCS(properties, axes); default: throw unexpectedDimension(properties, axes, 2); diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DefaultEllipsoidalCS.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DefaultEllipsoidalCS.java index edaa12e..1d77329 100644 --- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DefaultEllipsoidalCS.java +++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DefaultEllipsoidalCS.java @@ -203,7 +203,7 @@ public class DefaultEllipsoidalCS extends AbstractCS implements EllipsoidalCS { */ private void validateAxes(final Map<String,?> properties) { int i = super.getDimension(); - int n = i - 2; // Number of vertical axes allowed. + int n = i - 2; // Number of vertical axes allowed. while (--i >= 0) { final AxisDirection direction = super.getAxis(i).getDirection(); if (AxisDirections.isVertical(direction) && --n < 0) { diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/CRSTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/CRSTest.java index c299eed..10c694b 100644 --- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/CRSTest.java +++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/CRSTest.java @@ -26,7 +26,6 @@ import org.opengis.referencing.crs.ProjectedCRS; import org.opengis.referencing.crs.GeodeticCRS; import org.opengis.referencing.crs.SingleCRS; import org.opengis.referencing.cs.CartesianCS; -import org.apache.sis.referencing.crs.DefaultCompoundCRS; import org.apache.sis.referencing.crs.DefaultGeographicCRS; import org.apache.sis.referencing.crs.DefaultProjectedCRS; import org.apache.sis.metadata.iso.extent.DefaultGeographicBoundingBox; @@ -50,7 +49,7 @@ import static org.apache.sis.test.Assert.*; * Tests the {@link CRS} class. * * @author Martin Desruisseaux (Geomatys) - * @version 0.8 + * @version 1.0 * @since 0.4 * @module */ @@ -323,8 +322,40 @@ public final strictfp class CRSTest extends TestCase { HardCodedCRS.TIME, HardCodedCRS.WGS84, HardCodedCRS.GEOID_3D, - new DefaultCompoundCRS(IdentifiedObjects.getProperties(HardCodedCRS.GEOID_4D), - HardCodedCRS.GEOID_3D, HardCodedCRS.TIME)); + HardCodedCRS.NESTED); + } + + /** + * Tests {@link CRS#reduce(CoordinateReferenceSystem, int...)} in the simpler case where + * there is no three-dimensional geographic CRS to separate. + * + * @throws FactoryException if an error occurred while creating a compound CRS. + * + * @since 1.0 + */ + @Test + public void testReduce() throws FactoryException { + assertSame(HardCodedCRS.TIME, CRS.reduce(HardCodedCRS.GEOID_4D, 3)); + assertSame(HardCodedCRS.GRAVITY_RELATED_HEIGHT, CRS.reduce(HardCodedCRS.GEOID_4D, 2)); + assertSame(HardCodedCRS.WGS84, CRS.reduce(HardCodedCRS.GEOID_4D, 0, 1)); + assertSame(HardCodedCRS.GEOID_4D, CRS.reduce(HardCodedCRS.GEOID_4D, 0, 1, 2, 3)); + assertSame(HardCodedCRS.NESTED, CRS.reduce(HardCodedCRS.NESTED, 0, 1, 2, 3)); + assertSame(HardCodedCRS.GEOID_3D, CRS.reduce(HardCodedCRS.NESTED, 0, 1, 2)); + assertEqualsIgnoreMetadata(HardCodedCRS.GEOID_3D, CRS.reduce(HardCodedCRS.GEOID_4D, 0, 1, 2)); + } + + /** + * Tests {@link CRS#reduce(CoordinateReferenceSystem, int...)} with a three-dimensional geographic CRS + * to be reduced to a two-dimensional CRS. + * + * @throws FactoryException if an error occurred while creating a CRS. + * + * @since 1.0 + */ + @Test + public void testReduceGeographic3D() throws FactoryException { + assertSame(CommonCRS.Vertical.ELLIPSOIDAL.crs(), CRS.reduce(HardCodedCRS.WGS84_3D, 2)); + assertSame(CommonCRS.WGS84.normalizedGeographic(), CRS.reduce(HardCodedCRS.WGS84_3D, 0, 1)); } /** diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/crs/HardCodedCRS.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/crs/HardCodedCRS.java index 47134b2..28aa9b2 100644 --- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/crs/HardCodedCRS.java +++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/crs/HardCodedCRS.java @@ -35,7 +35,7 @@ import static org.apache.sis.referencing.IdentifiedObjects.getProperties; * Collection of coordinate reference systems for testing purpose. * * @author Martin Desruisseaux (Geomatys) - * @version 0.7 + * @version 1.0 * @since 0.4 * @module */ @@ -237,6 +237,12 @@ public final strictfp class HardCodedCRS { properties("WGS 84 + height + time", null), WGS84, GRAVITY_RELATED_HEIGHT, TIME); /** + * A (λ,φ,H,t) CRS as a nested compound CRS. + */ + public static final DefaultCompoundCRS NESTED = new DefaultCompoundCRS( + properties("(WGS 84 + height) + time", null), GEOID_3D, TIME); + + /** * A two-dimensional Cartesian coordinate reference system with (column, row) axes. * By default, this CRS has no transformation path to any other CRS (i.e. a map using * this CS can't be reprojected to a geographic coordinate reference system for example).
