This is an automated email from the ASF dual-hosted git repository.
asf-gitbox-commits 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 2fb91f995e Stronger consolidation of the paths by which a geometry can
be transformed. This consolidation allows us to add an optional verification of
transform domain.
2fb91f995e is described below
commit 2fb91f995ecb91c43aa1f118fe294fa7474c5765
Author: Martin Desruisseaux <[email protected]>
AuthorDate: Fri Oct 30 19:19:31 2026 +0100
Stronger consolidation of the paths by which a geometry can be transformed.
This consolidation allows us to add an optional verification of transform
domain.
---
.../org/apache/sis/feature/internal/Resources.java | 5 +
.../sis/feature/internal/Resources.properties | 1 +
.../sis/feature/internal/Resources_fr.properties | 1 +
.../apache/sis/filter/BinaryGeometryFilter.java | 7 +-
.../org/apache/sis/filter/sqlmm/ST_Transform.java | 2 +-
.../org/apache/sis/filter/sqlmm/TwoGeometries.java | 7 +-
.../sis/geometry/wrapper/GeometryWrapper.java | 115 ++++++-------
.../geometry/wrapper/SpatialOperationContext.java | 177 ++++++++++-----------
.../geometry/wrapper/jts/GeometryTransform.java | 2 +
.../org/apache/sis/geometry/wrapper/jts/JTS.java | 4 +-
.../apache/sis/geometry/wrapper/jts/Wrapper.java | 19 ++-
.../operation/AbstractCoordinateOperation.java | 5 +-
.../operation/projection/AuthalicMercator.java | 5 +-
.../referencing/operation/projection/Mercator.java | 8 +-
.../operation/projection/NormalizedProjection.java | 42 ++---
.../operation/projection/TransverseMercator.java | 10 +-
.../operation/projection/ZonedGridSystem.java | 8 +-
.../operation/transform/AbstractMathTransform.java | 12 +-
.../operation/transform/DomainDefinition.java | 69 ++++++--
.../operation/transform/MathTransforms.java | 8 +-
.../operation/transform/package-info.java | 4 +-
.../org/apache/sis/geometries/adapter/Wrapper.java | 31 +---
22 files changed, 292 insertions(+), 250 deletions(-)
diff --git
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/internal/Resources.java
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/internal/Resources.java
index 9981631559..cbe77b8165 100644
---
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/internal/Resources.java
+++
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/internal/Resources.java
@@ -198,6 +198,11 @@ public class Resources extends IndexedResourceBundle {
*/
public static final short EmptyTileOrImageRegion = 20;
+ /**
+ * The geometry extends into a region where the “{0}” operation may
not behave correctly.
+ */
+ public static final short GeometryOutOfTransformDomain_1 = 97;
+
/**
* Grid coordinates ({3}) are outside the coverage domain. The
coordinate in the {0} dimension
* shall be between {1,number} and {2,number} inclusive.
diff --git
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/internal/Resources.properties
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/internal/Resources.properties
index c7aa83a018..cee881ca6f 100644
---
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/internal/Resources.properties
+++
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/internal/Resources.properties
@@ -45,6 +45,7 @@ DependencyNotFound_3 = Operation \u201c{0}\u201d
requires a \u201c{
DuplicatedSampleDimensionIndex_1 = Sample dimension index {0} is duplicated.
EmptyImage = Image has zero pixel.
EmptyTileOrImageRegion = Empty tile or image region.
+GeometryOutOfTransformDomain_1 = The geometry extends into a region where
the \u201c{0}\u201d operation may not behave correctly.
GridCoordinateOutsideCoverage_4 = Grid coordinates ({3}) are outside the
coverage domain. The coordinate in the {0} dimension shall be between
{1,number} and {2,number} inclusive.
GridEnvelopeMustBeNDimensional_1 = The grid envelope must have at least {0}
dimensions.
GridExtentsAreDisjoint_5 = The specified grid extent is outside the
domain. Indices [{3,number} \u2026 {4,number}] specified in dimension {0} do
not intersect the [{1,number} \u2026 {2,number}] grid extent.
diff --git
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/internal/Resources_fr.properties
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/internal/Resources_fr.properties
index d3d959c150..838e0a9270 100644
---
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/internal/Resources_fr.properties
+++
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/internal/Resources_fr.properties
@@ -50,6 +50,7 @@ DependencyNotFound_3 = L\u2019op\u00e9ration
\u00ab\u202f{0}\u202f\
DuplicatedSampleDimensionIndex_1 = L\u2019index de dimension
d\u2019\u00e9chantillonnage {0} est r\u00e9p\u00e9t\u00e9.
EmptyImage = L\u2019image a z\u00e9ro pixel.
EmptyTileOrImageRegion = La tuile ou la r\u00e9gion de l\u2019image
est vide.
+GeometryOutOfTransformDomain_1 = La g\u00e9om\u00e9trie s\u2019\u00e9tend
jusque dans une r\u00e9gion o\u00f9 l\u2019op\u00e9ration
\u00ab\u202f{0}\u202f\u00bb risque de ne pas se comporter correctement.
GridCoordinateOutsideCoverage_4 = Les coordonn\u00e9es de grille ({3}) sont
en dehors du domaine de la grille. La coordonn\u00e9e de la dimension {0} doit
\u00eatre entre {1,number} et {2,number} inclusivement.
GridEnvelopeMustBeNDimensional_1 = L\u2019enveloppe de la grille doit avoir
au moins {0} dimensions.
GridExtentsAreDisjoint_5 = L\u2019\u00e9tendue de grille
sp\u00e9cifi\u00e9e est en dehors du domaine. Les indices [{3,number} \u2026
{4,number}] qui ont \u00e9t\u00e9 sp\u00e9cifi\u00e9es dans la dimension {0}
n\u2019interceptent pas l\u2019\u00e9tendue [{1,number} \u2026 {2,number}] de
la grille.
diff --git
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/BinaryGeometryFilter.java
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/BinaryGeometryFilter.java
index 6411aaf762..1dd43d7fa3 100644
---
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/BinaryGeometryFilter.java
+++
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/BinaryGeometryFilter.java
@@ -18,6 +18,8 @@ package org.apache.sis.filter;
import java.util.List;
import javax.measure.Unit;
+import org.opengis.util.FactoryException;
+import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.operation.TransformException;
import org.apache.sis.geometry.wrapper.Geometries;
import org.apache.sis.geometry.wrapper.GeometryWrapper;
@@ -216,13 +218,14 @@ abstract class BinaryGeometryFilter<R> extends Node
implements SpatialOperator<R
*/
final GeometryWrapper geometry = wrapper.apply(null);
if (geometry != null) try {
- final GeometryWrapper transformed =
geometry.transform(optimization.findExpectedCRS(other).orElse(null));
+ final CoordinateReferenceSystem crs =
optimization.findExpectedCRS(other).orElse(null);
+ final GeometryWrapper transformed = geometry.transform(crs,
true);
if (geometry != transformed) {
literal = Optimization.literal(transformed);
if (literal == effective1) effective1 = literal;
else effective2 = literal;
}
- } catch (PropertyNotFoundException | TransformException e) {
+ } catch (PropertyNotFoundException | FactoryException |
TransformException e) {
optimization.warning(e, false);
}
/*
diff --git
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/ST_Transform.java
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/ST_Transform.java
index 2f01362a58..b399f6e4cf 100644
---
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/ST_Transform.java
+++
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/ST_Transform.java
@@ -130,7 +130,7 @@ final class ST_Transform<R> extends FunctionWithSRID<R> {
final GeometryWrapper value = geometry.apply(input);
if (value != null) try {
// Note: `transform(…)` does nothing if the CRS is null.
- return
getGeometryLibrary().getGeometry(value.transform(getTargetCRS(input)));
+ return
getGeometryLibrary().getGeometry(value.transform(getTargetCRS(input), false));
} catch (BackingStoreException e) {
final Throwable cause = e.getCause();
warning((cause instanceof Exception) ? (Exception) cause : e);
diff --git
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/TwoGeometries.java
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/TwoGeometries.java
index a42060b489..fee6aabe35 100644
---
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/TwoGeometries.java
+++
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/TwoGeometries.java
@@ -17,6 +17,8 @@
package org.apache.sis.filter.sqlmm;
import java.util.List;
+import org.opengis.util.FactoryException;
+import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.operation.TransformException;
import org.apache.sis.filter.Optimization;
import org.apache.sis.geometry.wrapper.Geometries;
@@ -77,14 +79,15 @@ class TwoGeometries<R> extends SpatialFunction<R> {
if (unwrap(geometry2) instanceof Literal<?,?>) {
final GeometryWrapper literal = geometry2.apply(null);
if (literal != null) try {
- final GeometryWrapper transformed =
literal.transform(optimization.findExpectedCRS(unwrap(geometry1)).orElse(null));
+ final CoordinateReferenceSystem crs =
optimization.findExpectedCRS(unwrap(geometry1)).orElse(null);
+ final GeometryWrapper transformed = literal.transform(crs,
true);
if (transformed != literal) {
@SuppressWarnings({"unchecked","rawtypes"})
final Expression<R,?>[] effective =
getParameters().toArray(Expression[]::new);
effective[1] = Optimization.literal(transformed);
return recreate(effective);
}
- } catch (PropertyNotFoundException | TransformException e) {
+ } catch (PropertyNotFoundException | FactoryException |
TransformException e) {
optimization.warning(e, false);
}
}
diff --git
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/geometry/wrapper/GeometryWrapper.java
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/geometry/wrapper/GeometryWrapper.java
index fa9ab844a7..dfa4641402 100644
---
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/geometry/wrapper/GeometryWrapper.java
+++
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/geometry/wrapper/GeometryWrapper.java
@@ -30,12 +30,15 @@ import
org.opengis.referencing.operation.CoordinateOperation;
import org.opengis.referencing.operation.MathTransform;
import org.opengis.referencing.operation.TransformException;
import org.opengis.util.FactoryException;
+import org.apache.sis.geometry.AbstractEnvelope;
import org.apache.sis.geometry.GeneralEnvelope;
+import org.apache.sis.feature.internal.Resources;
import org.apache.sis.filter.sqlmm.SQLMM;
import org.apache.sis.referencing.CRS;
-import org.apache.sis.referencing.operation.transform.MathTransforms;
+import org.apache.sis.referencing.operation.transform.DomainDefinition;
+import org.apache.sis.referencing.operation.transform.AbstractMathTransform;
import
org.apache.sis.referencing.operation.transform.TransformFactoryException;
-import org.apache.sis.referencing.internal.shared.ReferencingUtilities;
+import
org.apache.sis.referencing.operation.transform.GeometryTransformException;
import org.apache.sis.metadata.iso.extent.DefaultGeographicBoundingBox;
import org.apache.sis.util.ArgumentChecks;
import org.apache.sis.util.Classes;
@@ -70,22 +73,10 @@ import org.opengis.coordinate.MismatchedDimensionException;
* @see Geometries#wrap(Object)
*/
public abstract class GeometryWrapper extends AbstractGeometry implements
Geometry {
- /**
- * Whether to compare the <abbr>CRS</abbr> of the geometry with the source
<abbr>CRS</abbr>
- * of the coordinate operation. For performance reasons, this flag should
be {@code false}
- * when the caller already knows that these <abbr>CRS</abbr>s are
equivalent.
- *
- * <p>This is fixed to {@code false} for now because all callers made the
above-cited check
- * in the current version of Apache <abbr>SIS</abbr>. But this flag may
become an option in
- * the future if transforms become used in different contexts.</p>
- */
- protected static final boolean VERIFY_GEOMETRY_CRS_BEFORE_OPERATION =
false;
-
/**
* The coordinate reference system, or {@code null} if unspecified.
* The value of this field should be set by subclass constructors.
*
- * <h4>Where this value come from</h4>
* This information is sometime redundant with information stored in the
geometry itself.
* However, even when the geometry library supports some kind of
<abbr>SRID</abbr> field,
* the value in geometry instances is sometime 0 or null. In such case,
this {@code crs}
@@ -273,9 +264,10 @@ public abstract class GeometryWrapper extends
AbstractGeometry implements Geomet
throws FactoryException, TransformException,
IncommensurableException
{
final var geometries = new GeometryWrapper[] {this, other};
- if (context.transform(geometries)) {
+ final var commonUnit = new Unit<?>[1];
+ if (context.transform(geometries, commonUnit)) {
double dv = distance.getValue().doubleValue();
- final Unit<?> unit =
ReferencingUtilities.getUnit(context.commonCRS);
+ final Unit<?> unit = commonUnit[0];
if (unit != null) {
dv = distance.getUnit().getConverterToAny(unit).convert(dv);
}
@@ -310,7 +302,7 @@ public abstract class GeometryWrapper extends
AbstractGeometry implements Geomet
throws FactoryException, TransformException,
IncommensurableException
{
final var geometries = new GeometryWrapper[] {this, other};
- if (context.transform(geometries)) {
+ if (context.transform(geometries, null)) {
return geometries[0].predicateSameCRS(type, geometries[1]);
}
/*
@@ -503,47 +495,20 @@ public abstract class GeometryWrapper extends
AbstractGeometry implements Geomet
throw new
UnconvertibleObjectException(Errors.format(Errors.Keys.CanNotConvertFromType_2,
geometry.getClass(), type));
}
- /**
- * Transforms this geometry using the given coordinate operation.
- * The caller is responsible to ensure that the source <abbr>CRS</abbr> of
the given {@code operation}
- * is equivalent to {@link #getCoordinateReferenceSystem()}. This is not
verified by this method.
- *
- * <p>This method is preferred to the {@link
#transform(CoordinateReferenceSystem)} method
- * because not all geometry libraries store the <abbr>CRS</abbr> of their
objects.</p>
- *
- * @param operation the coordinate operation to apply.
- * @return the transformed geometry (may be the same geometry instance,
but never {@code null}).
- * @throws UnsupportedOperationException if this operation is not
supported for current implementation.
- * @throws FactoryException if a problem happened while building a
<abbr>CRS</abbr> from a <abbr>SRID</abbr>.
- * @throws TransformException if the geometry cannot be transformed.
- * @throws BackingStoreException if the operation failed because of
another checked exception.
- */
- protected GeometryWrapper transform(final CoordinateOperation operation)
- throws FactoryException, TransformException
- {
- MathTransform transform = operation.getMathTransform();
- if (VERIFY_GEOMETRY_CRS_BEFORE_OPERATION && crs != null) {
- CoordinateOperation step = CRS.findOperation(crs,
operation.getSourceCRS(), null);
- transform = MathTransforms.concatenate(step.getMathTransform(),
transform);
- }
- final GeometryWrapper wrapper = transform(transform);
- wrapper.setCoordinateReferenceSystem(operation.getTargetCRS());
- return wrapper;
- }
-
/**
* Transforms this geometry using the given transform.
- * If the transform is identity, then the geometry is returned unchanged.
- * Otherwise, a new geometry is returned without <abbr>CRS</abbr>.
+ * If the transform is identity, then the geometry is returned unchanged
(with the same <abbr>CRS</abbr>).
+ * Otherwise, a new geometry is returned with the given target
<abbr>CRS</abbr>.
*
* @param transform the math transform to apply.
- * @return the transformed geometry (may be the same geometry instance,
but never {@code null}).
+ * @param targetCRS <abbr>CRS</abbr> to set on the returned geometry, or
{@code null} if none.
+ * @return the transformed geometry (may be the same geometry wrapper
instance, but never {@code null}).
* @throws UnsupportedOperationException if this operation is not
supported for current implementation.
* @throws TransformException if the geometry cannot be transformed.
- * @throws FactoryException should never happen (declared because some
constructors declare this exception).
+ * @throws FactoryException if a problem happened while mapping a
<abbr>CRS</abbr> from/to a <abbr>SRID</abbr>.
* @throws BackingStoreException if the operation failed because of
another checked exception.
*/
- protected GeometryWrapper transform(final MathTransform transform)
+ protected GeometryWrapper transform(final MathTransform transform, final
CoordinateReferenceSystem targetCRS)
throws FactoryException, TransformException
{
if (transform.isIdentity()) {
@@ -556,6 +521,8 @@ public abstract class GeometryWrapper extends
AbstractGeometry implements Geomet
* Transforms this geometry to the specified Coordinate Reference System
(<abbr>CRS</abbr>).
* If the given <abbr>CRS</abbr> is {@code null} or the same as the
current <abbr>CRS</abbr>,
* or if the geometry has no <abbr>CRS</abbr>, then this wrapper is
returned unchanged.
+ * In the latter case, the geometry <abbr>CRS</abbr> is unchanged (not
replaced by {@code targetCRS})
+ * in order to avoid modifying an existing instance potentially used
elsewhere.
*
* <p>Consider using {@link
SpatialOperationContext#transform(GeometryWrapper)} instead of this method,
* for performance reasons, accuracy reasons (domain of validity) and
because not all geometry libraries
@@ -572,7 +539,40 @@ public abstract class GeometryWrapper extends
AbstractGeometry implements Geomet
*/
@Override
public final GeometryWrapper transform(final CoordinateReferenceSystem
targetCRS) throws TransformException {
+ try {
+ return transform(targetCRS, false);
+ } catch (FactoryException e) {
+ throw new TransformFactoryException(e);
+ }
+ }
+
+ /**
+ * Transforms this geometry to the specified <abbr>CRS</abbr> with an
optional check of the domain of validity.
+ * If the given <abbr>CRS</abbr> is {@code null} or the same as the
current <abbr>CRS</abbr>,
+ * or if the geometry has no <abbr>CRS</abbr>, then this wrapper is
returned unchanged.
+ * In the latter case, the geometry <abbr>CRS</abbr> is unchanged (not
replaced by {@code targetCRS})
+ * in order to avoid modifying an existing instance potentially used
elsewhere.
+ *
+ * <p>Consider using {@link
SpatialOperationContext#transform(GeometryWrapper)} instead of this method,
+ * for performance reasons, accuracy reasons (domain of validity) and
because not all geometry libraries
+ * store information about the <abbr>CRS</abbr> in their geometric
objects.</p>
+ *
+ * @param targetCRS the target coordinate reference system, or {@code
null}.
+ * @param verifyDomain whether to verify the transform domain of
validity.
+ * @return the transformed geometry (may be the same geometry but never
{@code null}).
+ * @throws UnsupportedOperationException if this operation is not
supported for current implementation.
+ * @throws FactoryException if an error occured while creating the
coordinate operation.
+ * @throws TransformException if the given geometry has no
<abbr>CRS</abbr> or cannot be transformed.
+ * @throws BackingStoreException if the operation failed because of
another checked exception.
+ *
+ * @see #getCoordinateReferenceSystem()
+ * @see SpatialOperationContext#transform(GeometryWrapper)
+ */
+ public final GeometryWrapper transform(final CoordinateReferenceSystem
targetCRS, final boolean verifyDomain)
+ throws FactoryException, TransformException
+ {
if (targetCRS == null || targetCRS == crs || crs == null) {
+ // Do not invoke `setCoordinateReferenceSystem(targetCRS)` — see
above Javadoc.
return this;
}
DefaultGeographicBoundingBox bbox = null;
@@ -586,11 +586,18 @@ public abstract class GeometryWrapper extends
AbstractGeometry implements Geomet
Logging.ignorableException(Geometries.LOGGER,
GeometryWrapper.class, "transform", ex);
}
}
- try {
- return transform(CRS.findOperation(crs, targetCRS, bbox));
- } catch (FactoryException e) {
- throw new TransformFactoryException(e);
+ final CoordinateOperation operation = CRS.findOperation(crs,
targetCRS, bbox);
+ final MathTransform transform = operation.getMathTransform();
+ if (verifyDomain && transform instanceof AbstractMathTransform) {
+ final var atr = (AbstractMathTransform) transform;
+ final var builder = new DomainDefinition();
+ builder.canApproachAsymptotes(true);
+ final AbstractEnvelope domain =
AbstractEnvelope.castOrCopy(atr.getDomain(builder).orElse(null));
+ if (domain != null && !domain.contains(getEnvelope())) {
+ throw new
GeometryTransformException(Resources.format(Resources.Keys.GeometryOutOfTransformDomain_1,
operation.getName().getCode()));
+ }
}
+ return transform(transform, operation.getTargetCRS());
}
/**
diff --git
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/geometry/wrapper/SpatialOperationContext.java
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/geometry/wrapper/SpatialOperationContext.java
index 18d18c930d..aa4e108deb 100644
---
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/geometry/wrapper/SpatialOperationContext.java
+++
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/geometry/wrapper/SpatialOperationContext.java
@@ -32,6 +32,8 @@ import org.opengis.referencing.crs.ProjectedCRS;
import org.opengis.referencing.crs.GeographicCRS;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.operation.OperationMethod;
+import org.opengis.referencing.operation.CoordinateOperation;
+import org.opengis.referencing.operation.MathTransform;
import org.opengis.referencing.operation.TransformException;
import org.apache.sis.referencing.ImmutableIdentifier;
import org.apache.sis.referencing.CRS;
@@ -86,8 +88,8 @@ public final class SpatialOperationContext implements
Serializable {
/**
* If the CRS needs to be in some units of measurement, the {@link
Unit#getSystemUnit()} value.
- * For example, is units need to be linear, then {@code systemUnit} shall
be {@link Units#METRE}.
- * Note that it does not mean that the units of measurement must be
meters; only that they must
+ * For example, if units need to be linear, then {@code systemUnit} shall
be {@link Units#METRE}.
+ * Note that it does not mean that the units of measurement must be
meters, only that they must
* be compatible with meters.
*/
@SuppressWarnings("serial") // Most SIS implementations are
serializable.
@@ -99,12 +101,6 @@ public final class SpatialOperationContext implements
Serializable {
*/
private final int skipIndex;
- /**
- * The common CRS found by {@link #transform(GeometryWrapper[])}. May be
null.
- */
- @SuppressWarnings("serial") // Most SIS implementations are
serializable.
- CoordinateReferenceSystem commonCRS;
-
/**
* Creates a new context.
*
@@ -151,8 +147,7 @@ public final class SpatialOperationContext implements
Serializable {
/**
* Transforms the specified geometry to the computation <abbr>CRS</abbr>.
- * Geometries with unspecified CRS will be used as-is, without
transformation.
- * The common CRS is stored in the {@link #commonCRS} field.
+ * Geometries with unspecified <abbr>CRS</abbr> will be used as-is,
without transformation.
*
* @param geometry the geometry to transform.
* @return the transformed geometry, or the same instance if no
transformation was applied.
@@ -163,7 +158,9 @@ public final class SpatialOperationContext implements
Serializable {
if (computationCRS != null) {
final CoordinateReferenceSystem sourceCRS =
to2D(geometry.getCoordinateReferenceSystem());
if (sourceCRS != null && !CRS.equivalent(computationCRS,
sourceCRS)) {
- geometry = geometry.transform(CRS.findOperation(sourceCRS,
computationCRS, areaOfInterest));
+ final CoordinateOperation operation =
CRS.findOperation(sourceCRS, computationCRS, areaOfInterest);
+ final MathTransform transform = operation.getMathTransform();
+ geometry = geometry.transform(transform,
operation.getTargetCRS());
}
}
return geometry;
@@ -172,107 +169,105 @@ public final class SpatialOperationContext implements
Serializable {
/**
* Transforms the specified geometries to the common <abbr>CRS</abbr>.
* If {@link #computationCRS} is {@code null}, then this method tries to
infer one automatically.
- * Geometries with unspecified CRS will be used as-is, without
transformation.
- * The common CRS is stored in the {@link #commonCRS} field.
+ * Geometries with unspecified <abbr>CRS</abbr> will be used as-is,
without coordinate operation.
+ * The unit of measurement is stored in the {@code commonUnit} array if
non-null.
*
* @param geometries the geometries to transform. Results will be stored
in-place.
+ * @param commonUnit where to store the unit of measurement of the
common CRS, or {@code null}.
* @return whether this method has been able to find a common CRS.
* @throws FactoryException if an error occurred while fetching a CRS.
* @throws TransformException if a coordinate conversion was required but
failed.
* @throws IncommensurableException if a geographic CRS does not use
angular units (should not happen).
*/
- final boolean transform(final GeometryWrapper[] geometries)
+ final boolean transform(final GeometryWrapper[] geometries, final
Unit<?>[] commonUnit)
throws FactoryException, TransformException,
IncommensurableException
{
- final CoordinateReferenceSystem[] allCRS = new
CoordinateReferenceSystem[geometries.length];
- try {
- for (int i = allCRS.length; --i >= 0;) {
- allCRS[i] = (i == skipIndex) ? computationCRS :
to2D(geometries[i].getCoordinateReferenceSystem());
- }
- } catch (BackingStoreException e) {
- throw e.unwrapOrRethrow(FactoryException.class);
- }
- /*
- * Search for an arbitrary non-null CRS. Then check in the next loop
if all other CRS are equal,
- * ignoring metadata and ignoring null CRS. If this is the case, then
we do not transform anything.
- */
- {
- int i = allCRS.length;
- do if (--i < 0) return true;
- while ((commonCRS = allCRS[i]) == null);
- /*
- * Found a CRS potentially common to all geometries (this will be
checked in next loop).
- * But if units of measurement are restricted to some kind, we
will accept that common CRS
- * only if its unit is of the requested type.
- */
- boolean reject = false;
- if (systemUnit != null && commonCRS != computationCRS) {
- reject = isCompatibleUnit(commonCRS, systemUnit);
- }
- if (!reject) {
- // Potential common CRS accepted, verify if all other
geometries use the same CRS.
- CoordinateReferenceSystem crs;
- do {
- if (--i < 0) return true; // All geometries are in
common CRS.
- crs = allCRS[i];
- } while (crs == null || CRS.equivalent(commonCRS, crs));
+ CoordinateReferenceSystem commonCRS = null;
+apply: {
+ final var allCRS = new
CoordinateReferenceSystem[geometries.length];
+ try {
+ for (int i = allCRS.length; --i >= 0;) {
+ allCRS[i] = (i == skipIndex) ? computationCRS :
to2D(geometries[i].getCoordinateReferenceSystem());
+ }
+ int i = allCRS.length;
+ do if (--i < 0) break apply;
+ while ((commonCRS = allCRS[i]) == null);
+ /*
+ * Found an arbitrary non-null CRS. If that CRS is compatible
with `systemUnit`,
+ * then verify if all other geometries use the same CRS. If
yes, we are done (exit).
+ * Otherwise, continue with the rest of this method for
searching another common CRS.
+ */
+ if (systemUnit == null || commonCRS == computationCRS ||
isCompatibleUnit(commonCRS, systemUnit)) {
+ CoordinateReferenceSystem crs;
+ do {
+ if (--i < 0) break apply; // All geometries are in
common CRS.
+ crs = allCRS[i];
+ } while (crs == null || CRS.equivalent(commonCRS, crs));
+ }
+ } catch (BackingStoreException e) {
+ throw e.unwrapOrRethrow(FactoryException.class);
}
- }
- /*
- * At least one geometry needs to be transformed. Get a CRS which can
be a common target
- * for the specified geometries. The `if` block should be executed
only if both operands
- * are dynamic (non-literal) values. The rules applied inside the
block are arbitrary
- * and may change in any future version.
- */
- commonCRS = computationCRS;
-select: if (commonCRS == null) {
/*
- * If there is a restriction on the unit of measurement, check if
an existing CRS
- * met that criterion. We do this check before invoking
`suggestCommonTarget(…)`
- * because that method may replace `ProjectedCRS` by
`GeographicCRS` in order to
- * cover a larger area, and we usually want the `ProjectedCRS`.
+ * At least one geometry needs to be transformed. Get a CRS which
can be a common target
+ * for the specified geometries. The `if` block should be executed
only if both operands
+ * are dynamic (non-literal) values. The rules applied inside the
block are arbitrary
+ * and may change in any future version.
*/
- if (systemUnit != null) {
- for (int i=allCRS.length; --i >= 0;) {
- commonCRS = allCRS[i];
- final Unit<?> unit =
ReferencingUtilities.getUnit(commonCRS);
- if (unit != null &&
systemUnit.equals(unit.getSystemUnit())) {
- break select; // Use the `commonCRS` we just
found.
+ commonCRS = computationCRS;
+select: if (commonCRS == null) {
+ /*
+ * If there is a restriction on the unit of measurement, check
if an existing CRS
+ * met that criterion. We do this check before invoking
`suggestCommonTarget(…)`
+ * because that method may replace `ProjectedCRS` by
`GeographicCRS` in order to
+ * cover a larger area, and we usually want the `ProjectedCRS`.
+ */
+ if (systemUnit != null) {
+ for (int i=allCRS.length; --i >= 0;) {
+ commonCRS = allCRS[i];
+ final Unit<?> unit =
ReferencingUtilities.getUnit(commonCRS);
+ if (unit != null &&
systemUnit.equals(unit.getSystemUnit())) {
+ break select; // Use the `commonCRS` we just
found.
+ }
}
}
+ /*
+ * If there are no restrictions on units of measurement, or if
no geometry CRS met that restriction,
+ * request a CRS which may be different than the CRS of all
geometries. The search takes in account
+ * the CRS domains of validity. The CRS found may be derived
in order to be made compatible with the
+ * desired units of measurement.
+ */
+ commonCRS = CRS.suggestCommonTarget(areaOfInterest, allCRS);
+ if (commonCRS == null) {
+ return false; // Geometries are in incompatible
CRS.
+ }
+ if (systemUnit != null) {
+ for (int i = allCRS.length; --i >= 0;) {
+ final CoordinateReferenceSystem crs = allCRS[i];
+ if (crs != null) {
+ commonCRS = usingSystemUnit(geometries[i], crs,
commonCRS, systemUnit);
+ break select;
+ }
+ }
+ return false; // Unable to use the desired units
of measurement.
+ }
}
/*
- * If there are no restrictions on units of measurement, or if no
geometry CRS met that restriction,
- * request a CRS which may be different than the CRS of all
geometries. The search takes in account
- * the CRS domains of validity. The CRS found may be derived in
order to be made compatible with the
- * desired units of measurement.
+ * At this point, `commonCRS` is the CRS to use for converting all
geometries.
+ * Perform the conversions in-place.
*/
- commonCRS = CRS.suggestCommonTarget(areaOfInterest, allCRS);
- if (commonCRS == null) {
- return false; // Geometries are in
incompatible CRS.
- }
- if (systemUnit != null) {
- for (int i = allCRS.length; --i >= 0;) {
- final CoordinateReferenceSystem crs = allCRS[i];
- if (crs != null) {
- commonCRS = usingSystemUnit(geometries[i], crs,
commonCRS, systemUnit);
- break select;
+ for (int i=0; i < allCRS.length; i++) {
+ if (i != skipIndex) {
+ final CoordinateReferenceSystem sourceCRS = allCRS[i];
+ if (!CRS.equivalent(commonCRS, sourceCRS)) {
+ final CoordinateOperation operation =
CRS.findOperation(sourceCRS, commonCRS, areaOfInterest);
+ final MathTransform transform =
operation.getMathTransform();
+ geometries[i] = geometries[i].transform(transform,
operation.getTargetCRS());
}
}
- return false; // Unable to use the desired units
of measurement.
}
}
- /*
- * At this point, `commonCRS` is the CRS to use for converting all
geometries.
- * Perform the conversions in-place.
- */
- for (int i=0; i < allCRS.length; i++) {
- if (i != skipIndex) {
- final CoordinateReferenceSystem sourceCRS = allCRS[i];
- if (!CRS.equivalent(commonCRS, sourceCRS)) {
- geometries[i] =
geometries[i].transform(CRS.findOperation(sourceCRS, commonCRS,
areaOfInterest));
- }
- }
+ if (commonUnit != null) {
+ commonUnit[0] = ReferencingUtilities.getUnit(commonCRS);
}
return true;
}
diff --git
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/geometry/wrapper/jts/GeometryTransform.java
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/geometry/wrapper/jts/GeometryTransform.java
index df0b24a0ee..0396188b6b 100644
---
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/geometry/wrapper/jts/GeometryTransform.java
+++
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/geometry/wrapper/jts/GeometryTransform.java
@@ -205,6 +205,8 @@ public abstract class GeometryTransform {
/**
* Transforms the given sequence of coordinate tuples, producing a new
sequence of tuples.
+ * Note that the {@link CoordinateSequence} interface has a {@code
setOrdinate(…)} method,
+ * so it may be necessary to copy the coordinate values even for an
identity transform.
*
* @param sequence sequence of coordinate tuples to transform.
* @param minPoints minimum number of points to preserve.
diff --git
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/geometry/wrapper/jts/JTS.java
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/geometry/wrapper/jts/JTS.java
index 487ee3ac60..770fa2d0cf 100644
---
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/geometry/wrapper/jts/JTS.java
+++
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/geometry/wrapper/jts/JTS.java
@@ -158,6 +158,7 @@ public final class JTS {
/**
* Transforms the given geometry using the given math transform.
* If the geometry or the transform is null or identity, then the geometry
is returned unchanged.
+ * The <abbr>CRS</abbr> of the geometry is unchanged, caller may need to
set that <abbr>CRS</abbr>.
*
* @param geometry the geometry to transform, or {@code null}.
* @param transform the transform to apply, or {@code null}.
@@ -166,8 +167,7 @@ public final class JTS {
*/
public static Geometry transform(Geometry geometry, final MathTransform
transform) throws TransformException {
if (geometry != null && transform != null && !transform.isIdentity()) {
- final var gct = new GeometryCoordinateTransform(transform,
geometry.getFactory());
- geometry = gct.transform(geometry);
+ geometry = new GeometryCoordinateTransform(transform,
geometry.getFactory()).transform(geometry);
}
return geometry;
}
diff --git
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/geometry/wrapper/jts/Wrapper.java
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/geometry/wrapper/jts/Wrapper.java
index d18d28329b..c3b24feff0 100644
---
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/geometry/wrapper/jts/Wrapper.java
+++
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/geometry/wrapper/jts/Wrapper.java
@@ -701,17 +701,24 @@ add: for (Geometry next = geometry;;) {
/**
* Transforms this geometry using the given transform.
- * If the transform is {@code null}, then the geometry is returned
unchanged.
- * Otherwise, a new geometry is returned without CRS.
+ * If the transform is {@code null} or identity, this wrapper is returned
unchanged (same <abbr>CRS</abbr>).
+ * Otherwise, a new geometry is returned without <abbr>CRS</abbr>.
*
* @param transform the math transform to apply, or {@code null}.
- * @return the transformed geometry (may be the same geometry instance,
but never {@code null}).
- * @throws FactoryException should never happen (declared because the
constructor declares this exception).
+ * @param targetCRS <abbr>CRS</abbr> to set on the returned geometry, or
{@code null} if none.
+ * @return the transformed geometry (may be the same geometry wrapper
instance, but never {@code null}).
+ * @throws FactoryException if a problem happened while mapping a
<abbr>CRS</abbr> from/to a <abbr>SRID</abbr>.
* @throws TransformException if the geometry cannot be transformed.
*/
@Override
- protected GeometryWrapper transform(final MathTransform transform) throws
FactoryException, TransformException {
- return rewrap(JTS.transform(geometry, transform));
+ protected GeometryWrapper transform(final MathTransform transform, final
CoordinateReferenceSystem targetCRS)
+ throws FactoryException, TransformException
+ {
+ final Wrapper result = rewrap(JTS.transform(geometry, transform));
+ if (targetCRS != null && result != this) {
+ result.setCoordinateReferenceSystem(targetCRS);
+ }
+ return result;
}
/**
diff --git
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/AbstractCoordinateOperation.java
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/AbstractCoordinateOperation.java
index 5b2c3a7256..f75668a0a3 100644
---
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/AbstractCoordinateOperation.java
+++
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/AbstractCoordinateOperation.java
@@ -94,7 +94,6 @@ import org.opengis.coordinate.CoordinateSet;
* <li>An estimation of the {@linkplain #getCoordinateOperationAccuracy()
operation accuracy}.</li>
* </ul>
*
- * <h2>Instantiation</h2>
* This class is conceptually <i>abstract</i>, even if it is technically
possible to instantiate it.
* Typical applications should create instances of the most specific subclass
prefixed by {@code Default} instead.
* An exception to this rule may occur when it is not possible to identify the
exact operation type.
@@ -103,8 +102,8 @@ import org.opengis.coordinate.CoordinateSet;
* This base class is immutable and thus thread-safe if the property
<em>values</em> (not necessarily the map itself)
* given to the constructor are also immutable. Most SIS subclasses and
related classes are immutable under similar
* conditions. This means that unless otherwise noted in the javadoc, {@code
CoordinateOperation} instances created
- * using only SIS factories and static constants can be shared by many objects
and passed between threads without
- * synchronization.
+ * using only <abbr>SIS</abbr> factories and static constants can be shared by
many objects and passed between threads
+ * without synchronization.
*
* @author Martin Desruisseaux (IRD, Geomatys)
* @version 1.7
diff --git
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/projection/AuthalicMercator.java
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/projection/AuthalicMercator.java
index d0a4560af9..ef6e7bfbef 100644
---
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/projection/AuthalicMercator.java
+++
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/projection/AuthalicMercator.java
@@ -23,7 +23,6 @@ import org.opengis.referencing.operation.Matrix;
import org.opengis.referencing.operation.TransformException;
import org.apache.sis.referencing.operation.matrix.Matrix2;
import org.apache.sis.referencing.operation.transform.DomainDefinition;
-import org.apache.sis.geometry.Envelope2D;
import static org.apache.sis.math.MathFunctions.atanh;
@@ -59,9 +58,7 @@ final class AuthalicMercator extends AuthalicConversion {
*/
@Override
public Optional<Envelope> getDomain(final DomainDefinition criteria) {
- return Optional.of(new Envelope2D(null,
- -LARGE_LONGITUDE_LIMIT, -POLAR_AREA_LIMIT,
- 2*LARGE_LONGITUDE_LIMIT, 2*POLAR_AREA_LIMIT));
+ return Optional.of(domain(criteria.canApproachAsymptotes()));
}
/**
diff --git
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/projection/Mercator.java
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/projection/Mercator.java
index 968c7b1f68..45808405c7 100644
---
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/projection/Mercator.java
+++
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/projection/Mercator.java
@@ -29,7 +29,6 @@ import org.opengis.referencing.operation.Matrix;
import org.opengis.referencing.operation.MathTransform;
import org.opengis.referencing.operation.OperationMethod;
import org.opengis.referencing.operation.TransformException;
-import org.apache.sis.geometry.Envelope2D;
import org.apache.sis.referencing.operation.provider.Mercator1SP;
import org.apache.sis.referencing.operation.provider.Mercator2SP;
import org.apache.sis.referencing.operation.provider.MercatorSpherical;
@@ -366,8 +365,8 @@ subst: if (variant.spherical || eccentricity == 0) {
/**
* Returns the domain of input coordinates. For a Mercator projection
other than Miller variant,
- * the limit is arbitrarily set to 84° of latitude North and South. This
is consistent with the
- * "World Mercator" domain of validity defined by EPSG:3395, which is 80°S
to 84°N.
+ * the limit exclude polar regions by default. This is consistent with the
<q>World Mercator</q>
+ * domain of validity defined by <abbr>EPSG</abbr>:3395, which is 80°S to
84°N.
*
* <p>The range of longitude values is set to an arbitrary range larger
than −180° … +180°,
* because the Mercator projection is mathematically capable to handle
coordinates beyond that range
@@ -376,8 +375,7 @@ subst: if (variant.spherical || eccentricity == 0) {
*/
@Override
public Optional<Envelope> getDomain(final DomainDefinition criteria) {
- final double limit = (variant == Variant.MILLER) ? -PI/2 :
-POLAR_AREA_LIMIT;
- return Optional.of(new Envelope2D(null, -LARGE_LONGITUDE_LIMIT, limit,
2*LARGE_LONGITUDE_LIMIT, -2*limit));
+ return Optional.of(domain(variant == Variant.MILLER ||
criteria.canApproachAsymptotes()));
}
/**
diff --git
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/projection/NormalizedProjection.java
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/projection/NormalizedProjection.java
index 22873ea359..225aef63e6 100644
---
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/projection/NormalizedProjection.java
+++
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/projection/NormalizedProjection.java
@@ -37,6 +37,9 @@ import org.opengis.util.FactoryException;
import org.apache.sis.util.Debug;
import org.apache.sis.util.CharSequences;
import org.apache.sis.util.ComparisonMode;
+import org.apache.sis.util.internal.shared.Constants;
+import org.apache.sis.util.internal.shared.Numerics;
+import org.apache.sis.geometry.Envelope2D;
import org.apache.sis.parameter.Parameters;
import org.apache.sis.parameter.ParameterBuilder;
import org.apache.sis.metadata.iso.citation.Citations;
@@ -50,8 +53,6 @@ import
org.apache.sis.referencing.operation.transform.TransformJoiner;
import org.apache.sis.referencing.operation.provider.MapProjection;
import org.apache.sis.referencing.internal.shared.Formulas;
import org.apache.sis.system.Modules;
-import org.apache.sis.util.internal.shared.Constants;
-import org.apache.sis.util.internal.shared.Numerics;
// Specific to the geoapi-3.1 and geoapi-4.0 branches:
import org.opengis.metadata.Identifier;
@@ -164,22 +165,6 @@ public abstract class NormalizedProjection extends
AbstractMathTransform2D imple
*/
static final int MAXIMUM_ITERATIONS = Formulas.MAXIMUM_ITERATIONS;
- /**
- * Arbitrary latitude threshold (in radians) for considering that a point
is in the polar area.
- * This is used for implementations of the {@link
#getDomain(DomainDefinition)} method.
- */
- static final double POLAR_AREA_LIMIT = PI/2 * (84d/90);
-
- /**
- * An arbitrarily large longitude value (in radians) for map projections
capable to have infinite
- * extent in the east-west direction. This is the case of {@link Mercator}
projection for example.
- * Longitudes have no real world meaning outside −180° … +180° range
(unless wraparound is applied),
- * but we nevertheless accept large values without wraparound because they
make envelope projection easier.
- * This is used for implementations of the {@link
#getDomain(DomainDefinition)} method,
- * which use arbitrary limits anyway.
- */
- static final double LARGE_LONGITUDE_LIMIT = 100*PI;
-
/**
* The internal parameter descriptors. Keys are implementation classes.
Values are parameter descriptor groups
* containing at least a parameter for the {@link #eccentricity} value,
and optionally other internal parameter
@@ -680,6 +665,25 @@ public abstract class NormalizedProjection extends
AbstractMathTransform2D imple
context.getMatrix(ContextualParameters.MatrixRole.NORMALIZATION), false);
}
+ /**
+ * Returns an arbitrary world-wide domain of validity (in radians) for
Mercator-like projections.
+ * These projections can have an infinite extent in the east-west
direction and generally exclude polar regions.
+ * Longitudes have no real world meaning outside the [−180° … +180°] range
(unless wraparounds are applied),
+ * but we nevertheless accept out of bounds values because they facilitate
calculations on projected envelopes.
+ * This is used for {@link #getDomain(DomainDefinition)} implementations,
which return arbitrary limits anyway.
+ *
+ * <p>The current implementation sets a longitude range of ±200° (i.e. the
world plus a margin)
+ * and a latitude range from 84°S to 84°N by default.
+ * These limits are arbitrary and may change in any future versions.</p>
+ *
+ * @param includePoles whether to extent to range of latitude values to
±90°.
+ * @return proposed value to return in {@link
#getDomain(DomainDefinition)} implementations.
+ */
+ static Envelope2D domain(final boolean includePoles) {
+ final double limit = includePoles ? -PI/2 : -PI/2 * (84d/90);
+ return new Envelope2D(null, -100*PI, limit, 2*100*PI, -2*limit);
+ }
+
/*
* TODO: consider adding a sqrt1ms(x) method for sqrt(1 - x*x), which
could be implemented as sqrt(fma(x, -x, 1)).
* The use of Math.fma(…) in this context would be valuable especially
when x is close to 1 (to be verified).
@@ -916,7 +920,7 @@ public abstract class NormalizedProjection extends
AbstractMathTransform2D imple
if (!super.equals(object, mode)) {
return false;
}
- final NormalizedProjection that = (NormalizedProjection) object;
+ final var that = (NormalizedProjection) object;
switch (mode) {
case STRICT:
case BY_CONTRACT: {
diff --git
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/projection/TransverseMercator.java
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/projection/TransverseMercator.java
index d92e5f149b..1efd6abd30 100644
---
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/projection/TransverseMercator.java
+++
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/projection/TransverseMercator.java
@@ -375,16 +375,14 @@ public class TransverseMercator extends
NormalizedProjection {
* Returns the domain of input coordinates.
* The limits defined by this method are arbitrary and may change in any
future implementation.
* Current implementation sets a limit at 40° of longitude on each side of
the central meridian
- * (this limit is mentioned in EPSG guidance notes)
- * and a limit at 84° of latitude (same as {@link Mercator} projection).
+ * (this limit is given in <abbr>EPSG</abbr> guidance notes) and a range
of latitude values which
+ * exclude polar regions by default.
*/
@Override
public Optional<Envelope> getDomain(final DomainDefinition criteria) {
- final Envelope2D domain = new Envelope2D();
+ final Envelope2D domain = domain(criteria.canApproachAsymptotes());
domain.x = -PI/2 * (40d/90);
- domain.y = -POLAR_AREA_LIMIT;
- domain.width = -2 * domain.x;
- domain.height = -2 * domain.y;
+ domain.width = -2 * domain.x;
return Optional.of(domain);
}
diff --git
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/projection/ZonedGridSystem.java
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/projection/ZonedGridSystem.java
index b9f2d4a150..181dde5a6d 100644
---
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/projection/ZonedGridSystem.java
+++
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/projection/ZonedGridSystem.java
@@ -153,12 +153,14 @@ public class ZonedGridSystem extends
AbstractMathTransform2D implements Serializ
* Returns the domain of input coordinates.
* The limits defined by this method are arbitrary and may change in any
future implementation.
* Current implementation sets a longitude range of ±180° (i.e. the world)
and a latitude range
- * from 84°S to 84°N.
+ * which excludes polar regions by default.
*/
@Override
public Optional<Envelope> getDomain(final DomainDefinition criteria) {
- final double y = -NormalizedProjection.POLAR_AREA_LIMIT;
- return Optional.of(new Envelope2D(null, -PI, y, 2*PI, -2*y));
+ final Envelope2D domain =
NormalizedProjection.domain(criteria.canApproachAsymptotes());
+ domain.x = -PI;
+ domain.width = 2*PI;
+ return Optional.of(domain);
}
/**
diff --git
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/AbstractMathTransform.java
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/AbstractMathTransform.java
index f96326aa89..c6407475f1 100644
---
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/AbstractMathTransform.java
+++
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/AbstractMathTransform.java
@@ -162,26 +162,24 @@ public abstract class AbstractMathTransform extends
FormattableObject
public abstract int getTargetDimensions();
/**
- * Returns the ranges of source coordinate values where this transform is
mathematically applicable.
+ * Returns the ranges of source coordinate values where this transform is
considered well-behaved.
* This is <em>not</em> the domain of validity for which a coordinate
reference system has been defined,
* because this method ignores real world considerations such as datum and
country boundaries.
- * This method can be used for cropping data in order to exclude areas
that may cause numerical problems.
+ * Instead this method estimates, for example, where the function is
monotonic and reasonably far from infinity.
+ *
+ * <p>This method can be used for cropping data in order to exclude areas
that may cause numerical problems.
* For example, the results of a Mercator projection tend to infinity when
the latitude values approach a pole.
* For avoiding data structures with unreasonably large values or {@link
Double#NaN} values,
* an arbitrary maximal latitude value (typically 80° or 84°) can be
enforced before projection.
* Those limits are arbitrary, the transform does not become suddenly
invalid after a limit.
* But enforcing such limits can (for example) avoid chaotic viewer
behavior when attempting
- * to render a world-wide image.
+ * to render a world-wide image.</p>
*
* <p>Many transforms, in particular affine transforms, have no
mathematical limits.
* Therefore, the default implementation returns an empty value, but it
does not mean that the associated
* {@linkplain
org.apache.sis.referencing.operation.AbstractCoordinateOperation coordinate
operation}
* has no geospatial domain of validity.</p>
*
- * <p>Callers do not need to search through {@linkplain
MathTransforms#getSteps(MathTransform) transform steps}.
- * The Apache <abbr>SIS</abbr> implementation of {@link
MathTransforms#concatenate(MathTransform, MathTransform)
- * concatenated transforms} does that automatically.</p>
- *
* @param criteria controls the definition of transform domain.
* @return estimation of ranges of source coordinate values where the
transform is numerically applicable.
* @throws TransformException if the domain cannot be estimated.
diff --git
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/DomainDefinition.java
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/DomainDefinition.java
index 2996b1a548..9d34cf0073 100644
---
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/DomainDefinition.java
+++
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/DomainDefinition.java
@@ -28,7 +28,7 @@ import org.apache.sis.geometry.GeneralEnvelope;
/**
- * Builder of the mathematical domain of validity of a {@code MathTransform}.
+ * Builder of a {@code MathTransform} domain of validity where the function is
considered well-behaved.
* Contrary to {@linkplain CRS#getDomainOfValidity(CoordinateReferenceSystem)
CRS domain of validity},
* this class estimates a domain based on mathematical behavior only, not on
real world considerations.
* For example, the Mercator projection tends to infinity when approaching
poles, so it is recommended
@@ -36,14 +36,9 @@ import org.apache.sis.geometry.GeneralEnvelope;
* This is different than the domain of validity of CRS, which is often
limited to a particular country.
* In general, the CRS domain of validity is much smaller than the domain
computed by this class.
*
- * <p>Current implementation does not yet provide ways to describe how a
domain is decided.
- * A future version may, for example, allows to specify a maximal deformation
tolerated for map projections.
- * In current implementation, the estimation can be customized by overriding
the
- * {@link #estimate(MathTransform)} or {@link #intersect(Envelope)}
methods.</p>
- *
- * <p>Each {@code DomainDefinition} instance should be used only once for an
{@link AbstractMathTransform}
- * instance, unless that transform is a chain of concatenated transforms (this
case is handled automatically
- * by Apache SIS). Usage example:</p>
+ * <p>Each {@code DomainDefinition} instance should be used only once for an
{@link AbstractMathTransform} instance,
+ * unless that transform is a chain of concatenated transforms (handled
automatically by Apache <abbr>SIS</abbr>).
+ * Usage example:</p>
*
* {@snippet lang="java" :
* AbstractMathTransform transform = …;
@@ -56,7 +51,7 @@ import org.apache.sis.geometry.GeneralEnvelope;
* when the default implementation is sufficient.
*
* @author Martin Desruisseaux (Geomatys)
- * @version 1.3
+ * @version 1.8
*
* @see MathTransforms#getDomain(MathTransform)
* @see AbstractMathTransform#getDomain(DomainDefinition)
@@ -65,6 +60,12 @@ import org.apache.sis.geometry.GeneralEnvelope;
* @since 1.3
*/
public class DomainDefinition {
+ /**
+ * Whether the domain is allowed to approach input values producing
infinite output values.
+ * By default, the domain is cropped at values considered reasonably far
from asymptotes.
+ */
+ private boolean canApproachAsymptotes;
+
/**
* Limits computed so far, or {@code null} if none.
*/
@@ -134,13 +135,57 @@ public class DomainDefinition {
public DomainDefinition() {
}
+ /**
+ * Returns whether the domain is allowed to approach input values
producing infinite output values.
+ * For example, the Mercator projection has asymptotes at 90°N and 90°S,
because the projection outputs
+ * tend toward infinity as the latitude approaches a pole.
+ * If this method returns {@code false} (the default), the domain is
cropped at an arbitrary distance
+ * from the asymptotes (for example, 84° of latitude).
+ *
+ * @return whether to allow the domain to approach asymptotes.
+ *
+ * @since 1.8
+ */
+ public boolean canApproachAsymptotes() {
+ return canApproachAsymptotes;
+ }
+
+ /**
+ * Sets whether the domain is allowed to approach input values producing
infinite output values.
+ * The default value is {@code false}, which crops the domain at some
distance from asymptotes.
+ * For example, Mercator projections may limit their domain to 84° of
latitude North and South.
+ * Setting this flag to {@code true} raises the limit (in the Mercator
projection case) to 90°.
+ *
+ * <p>A value of {@code true} does not mean that the domain will extend to
the asymptotes,
+ * only that it is <em>allowed</em> to do so. A transform may choose to
restrict the domain
+ * anyway, for example because the function is not stable that far.</p>
+ *
+ * <h4>Guidelines for approaching (or not) the asymptotes</h4>
+ * <p>The domain of validity should stay relatively far from asymptotes
({@code allowed = false})
+ * when the domain is used for cropping map bounds at rendering time. For
example, a world-wide map
+ * rendered in a Mercator projection needs to exclude polar regions when
computing the initial zoom,
+ * otherwise most of Earth's surface would be squashed by the near
infinite values of polar regions.</p>
+ *
+ * <p>It can be useful to let the domain of validity extent to infinity
({@code allowed = true})
+ * when the domain is used for {@linkplain
GeneralEnvelope#contains(Envelope) testing inclusions}.
+ * As long as the function is monotonic, operations based on comparisons
of numerical values stay
+ * generally reliable.</p>
+ *
+ * @param allowed whether to allow the domain to approach asymptotes.
+ *
+ * @since 1.8
+ */
+ public void canApproachAsymptotes(final boolean allowed) {
+ canApproachAsymptotes = allowed;
+ }
+
/**
* Estimates the domain of the given math transform and intersects it with
previously computed domains.
* The result can be obtained by a call to {@link #result()}.
*
* <p>The default implementation invokes {@link
AbstractMathTransform#getDomain(DomainDefinition)} if possible,
- * or does nothing otherwise. The domain provided by the transform is
given to {@link #intersect(Envelope)}.
- * Subclasses can override for modifying this behavior.</p>
+ * or does nothing otherwise. The transform implementation should callback
the {@link #intersect(Envelope)} method.
+ * {@code DomainDefinition} Subclasses can override for modifying the
default behavior.</p>
*
* @param evaluated the transform for which to estimate the domain.
* @throws TransformException if the domain cannot be estimated.
diff --git
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/MathTransforms.java
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/MathTransforms.java
index 3880e732a9..81f0645bb5 100644
---
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/MathTransforms.java
+++
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/MathTransforms.java
@@ -786,14 +786,12 @@ public final class MathTransforms {
}
/**
- * Returns the ranges of source coordinate values where the given
transform is mathematically applicable.
+ * Returns the ranges of source coordinate values where the given
transform is considered well-behaved.
* This is <em>not</em> the domain of validity for which a coordinate
reference system has been defined,
* because this method ignores real world considerations such as datum and
country boundaries.
+ * Instead this method estimates, for example, where the function is
monotonic and reasonably far from infinity.
* This method is for allowing callers to crop their data for removing
areas that may cause numerical problems,
- * for example latitudes too close to a pole used as inputs of a Mercator
projection.
- *
- * <p>See {@link AbstractMathTransform#getDomain(DomainDefinition)} for
more information.
- * This static method delegates to above-cited method if possible, or
returns an empty value otherwise.</p>
+ * such as latitudes too close to a pole used as inputs of a Mercator
projection.
*
* @param evaluated transform for which to evaluate a domain, or {@code
null}.
* @return estimation of ranges of source coordinate values where the
transform is numerically applicable.
diff --git
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/package-info.java
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/package-info.java
index ab8dadcf18..19d5cdd63f 100644
---
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/package-info.java
+++
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/package-info.java
@@ -49,12 +49,12 @@
* transform factory}.
*
*
- * <h2>Non-spatial coordinates</h2>
+ * <h2>Transform between values that are not coordinates</h2>
* {@code MathTransform} usually performs conversions or transformations from
points given in a
* {@linkplain
org.apache.sis.referencing.operation.AbstractCoordinateOperation#getSourceCRS()
* source coordinate reference system} to coordinate values for the same
points in the
* {@linkplain
org.apache.sis.referencing.operation.AbstractCoordinateOperation#getTargetCRS()
- * target coordinate reference system}. However, the conversions are not
necessarily between CRS;
+ * target coordinate reference system}. However, the conversions are not
necessarily between <abbr>CRS</abbr>s.
* a {@code MathTransform} can also be used for converting the sample values
in a raster for example.
* Such kind of transforms are named {@linkplain
org.apache.sis.referencing.operation.transform.TransferFunction
* transfer functions}.
diff --git
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/adapter/Wrapper.java
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/adapter/Wrapper.java
index 617a40325e..8dfe89e7d4 100644
---
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/adapter/Wrapper.java
+++
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/adapter/Wrapper.java
@@ -30,10 +30,8 @@ import org.opengis.filter.DistanceOperatorName;
import org.opengis.filter.SpatialOperatorName;
import org.opengis.geometry.DirectPosition;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
-import org.opengis.referencing.operation.CoordinateOperation;
import org.opengis.referencing.operation.MathTransform;
import org.opengis.referencing.operation.TransformException;
-import org.opengis.util.FactoryException;
import org.apache.sis.filter.sqlmm.SQLMM;
import org.apache.sis.geometries.Curve;
import org.apache.sis.geometries.DE9IM;
@@ -60,8 +58,6 @@ import org.apache.sis.maths.Vectors;
import org.apache.sis.maths.Vector;
import org.apache.sis.measure.Quantities;
import org.apache.sis.measure.Units;
-import org.apache.sis.referencing.CRS;
-import org.apache.sis.referencing.operation.transform.MathTransforms;
import org.apache.sis.util.ArgumentChecks;
import org.apache.sis.util.Debug;
import org.apache.sis.util.UnconvertibleObjectException;
@@ -634,40 +630,23 @@ add: for (Geometry next = geometry;;) {
return
GeometryFactory.DEFAULT.createLinearRing(GeometryFactory.DEFAULT.createDataPoints(source.getDataPoints()));
}
- /**
- * Transforms this geometry using the given coordinate operation.
- * If the operation is {@code null}, then the geometry is returned
unchanged.
- *
- * @param operation the coordinate operation to apply.
- * @throws FactoryException if transformation to the target CRS cannot be
found.
- * @throws TransformException if the geometry cannot be transformed.
- */
- @Override
- protected GeometryWrapper transform(final CoordinateOperation operation)
- throws FactoryException, TransformException
- {
- MathTransform mt = operation.getMathTransform();
- if (VERIFY_GEOMETRY_CRS_BEFORE_OPERATION && crs != null) {
- final CoordinateOperation step = CRS.findOperation(crs,
operation.getSourceCRS(), null);
- mt = MathTransforms.concatenate(step.getMathTransform(), mt);
- }
- return new Wrapper(new GeometryProcessor().transform(geometry,
operation.getTargetCRS(), mt));
- }
-
/**
* Transforms this geometry using the given transform.
* If the transform is {@code null}, then the geometry is returned
unchanged.
*
* @param transform the math transform to apply.
+ * @param targetCRS <abbr>CRS</abbr> to set on the returned geometry, or
{@code null} if none.
* @return the transformed geometry (may be the same geometry instance,
but never {@code null}).
* @throws TransformException if the geometry cannot be transformed.
*/
@Override
- protected GeometryWrapper transform(final MathTransform transform) throws
TransformException {
+ protected GeometryWrapper transform(final MathTransform transform, final
CoordinateReferenceSystem targetCRS)
+ throws TransformException
+ {
if (transform.isIdentity()) {
return this;
}
- return new Wrapper(this, new GeometryProcessor().transform(geometry,
null, transform));
+ return new Wrapper(this, new GeometryProcessor().transform(geometry,
targetCRS, transform));
}
/**