sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject svn commit: r1779003 - in /sis/branches/JDK8/core: sis-metadata/src/main/java/org/apache/sis/metadata/iso/extent/ sis-metadata/src/test/java/org/apache/sis/metadata/iso/extent/ sis-referencing/src/main/java/org/apache/sis/referencing/
Date Mon, 16 Jan 2017 10:09:52 GMT
Author: desruisseaux
Date: Mon Jan 16 10:09:52 2017
New Revision: 1779003

URL: http://svn.apache.org/viewvc?rev=1779003&view=rev
Log:
First draft of a CRS.suggestTargetCRS(CoordinateReferenceSystem... sourceCRS) method.
As a side-effect of this work, contains SIS-347 fix (Extents.area(…) wrongly returned 0
when the box is 360° wide).

Modified:
    sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/extent/DefaultGeographicBoundingBox.java
    sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/extent/Extents.java
    sis/branches/JDK8/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/extent/ExtentsTest.java
    sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/CRS.java

Modified: sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/extent/DefaultGeographicBoundingBox.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/extent/DefaultGeographicBoundingBox.java?rev=1779003&r1=1779002&r2=1779003&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/extent/DefaultGeographicBoundingBox.java
[UTF-8] (original)
+++ sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/extent/DefaultGeographicBoundingBox.java
[UTF-8] Mon Jan 16 10:09:52 2017
@@ -16,6 +16,7 @@
  */
 package org.apache.sis.metadata.iso.extent;
 
+import java.util.Objects;
 import javax.xml.bind.annotation.XmlElement;
 import javax.xml.bind.annotation.XmlRootElement;
 import javax.xml.bind.annotation.XmlType;
@@ -37,9 +38,6 @@ import org.apache.sis.xml.NilReason;
 
 import static java.lang.Double.doubleToLongBits;
 
