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
The following commit(s) were added to refs/heads/geoapi-4.0 by this push:
new b38a9a6 Refactor the ReferencingUtilities.adjustWraparoundAxes(…)
static method as a WraparoundAdjustment class. The intent is to make easier to
improve it with handling of ProjectedCRS for now, maybe additional kinds of CRS
in the future.
b38a9a6 is described below
commit b38a9a68d87216c517f260a4d49e9fea1159b9ea
Author: Martin Desruisseaux <[email protected]>
AuthorDate: Mon Mar 18 20:31:58 2019 +0100
Refactor the ReferencingUtilities.adjustWraparoundAxes(…) static method as
a WraparoundAdjustment class.
The intent is to make easier to improve it with handling of ProjectedCRS
for now, maybe additional kinds of CRS in the future.
---
.../apache/sis/coverage/grid/GridDerivation.java | 9 +-
.../internal/referencing/ReferencingUtilities.java | 215 ---------------
.../internal/referencing/WraparoundAdjustment.java | 297 +++++++++++++++++++++
.../referencing/ReferencingUtilitiesTest.java | 111 +-------
.../referencing/WraparoundAdjustmentTest.java | 152 +++++++++++
.../sis/test/suite/ReferencingTestSuite.java | 1 +
6 files changed, 456 insertions(+), 329 deletions(-)
diff --git
a/core/sis-raster/src/main/java/org/apache/sis/coverage/grid/GridDerivation.java
b/core/sis-raster/src/main/java/org/apache/sis/coverage/grid/GridDerivation.java
index bd037e5..67d5186 100644
---
a/core/sis-raster/src/main/java/org/apache/sis/coverage/grid/GridDerivation.java
+++
b/core/sis-raster/src/main/java/org/apache/sis/coverage/grid/GridDerivation.java
@@ -32,7 +32,7 @@ import
org.apache.sis.referencing.operation.transform.MathTransforms;
import org.apache.sis.referencing.operation.transform.TransformSeparator;
import org.apache.sis.referencing.operation.matrix.Matrices;
import org.apache.sis.referencing.CRS;
-import org.apache.sis.internal.referencing.ReferencingUtilities;
+import org.apache.sis.internal.referencing.WraparoundAdjustment;
import org.apache.sis.internal.referencing.DirectPositionView;
import org.apache.sis.geometry.GeneralDirectPosition;
import org.apache.sis.geometry.GeneralEnvelope;
@@ -473,7 +473,7 @@ public class GridDerivation {
*
* @see GridExtent#subsample(int[])
*/
- public GridDerivation subgrid(Envelope areaOfInterest, double...
resolution) {
+ public GridDerivation subgrid(final Envelope areaOfInterest, double...
resolution) {
ensureSubgridNotSet();
MathTransform cornerToCRS = base.requireGridToCRS();
subGridSetter = "subgrid";
@@ -502,8 +502,9 @@ public class GridDerivation {
dimension = baseExtent.getDimension(); // Non-null since
'base.requireGridToCRS()' succeed.
GeneralEnvelope indices = null;
if (areaOfInterest != null) {
- areaOfInterest =
ReferencingUtilities.adjustWraparoundAxes(areaOfInterest, base.envelope,
baseToAOI);
- indices = Envelopes.transform(cornerToCRS.inverse(),
areaOfInterest);
+ final WraparoundAdjustment adj = new
WraparoundAdjustment(areaOfInterest);
+ adj.shiftInto(base.envelope, baseToAOI);
+ indices = adj.result(cornerToCRS.inverse());
clipExtent(indices);
}
if (indices == null || indices.getDimension() != dimension) {
diff --git
a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/ReferencingUtilities.java
b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/ReferencingUtilities.java
index 8611537..a5441de 100644
---
a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/ReferencingUtilities.java
+++
b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/ReferencingUtilities.java
@@ -28,8 +28,6 @@ import org.opengis.metadata.citation.Citation;
import org.opengis.parameter.ParameterValueGroup;
import org.opengis.parameter.ParameterDescriptorGroup;
import org.opengis.parameter.GeneralParameterDescriptor;
-import org.opengis.geometry.DirectPosition;
-import org.opengis.geometry.Envelope;
import org.opengis.referencing.cs.*;
import org.opengis.referencing.crs.*;
import org.opengis.referencing.IdentifiedObject;
@@ -39,22 +37,13 @@ import org.opengis.referencing.datum.PrimeMeridian;
import org.opengis.referencing.datum.GeodeticDatum;
import org.opengis.referencing.datum.VerticalDatum;
import org.opengis.referencing.datum.VerticalDatumType;
-import org.opengis.referencing.operation.MathTransform;
-import org.opengis.referencing.operation.CoordinateOperation;
import org.opengis.referencing.operation.CoordinateOperationFactory;
-import org.opengis.referencing.operation.TransformException;
import org.opengis.util.FactoryException;
import org.apache.sis.internal.system.DefaultFactories;
-import org.apache.sis.internal.metadata.AxisDirections;
-import org.apache.sis.measure.Longitude;
-import org.apache.sis.measure.Units;
import org.apache.sis.util.Static;
import org.apache.sis.util.Utilities;
import org.apache.sis.util.CharSequences;
import org.apache.sis.util.resources.Errors;
-import org.apache.sis.math.MathFunctions;
-import org.apache.sis.geometry.Envelopes;
-import org.apache.sis.geometry.GeneralEnvelope;
import org.apache.sis.referencing.CommonCRS;
import org.apache.sis.referencing.IdentifiedObjects;
import org.apache.sis.referencing.datum.DefaultPrimeMeridian;
@@ -107,39 +96,6 @@ public final class ReferencingUtilities extends Static {
}
/**
- * Returns the range (maximum - minimum) of the given axis if it has
wraparound meaning,
- * or {@link Double#NaN} otherwise. This method implements a fallback for
the longitude
- * axis if it does not declare the minimum and maximum values as expected.
- *
- * @param cs the coordinate system for which to get wraparound
range, or {@code null}.
- * @param dimension dimension of the axis to test.
- * @return the wraparound range, or {@link Double#NaN} if none.
- *
- * @since 1.0
- */
- public static double getWraparoundRange(final CoordinateSystem cs, final
int dimension) {
- if (cs != null) {
- final CoordinateSystemAxis axis = cs.getAxis(dimension);
- if (axis != null &&
RangeMeaning.WRAPAROUND.equals(axis.getRangeMeaning())) {
- double period = axis.getMaximumValue() -
axis.getMinimumValue();
- if (period > 0 && period != Double.POSITIVE_INFINITY) {
- return period;
- }
- final AxisDirection dir =
AxisDirections.absolute(axis.getDirection());
- if (AxisDirection.EAST.equals(dir) && cs instanceof
EllipsoidalCS) {
- period = Longitude.MAX_VALUE - Longitude.MIN_VALUE;
- final Unit<?> unit = axis.getUnit();
- if (unit != null) {
- period =
Units.DEGREE.getConverterTo(Units.ensureAngular(unit)).convert(period);
- }
- return period;
- }
- }
- }
- return Double.NaN;
- }
-
- /**
* Returns the unit used for all axes in the given coordinate system.
* If not all axes use the same unit, then this method returns {@code
null}.
*
@@ -532,175 +488,4 @@ public final class ReferencingUtilities extends Static {
}
return mapping;
}
-
- /**
- * Returns an envelope with coordinates equivalent to the given
coordinates,
- * but potentially shifted for intersecting the given domain of validity.
- * The dimensions that may be shifted are the ones having an axis with
wraparound meaning.
- *
- * <p>The coordinate reference system must be specified in the given
{@code areaOfInterest},
- * or (as a fallback) in the given {@code domainOfValidity}. If none of
those envelope have
- * a CRS, then this method does nothing. If any envelope is null, then
this method returns
- * {@code areaOfInterest} unchanged.</p>
- *
- * <p>This method does not intersect the area of interest with the domain
of validity.
- * It is up to the caller to compute that intersection after this method
call, if desired.</p>
- *
- * @param areaOfInterest the envelope to potentially shift toward the
domain of validity, or {@code null} if none.
- * @param domainOfValidity the domain of validity, or {@code null} if
none.
- * @param validToAOI if the envelopes do not use the same CRS, the
transformation from {@code domainOfValidity}
- * to {@code areaOfInterest}. Otherwise {@code
null}. This method does not check by itself if
- * a coordinate operation is needed; it must be
supplied.
- * @return the given area of interest, possibly shifted toward the domain
of validity. May also be expanded.
- * @throws TransformException if an envelope transformation was required
but failed.
- *
- * @see GeneralEnvelope#simplify()
- *
- * @since 1.0
- */
- public static Envelope adjustWraparoundAxes(final Envelope areaOfInterest,
Envelope domainOfValidity,
- CoordinateOperation validToAOI) throws TransformException
- {
- CoordinateReferenceSystem crs;
- if (areaOfInterest != null && domainOfValidity != null &&
- ((crs = areaOfInterest.getCoordinateReferenceSystem()) !=
null ||
- (crs = domainOfValidity.getCoordinateReferenceSystem()) !=
null))
- {
- GeneralEnvelope shifted = null;
- final DirectPosition lowerCorner = areaOfInterest.getLowerCorner();
- final DirectPosition upperCorner = areaOfInterest.getUpperCorner();
- final CoordinateSystem cs = crs.getCoordinateSystem();
- for (int i=cs.getDimension(); --i >= 0;) {
- final double period = getWraparoundRange(cs, i);
- if (period > 0) {
- /*
- * Found an axis (typically the longitude axis) with
wraparound range meaning.
- * We are going to need the domain of validity in the same
CRS than the AOI.
- * Transform that envelope when first needed.
- */
- if (validToAOI != null) {
- final MathTransform mt = validToAOI.getMathTransform();
- validToAOI = null;
- if (!mt.isIdentity()) {
- domainOfValidity = Envelopes.transform(mt,
domainOfValidity);
- }
- }
- /*
- * "Unroll" the range. For example if we have [+160 …
-170]° of longitude, we can replace by [160 … 190]°.
- * We do not change the 'lower' or 'upper' value now in
order to avoid rounding error. Instead we compute
- * how many periods we need to add to those values. We
adjust the side which results in the value closest
- * to zero, in order to reduce rounding error if no more
adjustment is done in the next block.
- */
- final double lower = lowerCorner.getOrdinate(i);
- final double upper = upperCorner.getOrdinate(i);
- double lowerCycles = 0; // In
number of periods.
- double upperCycles = 0;
- double delta = upper - lower;
- if (MathFunctions.isNegative(delta)) { // Use
'isNegative' for catching [+0 … -0] range.
- final double cycles = (delta == 0) ? -1 :
Math.floor(delta / period); // Always negative.
- delta = cycles * period;
- if (Math.abs(lower + delta) < Math.abs(upper - delta))
{
- lowerCycles = cycles;
// Will subtract periods to 'lower'.
- } else {
- upperCycles = -cycles;
// Will add periods to 'upper'.
- }
- }
- /*
- * The range may be before or after the domain of
validity. Compute the distance from current
- * lower/upper coordinate to the coordinate of validity
domain (the sign tells us whether we
- * are before or after). The cases can be:
- *
- *
┌─────────────┬────────────┬────────────────────────────┬───────────────────────────────┐
- * │lowerIsBefore│upperIsAfter│ Meaning
│ Action │
- *
├─────────────┼────────────┼────────────────────────────┼───────────────────────────────┤
- * │ false │ false │ AOI is inside valid area
│ Nothing to do │
- * │ true │ true │ AOI encompasses valid
area │ Nothing to do │
- * │ true │ false │ AOI on left of valid
area │ Add positive amount of period │
- * │ false │ true │ AOI on right of valid
area │ Add negative amount of period │
- *
└─────────────┴────────────┴────────────────────────────┴───────────────────────────────┘
- *
- * We try to compute multiples of 'periods' instead than
just adding or subtracting 'periods' once in
- * order to support images that cover more than one
period, for example images over 720° of longitude.
- * It may happen for example if an image shows data under
the trajectory of a satellite.
- */
- final double validStart =
domainOfValidity.getMinimum(i);
- final double validEnd =
domainOfValidity.getMaximum(i);
- final double lowerToValidStart = ((validStart - lower) /
period) - lowerCycles; // In number of periods.
- final double upperToValidEnd = ((validEnd - upper) /
period) - upperCycles;
- final boolean lowerIsBefore = (lowerToValidStart > 0);
- final boolean upperIsAfter = (upperToValidEnd < 0);
- if (lowerIsBefore != upperIsAfter) {
- final double upperToValidStart = ((validStart - upper)
/ period) - upperCycles;
- final double lowerToValidEnd = ((validEnd - lower)
/ period) - lowerCycles;
- if (lowerIsBefore) {
- /*
- * We need to add an integer amount of 'period' to
both sides in order to move the range
- * inside the valid area. We need
⎣lowerToValidStart⎦ for reaching the point where:
- *
- * (validStart - period) < (new lower) ≦
validStart
- *
- * But we may add more because there will be no
intersection without following condition:
- *
- * (new upper) ≧ validStart
- *
- * That second condition is met by
⎡upperToValidStart⎤. Note: ⎣x⎦=floor(x) and ⎡x⎤=ceil(x).
- */
- final double cycles =
Math.max(Math.floor(lowerToValidStart), Math.ceil(upperToValidStart));
- /*
- * If after the shift we see that the following
condition hold:
- *
- * (new lower) + period < validEnd
- *
- * Then we may have a situation like below:
- *
┌────────────────────────────────────────────┐
- * │ Domain of
validity │
- *
└────────────────────────────────────────────┘
- * ┌────────────────────┐
┌─────
- * │ Area of interest │
│ AOI
- * └────────────────────┘
└─────
- *
↖……………………………………………………………period……………………………………………………………↗︎
- *
- * The user may be requesting two extremums of the
domain of validity. We can not express
- * that with a single envelope. Instead, we will
expand the Area Of Interest to encompass
- * the full domain of validity.
- */
- if (cycles + 1 < lowerToValidEnd) {
- upperCycles += Math.ceil(upperToValidEnd);
- } else {
- upperCycles += cycles;
- }
- lowerCycles += cycles;
- } else {
- /*
- * Same reasoning than above with sign reverted
and lower/upper variables interchanged.
- * In this block, 'upperToValidEnd' and
'lowerToValidEnd' are negative, contrarily to
- * above block where they were positive.
- */
- final double cycles =
Math.min(Math.ceil(upperToValidEnd), Math.floor(lowerToValidEnd));
- if (cycles - 1 > upperToValidStart) {
- lowerCycles += Math.floor(lowerToValidStart);
- } else {
- lowerCycles += cycles;
- }
- upperCycles += cycles;
- }
- }
- /*
- * If there is change to apply, copy the envelope when
first needed.
- */
- if (lowerCycles != 0 || upperCycles != 0) {
- if (shifted == null) {
- shifted = new GeneralEnvelope(areaOfInterest);
- }
- shifted.setRange(i, lower + lowerCycles * period,
// TODO: use Math.fma in JDK9.
- upper + upperCycles * period);
- }
- }
- }
- if (shifted != null) {
- return shifted;
- }
- }
- return areaOfInterest;
- }
}
diff --git
a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/WraparoundAdjustment.java
b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/WraparoundAdjustment.java
new file mode 100644
index 0000000..2641126
--- /dev/null
+++
b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/WraparoundAdjustment.java
@@ -0,0 +1,297 @@
+/*
+ * 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.internal.referencing;
+
+import javax.measure.Unit;
+import org.opengis.geometry.DirectPosition;
+import org.opengis.geometry.Envelope;
+import org.opengis.referencing.cs.AxisDirection;
+import org.opengis.referencing.cs.CoordinateSystem;
+import org.opengis.referencing.cs.CoordinateSystemAxis;
+import org.opengis.referencing.cs.EllipsoidalCS;
+import org.opengis.referencing.cs.RangeMeaning;
+import org.opengis.referencing.crs.CoordinateReferenceSystem;
+import org.opengis.referencing.crs.ProjectedCRS;
+import org.opengis.referencing.operation.MathTransform;
+import org.opengis.referencing.operation.CoordinateOperation;
+import org.opengis.referencing.operation.TransformException;
+import org.apache.sis.referencing.operation.transform.MathTransforms;
+import org.apache.sis.internal.metadata.AxisDirections;
+import org.apache.sis.measure.Longitude;
+import org.apache.sis.measure.Units;
+import org.apache.sis.math.MathFunctions;
+import org.apache.sis.geometry.Envelopes;
+import org.apache.sis.geometry.GeneralEnvelope;
+
+
+/**
+ * Adjustments applied on an envelope for handling wraparound axes. The
adjustments consist in shifting
+ * some axes by an integer amount of periods, typically (not necessarily) 360°
of longitude.
+ *
+ * @author Martin Desruisseaux (Geomatys)
+ * @version 1.0
+ * @since 1.0
+ * @module
+ */
+public final class WraparoundAdjustment {
+ /**
+ * The envelope to potentially shift in order to fit in the domain of
validity. If a shift is needed, then
+ * this envelope will be replaced by a new envelope; the user-specified
envelope will not be modified.
+ */
+ private Envelope areaOfInterest;
+
+ /**
+ * If {@link #areaOfInterest} has been converted to a geographic CRS, the
transformation back to its original CRS.
+ * Otherwise {@code null}.
+ */
+ private MathTransform geographicToAOI;
+
+ /**
+ * Creates a new instance for adjusting the given envelope.
+ * The given envelope will not be modified; a copy will be created if
needed.
+ *
+ * @param areaOfInterest the envelope to potentially shift toward the
domain of validity.
+ */
+ public WraparoundAdjustment(final Envelope areaOfInterest) {
+ this.areaOfInterest = areaOfInterest;
+ }
+
+ /**
+ * Returns the range (maximum - minimum) of the given axis if it has
wraparound meaning,
+ * or {@link Double#NaN} otherwise. This method implements a fallback for
the longitude
+ * axis if it does not declare the minimum and maximum values as expected.
+ *
+ * @param cs the coordinate system for which to get wraparound
range, or {@code null}.
+ * @param dimension dimension of the axis to test.
+ * @return the wraparound range, or {@link Double#NaN} if none.
+ */
+ static double range(final CoordinateSystem cs, final int dimension) {
+ if (cs != null) {
+ final CoordinateSystemAxis axis = cs.getAxis(dimension);
+ if (axis != null &&
RangeMeaning.WRAPAROUND.equals(axis.getRangeMeaning())) {
+ double period = axis.getMaximumValue() -
axis.getMinimumValue();
+ if (period > 0 && period != Double.POSITIVE_INFINITY) {
+ return period;
+ }
+ final AxisDirection dir =
AxisDirections.absolute(axis.getDirection());
+ if (AxisDirection.EAST.equals(dir) && cs instanceof
EllipsoidalCS) {
+ period = Longitude.MAX_VALUE - Longitude.MIN_VALUE;
+ final Unit<?> unit = axis.getUnit();
+ if (unit != null) {
+ period =
Units.DEGREE.getConverterTo(Units.ensureAngular(unit)).convert(period);
+ }
+ return period;
+ }
+ }
+ }
+ return Double.NaN;
+ }
+
+ /**
+ * Computes an envelope with coordinates equivalent to the {@code
areaOfInterest} specified
+ * at construction time, but potentially shifted for intersecting the
given domain of validity.
+ * The dimensions that may be shifted are the ones having an axis with
wraparound meaning.
+ * The envelope may have been converted to a geographic CRS for performing
this operation.
+ *
+ * <p>The coordinate reference system must be specified in the {@code
areaOfInterest}
+ * specified at construction time, or (as a fallback) in the given {@code
domainOfValidity}.
+ * If none of those envelopes have a CRS, then this method does
nothing.</p>
+ *
+ * <p>This method does not intersect the area of interest with the domain
of validity.
+ * It is up to the caller to compute that intersection after this method
call, if desired.</p>
+ *
+ * @param domainOfValidity the domain of validity, or {@code null} if
none.
+ * @param validToAOI if the envelopes do not use the same CRS, the
transformation from {@code domainOfValidity}
+ * to {@code areaOfInterest}. Otherwise {@code
null}. This method does not check by itself if
+ * a coordinate operation is needed; it must be
supplied.
+ * @throws TransformException if an envelope transformation was required
but failed.
+ *
+ * @see GeneralEnvelope#simplify()
+ */
+ public void shiftInto(Envelope domainOfValidity, CoordinateOperation
validToAOI) throws TransformException {
+ CoordinateReferenceSystem crs =
areaOfInterest.getCoordinateReferenceSystem();
+ if (crs == null) {
+ crs = domainOfValidity.getCoordinateReferenceSystem(); //
Assumed to apply to AOI too.
+ if (crs == null) {
+ return;
+ }
+ }
+ /*
+ * If the coordinate reference system is a projected CRS, it will not
have any wraparound axis.
+ * We need to perform the verification in its base geographic CRS
instead, and remember that we
+ * may need to transform the result later.
+ */
+ GeneralEnvelope shifted = null; // To be initialized to a copy
of 'areaOfInterest' when first needed.
+ if (crs instanceof ProjectedCRS) {
+ final ProjectedCRS p = (ProjectedCRS) crs;
+ crs = p.getBaseCRS();
+ geographicToAOI = p.getConversionFromBase().getMathTransform();
+ areaOfInterest = shifted =
Envelopes.transform(geographicToAOI.inverse(), areaOfInterest);
+ }
+ /*
+ * We will not reference 'areaOfInterest' anymore after we got its two
corner points.
+ * The following loop search for "wraparound" axis.
+ */
+ final DirectPosition lowerCorner = areaOfInterest.getLowerCorner();
+ final DirectPosition upperCorner = areaOfInterest.getUpperCorner();
+ final CoordinateSystem cs = crs.getCoordinateSystem();
+ for (int i=cs.getDimension(); --i >= 0;) {
+ final double period = range(cs, i);
+ if (period > 0) {
+ /*
+ * Found an axis (typically the longitude axis) with
wraparound range meaning.
+ * We are going to need the domain of validity in the same CRS
than the AOI.
+ * Transform that envelope when first needed.
+ */
+ if (validToAOI != null) {
+ MathTransform mt = validToAOI.getMathTransform();
+ if (geographicToAOI != null) {
+ mt = MathTransforms.concatenate(mt,
geographicToAOI.inverse());
+ }
+ validToAOI = null;
+ if (!mt.isIdentity()) {
+ domainOfValidity = Envelopes.transform(mt,
domainOfValidity);
+ }
+ }
+ /*
+ * "Unroll" the range. For example if we have [+160 … -170]°
of longitude, we can replace by [160 … 190]°.
+ * We do not change the 'lower' or 'upper' value now in order
to avoid rounding error. Instead we compute
+ * how many periods we need to add to those values. We adjust
the side which results in the value closest
+ * to zero, in order to reduce rounding error if no more
adjustment is done in the next block.
+ */
+ final double lower = lowerCorner.getOrdinate(i);
+ final double upper = upperCorner.getOrdinate(i);
+ double lowerCycles = 0; // In
number of periods.
+ double upperCycles = 0;
+ double delta = upper - lower;
+ if (MathFunctions.isNegative(delta)) { // Use
'isNegative' for catching [+0 … -0] range.
+ final double cycles = (delta == 0) ? -1 : Math.floor(delta
/ period); // Always negative.
+ delta = cycles * period;
+ if (Math.abs(lower + delta) < Math.abs(upper - delta)) {
+ lowerCycles = cycles;
// Will subtract periods to 'lower'.
+ } else {
+ upperCycles = -cycles;
// Will add periods to 'upper'.
+ }
+ }
+ /*
+ * The range may be before or after the domain of validity.
Compute the distance from current
+ * lower/upper coordinate to the coordinate of validity domain
(the sign tells us whether we
+ * are before or after). The cases can be:
+ *
+ *
┌─────────────┬────────────┬────────────────────────────┬───────────────────────────────┐
+ * │lowerIsBefore│upperIsAfter│ Meaning │
Action │
+ *
├─────────────┼────────────┼────────────────────────────┼───────────────────────────────┤
+ * │ false │ false │ AOI is inside valid area │
Nothing to do │
+ * │ true │ true │ AOI encompasses valid area │
Nothing to do │
+ * │ true │ false │ AOI on left of valid area │
Add positive amount of period │
+ * │ false │ true │ AOI on right of valid area │
Add negative amount of period │
+ *
└─────────────┴────────────┴────────────────────────────┴───────────────────────────────┘
+ *
+ * We try to compute multiples of 'periods' instead than just
adding or subtracting 'periods' once in
+ * order to support images that cover more than one period,
for example images over 720° of longitude.
+ * It may happen for example if an image shows data under the
trajectory of a satellite.
+ */
+ final double validStart =
domainOfValidity.getMinimum(i);
+ final double validEnd =
domainOfValidity.getMaximum(i);
+ final double lowerToValidStart = ((validStart - lower) /
period) - lowerCycles; // In number of periods.
+ final double upperToValidEnd = ((validEnd - upper) /
period) - upperCycles;
+ final boolean lowerIsBefore = (lowerToValidStart > 0);
+ final boolean upperIsAfter = (upperToValidEnd < 0);
+ if (lowerIsBefore != upperIsAfter) {
+ final double upperToValidStart = ((validStart - upper) /
period) - upperCycles;
+ final double lowerToValidEnd = ((validEnd - lower) /
period) - lowerCycles;
+ if (lowerIsBefore) {
+ /*
+ * We need to add an integer amount of 'period' to
both sides in order to move the range
+ * inside the valid area. We need ⎣lowerToValidStart⎦
for reaching the point where:
+ *
+ * (validStart - period) < (new lower) ≦ validStart
+ *
+ * But we may add more because there will be no
intersection without following condition:
+ *
+ * (new upper) ≧ validStart
+ *
+ * That second condition is met by
⎡upperToValidStart⎤. Note: ⎣x⎦=floor(x) and ⎡x⎤=ceil(x).
+ */
+ final double cycles =
Math.max(Math.floor(lowerToValidStart), Math.ceil(upperToValidStart));
+ /*
+ * If after the shift we see that the following
condition hold:
+ *
+ * (new lower) + period < validEnd
+ *
+ * Then we may have a situation like below:
+ *
┌────────────────────────────────────────────┐
+ * │ Domain of validity
│
+ *
└────────────────────────────────────────────┘
+ * ┌────────────────────┐
┌─────
+ * │ Area of interest │
│ AOI
+ * └────────────────────┘
└─────
+ *
↖……………………………………………………………period……………………………………………………………↗︎
+ *
+ * The user may be requesting two extremums of the
domain of validity. We can not express
+ * that with a single envelope. Instead, we will
expand the Area Of Interest to encompass
+ * the full domain of validity.
+ */
+ if (cycles + 1 < lowerToValidEnd) {
+ upperCycles += Math.ceil(upperToValidEnd);
+ } else {
+ upperCycles += cycles;
+ }
+ lowerCycles += cycles;
+ } else {
+ /*
+ * Same reasoning than above with sign reverted and
lower/upper variables interchanged.
+ * In this block, 'upperToValidEnd' and
'lowerToValidEnd' are negative, contrarily to
+ * above block where they were positive.
+ */
+ final double cycles =
Math.min(Math.ceil(upperToValidEnd), Math.floor(lowerToValidEnd));
+ if (cycles - 1 > upperToValidStart) {
+ lowerCycles += Math.floor(lowerToValidStart);
+ } else {
+ lowerCycles += cycles;
+ }
+ upperCycles += cycles;
+ }
+ }
+ /*
+ * If there is change to apply, copy the envelope when first
needed.
+ */
+ if (lowerCycles != 0 || upperCycles != 0) {
+ if (shifted == null) {
+ areaOfInterest = shifted = new
GeneralEnvelope(areaOfInterest);
+ }
+ shifted.setRange(i, lower + lowerCycles * period, //
TODO: use Math.fma in JDK9.
+ upper + upperCycles * period);
+ }
+ }
+ }
+ }
+
+ /**
+ * Returns the (potentially shifted and/or expanded) area of interest
converted by the given transform.
+ *
+ * @param mt a transform from the CRS of the {@code areaOfInterest}
given to the constructor.
+ * @return the area of interest transformed by the given {@code
MathTransform}.
+ * @throws TransformException if the transformation failed.
+ */
+ public GeneralEnvelope result(MathTransform mt) throws TransformException {
+ if (geographicToAOI != null) {
+ mt = MathTransforms.concatenate(geographicToAOI, mt);
+ }
+ return Envelopes.transform(mt, areaOfInterest);
+ }
+}
diff --git
a/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/ReferencingUtilitiesTest.java
b/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/ReferencingUtilitiesTest.java
index 207c714..a9b1143 100644
---
a/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/ReferencingUtilitiesTest.java
+++
b/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/ReferencingUtilitiesTest.java
@@ -17,25 +17,20 @@
package org.apache.sis.internal.referencing;
import javax.measure.Unit;
-import org.apache.sis.geometry.GeneralEnvelope;
-import org.opengis.geometry.Envelope;
import org.opengis.referencing.cs.*;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.crs.GeographicCRS;
import org.opengis.referencing.datum.PrimeMeridian;
import org.opengis.referencing.datum.VerticalDatum;
import org.opengis.referencing.IdentifiedObject;
-import org.opengis.referencing.operation.CoordinateOperation;
-import org.opengis.referencing.operation.TransformException;
import org.apache.sis.referencing.datum.HardCodedDatum;
import org.apache.sis.referencing.crs.HardCodedCRS;
-import org.apache.sis.referencing.cs.HardCodedCS;
import org.apache.sis.util.Utilities;
import org.apache.sis.measure.Units;
import org.apache.sis.test.TestCase;
import org.junit.Test;
-import static org.apache.sis.test.ReferencingAssert.*;
+import static org.junit.Assert.*;
import static org.apache.sis.internal.referencing.ReferencingUtilities.*;
@@ -62,16 +57,6 @@ public final strictfp class ReferencingUtilitiesTest extends
TestCase {
}
/**
- * Tests {@link ReferencingUtilities#getWraparoundRange(CoordinateSystem,
int)}.
- */
- @Test
- public void testGetWraparoundRange() {
- assertTrue (Double.isNaN(getWraparoundRange(HardCodedCS.GEODETIC_φλ,
0)));
- assertEquals(360, getWraparoundRange(HardCodedCS.GEODETIC_φλ, 1),
STRICT);
- assertEquals(400, getWraparoundRange(HardCodedCS.ELLIPSOIDAL_gon, 0),
STRICT);
- }
-
- /**
* Tests {@link ReferencingUtilities#isEllipsoidalHeight(VerticalDatum)}.
*/
@Test
@@ -140,98 +125,4 @@ public final strictfp class ReferencingUtilitiesTest
extends TestCase {
assertEquals("timeCS",
toPropertyName(CoordinateSystem.class, TimeCS .class).toString());
assertEquals("verticalCS",
toPropertyName(CoordinateSystem.class, VerticalCS .class).toString());
}
-
- /**
- * Tests {@link ReferencingUtilities#adjustWraparoundAxes(Envelope,
Envelope, CoordinateOperation)}
- * with an envelope crossing the anti-meridian.
- *
- * @throws TransformException should never happen since this test does not
transform coordinates.
- *
- * @since 1.0
- */
- @Test
- public void testAdjustWraparoundAxesOverAntiMeridian() throws
TransformException {
- final GeneralEnvelope domainOfValidity = new
GeneralEnvelope(HardCodedCRS.WGS84);
- domainOfValidity.setRange(0, 80, 280);
- domainOfValidity.setRange(1, -90, +90);
-
- final GeneralEnvelope areaOfInterest = new
GeneralEnvelope(HardCodedCRS.WGS84);
- areaOfInterest.setRange(0, 140, -179); // Cross
anti-meridian.
- areaOfInterest.setRange(1, -90, 90);
-
- final GeneralEnvelope expected = new
GeneralEnvelope(HardCodedCRS.WGS84);
- expected.setRange(0, 140, 181);
- expected.setRange(1, -90, +90);
-
- final Envelope actual =
ReferencingUtilities.adjustWraparoundAxes(areaOfInterest, domainOfValidity,
null);
- assertEnvelopeEquals(expected, actual);
- }
-
- /**
- * Tests {@link ReferencingUtilities#adjustWraparoundAxes(Envelope,
Envelope, CoordinateOperation)}
- * with an envelope shifted by 360° before or after the grid valid area.
- *
- * @throws TransformException should never happen since this test does not
transform coordinates.
- *
- * @since 1.0
- */
- @Test
- public void testAdjustWraparoundAxesWithShiftedAOI() throws
TransformException {
- final GeneralEnvelope domainOfValidity = new
GeneralEnvelope(HardCodedCRS.WGS84);
- domainOfValidity.setRange(0, 80, 100);
- domainOfValidity.setRange(1, -70, +70);
-
- final GeneralEnvelope areaOfInterest = new
GeneralEnvelope(HardCodedCRS.WGS84);
- areaOfInterest.setRange(0, 70, 90);
- areaOfInterest.setRange(1, -80, 60);
-
- final GeneralEnvelope expected = new GeneralEnvelope(areaOfInterest);
-
- Envelope actual =
ReferencingUtilities.adjustWraparoundAxes(areaOfInterest, domainOfValidity,
null);
- assertEnvelopeEquals(expected, actual);
-
- areaOfInterest.setRange(0, -290, -270); // [70 …
90] - 360
- actual = ReferencingUtilities.adjustWraparoundAxes(areaOfInterest,
domainOfValidity, null);
- assertEnvelopeEquals(expected, actual);
-
- areaOfInterest.setRange(0, 430, 450); // [70 …
90] + 360
- actual = ReferencingUtilities.adjustWraparoundAxes(areaOfInterest,
domainOfValidity, null);
- assertEnvelopeEquals(expected, actual);
- }
-
- /**
- * Tests {@link ReferencingUtilities#adjustWraparoundAxes(Envelope,
Envelope, CoordinateOperation)}
- * with an envelope that cause the method to expand the area of interest.
Illustration:
- *
- * {@preformat text
- * ┌────────────────────────────────────────────┐
- * │ Domain of validity │
- * └────────────────────────────────────────────┘
- * ┌────────────────────┐ ┌─────
- * │ Area of interest │ │ AOI
- * └────────────────────┘ └─────
- * ↖………………………………………………………360° period……………………………………………………↗︎
- * }
- *
- * @throws TransformException should never happen since this test does not
transform coordinates.
- *
- * @since 1.0
- */
- @Test
- public void testAdjustWraparoundAxesCausingExpansion() throws
TransformException {
- final GeneralEnvelope domainOfValidity = new
GeneralEnvelope(HardCodedCRS.WGS84);
- domainOfValidity.setRange(0, 5, 345);
- domainOfValidity.setRange(1, -70, +70);
-
- final GeneralEnvelope areaOfInterest = new
GeneralEnvelope(HardCodedCRS.WGS84);
- areaOfInterest.setRange(0, -30, 40);
- areaOfInterest.setRange(1, -60, 60);
-
- final GeneralEnvelope expected = new
GeneralEnvelope(HardCodedCRS.WGS84);
- expected.setRange(0, -30, 400);
- expected.setRange(1, -60, 60);
-
- final Envelope actual =
ReferencingUtilities.adjustWraparoundAxes(areaOfInterest, domainOfValidity,
null);
- assertEnvelopeEquals(expected, actual);
- }
}
diff --git
a/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/WraparoundAdjustmentTest.java
b/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/WraparoundAdjustmentTest.java
new file mode 100644
index 0000000..e6634ed
--- /dev/null
+++
b/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/WraparoundAdjustmentTest.java
@@ -0,0 +1,152 @@
+/*
+ * 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.internal.referencing;
+
+import org.apache.sis.geometry.GeneralEnvelope;
+import org.opengis.geometry.Envelope;
+import org.opengis.referencing.cs.*;
+import org.opengis.referencing.operation.CoordinateOperation;
+import org.opengis.referencing.operation.TransformException;
+import org.apache.sis.referencing.crs.HardCodedCRS;
+import org.apache.sis.referencing.cs.HardCodedCS;
+import org.apache.sis.referencing.operation.transform.MathTransforms;
+import org.apache.sis.test.DependsOnMethod;
+import org.apache.sis.test.TestCase;
+import org.junit.Test;
+
+import static org.apache.sis.test.ReferencingAssert.*;
+
+
+/**
+ * Tests {@link WraparoundAdjustment}.
+ *
+ * @author Martin Desruisseaux (Geomatys)
+ * @version 1.0
+ * @since 1.0
+ * @module
+ */
+public final strictfp class WraparoundAdjustmentTest extends TestCase {
+ /**
+ * Tests {@link WraparoundAdjustment#range(CoordinateSystem, int)}.
+ */
+ @Test
+ public void testRange() {
+ assertTrue
(Double.isNaN(WraparoundAdjustment.range(HardCodedCS.GEODETIC_φλ, 0)));
+ assertEquals(360, WraparoundAdjustment.range(HardCodedCS.GEODETIC_φλ,
1), STRICT);
+ assertEquals(400,
WraparoundAdjustment.range(HardCodedCS.ELLIPSOIDAL_gon, 0), STRICT);
+ }
+
+ /**
+ * Convenience method for the tests.
+ */
+ private static Envelope adjustWraparoundAxes(Envelope areaOfInterest,
Envelope domainOfValidity, CoordinateOperation validToAOI)
+ throws TransformException
+ {
+ WraparoundAdjustment adj = new WraparoundAdjustment(areaOfInterest);
+ adj.shiftInto(domainOfValidity, validToAOI);
+ return adj.result(MathTransforms.identity(2));
+ }
+
+ /**
+ * Tests {@link WraparoundAdjustment#shiftInto(Envelope,
CoordinateOperation)}
+ * with an envelope crossing the anti-meridian.
+ *
+ * @throws TransformException should never happen since this test does not
transform coordinates.
+ */
+ @Test
+ public void testOverAntiMeridian() throws TransformException {
+ final GeneralEnvelope domainOfValidity = new
GeneralEnvelope(HardCodedCRS.WGS84);
+ domainOfValidity.setRange(0, 80, 280);
+ domainOfValidity.setRange(1, -90, +90);
+
+ final GeneralEnvelope areaOfInterest = new
GeneralEnvelope(HardCodedCRS.WGS84);
+ areaOfInterest.setRange(0, 140, -179); // Cross
anti-meridian.
+ areaOfInterest.setRange(1, -90, 90);
+
+ final GeneralEnvelope expected = new
GeneralEnvelope(HardCodedCRS.WGS84);
+ expected.setRange(0, 140, 181);
+ expected.setRange(1, -90, +90);
+
+ final Envelope actual = adjustWraparoundAxes(areaOfInterest,
domainOfValidity, null);
+ assertEnvelopeEquals(expected, actual);
+ }
+
+ /**
+ * Tests {@link WraparoundAdjustment#shiftInto(Envelope,
CoordinateOperation)}
+ * with an envelope shifted by 360° before or after the grid valid area.
+ *
+ * @throws TransformException should never happen since this test does not
transform coordinates.
+ */
+ @Test
+ @DependsOnMethod("testRange")
+ public void testWithShiftedAOI() throws TransformException {
+ final GeneralEnvelope domainOfValidity = new
GeneralEnvelope(HardCodedCRS.WGS84);
+ domainOfValidity.setRange(0, 80, 100);
+ domainOfValidity.setRange(1, -70, +70);
+
+ final GeneralEnvelope areaOfInterest = new
GeneralEnvelope(HardCodedCRS.WGS84);
+ areaOfInterest.setRange(0, 70, 90);
+ areaOfInterest.setRange(1, -80, 60);
+
+ final GeneralEnvelope expected = new GeneralEnvelope(areaOfInterest);
+
+ Envelope actual = adjustWraparoundAxes(areaOfInterest,
domainOfValidity, null);
+ assertEnvelopeEquals(expected, actual);
+
+ areaOfInterest.setRange(0, -290, -270); // [70 …
90] - 360
+ actual = adjustWraparoundAxes(areaOfInterest, domainOfValidity, null);
+ assertEnvelopeEquals(expected, actual);
+
+ areaOfInterest.setRange(0, 430, 450); // [70 …
90] + 360
+ actual = adjustWraparoundAxes(areaOfInterest, domainOfValidity, null);
+ assertEnvelopeEquals(expected, actual);
+ }
+
+ /**
+ * Tests {@link WraparoundAdjustment#shiftInto(Envelope,
CoordinateOperation)}
+ * with an envelope that cause the method to expand the area of interest.
Illustration:
+ *
+ * {@preformat text
+ * ┌────────────────────────────────────────────┐
+ * │ Domain of validity │
+ * └────────────────────────────────────────────┘
+ * ┌────────────────────┐ ┌─────
+ * │ Area of interest │ │ AOI
+ * └────────────────────┘ └─────
+ * ↖………………………………………………………360° period……………………………………………………↗︎
+ * }
+ *
+ * @throws TransformException should never happen since this test does not
transform coordinates.
+ */
+ @Test
+ public void testAxesCausingExpansion() throws TransformException {
+ final GeneralEnvelope domainOfValidity = new
GeneralEnvelope(HardCodedCRS.WGS84);
+ domainOfValidity.setRange(0, 5, 345);
+ domainOfValidity.setRange(1, -70, +70);
+
+ final GeneralEnvelope areaOfInterest = new
GeneralEnvelope(HardCodedCRS.WGS84);
+ areaOfInterest.setRange(0, -30, 40);
+ areaOfInterest.setRange(1, -60, 60);
+
+ final GeneralEnvelope expected = new
GeneralEnvelope(HardCodedCRS.WGS84);
+ expected.setRange(0, -30, 400);
+ expected.setRange(1, -60, 60);
+
+ final Envelope actual = adjustWraparoundAxes(areaOfInterest,
domainOfValidity, null);
+ assertEnvelopeEquals(expected, actual);
+ }
+}
diff --git
a/core/sis-referencing/src/test/java/org/apache/sis/test/suite/ReferencingTestSuite.java
b/core/sis-referencing/src/test/java/org/apache/sis/test/suite/ReferencingTestSuite.java
index 8cd71a7..5494363 100644
---
a/core/sis-referencing/src/test/java/org/apache/sis/test/suite/ReferencingTestSuite.java
+++
b/core/sis-referencing/src/test/java/org/apache/sis/test/suite/ReferencingTestSuite.java
@@ -36,6 +36,7 @@ import org.junit.BeforeClass;
org.apache.sis.internal.referencing.j2d.ShapeUtilitiesTest.class,
org.apache.sis.internal.referencing.PositionalAccuracyConstantTest.class,
org.apache.sis.internal.referencing.ReferencingUtilitiesTest.class,
+ org.apache.sis.internal.referencing.WraparoundAdjustmentTest.class,
org.apache.sis.internal.referencing.WKTUtilitiesTest.class,
org.apache.sis.internal.jaxb.referencing.CodeTest.class,
org.apache.sis.internal.jaxb.referencing.SecondDefiningParameterTest.class,