sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject svn commit: r1533254 - in /sis/branches/JDK7/core: sis-referencing/src/main/java/org/apache/sis/referencing/datum/ sis-utility/src/main/java/org/apache/sis/internal/util/
Date Thu, 17 Oct 2013 20:33:54 GMT
Author: desruisseaux
Date: Thu Oct 17 20:33:54 2013
New Revision: 1533254

URL: http://svn.apache.org/r1533254
Log:
Initial port of DefaultGeodeticDatum. The getAffineTransform(GeodeticDatum target) method
will need to be revisited,
since current implementation has no safety against concatenation of datum shifts between unrelated
geographic areas.

Added:
    sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/DefaultGeodeticDatum.java
  (with props)
Modified:
    sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/AbstractDatum.java
    sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/BursaWolfParameters.java
    sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/internal/util/CollectionsExt.java

Modified: sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/AbstractDatum.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/AbstractDatum.java?rev=1533254&r1=1533253&r2=1533254&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/AbstractDatum.java
[UTF-8] (original)
+++ sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/AbstractDatum.java
[UTF-8] Thu Oct 17 20:33:54 2013
@@ -286,6 +286,8 @@ public class AbstractDatum extends Abstr
      * {@linkplain #getAnchorPoint() anchor point}, {@linkplain #getRealizationEpoch() realization
epoch},
      * {@linkplain #getDomainOfValidity() domain of validity} and the {@linkplain #getScope()
scope}
      * properties are ignored.
