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 9a63af427fb31f2026341883c90b2759b60ed3ce Author: Martin Desruisseaux <[email protected]> AuthorDate: Fri May 27 16:52:43 2022 +0200 Add a `Envelopes.transformWraparounds(…)` method for getting the individual envelopes before their union is computed. This method is useful only if the transforms chain contains at least one `WraparoundTransform` step. --- .../java/org/apache/sis/geometry/Envelopes.java | 124 +++++++++++++++------ .../org/apache/sis/geometry/EnvelopesTest.java | 22 +++- 2 files changed, 109 insertions(+), 37 deletions(-) diff --git a/core/sis-referencing/src/main/java/org/apache/sis/geometry/Envelopes.java b/core/sis-referencing/src/main/java/org/apache/sis/geometry/Envelopes.java index f331abe68e..d7d7bd14d7 100644 --- a/core/sis-referencing/src/main/java/org/apache/sis/geometry/Envelopes.java +++ b/core/sis-referencing/src/main/java/org/apache/sis/geometry/Envelopes.java @@ -22,7 +22,9 @@ package org.apache.sis.geometry; * force installation of the Java2D module (e.g. JavaFX/SWT). */ import java.util.Set; +import java.util.List; import java.util.Optional; +import java.util.ArrayList; import java.util.ConcurrentModificationException; import java.time.Instant; import org.opengis.geometry.Envelope; @@ -98,7 +100,7 @@ import static org.apache.sis.util.StringBuilders.trimFractionalPart; * * @author Martin Desruisseaux (IRD, Geomatys) * @author Johann Sorel (Geomatys) - * @version 1.1 + * @version 1.3 * * @see org.apache.sis.metadata.iso.extent.Extents * @see CRS @@ -362,49 +364,28 @@ public final class Envelopes extends Static { } /** - * Transforms an envelope using the given math transform. - * The transformation is only approximated: the returned envelope may be bigger than necessary, - * or smaller than required if the bounding box contains a pole. - * The coordinate reference system of the returned envelope will be null. - * - * <h4>Limitation</h4> - * This method can not handle the case where the envelope contains the North or South pole, - * or when it crosses the ±180° longitude, because {@link MathTransform} does not carry sufficient information. - * For a more robust envelope transformation, use {@link #transform(CoordinateOperation, Envelope)} instead. + * Shared implementation of {@link #transform(MathTransform, Envelope)} + * and {@link #transformWraparounds(MathTransform, Envelope)}. + * Offers also the opportunity to save the transformed center coordinates. * * @param transform the transform to use. - * @param envelope envelope to transform, or {@code null}. This envelope will not be modified. - * @return the transformed envelope, or {@code null} if {@code envelope} was null. - * @throws TransformException if a transform failed. - * - * @see #transform(CoordinateOperation, Envelope) - * - * @since 0.5 - */ - public static GeneralEnvelope transform(final MathTransform transform, final Envelope envelope) - throws TransformException - { - ArgumentChecks.ensureNonNull("transform", transform); - return (envelope != null) ? transform(transform, envelope, null) : null; - } - - /** - * Implementation of {@link #transform(MathTransform, Envelope)} with the opportunity to - * save the projected center coordinate. - * - * @param targetPt after this method call, the center of the source envelope projected to the target CRS. - * The length of this array must be the number of target dimensions. - * May be {@code null} if this information is not needed. + * @param envelope envelope to transform. This envelope will not be modified. + * @param targetPt after this method call, the center of the source envelope transformed to the target CRS. + * The length of this array must be the number of target dimensions. + * May be {@code null} if this information is not needed. + * @param results where to store the individual results when the transform contains wraparound steps, + * or {@code null} for computing the union of all results instead. + * @return the transformed envelope. May be {@code null} if {@code results} was non-null. */ @SuppressWarnings("null") - private static GeneralEnvelope transform(final MathTransform transform, final Envelope envelope, double[] targetPt) - throws TransformException + private static GeneralEnvelope transform(final MathTransform transform, final Envelope envelope, + double[] targetPt, final List<GeneralEnvelope> results) throws TransformException { if (transform.isIdentity()) { /* * Slight optimization: Just copy the envelope. Note that we need to set the CRS * to null because we don't know what the target CRS was supposed to be. Even if - * an identity transform often imply that the target CRS is the same one than the + * an identity transform often implies that the target CRS is the same one than the * source CRS, it is not always the case. The metadata may be differents, or the * transform may be a datum shift without Bursa-Wolf parameters, etc. */ @@ -590,6 +571,15 @@ nextPoint: for (int pointIndex = 0;;) { // Break condition at th System.arraycopy(coordinates, coordinates.length - targetDim, targetPt, 0, targetDim); targetPt = null; } + /* + * If the caller wants individual results, store them in the list. + * Do not filter empty envelopes, because some callers such as + * `GridExtent` have algorithms for completing empty envelopes. + */ + if (results != null) { + results.add(transformed); + transformed = null; + } } while (wc.translate()); return transformed; } @@ -653,7 +643,7 @@ nextPoint: for (int pointIndex = 0;;) { // Break condition at th } final MathTransform mt = operation.getMathTransform(); final double[] centerPt = new double[mt.getTargetDimensions()]; - final GeneralEnvelope transformed = transform(mt, envelope, centerPt); + final GeneralEnvelope transformed = transform(mt, envelope, centerPt, null); /* * If the source envelope crosses the expected range of valid coordinates, also projects * the range bounds as a safety. Example: if the source envelope goes from 150 to 200°E, @@ -954,6 +944,68 @@ poles: for (int i=0; i<dimension; i++) { return transformed; } + /** + * Transforms an envelope using the given math transform. + * The transformation is only approximated: the returned envelope may be bigger than necessary, + * or smaller than required if the bounding box contains a pole. + * The coordinate reference system of the returned envelope will be null. + * + * <h4>Limitation</h4> + * This method can not handle the case where the envelope contains the North or South pole. + * Envelopes crossing the ±180° longitude are handled only if the given transform contains + * {@link org.apache.sis.referencing.operation.transform.WraparoundTransform} steps; + * this method does not add such steps itself. + * For a more robust envelope transformation, use {@link #transform(CoordinateOperation, Envelope)} instead. + * + * @param transform the transform to use. + * @param envelope envelope to transform, or {@code null}. This envelope will not be modified. + * @return the transformed envelope, or {@code null} if {@code envelope} was null. + * @throws TransformException if a transform failed. + * + * @see #transform(CoordinateOperation, Envelope) + * + * @since 0.5 + */ + public static GeneralEnvelope transform(final MathTransform transform, final Envelope envelope) + throws TransformException + { + ArgumentChecks.ensureNonNull("transform", transform); + return (envelope != null) ? transform(transform, envelope, null, null) : null; + } + + /** + * Transforms potentially many times an envelope using the given math transform. + * If the given envelope is {@code null}, then this method returns an empty envelope. + * Otherwise if the transform does not contain any + * {@link org.apache.sis.referencing.operation.transform.WraparoundTransform} step, + * then this method is equivalent to {@link #transform(MathTransform, Envelope)} returned in an array of length 1. + * Otherwise this method returns many transformed envelopes where each envelope describes approximately the same region. + * If the envelope CRS is geographic, the many envelopes are the same envelope shifted by 360° of longitude. + * If the envelope CRS is projected, then the 360° shifts are applied before the map projection. + * It may result in very different coordinates. + * + * @param transform the transform to use. + * @param envelope envelope to transform, or {@code null}. This envelope will not be modified. + * @return the transformed envelopes, or an empty array if {@code envelope} was null. + * @throws TransformException if a transform failed. + * + * @since 1.3 + */ + public static GeneralEnvelope[] transformWraparounds(final MathTransform transform, final Envelope envelope) + throws TransformException + { + ArgumentChecks.ensureNonNull("transform", transform); + if (envelope == null) { + return new GeneralEnvelope[0]; + } + final List<GeneralEnvelope> results = new ArrayList<>(4); + final GeneralEnvelope transformed = transform(transform, envelope, null, results); + if (results.isEmpty() && transformed != null) { + return new GeneralEnvelope[] {transformed}; + } + return results.toArray(new GeneralEnvelope[results.size()]); + } + /** * Returns the bounding box of a geometry defined in <cite>Well Known Text</cite> (WKT) format. * This method does not check the consistency of the provided WKT. For example it does not check diff --git a/core/sis-referencing/src/test/java/org/apache/sis/geometry/EnvelopesTest.java b/core/sis-referencing/src/test/java/org/apache/sis/geometry/EnvelopesTest.java index 962356e193..fe740855aa 100644 --- a/core/sis-referencing/src/test/java/org/apache/sis/geometry/EnvelopesTest.java +++ b/core/sis-referencing/src/test/java/org/apache/sis/geometry/EnvelopesTest.java @@ -27,6 +27,7 @@ import org.opengis.referencing.crs.GeographicCRS; import org.opengis.referencing.crs.CoordinateReferenceSystem; import org.opengis.referencing.operation.CoordinateOperation; import org.opengis.referencing.operation.Conversion; +import org.opengis.referencing.operation.MathTransform; import org.opengis.referencing.operation.MathTransform2D; import org.opengis.referencing.operation.TransformException; import org.apache.sis.referencing.operation.transform.MathTransformWrapper; @@ -34,6 +35,7 @@ import org.apache.sis.referencing.crs.DefaultCompoundCRS; import org.apache.sis.referencing.crs.HardCodedCRS; import org.apache.sis.referencing.cs.AxesConvention; import org.apache.sis.referencing.CRS; +import org.apache.sis.referencing.operation.transform.WraparoundTransform; import org.apache.sis.test.DependsOn; import org.apache.sis.test.DependsOnMethod; import org.junit.Test; @@ -47,7 +49,7 @@ import static org.opengis.test.Validators.validate; * This class inherits the test methods defined in {@link TransformTestCase}. * * @author Martin Desruisseaux (IRD, Geomatys) - * @version 1.1 + * @version 1.3 * @since 0.3 * @module */ @@ -302,4 +304,22 @@ public final strictfp class EnvelopesTest extends TransformTestCase<GeneralEnvel assertEquals(Instant.parse("2019-12-23T00:00:00Z"), range.getMinValue()); assertEquals(Instant.parse("2020-05-31T18:00:00Z"), range.getMaxValue()); } + + /** + * Tests {@link Envelopes#transformWraparounds(MathTransform, Envelope)}. + * + * @throws TransformException if a coordinate transformation failed. + */ + @Test + public void testTransformWraparounds() throws TransformException { + final GeneralEnvelope envelope = new GeneralEnvelope(HardCodedCRS.WGS84); + envelope.setRange(0, -200, -100); + envelope.setRange(1, 5, 9); + final MathTransform tr = WraparoundTransform.create(2, 0, 360, -180, 0); + assertTrue(tr instanceof WraparoundTransform); + final GeneralEnvelope[] results = Envelopes.transformWraparounds(tr, envelope); + assertEquals(2, results.length); + assertEnvelopeEquals(new GeneralEnvelope(new double[] {-200, 5}, new double[] {-100, 9}), results[0]); + assertEnvelopeEquals(new GeneralEnvelope(new double[] { 160, 5}, new double[] { 260, 9}), results[1]); + } }