-// Branch-dependent imports
-import java.util.Objects;
-
 
 /**
  * Geographic position of the dataset. This is only an approximate so specifying the coordinate
@@ -289,7 +287,7 @@ public class DefaultGeographicBoundingBo
      */
     public void setWestBoundLongitude(double newValue) {
         checkWritePermission();
-        if (newValue != Longitude.MAX_VALUE) { // Do not normalize +180° to -180°.
+        if (newValue != Longitude.MAX_VALUE) {                  // Do not normalize +180°
to -180°.
             newValue = Longitude.normalize(newValue);
         }
         westBoundLongitude = newValue;
@@ -322,7 +320,7 @@ public class DefaultGeographicBoundingBo
      */
     public void setEastBoundLongitude(double newValue) {
         checkWritePermission();
-        if (newValue != Longitude.MAX_VALUE) { // Do not normalize +180° to -180°.
+        if (newValue != Longitude.MAX_VALUE) {                      // Do not normalize +180°
to -180°.
             newValue = Longitude.normalize(newValue);
         }
         eastBoundLongitude = newValue;
@@ -405,7 +403,7 @@ public class DefaultGeographicBoundingBo
     private static void verifyBounds(final double southBoundLatitude, final double northBoundLatitude)
             throws IllegalArgumentException
     {
-        if (southBoundLatitude > northBoundLatitude) { // Accept NaN.
+        if (southBoundLatitude > northBoundLatitude) {                          // Accept
NaN.
             throw new IllegalArgumentException(Errors.format(Errors.Keys.IllegalOrdinateRange_3,
                     new Latitude(southBoundLatitude), new Latitude(northBoundLatitude),
                     Vocabulary.format(Vocabulary.Keys.Latitude)));
@@ -421,7 +419,7 @@ public class DefaultGeographicBoundingBo
         southBoundLatitude = Latitude.clamp(southBoundLatitude);
         northBoundLatitude = Latitude.clamp(northBoundLatitude);
         final double span = eastBoundLongitude - westBoundLongitude;
-        if (!(span >= (Longitude.MAX_VALUE - Longitude.MIN_VALUE))) { // 'span' may be
NaN.
+        if (!(span >= (Longitude.MAX_VALUE - Longitude.MIN_VALUE))) {           // 'span'
may be NaN.
             westBoundLongitude = Longitude.normalize(westBoundLongitude);
             eastBoundLongitude = Longitude.normalize(eastBoundLongitude);
             if (span != 0) {
@@ -513,7 +511,7 @@ public class DefaultGeographicBoundingBo
         ArgumentChecks.ensureNonNull("envelope", envelope);
         checkWritePermission();
         ReferencingServices.getInstance().setBounds(envelope, this);
-        setInclusion(Boolean.TRUE); // Set only on success.
+        setInclusion(Boolean.TRUE);                                     // Set only on success.
     }
 
     /**
@@ -650,7 +648,7 @@ public class DefaultGeographicBoundingBo
          */
         final boolean i1 = getInclusion(this.getInclusion());
         final boolean i2 = getInclusion(box. getInclusion());
-        final int status = denormalize(λmin, λmax); // Must be after call to getInclusion().
+        final int status = denormalize(λmin, λmax);             // Must be after call to
getInclusion().
         switch (status) {
             case -1: λmin -= Longitude.MAX_VALUE - Longitude.MIN_VALUE; break;
             case +1: λmax += Longitude.MAX_VALUE - Longitude.MIN_VALUE; break;
@@ -753,9 +751,11 @@ public class DefaultGeographicBoundingBo
         if (object == this) {
             return true;
         }
-        // Above code really requires DefaultGeographicBoundingBox.class, not getClass().
-        // This code is used only for performance raison. The super-class implementation
-        // is generic enough for all other cases.
+        /*
+         * Above code really requires DefaultGeographicBoundingBox.class, not getClass().
+         * This code is used only for performance raison. The super-class implementation
+         * is generic enough for all other cases.
+         */
         if (object != null && object.getClass() == DefaultGeographicBoundingBox.class)
{
             final DefaultGeographicBoundingBox that = (DefaultGeographicBoundingBox) object;
             return Objects.equals(getInclusion(), that.getInclusion()) &&

Modified: sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/extent/Extents.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/extent/Extents.java?rev=1779003&r1=1779002&r2=1779003&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/extent/Extents.java
[UTF-8] (original)
+++ sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/extent/Extents.java
[UTF-8] Mon Jan 16 10:09:52 2017
@@ -132,8 +132,10 @@ public final class Extents extends Stati
                 if (element instanceof GeographicBoundingBox) {
                     final GeographicBoundingBox item = (GeographicBoundingBox) element;
                     if (bounds == null) {
-                        // We use DefaultGeographicBoundingBox.getInclusion(Boolean) below
because
-                        // add(…) method that we use cares about the case where inclusion
is false.
+                        /*
+                         * We use DefaultGeographicBoundingBox.getInclusion(Boolean) below
because
+                         * add(…) method that we use cares about the case where inclusion
is false.
+                         */
                         if (DefaultGeographicBoundingBox.getInclusion(item.getInclusion()))
{
                             bounds = item;
                         }
@@ -338,7 +340,7 @@ public final class Extents extends Stati
                 final Date startTime, endTime;
                 if (t instanceof DefaultTemporalExtent) {
                     final DefaultTemporalExtent dt = (DefaultTemporalExtent) t;
-                    startTime = dt.getStartTime(); // Maybe user has overridden those methods.
+                    startTime = dt.getStartTime();                  // Maybe user has overridden
those methods.
                     endTime   = dt.getEndTime();
                 } else {
                     final TemporalPrimitive p = t.getExtent();
@@ -389,7 +391,7 @@ public final class Extents extends Stati
                 Date   endTime = null;
                 if (t instanceof DefaultTemporalExtent) {
                     final DefaultTemporalExtent dt = (DefaultTemporalExtent) t;
-                    if (location != 1) startTime = dt.getStartTime(); // Maybe user has overridden
those methods.
+                    if (location != 1) startTime = dt.getStartTime();       // Maybe user
has overridden those methods.
                     if (location != 0)   endTime = dt.getEndTime();
                 } else {
                     final TemporalPrimitive p = t.getExtent();
@@ -452,8 +454,17 @@ public final class Extents extends Stati
         if (box == null) {
             return Double.NaN;
         }
-        double Δλ = box.getEastBoundLongitude() - box.getWestBoundLongitude(); // Negative
if spanning the anti-meridian
-        Δλ -= floor(Δλ / (Longitude.MAX_VALUE - Longitude.MIN_VALUE)) * (Longitude.MAX_VALUE
- Longitude.MIN_VALUE);
+        double Δλ = box.getEastBoundLongitude() - box.getWestBoundLongitude();
+        final double span = Longitude.MAX_VALUE - Longitude.MIN_VALUE;
+        if (Δλ > span) {
+            Δλ = span;
+        } else if (Δλ < 0) {
+            if (Δλ < -span) {
+                Δλ = -span;
+            } else {
+                Δλ += span;
+            }
+        }
         return (AUTHALIC_RADIUS * AUTHALIC_RADIUS) * toRadians(Δλ) *
                max(0, sin(toRadians(box.getNorthBoundLatitude())) -
                       sin(toRadians(box.getSouthBoundLatitude())));

Modified: sis/branches/JDK8/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/extent/ExtentsTest.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/extent/ExtentsTest.java?rev=1779003&r1=1779002&r2=1779003&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/extent/ExtentsTest.java
[UTF-8] (original)
+++ sis/branches/JDK8/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/extent/ExtentsTest.java
[UTF-8] Mon Jan 16 10:09:52 2017
@@ -127,6 +127,11 @@ public final strictfp class ExtentsTest
         box.setNorthBoundLatitude(-90+MINUTE);
         assertEquals(499.5, Extents.area(box), 0.1);
         /*
+         * Spanning 360° of longitude.
+         */
+        box.setBounds(-180, +180, -90, 90);
+        assertEquals(5.1E+14, Extents.area(box), 1E+11);
+        /*
          * EPSG:1241    USA - CONUS including EEZ
          * This is only an anti-regression test - the value has not been validated.
          * However the expected area MUST be greater than the Alaska's one below,
@@ -134,7 +139,7 @@ public final strictfp class ExtentsTest
          */
         box.setBounds(-129.16, -65.70, 23.82, 49.38);
         assertFalse(DefaultGeographicBoundingBoxTest.isSpanningAntiMeridian(box));
-        assertEquals(15967665, Extents.area(box) / 1E6, 1); // Compare in km²
+        assertEquals(15967665, Extents.area(box) / 1E6, 1);                             //
Compare in km²
         /*
          * EPSG:2373    USA - Alaska including EEZ    (spanning the anti-meridian).
          * This is only an anti-regression test - the value has not been validated.
@@ -142,6 +147,6 @@ public final strictfp class ExtentsTest
          */
         box.setBounds(167.65, -129.99, 47.88, 74.71);
         assertTrue(DefaultGeographicBoundingBoxTest.isSpanningAntiMeridian(box));
-        assertEquals(9845438, Extents.area(box) / 1E6, 1); // Compare in km²
+        assertEquals(9845438, Extents.area(box) / 1E6, 1);                              //
Compare in km²
     }
 }

Modified: sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/CRS.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/CRS.java?rev=1779003&r1=1779002&r2=1779003&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/CRS.java
[UTF-8] (original)
+++ sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/CRS.java
[UTF-8] Mon Jan 16 10:09:52 2017
@@ -35,6 +35,7 @@ import org.opengis.referencing.crs.Coord
 import org.opengis.referencing.crs.CRSAuthorityFactory;
 import org.opengis.referencing.crs.GeodeticCRS;
 import org.opengis.referencing.crs.GeographicCRS;
+import org.opengis.referencing.crs.GeneralDerivedCRS;
 import org.opengis.referencing.crs.ProjectedCRS;
 import org.opengis.referencing.crs.TemporalCRS;
 import org.opengis.referencing.crs.VerticalCRS;
@@ -274,6 +275,164 @@ public final class CRS extends Static {
     }
 
     /**
+     * Suggests a coordinate reference system which could be a common target for coordinate
operations having the
+     * given sources. This method compares the {@linkplain #getGeographicBoundingBox(CoordinateReferenceSystem)
+     * domain of validity} of all given CRSs. If a CRS has a domain of validity that contains
the domain of all other
+     * CRS, than that CRS is returned. Otherwise this method verifies if a {@linkplain GeneralDerivedCRS#getBaseCRS()
+     * base CRS} (usually a {@linkplain org.apache.sis.referencing.crs.DefaultGeographicCRS
geographic CRS} instance)
+     * would be suitable. If no suitable CRS is found, then this method returns {@code null}.
+     *
+     * <div class="note"><b>Use case:</b>
+     * before to test if two arbitrary envelopes {@linkplain GeneralEnvelope#intersects(Envelope)
intersect} each other,
+     * they need to be {@linkplain Envelopes#transform(Envelope, CoordinateReferenceSystem)
transformed} in the same CRS.
+     * However if one CRS is a Transverse Mercator projection while the other CRS is a world-wide
geographic CRS, then
+     * attempts to use the Transverse Mercator projection as the common CRS is likely to
fail since the geographic envelope
+     * may span an area far outside the projection domain of validity. This {@code suggestTargetCRS(…)}
method can used
+     * for choosing a common CRS which is less likely to fail.</div>
+     *
+     * @param  regionOfInterest  the geographic area for which the coordinate operations
will be applied,
+     *                           or {@code null} if unknown.
+     * @param  sourceCRS         the coordinate reference systems for which a common target
CRS is desired.
+     * @return a CRS that may be used as a common target for all the given source CRS in
the given region of interest,
+     *         or {@code null} if this method did not find a common target CRS. The returned
CRS may be different than
+     *         all given CRS.
+     *
+     * @since 0.8
+     */
+    public static CoordinateReferenceSystem suggestTargetCRS(GeographicBoundingBox regionOfInterest,
+                                                             CoordinateReferenceSystem...
sourceCRS)
+    {
+        /*
+         * Compute the union of the domain of validity of all CRS. If a CRS does not specify
a domain of validity,
+         * then assume that the CRS is valid for the whole world if the CRS is geodetic or
return null otherwise.
+         * Opportunistically remember the domain of validity of each CRS in this loop since
we will need them later.
+         */
+        boolean worldwide = false;
+        DefaultGeographicBoundingBox domain = null;
+        final GeographicBoundingBox[] domains = new GeographicBoundingBox[sourceCRS.length];
+        for (int i=0; i < sourceCRS.length; i++) {
+            final CoordinateReferenceSystem crs = sourceCRS[i];
+            GeographicBoundingBox bbox = getGeographicBoundingBox(crs);
+            if (bbox == null) {
+                /*
+                 * If no domain of validity is specified and we can not fallback
+                 * on some knowledge about what the CRS is, abandon.
+                 */
+                if (!(crs instanceof GeodeticCRS)) {
+                    return null;
+                }
+                /*
+                 * If no region of interest has been specified, conservatively assume that
the caller is
+                 * interested in a worldwide area. Since we have a Geodetic CRS, we will
not find better.
+                 */
+                if (regionOfInterest == null) {
+                    return crs;
+                }
+                /*
+                 * Geodetic CRS (geographic or geocentric) can generally be presumed valid
in a worldwide area.
+                 * Since the caller has specified an area of interest, that will be taken
as our validity domain.
+                 * The 'worldwide' flag is a little optimization for remembering that we
do not need to compute
+                 * the union anymore, but we still need to continue the loop for fetching
all bounding boxes.
+                 */
+                bbox = regionOfInterest;
+                worldwide = true;
+            } else if (!worldwide) {
+                if (domain == null) {
+                    domain = new DefaultGeographicBoundingBox(bbox);
+                } else {
+                    domain.add(bbox);
+                }
+            }
+            domains[i] = bbox;
+        }
+        /*
+         * At this point we got the union of the domain of validity of all CRS. We are interested
only in the
+         * part that intersect the region of interest. If the union is whole world, we do
not need to compute
+         * the intersection; we can just leave the region of interest unchanged.
+         */
+        if (domain != null && !worldwide) {
+            if (regionOfInterest != null) {
+                domain.intersect(regionOfInterest);
+            }
+            regionOfInterest = domain;
+            domain = null;
+        }
+        /*
+         * Iterate again over the domain of validity of all CRS.  For each domain of validity,
compute the area
+         * which is inside the domain or interest and the area which is outside. The "best
CRS" will be the one
+         * which comply with the following rules, in preference order:
+         *
+         *   1) The CRS which is valid over the largest area of the region of interest.
+         *   2) If two CRS are equally good according rule 1, then the CRS with the smallest
"outside area".
+         *
+         * Example: given two source CRS, a geographic one and a projected one:
+         *
+         *   - If the projected CRS contains fully the region of interest, then it will be
returned.
+         *     The preference is given to the projected CRS because geometric are likely
to be more
+         *     accurate in that space. Furthermore forward conversions from geographic to
projected
+         *     CRS are usually faster than inverse conversions.
+         *
+         *   - Otherwise (i.e. if the region of interest is likely to be wider than the projected
CRS
+         *     domain of validity), then the geographic CRS will be returned.
+         */
+        CoordinateReferenceSystem bestCRS = null;
+        final double roiArea  = Extents.area(regionOfInterest);   // NaN if 'regionOfInterest'
is null.
+        double maxInsideArea  = 0;
+        double minOutsideArea = Double.POSITIVE_INFINITY;
+        boolean tryDerivedCRS = false;
+        do {
+            for (int i=0; i < domains.length; i++) {
+                final GeographicBoundingBox bbox = domains[i];
+                double insideArea  = Extents.area(bbox);
+                double outsideArea = 0;
+                if (regionOfInterest != null) {
+                    if (domain == null) {
+                        domain = new DefaultGeographicBoundingBox(bbox);
+                    } else {
+                        domain.setBounds(bbox);
+                    }
+                    domain.intersect(regionOfInterest);
+                    final double area = insideArea;
+                    insideArea = Extents.area(bbox);
+                    outsideArea = area - insideArea;
+                }
+                if (insideArea > maxInsideArea || (insideArea == maxInsideArea &&
outsideArea < minOutsideArea)) {
+                    maxInsideArea  = insideArea;
+                    minOutsideArea = outsideArea;
+                    bestCRS        = sourceCRS[i];
+                }
+            }
+            /*
+             * If the best CRS does not cover fully the region of interest, then we will
redo the check again
+             * but using base CRS instead. For example if the list of source CRS had some
projected CRS, we
+             * will try with the geographic CRS on which those projected CRS are based.
+             */
+            if (maxInsideArea < roiArea) {
+                if (tryDerivedCRS) break;                                               //
Do not try twice.
+                final SingleCRS[] derivedCRS = new SingleCRS[sourceCRS.length];
+                for (int i=0; i < derivedCRS.length; i++) {
+                    GeographicBoundingBox bbox = null;
+                    final CoordinateReferenceSystem crs = sourceCRS[i];
+                    if (crs instanceof GeneralDerivedCRS) {
+                        final SingleCRS baseCRS = ((GeneralDerivedCRS) crs).getBaseCRS();
+                        bbox = getGeographicBoundingBox(baseCRS);
+                        if (bbox == null) {
+                            bbox = regionOfInterest;
+                        }
+                        tryDerivedCRS = true;
+                        derivedCRS[i] = baseCRS;
+                    }
+                    domains[i] = bbox;
+                }
+                sourceCRS = derivedCRS;
+            } else {
+                break;
+            }
+        } while (tryDerivedCRS);
+        return bestCRS;
+    }
+
+    /**
      * Finds a mathematical operation that transforms or converts coordinates from the given
source to the
      * given target coordinate reference system. If an estimation of the geographic area
containing the points
      * to transform is known, it can be specified for helping this method to find a better
suited operation.
@@ -387,9 +546,8 @@ public final class CRS extends Static {
     /**
      * Returns the valid geographic area for the given coordinate operation, or {@code null}
if unknown.
      * This method explores the {@linkplain AbstractCoordinateOperation#getDomainOfValidity()
domain of validity}
-     * associated with the given operation. If more than one geographic bounding box is found,
then they will be
-     * {@linkplain org.apache.sis.metadata.iso.extent.DefaultGeographicBoundingBox#add(GeographicBoundingBox)
added}
-     * together.
+     * associated with the given operation. If more than one geographic bounding box is found,
then this method
+     * computes their {@linkplain DefaultGeographicBoundingBox#add(GeographicBoundingBox)
union}.
      *
      * @param  operation  the coordinate operation for which to get the domain of validity,
or {@code null}.
      * @return the geographic area where the operation is valid, or {@code null} if unspecified.
@@ -408,8 +566,8 @@ public final class CRS extends Static {
     /**
      * Returns the valid geographic area for the given coordinate reference system, or {@code
null} if unknown.
      * This method explores the {@linkplain org.apache.sis.referencing.crs.AbstractCRS#getDomainOfValidity()
domain of
-     * validity} associated with the given CRS. If more than one geographic bounding box
is found, then they will be
-     * {@linkplain org.apache.sis.metadata.iso.extent.DefaultGeographicBoundingBox#add(GeographicBoundingBox)
added}
+     * validity} associated with the given CRS. If more than one geographic bounding box
is found, then this method
+     * computes their {@linkplain DefaultGeographicBoundingBox#add(GeographicBoundingBox)
union}.
      * together.
      *
      * @param  crs  the coordinate reference system for which to get the domain of validity,
or {@code null}.



Mime
View raw message