+     *
+     * @return The hash code value for the given comparison mode.
      */
     @Override
     public int hashCode(final ComparisonMode mode) throws IllegalArgumentException {

Modified: sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/BursaWolfParameters.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/BursaWolfParameters.java?rev=1533254&r1=1533253&r2=1533254&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/BursaWolfParameters.java
[UTF-8] (original)
+++ sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/BursaWolfParameters.java
[UTF-8] Thu Oct 17 20:33:54 2013
@@ -24,6 +24,7 @@ import org.apache.sis.referencing.operat
 import org.apache.sis.referencing.operation.matrix.Matrices;
 import org.apache.sis.io.wkt.FormattableObject;
 import org.apache.sis.io.wkt.Formatter;
+import org.apache.sis.util.Immutable;
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.internal.util.Numerics;
 import org.apache.sis.referencing.IdentifiedObjects;
@@ -142,19 +143,14 @@ import java.util.Objects;
  * @version 0.4
  * @module
  */
-public class BursaWolfParameters extends FormattableObject implements Cloneable, Serializable
{
+@Immutable
+public class BursaWolfParameters extends FormattableObject implements Serializable {
     /**
      * Serial number for inter-operability with different versions.
      */
     private static final long serialVersionUID = 754825592343010900L;
 
     /**
-     * The array to be returned by {@link DefaultGeodeticDatum#getBursaWolfParameters()}
-     * when there is no Bursa Wolf parameters.
-     */
-    static final BursaWolfParameters[] EMPTY_ARRAY = new BursaWolfParameters[0];
-
-    /**
      * The conversion factor from <cite>parts per million</cite> to scale minus
one.
      */
     private static final double PPM = 1E+6;
@@ -168,37 +164,37 @@ public class BursaWolfParameters extends
      * X-axis translation in metres (EPSG:8605).
      * The legacy OGC parameter name is {@code "dx"}.
      */
-    public double tX;
+    public final double tX;
 
     /**
      * Y-axis translation in metres (EPSG:8606).
      * The legacy OGC parameter name is {@code "dy"}.
      */
-    public double tY;
+    public final double tY;
 
     /**
      * Z-axis translation in metres (EPSG:8607).
      * The legacy OGC parameter name is {@code "dz"}.
      */
-    public double tZ;
+    public final double tZ;
 
     /**
      * X-axis rotation in arc seconds (EPSG:8608), sign following the <cite>Position
Vector</cite> convention.
      * The legacy OGC parameter name is {@code "ex"}.
      */
-    public double rX;
+    public final double rX;
 
     /**
      * Y-axis rotation in arc seconds (EPSG:8609), sign following the <cite>Position
Vector</cite> convention.
      * The legacy OGC parameter name is {@code "ey"}.
      */
-    public double rY;
+    public final double rY;
 
     /**
      * Z-axis rotation in arc seconds (EPSG:8610), sign following the <cite>Position
Vector</cite> convention.
      * The legacy OGC parameter name is {@code "ez"}.
      */
-    public double rZ;
+    public final double rZ;
 
     /**
      * The scale difference in parts per million (EPSG:8611).
@@ -208,7 +204,7 @@ public class BursaWolfParameters extends
      *           of 100.001 km in the target coordinate reference system, the scale difference
is 1 ppm
      *           (the ratio being 1.000001).}
      */
-    public double dS;
+    public final double dS;
 
     /**
      * The target datum for this set of parameters, or {@code null} if unspecified.
@@ -222,65 +218,33 @@ public class BursaWolfParameters extends
     public final GeodeticDatum targetDatum;
 
     /**
-     * Creates a new instance with all parameters set to 0.
-     *
-     * @param target The target datum (usually WGS 84) for this set of parameters, or {@code
null} if unspecified.
-     */
-    public BursaWolfParameters(final GeodeticDatum target) {
-        this.targetDatum = target;
-    }
-
-    /**
-     * Returns {@code true} if this Bursa Wolf parameters performs no operation.
-     * This is true when all parameters are set to zero.
-     *
-     * @return {@code true} if the parameters describe no operation.
-     */
-    public boolean isIdentity() {
-        return tX == 0 && tY == 0 && tZ == 0 &&
-               rX == 0 && rY == 0 && rZ == 0 &&
-               dS == 0;
-    }
-
-    /**
-     * Returns {@code true} if this Bursa Wolf parameters contains only translation terms.
-     *
-     * @return {@code true} if the parameters describe to a translation only.
-     */
-    public boolean isTranslation() {
-        return rX == 0 && rY == 0 && rZ == 0 && dS == 0;
-    }
-
-    /**
-     * Returns an affine transform that can be used to define this Bursa Wolf parameters.
-     * The formula is as below, where {@code R} is a conversion factor from arc-seconds to
radians:
-     *
-     * <blockquote><pre> R = toRadians(1″)
-     * S = 1 + {@linkplain #dS}/1000000
-     * ┌    ┐    ┌                               ┐  ┌   ┐
-     * │ X' │    │      S   -{@linkplain #rZ}*RS   +{@linkplain #rY}*RS   {@linkplain
#tX} │  │ X │
-     * │ Y' │  = │ +{@linkplain #rZ}*RS        S   -{@linkplain #rX}*RS   {@linkplain
#tY} │  │ Y │
-     * │ Z' │    │ -{@linkplain #rY}*RS   +{@linkplain #rX}*RS        S   {@linkplain
#tZ} │  │ Z │
-     * │ 1  │    │      0        0        0    1 │  │ 1 │
-     * └    ┘    └                               ┘  └   ┘</pre></blockquote>
-     *
-     * This affine transform can be applied on <strong>geocentric</strong> coordinates.
+     * Creates a new instance with the given parameters.
      *
-     * @return An affine transform created from the parameters.
-     */
-    public Matrix getAffineTransform() {
-        final double  S = 1 + dS / PPM;
-        final double RS = TO_RADIANS * S;
-        return new Matrix4(
-                 S,  -rZ*RS,  +rY*RS,  tX,
-            +rZ*RS,       S,  -rX*RS,  tY,
-            -rY*RS,  +rX*RS,       S,  tZ,
-                 0,       0,       0,   1);
+     * @param tX X-axis translation in metres.
+     * @param tY Y-axis translation in metres.
+     * @param tZ Z-axis translation in metres.
+     * @param rX X-axis rotation in arc seconds.
+     * @param rY Y-axis rotation in arc seconds.
+     * @param rZ Z-axis rotation in arc seconds.
+     * @param dS The scale difference in parts per million.
+     * @param targetDatum The target datum (usually WGS 84) for this set of parameters, or
{@code null} if unspecified.
+     */
+    public BursaWolfParameters(final double tX, final double tY, final double tZ,
+                               final double rX, final double rY, final double rZ,
+                               final double dS, final GeodeticDatum targetDatum)
+    {
+        this.tX = tX;
+        this.tY = tY;
+        this.tZ = tZ;
+        this.rX = rX;
+        this.rY = rY;
+        this.rZ = rZ;
+        this.dS = dS;
+        this.targetDatum = targetDatum;
     }
 
     /**
-     * Sets the Bursa-Wolf parameters from the given matrix.
-     * This method is the converse of {@link #getAffineTransform()}.
+     * Creates Bursa-Wolf parameters from the given matrix.
      * The matrix shall comply to the following constraints:
      *
      * <ul>
@@ -291,9 +255,14 @@ public class BursaWolfParameters extends
      *
      * @param  matrix The matrix to fit as a Bursa-Wolf construct.
      * @param  tolerance The tolerance error for the antisymmetric matrix test. Should be
a small number like {@code 1E-8}.
+     * @param  targetDatum The target datum (usually WGS 84) for this set of parameters,
or {@code null} if unspecified.
      * @throws IllegalArgumentException if the specified matrix does not meet the conditions.
+     *
+     * @see #getAffineTransform()
      */
-    public void setAffineTransform(final Matrix matrix, final double tolerance) throws IllegalArgumentException
{
+    public BursaWolfParameters(final Matrix matrix, final double tolerance, final GeodeticDatum
targetDatum)
+            throws IllegalArgumentException
+    {
         final int numRow = matrix.getNumRow();
         final int numCol = matrix.getNumCol();
         if (numRow != SIZE || numCol != SIZE) {
@@ -311,6 +280,7 @@ public class BursaWolfParameters extends
                           matrix.getElement(2,2)) / 3;
         final double RS = TO_RADIANS * S;
         dS = (S-1) * PPM;
+        double rX=0, rY=0, rZ=0;
         for (int j=0; j < SIZE-1; j++) {
             if (!(abs((matrix.getElement(j,j) - 1)*PPM - dS) <= tolerance)) {
                 throw new IllegalArgumentException(Errors.format(Errors.Keys.NonUniformScale));
@@ -329,22 +299,72 @@ public class BursaWolfParameters extends
                 }
             }
         }
-        assert Matrices.equals(matrix, getAffineTransform(), tolerance*RS, false) : matrix;
+        this.rX = rX;
+        this.rY = rY;
+        this.rZ = rZ;
+        this.targetDatum = targetDatum;
     }
 
     /**
-     * Returns a copy of this object.
+     * Returns {@code true} if the {@linkplain #targetDatum target datum} is equals (at least
on computation purpose)
+     * to the WGS84 datum. This method may conservatively returns {@code false} if the specified
datum is uncertain.
      *
-     * @return A clone of the parameters.
+     * @return {@code true} if the given datum is equal to WGS84 for computational purpose.
      */
-    @Override
-    public BursaWolfParameters clone() {
-        try {
-            return (BursaWolfParameters) super.clone();
-        }  catch (CloneNotSupportedException exception) {
-            // Should not happen, since we are cloneable.
-            throw new AssertionError(exception);
-        }
+    final boolean isToWGS84() {
+        return (targetDatum != null) &&
+                (IdentifiedObjects.nameMatches(targetDatum, "WGS 84") ||
+                 IdentifiedObjects.nameMatches(targetDatum, "WGS84"));
+    }
+
+    /**
+     * Returns {@code true} if this Bursa Wolf parameters performs no operation.
+     * This is true when all parameters are set to zero.
+     *
+     * @return {@code true} if the parameters describe no operation.
+     */
+    public boolean isIdentity() {
+        return tX == 0 && tY == 0 && tZ == 0 &&
+               rX == 0 && rY == 0 && rZ == 0 &&
+               dS == 0;
+    }
+
+    /**
+     * Returns {@code true} if this Bursa Wolf parameters contains only translation terms.
+     *
+     * @return {@code true} if the parameters describe to a translation only.
+     */
+    public boolean isTranslation() {
+        return rX == 0 && rY == 0 && rZ == 0 && dS == 0;
+    }
+
+    /**
+     * Returns an affine transform that can be used to define this Bursa Wolf parameters.
+     * The formula is as below, where {@code R} is a conversion factor from arc-seconds to
radians:
+     *
+     * <blockquote><pre> R = toRadians(1″)
+     * S = 1 + {@linkplain #dS}/1000000
+     * ┌    ┐    ┌                               ┐  ┌   ┐
+     * │ X' │    │      S   -{@linkplain #rZ}*RS   +{@linkplain #rY}*RS   {@linkplain
#tX} │  │ X │
+     * │ Y' │  = │ +{@linkplain #rZ}*RS        S   -{@linkplain #rX}*RS   {@linkplain
#tY} │  │ Y │
+     * │ Z' │    │ -{@linkplain #rY}*RS   +{@linkplain #rX}*RS        S   {@linkplain
#tZ} │  │ Z │
+     * │ 1  │    │      0        0        0    1 │  │ 1 │
+     * └    ┘    └                               ┘  └   ┘</pre></blockquote>
+     *
+     * This affine transform can be applied on <strong>geocentric</strong> coordinates.
+     *
+     * @return An affine transform created from the parameters.
+     *
+     * @see DefaultGeodeticDatum#getAffineTransform(GeodeticDatum)
+     */
+    public Matrix getAffineTransform() {
+        final double  S = 1 + dS / PPM;
+        final double RS = TO_RADIANS * S;
+        return new Matrix4(
+                 S,  -rZ*RS,  +rY*RS,  tX,
+            +rZ*RS,       S,  -rX*RS,  tY,
+            -rY*RS,  +rX*RS,       S,  tZ,
+                 0,       0,       0,   1);
     }
 
     /**
@@ -402,7 +422,7 @@ public class BursaWolfParameters extends
         formatter.append(rY);
         formatter.append(rZ);
         formatter.append(dS);
-        if (true /*!DefaultGeodeticDatum.isWGS84(targetDatum)*/) {
+        if (isToWGS84()) {
             return "TOWGS84";
         }
         String keyword = super.formatTo(formatter); // Declare the WKT as invalid.

Added: sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/DefaultGeodeticDatum.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/DefaultGeodeticDatum.java?rev=1533254&view=auto
==============================================================================
--- sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/DefaultGeodeticDatum.java
(added)
+++ sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/DefaultGeodeticDatum.java
[UTF-8] Thu Oct 17 20:33:54 2013
@@ -0,0 +1,435 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.referencing.datum;
+
+import java.util.Map;
+import java.util.Set;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Collections;
+import javax.xml.bind.annotation.XmlType;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlRootElement;
+import org.opengis.referencing.datum.Ellipsoid;
+import org.opengis.referencing.datum.PrimeMeridian;
+import org.opengis.referencing.datum.GeodeticDatum;
+import org.opengis.referencing.operation.Matrix;
+import org.apache.sis.referencing.GeodeticObjects;
+import org.apache.sis.referencing.operation.matrix.MatrixSIS;
+import org.apache.sis.referencing.operation.matrix.NoninvertibleMatrixException;
+import org.apache.sis.internal.util.CollectionsExt;
+import org.apache.sis.util.logging.Logging;
+import org.apache.sis.util.ComparisonMode;
+import org.apache.sis.util.Immutable;
+import org.apache.sis.io.wkt.Formatter;
+
+import static org.apache.sis.util.Utilities.deepEquals;
+import static org.apache.sis.util.ArgumentChecks.ensureNonNull;
+
+// Related to JDK7
+import java.util.Objects;
+
+
+/**
+ * Defines the location and precise orientation in 3-dimensional space of a defined ellipsoid
+ * (or sphere) that approximates the shape of the earth. Used also for Cartesian coordinate
+ * system centered in this ellipsoid (or sphere).
+ *
+ * {@section Creating new geodetic datum instances}
+ * New instances can be created either directly by specifying all information to a factory
method (choices 3
+ * and 4 below), or indirectly by specifying the identifier of an entry in a database (choices
1 and 2 below).
+ * Choice 1 in the following list is the easiest but most restrictive way to get a geodetic
datum.
+ * The other choices provide more freedom.
+ *
+ * <ol>
+ *   <li>Create a {@code GeodeticDatum} from one of the static convenience shortcuts
listed in
+ *       {@link org.apache.sis.referencing.GeodeticObjects#datum()}.</li>
+ *   <li>Create a {@code GeodeticDatum} from an identifier in a database by invoking
+ *       {@link org.opengis.referencing.datum.DatumAuthorityFactory#createGeodeticDatum(String)}.</li>
+ *   <li>Create a {@code GeodeticDatum} by invoking the {@code createGeodeticDatum(…)}
+ *       method defined in the {@link org.opengis.referencing.datum.DatumFactory} interface.</li>
+ *   <li>Create a {@code DefaultGeodeticDatum} by invoking the
+ *       {@link #DefaultGeodeticDatum(Map, Ellipsoid, PrimeMeridian) constructor}.</li>
+ * </ol>
+ *
+ * <b>Example:</b> the following code gets a <cite>World Geodetic System
1984</cite> datum:
+ *
+ * {@preformat java
+ *     GeodeticDatum datum = GeodeticObjects.WGS84.datum();
+ * }
+ *
+ * @author  Martin Desruisseaux (IRD, Geomatys)
+ * @since   0.4 (derived from geotk-1.2)
+ * @version 0.4
+ * @module
+ *
+ * @see DefaultEllipsoid
+ * @see DefaultPrimeMeridian
+ * @see org.apache.sis.referencing.GeodeticObjects#datum()
+ */
+@Immutable
+@XmlType(name = "GeodeticDatumType", propOrder = {
+    "primeMeridian",
+    "ellipsoid"
+})
+@XmlRootElement(name = "GeodeticDatum")
+public class DefaultGeodeticDatum extends AbstractDatum implements GeodeticDatum {
+    /**
+     * Serial number for inter-operability with different versions.
+     */
+    private static final long serialVersionUID = 8832100095648302943L;
+
+    /**
+     * The <code>{@value #BURSA_WOLF_KEY}</code> property for
+     * {@link #getAffineTransform(GeodeticDatum) datum shifts}.
+     */
+    public static final String BURSA_WOLF_KEY = "bursaWolf";
+
+    /**
+     * The array to be returned by {@link #getBursaWolfParameters()} when there is no Bursa-Wolf
parameters.
+     */
+    private static final BursaWolfParameters[] EMPTY_ARRAY = new BursaWolfParameters[0];
+
+    /**
+     * The ellipsoid.
+     */
+    @XmlElement
+    private final Ellipsoid ellipsoid;
+
+    /**
+     * The prime meridian.
+     */
+    @XmlElement
+    private final PrimeMeridian primeMeridian;
+
+    /**
+     * Bursa Wolf parameters for datum shifts, or {@code null} if none.
+     */
+    private final BursaWolfParameters[] bursaWolf;
+
+    /**
+     * Creates a geodetic datum using the Greenwich prime meridian. This is a convenience
constructor for
+     * {@link #DefaultGeodeticDatum(Map, Ellipsoid, PrimeMeridian) DefaultGeodeticDatum(Map,
…)}
+     * with a map containing only the {@value org.opengis.referencing.IdentifiedObject#NAME_KEY}
property
+     * and the {@link #getPrimeMeridian() prime meridian} fixed to Greenwich.
+     *
+     * @param name      The datum name.
+     * @param ellipsoid The ellipsoid.
+     */
+    public DefaultGeodeticDatum(final String name, final Ellipsoid ellipsoid) {
+        this(Collections.singletonMap(NAME_KEY, name), ellipsoid, GeodeticObjects.WGS84.primeMeridian());
+    }
+
+    /**
+     * Creates a geodetic datum from the given properties. The properties map is given
+     * unchanged to the {@link AbstractDatum#AbstractDatum(Map) super-class constructor}.
+     * In addition to the properties documented in the parent constructor,
+     * the following properties are understood by this constructor:
+     *
+     * <table class="sis">
+     *   <tr>
+     *     <th>Property name</th>
+     *     <th>Value type</th>
+     *     <th>Returned by</th>
+     *   </tr>
+     *   <tr>
+     *     <td>{@value #BURSA_WOLF_KEY}</td>
+     *     <td>{@link BursaWolfParameters} or {@code BursaWolfParameters[]}</td>
+     *     <td>{@link #getBursaWolfParameters()}</td>
+     *   </tr>
+     * </table>
+     *
+     * @param properties    The properties to be given to the identified object.
+     * @param ellipsoid     The ellipsoid.
+     * @param primeMeridian The prime meridian.
+     */
+    public DefaultGeodeticDatum(final Map<String,?> properties,
+                                final Ellipsoid     ellipsoid,
+                                final PrimeMeridian primeMeridian)
+    {
+        super(properties);
+        ensureNonNull("ellipsoid",     ellipsoid);
+        ensureNonNull("primeMeridian", primeMeridian);
+        this.ellipsoid     = ellipsoid;
+        this.primeMeridian = primeMeridian;
+        bursaWolf = CollectionsExt.nonEmpty(CollectionsExt.nonNullArraySet(
+                BURSA_WOLF_KEY, properties.get(BURSA_WOLF_KEY), EMPTY_ARRAY));
+    }
+
+    /**
+     * Creates a new datum with the same values than the specified one.
+     * This copy constructor provides a way to convert an arbitrary implementation into a
SIS one
+     * or a user-defined one (as a subclass), usually in order to leverage some implementation-specific
API.
+     *
+     * <p>This constructor performs a shallow copy, i.e. the properties are not cloned.</p>
+     *
+     * @param datum The datum to copy.
+     *
+     * @see #castOrCopy(GeodeticDatum)
+     */
+    protected DefaultGeodeticDatum(final GeodeticDatum datum) {
+        super(datum);
+        ellipsoid     = datum.getEllipsoid();
+        primeMeridian = datum.getPrimeMeridian();
+        bursaWolf     = (datum instanceof DefaultGeodeticDatum) ?
+                        ((DefaultGeodeticDatum) datum).bursaWolf : null;
+    }
+
+    /**
+     * Returns a SIS datum implementation with the same values than the given arbitrary implementation.
+     * If the given object is {@code null}, then this method returns {@code null}.
+     * Otherwise if the given object is already a SIS implementation, then the given object
is returned unchanged.
+     * Otherwise a new SIS implementation is created and initialized to the attribute values
of the given object.
+     *
+     * @param  object The object to get as a SIS implementation, or {@code null} if none.
+     * @return A SIS implementation containing the values of the given object (may be the
+     *         given object itself), or {@code null} if the argument was null.
+     */
+    public static DefaultGeodeticDatum castOrCopy(final GeodeticDatum object) {
+        return (object == null) || (object instanceof DefaultGeodeticDatum)
+                ? (DefaultGeodeticDatum) object : new DefaultGeodeticDatum(object);
+    }
+
+    /**
+     * Returns the ellipsoid given at construction time.
+     *
+     * @return The ellipsoid.
+     */
+    @Override
+    public Ellipsoid getEllipsoid() {
+        return ellipsoid;
+    }
+
+    /**
+     * Returns the prime meridian given at construction time.
+     *
+     * @return The prime meridian.
+     */
+    @Override
+    public PrimeMeridian getPrimeMeridian() {
+        return primeMeridian;
+    }
+
+    /**
+     * Returns all Bursa Wolf parameters specified in the {@code properties} map at construction
time.
+     *
+     * @return The Bursa Wolf parameters, or an empty array if none.
+     */
+    public BursaWolfParameters[] getBursaWolfParameters() {
+        return (bursaWolf != null) ? bursaWolf.clone() : EMPTY_ARRAY;
+    }
+
+    /**
+     * Returns Bursa Wolf parameters for a datum shift toward the specified target, or {@code
null} if none.
+     * This method searches only for Bursa-Wolf parameters explicitly specified in the {@code
properties} map
+     * given at construction time. This method doesn't try to infer a set of parameters from
indirect informations.
+     * For example it does not try to inverse the parameters specified in the {@code target}
datum if none were found
+     * in this datum. If a more elaborated search is wanted, use {@link #getAffineTransform(GeodeticDatum)}
instead.
+     *
+     * @param  target The target geodetic datum.
+     * @return Bursa Wolf parameters from this datum to the given target datum, or {@code
null} if none.
+     */
+    public BursaWolfParameters getBursaWolfParameters(final GeodeticDatum target) {
+        if (bursaWolf != null) {
+            for (final BursaWolfParameters candidate : bursaWolf) {
+                if (deepEquals(target, candidate.targetDatum, ComparisonMode.IGNORE_METADATA))
{
+                    return candidate;
+                }
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Returns a direct reference to the {@link #bursaWolf} of the given datum if it exists,
+     * or {@code null} otherwise. This method does not clone the array - do not modify!
+     */
+    private static BursaWolfParameters[] bursaWolf(final GeodeticDatum datum) {
+        return (datum instanceof DefaultGeodeticDatum) ? ((DefaultGeodeticDatum) datum).bursaWolf
: null;
+    }
+
+    /**
+     * Returns a matrix that can be used to define a transformation to the specified datum.
+     * If no transformation path is found, then this method returns {@code null}.
+     *
+     * @param  targetDatum The target datum.
+     * @return An affine transform from {@code this} to {@code target}, or {@code null} if
none.
+     *
+     * @see BursaWolfParameters#getAffineTransform()
+     */
+    public Matrix getAffineTransform(final GeodeticDatum targetDatum) {
+        ensureNonNull("targetDatum", targetDatum);
+        try {
+            return getAffineTransform(this, targetDatum, null);
+        } catch (NoninvertibleMatrixException e) {
+            /*
+             * Should never happen, unless the user has overriden BursaWolfParameters.getAffineTransform()
+             * and create an invalid matrix. Returning 'null' is compliant with this method
contract.
+             */
+            Logging.unexpectedException(DefaultGeodeticDatum.class, "getAffineTransform",
e);
+            return null;
+        }
+    }
+
+    /**
+     * Returns a matrix that can be used to define a transformation to the specified datum.
+     * If no transformation path is found, then this method returns {@code null}.
+     *
+     * @param  source The source datum, or {@code null}.
+     * @param  target The target datum, or {@code null}.
+     * @param  exclusion The set of datum to exclude from the search, or {@code null}.
+     *         This is used in order to avoid never-ending recursivity.
+     * @return An affine transform from {@code source} to {@code target}, or {@code null}
if none.
+     */
+    private static Matrix getAffineTransform(final GeodeticDatum source, final GeodeticDatum
target,
+            Set<GeodeticDatum> exclusion) throws NoninvertibleMatrixException
+    {
+        final BursaWolfParameters[] sourceParam = bursaWolf(source);
+        if (sourceParam != null) {
+            for (final BursaWolfParameters candidate : sourceParam) {
+                if (deepEquals(target, candidate.targetDatum, ComparisonMode.IGNORE_METADATA))
{
+                    return candidate.getAffineTransform();
+                }
+            }
+        }
+        /*
+         * No transformation found to the specified target datum.
+         * Search if a transform exists in the opposite direction.
+         */
+        final BursaWolfParameters[] targetParam = bursaWolf(target);
+        if (targetParam != null) {
+            for (final BursaWolfParameters candidate : targetParam) {
+                if (deepEquals(source, candidate.targetDatum, ComparisonMode.IGNORE_METADATA))
{
+                    return MatrixSIS.castOrCopy(candidate.getAffineTransform()).inverse();
+                }
+            }
+        }
+        /*
+         * No direct tranformation found. Search for a path through some intermediate datum.
+         * First, search if there is some BursaWolfParameters for the same target in both
+         * 'source' and 'target' datum. If such an intermediate is found, ask for a path
as below:
+         *
+         *    source   →   [common datum]   →   target
+         */
+        if (sourceParam != null && targetParam != null) {
+            for (int i=0; i<sourceParam.length; i++) {
+                final GeodeticDatum sourceStep = sourceParam[i].targetDatum;
+                for (int j=0; j<targetParam.length; j++) {
+                    final GeodeticDatum targetStep = targetParam[j].targetDatum;
+                    if (deepEquals(sourceStep, targetStep, ComparisonMode.IGNORE_METADATA))
{
+                        if (exclusion == null) {
+                            exclusion = new HashSet<>();
+                        }
+                        if (exclusion.add(source)) {
+                            if (exclusion.add(target)) {
+                                final Matrix step1 = getAffineTransform(source, sourceStep,
exclusion);
+                                if (step1 != null) {
+                                    final Matrix step2 = getAffineTransform(targetStep, target,
exclusion);
+                                    if (step2 != null) {
+                                        /*
+                                         * MatrixSIS.multiply(MatrixSIS) is equivalent to
AffineTransform.concatenate(…):
+                                         * First transform by the supplied transform and
then transform the result
+                                         * by the original transform.
+                                         */
+                                        return MatrixSIS.castOrCopy(step2).multiply(step1);
+                                    }
+                                }
+                                exclusion.remove(target);
+                            }
+                            exclusion.remove(source);
+                        }
+                    }
+                }
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Compare this datum with the specified object for equality.
+     *
+     * @param  object The object to compare to {@code this}.
+     * @param  mode {@link ComparisonMode#STRICT STRICT} for performing a strict comparison,
or
+     *         {@link ComparisonMode#IGNORE_METADATA IGNORE_METADATA} for comparing only
properties
+     *         relevant to transformations.
+     * @return {@code true} if both objects are equal.
+     */
+    @Override
+    public boolean equals(final Object object, final ComparisonMode mode) {
+        if (object == this) {
+            return true; // Slight optimization.
+        }
+        if (super.equals(object, mode)) {
+            switch (mode) {
+                case STRICT: {
+                    final DefaultGeodeticDatum that = (DefaultGeodeticDatum) object;
+                    return Objects.equals(this.ellipsoid,     that.ellipsoid)     &&
+                           Objects.equals(this.primeMeridian, that.primeMeridian) &&
+                            Arrays.equals(this.bursaWolf,     that.bursaWolf);
+                }
+                default: {
+                    final GeodeticDatum that = (GeodeticDatum) object;
+                    return deepEquals(getEllipsoid(),     that.getEllipsoid(),     mode)
&&
+                           deepEquals(getPrimeMeridian(), that.getPrimeMeridian(), mode);
+                    /*
+                     * HACK: We do not consider Bursa Wolf parameters as a non-metadata field.
+                     *       This is needed in order to get equalsIgnoreMetadata(...) to
returns
+                     *       'true' when comparing the WGS84 constant in this class with
a WKT
+                     *       DATUM element with a TOWGS84[0,0,0,0,0,0,0] element. Furthermore,
+                     *       the Bursa Wolf parameters are not part of ISO 19111 specification.
+                     *       We don't want two CRS to be considered as different because
one has
+                     *       more of those transformation informations (which is nice, but
doesn't
+                     *       change the CRS itself).
+                     */
+                }
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Computes a hash value consistent with the given comparison mode.
+     *
+     * @return The hash code value for the given comparison mode.
+     */
+    @Override
+    public int hashCode(final ComparisonMode mode) throws IllegalArgumentException {
+        return (Objects.hashCode(ellipsoid) * 31 + Objects.hashCode(primeMeridian)) * 31
+ super.hashCode(mode);
+    }
+
+    /**
+     * Formats the inner part of a <cite>Well Known Text</cite> (WKT) element.
+     *
+     * @param  formatter The formatter to use.
+     * @return The WKT element name, which is {@code "DATUM"}.
+     */
+    @Override
+    public String formatTo(final Formatter formatter) {
+        // Do NOT invokes the super-class method, because
+        // horizontal datum do not write the datum type.
+        formatter.append(ellipsoid);
+        if (bursaWolf != null) {
+            for (final BursaWolfParameters candidate : bursaWolf) {
+                if (candidate.isToWGS84()) {
+                    formatter.append(candidate);
+                    break;
+                }
+            }
+        }
+        return "DATUM";
+    }
+}

Propchange: sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/DefaultGeodeticDatum.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/DefaultGeodeticDatum.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain;charset=UTF-8

Modified: sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/internal/util/CollectionsExt.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/internal/util/CollectionsExt.java?rev=1533254&r1=1533253&r2=1533254&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/internal/util/CollectionsExt.java
[UTF-8] (original)
+++ sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/internal/util/CollectionsExt.java
[UTF-8] Thu Oct 17 20:33:54 2013
@@ -17,6 +17,7 @@
 package org.apache.sis.internal.util;
 
 import java.util.*;
+import java.lang.reflect.Array;
 import org.apache.sis.util.Static;
 import org.apache.sis.util.collection.CodeListSet;
 import org.apache.sis.util.resources.Errors;
@@ -91,16 +92,29 @@ public final class CollectionsExt extend
     }
 
     /**
+     * Returns the given array if non-empty, or {@code null} if the given array is null or
empty.
+     * This method is generally not recommended, since public API should prefer empty array
instead of null.
+     * However this method is occasionally useful for managing private fields.
+     *
+     * @param  <E> The type of elements in the array.
+     * @param  array The array, or {@code null}.
+     * @return The given array, or {@code null} if the given array was empty.
+     */
+    public static <E> E[] nonEmpty(final E[] array) {
+        return (array != null && array.length == 0) ? null : array;
+    }
+
+    /**
      * Returns the given collection if non-empty, or {@code null} if the given collection
is null or empty.
-     * This method is generally not recommended, since public API should prefer empty collection
instead of
-     * null. However it is occasionally useful for managing private fields, especially for
inter-operability
+     * This method is generally not recommended, since public API should prefer empty collection
instead of null.
+     * However this method is occasionally useful for managing private fields, especially
for inter-operability
      * with frameworks that may expect or return null (e.g. if we want to exclude completely
an empty collection
      * from marshalling with JAXB).
      *
      * @param  <T> The type of the collection.
      * @param  <E> The type of elements in the collection.
      * @param  c   The collection, or {@code null}.
-     * @return The given collection, or an empty set of the given collection was null.
+     * @return The given collection, or {@code null} if the given collection was empty.
      */
     public static <T extends Collection<E>, E> T nonEmpty(final T c) {
         return (c != null && c.isEmpty()) ? null : c;
@@ -111,7 +125,7 @@ public final class CollectionsExt extend
      *
      * @param  <E> The type of elements in the collection.
      * @param  c The collection, or {@code null}.
-     * @return The given collection, or an empty set of the given collection was null.
+     * @return The given collection, or an empty set if the given collection was null.
      */
     public static <E> Collection<E> nonNull(final Collection<E> c) {
         return (c != null) ? c : Collections.<E>emptySet();
@@ -122,13 +136,68 @@ public final class CollectionsExt extend
      *
      * @param  <E> The type of elements in the collection.
      * @param  c The collection, or {@code null}.
-     * @return The given collection, or an empty set of the given collection was null.
+     * @return The given collection, or an empty set if the given collection was null.
      */
     public static <E> Set<E> nonNull(final Set<E> c) {
         return (c != null) ? c : Collections.<E>emptySet();
     }
 
     /**
+     * Given a value which is either {@code null}, an instance of {@code <E>} or an
array of {@code <E>},
+     * returns a non-null array containing all elements without null and without duplicated
values.
+     * More specifically:
+     *
+     * <ul>
+     *   <li>If the given value is {@code null}, then this method returns {@code emptyArray}.</li>
+     *   <li>If the given value is an instance of {@code <E>}, then this method
returns an array of length 1
+     *       which contain only {@code value}.</li>
+     *   <li>If the given value is an array of {@code <E>}, then this method
returns copy of that array,
+     *       omitting {@code null} elements and duplicated elements.</li>
+     *   <li>Otherwise this method throws {@link IllegalArgumentException}.</li>
+     * </ul>
+     *
+     * {@note It would be very easy to add support for <code>value</code> argument
of type <code>Object[]</code>
+     *        or collections. But we do not provide such support for now because this method
is used mostly as a
+     *        helper method for constructors of <code>AbstractIdentifiedObject</code>
subclasses receiving a map
+     *        of properties, and the contract of our constructors do not allow those other
types for now.}
+     *
+     * @param  <E>        The type of elements in the array to be returned.
+     * @param  name       The parameter name, used only for formatting an error message in
case of failure.
+     * @param  value      The value to return as an array, or {@code null}.
+     * @param  emptyArray An instance of {@code new E[0]}. This argument can not be null.
+     * @return The given value as an array of {@code <E>}. Never null.
+     * throws  IllegalArgumentException If the given value is not null, an instance of {@code
<E>}
+     *         or an array of {@code <E>}.
+     *
+     * @since 0.4
+     */
+    @SuppressWarnings("unchecked")
+    public static <E> E[] nonNullArraySet(final String name, final Object value, final
E[] emptyArray)
+            throws IllegalArgumentException
+    {
+        if (value == null) {
+            return emptyArray;
+        }
+        Class<?> type = emptyArray.getClass();
+        final Class<?> valueType = value.getClass();
+        if (valueType.isArray()) {
+            if (type.isAssignableFrom(valueType)) {
+                final Set<E> set = new LinkedHashSet<>(Arrays.asList((E[]) value));
+                set.remove(null);
+                return set.toArray(emptyArray);
+            }
+        } else {
+            type = type.getComponentType();
+            if (type.isAssignableFrom(valueType)) {
+                final E[] array = (E[]) Array.newInstance(type, 1);
+                array[0] = (E) value;
+                return array;
+            }
+        }
+        throw new IllegalArgumentException(Errors.format(Errors.Keys.IllegalPropertyClass_2,
name, valueType));
+    }
+
+    /**
      * Returns the specified array as an immutable set, or {@code null} if the array is null.
      * If the given array contains duplicated elements, i.e. elements that are equal in the
      * sense of {@link Object#equals(Object)}, then only the last instance of the duplicated



Mime
View raw message