sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject svn commit: r1802945 [2/3] - in /sis/trunk: ./ core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/ core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/ core/sis-referencing/src/main/java/org/apache/sis/internal/referenc...
Date Tue, 25 Jul 2017 14:07:14 GMT
Modified: sis/trunk/storage/sis-gdal/src/main/c/org_apache_sis_storage_gdal_PJ.c
URL: http://svn.apache.org/viewvc/sis/trunk/storage/sis-gdal/src/main/c/org_apache_sis_storage_gdal_PJ.c?rev=1802945&r1=1802944&r2=1802945&view=diff
==============================================================================
--- sis/trunk/storage/sis-gdal/src/main/c/org_apache_sis_storage_gdal_PJ.c [UTF-8] (original)
+++ sis/trunk/storage/sis-gdal/src/main/c/org_apache_sis_storage_gdal_PJ.c [UTF-8] Tue Jul 25 14:07:13 2017
@@ -16,7 +16,7 @@
  */
 #include <math.h>
 #include <string.h>
-#include <projects.h>
+#include <proj_api.h>
 #include "org_apache_sis_storage_gdal_PJ.h"
 
 
@@ -35,10 +35,10 @@
  * \return The address of the PJ structure, or NULL if the operation fails (for example
  *         because the "ptr" field was not found).
  */
-PJ *getPJ(JNIEnv *env, jobject object)
+projPJ getPJ(JNIEnv *env, jobject object)
 {
     jfieldID id = (*env)->GetFieldID(env, (*env)->GetObjectClass(env, object), PJ_FIELD_NAME, PJ_FIELD_TYPE);
-    return (id) ? (PJ*) (*env)->GetLongField(env, object, id) : NULL;
+    return (id) ? (projPJ) (*env)->GetLongField(env, object, id) : NULL;
 }
 
 /*!
@@ -70,7 +70,7 @@ JNIEXPORT jlong JNICALL Java_org_apache_
 {
     const char *def_utf = (*env)->GetStringUTFChars(env, definition, NULL);
     if (!def_utf) return 0;             // OutOfMemoryError already thrown.
-    PJ *pj = pj_init_plus(def_utf);
+    projPJ pj = pj_init_plus(def_utf);
     (*env)->ReleaseStringUTFChars(env, definition, def_utf);
     return (jlong) pj;
 }
@@ -87,7 +87,7 @@ JNIEXPORT jlong JNICALL Java_org_apache_
 JNIEXPORT jlong JNICALL Java_org_apache_sis_storage_gdal_PJ_allocateGeoPJ
   (JNIEnv *env, jclass class, jobject projected)
 {
-    PJ *pj = getPJ(env, projected);
+    projPJ pj = getPJ(env, projected);
     return (pj) ? (jlong) pj_latlong_from_proj(pj) : 0;
 }
 
@@ -102,7 +102,7 @@ JNIEXPORT jlong JNICALL Java_org_apache_
 JNIEXPORT jstring JNICALL Java_org_apache_sis_storage_gdal_PJ_getCode
   (JNIEnv *env, jobject object)
 {
-    PJ *pj = getPJ(env, object);
+    projPJ pj = getPJ(env, object);
     if (pj) {
         const char *desc = pj_get_def(pj, 0);
         if (desc) {
@@ -116,27 +116,6 @@ JNIEXPORT jstring JNICALL Java_org_apach
 
 /*!
  * \brief
- * Returns the description associated to the PJ structure.
- *
- * \param  env    - The JNI environment.
- * \param  object - The Java object wrapping the PJ structure (not allowed to be NULL).
- * \return The description associated to the PJ structure.
- */
-JNIEXPORT jstring JNICALL Java_org_apache_sis_storage_gdal_PJ_getName
-  (JNIEnv *env, jobject object)
-{
-    PJ *pj = getPJ(env, object);
-    if (pj) {
-        const char *desc = pj->descr;
-        if (desc) {
-            return (*env)->NewStringUTF(env, desc);
-        }
-    }
-    return NULL;
-}
-
-/*!
- * \brief
  * Returns the CRS type as one of the PJ.Type enum: GEOGRAPHIC, GEOCENTRIC or PROJECTED.
  * This function should never return NULL, unless class or fields have been renamed in
  * such a way that we can not find anymore the expected enum values.
@@ -148,7 +127,7 @@ JNIEXPORT jstring JNICALL Java_org_apach
 JNIEXPORT jobject JNICALL Java_org_apache_sis_storage_gdal_PJ_getType
   (JNIEnv *env, jobject object)
 {
-    PJ *pj = getPJ(env, object);
+    projPJ pj = getPJ(env, object);
     if (pj) {
         const char *type;
         if (pj_is_latlong(pj)) {
@@ -171,76 +150,27 @@ JNIEXPORT jobject JNICALL Java_org_apach
 
 /*!
  * \brief
- * Returns the semi-major axis length.
- *
- * \param  env    - The JNI environment.
- * \param  object - The Java object wrapping the PJ structure (not allowed to be NULL).
- * \return The semi-major axis length.
- */
-JNIEXPORT jdouble JNICALL Java_org_apache_sis_storage_gdal_PJ_getSemiMajorAxis
-  (JNIEnv *env, jobject object)
-{
-    PJ *pj = getPJ(env, object);
-    return pj ? pj->a_orig : NAN;
-}
-
-/*!
- * \brief
- * Computes the semi-minor axis length from the semi-major axis length and the eccentricity
- * squared.
- *
- * \param  env    - The JNI environment.
- * \param  object - The Java object wrapping the PJ structure (not allowed to be NULL).
- * \return The semi-minor axis length.
- */
-JNIEXPORT jdouble JNICALL Java_org_apache_sis_storage_gdal_PJ_getSemiMinorAxis
-  (JNIEnv *env, jobject object)
-{
-    PJ *pj = getPJ(env, object);
-    if (!pj) return NAN;
-    double a = pj->a_orig;
-    return sqrt(a*a * (1.0 - pj->es_orig));
-}
-
-/*!
- * \brief
- * Returns the eccentricity squared.
- *
- * \param  env    - The JNI environment.
- * \param  object - The Java object wrapping the PJ structure (not allowed to be NULL).
- * \return The eccentricity.
- */
-JNIEXPORT jdouble JNICALL Java_org_apache_sis_storage_gdal_PJ_getEccentricitySquared
-  (JNIEnv *env, jobject object)
-{
-    PJ *pj = getPJ(env, object);
-    return pj ? pj->es_orig : NAN;
-}
-
-/*!
- * \brief
- * Returns an array of character indicating the direction of each axis.
+ * Returns the semi-major axis length and eccentricity squared in an array of length 2.
  *
  * \param  env    - The JNI environment.
  * \param  object - The Java object wrapping the PJ structure (not allowed to be NULL).
- * \return The axis directions.
+ * \return The semi-major axis length and eccentricity squared in an array of length 2.
  */
-JNIEXPORT jcharArray JNICALL Java_org_apache_sis_storage_gdal_PJ_getAxisDirections
+JNIEXPORT jdoubleArray JNICALL Java_org_apache_sis_storage_gdal_PJ_getEllipsoidDefinition
   (JNIEnv *env, jobject object)
 {
-    PJ *pj = getPJ(env, object);
+    projPJ pj = getPJ(env, object);
     if (pj) {
-        int length = strlen(pj->axis);
-        jcharArray array = (*env)->NewCharArray(env, length);
+        double major_axis;
+        double eccentricity_squared;
+        pj_get_spheroid_defn(pj, &major_axis, &eccentricity_squared);
+        jdoubleArray array = (*env)->NewDoubleArray(env, 2);
         if (array) {
-            jchar* axis = (*env)->GetCharArrayElements(env, array, NULL);
-            if (axis) {
-                // Don't use memcp because the type may not be the same.
-                int i;
-                for (i=0; i<length; i++) {
-                    axis[i] = pj->axis[i];
-                }
-                (*env)->ReleaseCharArrayElements(env, array, axis, 0);
+            jdouble* def = (*env)->GetDoubleArrayElements(env, array, NULL);
+            if (def) {
+                def[0] = major_axis;
+                def[1] = eccentricity_squared;
+                (*env)->ReleaseDoubleArrayElements(env, array, def, 0);
             }
             return array;
         }
@@ -250,40 +180,6 @@ JNIEXPORT jcharArray JNICALL Java_org_ap
 
 /*!
  * \brief
- * Longitude of the prime meridian measured from the Greenwich meridian, positive eastward.
- *
- * \param env    - The JNI environment.
- * \param object - The Java object wrapping the PJ structure (not allowed to be NULL).
- * \return The prime meridian longitude, in degrees.
- */
-JNIEXPORT jdouble JNICALL Java_org_apache_sis_storage_gdal_PJ_getGreenwichLongitude
-  (JNIEnv *env, jobject object)
-{
-    PJ *pj = getPJ(env, object);
-    return (pj) ? (pj->from_greenwich)*(180/M_PI) : NAN;
-}
-
-/*!
- * \brief
- * Returns the conversion factor from linear units to metres.
- *
- * \param env      - The JNI environment.
- * \param object   - The Java object wrapping the PJ structure (not allowed to be NULL).
- * \param vertical - JNI_FALSE for horizontal axes, or JNI_TRUE for the vertical axis.
- * \return The conversion factor to metres.
- */
-JNIEXPORT jdouble JNICALL Java_org_apache_sis_storage_gdal_PJ_getLinearUnitToMetre
-  (JNIEnv *env, jobject object, jboolean vertical)
-{
-    PJ *pj = getPJ(env, object);
-    if (pj) {
-        return (vertical) ? pj->vto_meter : pj->to_meter;
-    }
-    return NAN;
-}
-
-/*!
- * \brief
  * Converts input values from degrees to radians before coordinate operation, or the output
  * values from radians to degrees after the coordinate operation.
  *
@@ -293,7 +189,7 @@ JNIEXPORT jdouble JNICALL Java_org_apach
  * \param dimension - Dimension of points in the coordinate array.
  * \param factor    - The scale factor to apply: M_PI/180 for inputs or 180/M_PI for outputs.
  */
-void convertAngularOrdinates(PJ *pj, double* data, jint numPts, int dimension, double factor) {
+void convertAngularOrdinates(projPJ pj, double* data, jint numPts, int dimension, double factor) {
     int dimToSkip;
     if (pj_is_latlong(pj)) {
         // Convert only the 2 first ordinates and skip all the other dimensions.
@@ -339,7 +235,7 @@ JNIEXPORT void JNICALL Java_org_apache_s
         if (c) (*env)->ThrowNew(env, c, "The target CRS and the coordinates array can not be null.");
         return;
     }
-    if (dimension < 2 || dimension > 100) { // Arbitrary upper value for catching potential misuse.
+    if (dimension < 2 || dimension > 100) {     // Arbitrary upper value for catching potential misuse.
         jclass c = (*env)->FindClass(env, "java/lang/IllegalArgumentException");
         if (c) (*env)->ThrowNew(env, c, "Illegal dimension. Must be in the [2-100] range.");
         return;
@@ -349,8 +245,8 @@ JNIEXPORT void JNICALL Java_org_apache_s
         if (c) (*env)->ThrowNew(env, c, "Illegal offset or illegal number of points.");
         return;
     }
-    PJ *src_pj = getPJ(env, object);
-    PJ *dst_pj = getPJ(env, target);
+    projPJ src_pj = getPJ(env, object);
+    projPJ dst_pj = getPJ(env, target);
     if (src_pj && dst_pj) {
         // Using GetPrimitiveArrayCritical/ReleasePrimitiveArrayCritical rather than
         // GetDoubleArrayElements/ReleaseDoubleArrayElements increase the chances that
@@ -385,9 +281,9 @@ JNIEXPORT void JNICALL Java_org_apache_s
 JNIEXPORT jstring JNICALL Java_org_apache_sis_storage_gdal_PJ_getLastError
   (JNIEnv *env, jobject object)
 {
-    PJ *pj = getPJ(env, object);
+    projPJ pj = getPJ(env, object);
     if (pj) {
-        int err = pj_ctx_get_errno(pj->ctx);
+        int err = *pj_get_errno_ref();
         if (err) {
             return (*env)->NewStringUTF(env, pj_strerrno(err));
         }
@@ -412,7 +308,7 @@ JNIEXPORT void JNICALL Java_org_apache_s
 {
     jfieldID id = (*env)->GetFieldID(env, (*env)->GetObjectClass(env, object), PJ_FIELD_NAME, PJ_FIELD_TYPE);
     if (id) {
-        PJ *pj = (PJ*) (*env)->GetLongField(env, object, id);
+        projPJ pj = (projPJ) (*env)->GetLongField(env, object, id);
         if (pj) {
             (*env)->SetLongField(env, object, id, (jlong) 0);
             pj_free(pj);

Modified: sis/trunk/storage/sis-gdal/src/main/c/org_apache_sis_storage_gdal_PJ.h
URL: http://svn.apache.org/viewvc/sis/trunk/storage/sis-gdal/src/main/c/org_apache_sis_storage_gdal_PJ.h?rev=1802945&r1=1802944&r2=1802945&view=diff
==============================================================================
--- sis/trunk/storage/sis-gdal/src/main/c/org_apache_sis_storage_gdal_PJ.h [UTF-8] (original)
+++ sis/trunk/storage/sis-gdal/src/main/c/org_apache_sis_storage_gdal_PJ.h [UTF-8] Tue Jul 25 14:07:13 2017
@@ -7,8 +7,6 @@
 #ifdef __cplusplus
 extern "C" {
 #endif
-#undef org_apache_sis_storage_gdal_PJ_DIMENSION_MAX
-#define org_apache_sis_storage_gdal_PJ_DIMENSION_MAX 100L
 /*
  * Class:     org_apache_sis_storage_gdal_PJ
  * Method:    allocatePJ
@@ -43,14 +41,6 @@ JNIEXPORT jstring JNICALL Java_org_apach
 
 /*
  * Class:     org_apache_sis_storage_gdal_PJ
- * Method:    getName
- * Signature: ()Ljava/lang/String;
- */
-JNIEXPORT jstring JNICALL Java_org_apache_sis_storage_gdal_PJ_getName
-  (JNIEnv *, jobject);
-
-/*
- * Class:     org_apache_sis_storage_gdal_PJ
  * Method:    getType
  * Signature: ()Lorg/apache/sis/storage/gdal/PJ/Type;
  */
@@ -59,54 +49,14 @@ JNIEXPORT jobject JNICALL Java_org_apach
 
 /*
  * Class:     org_apache_sis_storage_gdal_PJ
- * Method:    getEccentricitySquared
- * Signature: ()D
- */
-JNIEXPORT jdouble JNICALL Java_org_apache_sis_storage_gdal_PJ_getEccentricitySquared
-  (JNIEnv *, jobject);
-
-/*
- * Class:     org_apache_sis_storage_gdal_PJ
- * Method:    getSemiMajorAxis
- * Signature: ()D
- */
-JNIEXPORT jdouble JNICALL Java_org_apache_sis_storage_gdal_PJ_getSemiMajorAxis
-  (JNIEnv *, jobject);
-
-/*
- * Class:     org_apache_sis_storage_gdal_PJ
- * Method:    getSemiMinorAxis
- * Signature: ()D
- */
-JNIEXPORT jdouble JNICALL Java_org_apache_sis_storage_gdal_PJ_getSemiMinorAxis
-  (JNIEnv *, jobject);
-
-/*
- * Class:     org_apache_sis_storage_gdal_PJ
- * Method:    getGreenwichLongitude
- * Signature: ()D
+ * Method:    getEllipsoidDefinition
+ * Signature: ()[D
  */
-JNIEXPORT jdouble JNICALL Java_org_apache_sis_storage_gdal_PJ_getGreenwichLongitude
+JNIEXPORT jdoubleArray JNICALL Java_org_apache_sis_storage_gdal_PJ_getEllipsoidDefinition
   (JNIEnv *, jobject);
 
 /*
  * Class:     org_apache_sis_storage_gdal_PJ
- * Method:    getAxisDirections
- * Signature: ()[C
- */
-JNIEXPORT jcharArray JNICALL Java_org_apache_sis_storage_gdal_PJ_getAxisDirections
-  (JNIEnv *, jobject);
-
-/*
- * Class:     org_apache_sis_storage_gdal_PJ
- * Method:    getLinearUnitToMetre
- * Signature: (Z)D
- */
-JNIEXPORT jdouble JNICALL Java_org_apache_sis_storage_gdal_PJ_getLinearUnitToMetre
-  (JNIEnv *, jobject, jboolean);
-
-/*
- * Class:     org_apache_sis_storage_gdal_PJ
  * Method:    transform
  * Signature: (Lorg/apache/sis/storage/gdal/PJ;I[DII)V
  */

Modified: sis/trunk/storage/sis-gdal/src/main/java/org/apache/sis/storage/gdal/PJ.java
URL: http://svn.apache.org/viewvc/sis/trunk/storage/sis-gdal/src/main/java/org/apache/sis/storage/gdal/PJ.java?rev=1802945&r1=1802944&r2=1802945&view=diff
==============================================================================
--- sis/trunk/storage/sis-gdal/src/main/java/org/apache/sis/storage/gdal/PJ.java [UTF-8] (original)
+++ sis/trunk/storage/sis-gdal/src/main/java/org/apache/sis/storage/gdal/PJ.java [UTF-8] Tue Jul 25 14:07:13 2017
@@ -17,6 +17,9 @@
 package org.apache.sis.storage.gdal;
 
 import java.util.Objects;
+import java.io.Serializable;
+import java.io.ObjectStreamException;
+import java.io.InvalidObjectException;
 import org.opengis.util.FactoryException;
 import org.opengis.util.InternationalString;
 import org.opengis.referencing.ReferenceIdentifier;
@@ -29,9 +32,7 @@ import org.opengis.referencing.operation
 import org.apache.sis.referencing.factory.InvalidGeodeticParameterException;
 import org.apache.sis.referencing.IdentifiedObjects;
 import org.apache.sis.metadata.iso.citation.Citations;
-import org.apache.sis.util.iso.SimpleInternationalString;
 import org.apache.sis.util.resources.Errors;
-import org.apache.sis.util.CharSequences;
 import org.apache.sis.internal.util.Constants;
 import org.apache.sis.internal.system.OS;
 
@@ -51,13 +52,8 @@ import org.apache.sis.internal.system.OS
  * @since   0.8
  * @module
  */
-final class PJ implements ReferenceIdentifier {
-    /**
-     * The maximal number of dimension accepted by the {@link #transform(PJ, int, double[], int, int)} method.
-     * This upper limit is actually somewhat arbitrary. This limit exists mostly as a safety against potential misuse.
-     */
-    static final int DIMENSION_MAX = 100;
-
+@SuppressWarnings("serial")     // serialVersionUID not needed since writeReplace() gives another kind of object.
+final class PJ implements ReferenceIdentifier, Serializable {
     /**
      * Loads the {@literal Proj.4} library.
      * This static initializer may throw a {@link UnsatisfiedLinkError} if the static library can not be loaded.
@@ -69,8 +65,8 @@ final class PJ implements ReferenceIdent
     }
 
     /**
-     * The pointer to {@code PJ} structure allocated in the C/C++ heap. This value has no
-     * meaning in Java code. <strong>Do not modify</strong>, since this value is used by Proj.4.
+     * The pointer to {@code PJ} structure allocated in the C/C++ heap. This value has no meaning in Java code,
+     * except 0 which means no native object. <strong>Do not modify</strong>, since this value is used by Proj.4.
      * Do not rename neither, unless you update accordingly the C code in JNI wrappers.
      */
     private final long ptr;
@@ -196,31 +192,10 @@ final class PJ implements ReferenceIdent
 
     /**
      * Returns the string representation of the PJ structure.
-     * Note that the string returned by Proj.4 contains <cite>End Of Line</cite> characters.
-     *
-     * <div class="note"><b>Example:</b> "Lat/long (Geodetic alias)"</div>
-     */
-    native String getName();
-
-    /**
-     * Returns the string representation of the PJ structure.
      *
      * @return the string representation, or {@code null} if none.
      */
     public InternationalString getDescription() {
-        String name = getName();
-        if (name != null) {
-            final StringBuilder buffer = new StringBuilder(name.length());
-            for (CharSequence line : CharSequences.splitOnEOL(getName())) {
-                line = CharSequences.trimWhitespaces(line);
-                if (buffer.length() != 0) buffer.append(' ');
-                buffer.append(line);
-            }
-            name = buffer.toString();
-            if (!name.isEmpty()) {
-               return new SimpleInternationalString(name);
-            }
-        }
         return null;
     }
 
@@ -262,69 +237,21 @@ final class PJ implements ReferenceIdent
     }
 
     /**
-     * Returns the square of the ellipsoid eccentricity (ε²). The eccentricity is related to axis length
-     * by ε=√(1-(<var>b</var>/<var>a</var>)²). The eccentricity of a sphere is zero.
+     * Returns the semi-major axis length and the square of the ellipsoid eccentricity (ε²).
+     * The eccentricity is related to axis length by ε=√(1-(<var>b</var>/<var>a</var>)²).
+     * The eccentricity of a sphere is zero. Other related quantities are:
+     *
+     * <ul>
+     *   <li>semi-minor axis length: b = a × √(1 - ε²)</li>
+     *   <li>inverse flattening: invf = 1 / (1 - √(1 - ε²))</li>
+     * </ul>
      *
-     * @return the eccentricity.
+     * @return the semi-major axis length and the eccentricity squared in an array of length 2.
      *
      * @see Ellipsoid#isSphere()
      * @see Ellipsoid#getInverseFlattening()
      */
-    public native double getEccentricitySquared();
-
-    /**
-     * Returns the inverse flattening, computed from the eccentricity.
-     * The inverse flattening factor of a sphere is infinity.
-     */
-    public double getInverseFlattening() {
-        return 1 / (1 - Math.sqrt(1 - getEccentricitySquared()));
-    }
-
-    /**
-     * Returns the value stored in the {@code a_orig} PJ field.
-     *
-     * @return the axis length stored in {@code a_orig}.
-     *
-     * @see Ellipsoid#getSemiMajorAxis()
-     */
-    public native double getSemiMajorAxis();
-
-    /**
-     * Returns the value computed from PJ fields by {@code √((a_orig)² × (1 - es_orig))}.
-     *
-     * @return the axis length computed by {@code √((a_orig)² × (1 - es_orig))}.
-     *
-     * @see Ellipsoid#getSemiMinorAxis()
-     */
-    public native double getSemiMinorAxis();
-
-    /**
-     * Longitude of the prime meridian measured from the Greenwich meridian, positive eastward.
-     *
-     * @return the prime meridian longitude, in degrees.
-     *
-     * @see PrimeMeridian#getGreenwichLongitude()
-     */
-    public native double getGreenwichLongitude();
-
-    /**
-     * Returns an array of character indicating the direction of each axis. Directions are
-     * characters like {@code 'e'} for East, {@code 'n'} for North and {@code 'u'} for Up.
-     *
-     * @return the axis directions.
-     *
-     * @see org.opengis.referencing.cs.CoordinateSystemAxis#getDirection()
-     */
-    public native char[] getAxisDirections();
-
-    /**
-     * Returns the conversion factor from the linear units to metres.
-     *
-     * @param  vertical {@code false} for the conversion factor of horizontal axes,
-     *         or {@code true} for the conversion factor of the vertical axis.
-     * @return the conversion factor to metres for the given axis.
-     */
-    public native double getLinearUnitToMetre(boolean vertical);
+    public native double[] getEllipsoidDefinition();
 
     /**
      * Transforms in-place the coordinates in the given array.
@@ -339,7 +266,7 @@ final class PJ implements ReferenceIdent
      * </ul>
      *
      * @param  target       the target CRS.
-     * @param  dimension    the dimension of each coordinate value. Must be in the [2-{@value #DIMENSION_MAX}] range.
+     * @param  dimension    the dimension of each coordinate value. Typically 2, 3 or 4.
      * @param  coordinates  the coordinates to transform, as a sequence of (<var>x</var>,<var>y</var>,&lt;<var>z</var>&gt;,…) tuples.
      * @param  offset       offset of the first coordinate in the given array.
      * @param  numPts       number of points to transform.
@@ -394,4 +321,52 @@ final class PJ implements ReferenceIdent
     @Override
     @SuppressWarnings("FinalizeDeclaration")
     protected final native void finalize();
+
+    /**
+     * The object serialized in place of {@literal Proj.4} wrappers.
+     * Deserialization checks for the presence of Proj.4 native library.
+     *
+     * @author  Martin Desruisseaux (Geomatys)
+     * @version 0.8
+     * @since   0.8
+     * @module
+     */
+    private static final class Serialized implements Serializable {
+        /**
+         * For cross-version compatibility.
+         */
+        private static final long serialVersionUID = -5705027681492462823L;
+
+        /**
+         * The {@literal Proj.4} definition string.
+         */
+        private final String definition;
+
+        /**
+         * Creates a new proxy for the given {@literal Proj.4} definition.
+         */
+        Serialized(final String definition) {
+            this.definition = definition;
+        }
+
+        /**
+         * Automatically invoked on deserialization for reconstructing the wrapper
+         * from the {@literal Proj.4} definition string.
+         */
+        protected final Object readResolve() throws ObjectStreamException {
+            try {
+                return new PJ(definition);
+            } catch (UnsatisfiedLinkError | NoClassDefFoundError | InvalidGeodeticParameterException e) {
+                throw (InvalidObjectException) new InvalidObjectException(Proj4.unavailable(e)).initCause(e);
+            }
+        }
+    }
+
+    /**
+     * Invoked automatically on serialization.
+     * Replaces this {@literal Proj.4} wrapper by a proxy without native resource.
+     */
+    protected final Object writeReplace() throws ObjectStreamException {
+        return new Serialized(getCode());
+    }
 }

Modified: sis/trunk/storage/sis-gdal/src/main/java/org/apache/sis/storage/gdal/Proj4.java
URL: http://svn.apache.org/viewvc/sis/trunk/storage/sis-gdal/src/main/java/org/apache/sis/storage/gdal/Proj4.java?rev=1802945&r1=1802944&r2=1802945&view=diff
==============================================================================
--- sis/trunk/storage/sis-gdal/src/main/java/org/apache/sis/storage/gdal/Proj4.java [UTF-8] (original)
+++ sis/trunk/storage/sis-gdal/src/main/java/org/apache/sis/storage/gdal/Proj4.java [UTF-8] Tue Jul 25 14:07:13 2017
@@ -18,15 +18,18 @@ package org.apache.sis.storage.gdal;
 
 import javax.measure.Unit;
 import javax.measure.quantity.Angle;
+import javax.measure.format.ParserException;
 import org.opengis.metadata.Identifier;
 import org.opengis.util.FactoryException;
 import org.opengis.parameter.ParameterValue;
 import org.opengis.parameter.ParameterValueGroup;
 import org.opengis.parameter.GeneralParameterValue;
+import org.opengis.metadata.extent.GeographicBoundingBox;
 import org.opengis.referencing.IdentifiedObject;
 import org.opengis.referencing.crs.GeodeticCRS;
 import org.opengis.referencing.crs.ProjectedCRS;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
+import org.opengis.referencing.cs.CoordinateSystemAxis;
 import org.opengis.referencing.cs.AxisDirection;
 import org.opengis.referencing.cs.CoordinateSystem;
 import org.opengis.referencing.cs.CartesianCS;
@@ -36,8 +39,11 @@ import org.opengis.referencing.datum.Geo
 import org.opengis.referencing.datum.PrimeMeridian;
 import org.opengis.referencing.operation.Projection;
 import org.opengis.referencing.operation.CoordinateOperation;
+import org.apache.sis.referencing.operation.AbstractCoordinateOperation;
 import org.apache.sis.referencing.factory.UnavailableFactoryException;
+import org.apache.sis.referencing.factory.InvalidGeodeticParameterException;
 import org.apache.sis.referencing.IdentifiedObjects;
+import org.apache.sis.referencing.CRS;
 import org.apache.sis.metadata.iso.citation.Citations;
 import org.apache.sis.internal.metadata.AxisDirections;
 import org.apache.sis.internal.system.Modules;
@@ -46,6 +52,7 @@ import org.apache.sis.util.logging.Loggi
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.util.Static;
+import org.apache.sis.util.iso.Types;
 import org.apache.sis.measure.Units;
 
 
@@ -55,7 +62,7 @@ import org.apache.sis.measure.Units;
  *
  * <ul>
  *   <li>{@linkplain #createCRS Create a Coordinate Reference System instance from a Proj.4 definition string}.</li>
- *   <li>Conversely, {@link #definition get a Proj.4 definition string from a Coordinate Reference System}.</li>
+ *   <li>Conversely, {@linkplain #definition get a Proj.4 definition string from a Coordinate Reference System}.</li>
  *   <li>{@linkplain #createOperation Create a coordinate operation backed by Proj.4 between two arbitrary CRS}.</li>
  * </ul>
  *
@@ -80,7 +87,7 @@ public final class Proj4 extends Static
      *
      * <div class="note"><b>Example:</b> Rel. 4.9.3, 15 August 2016</div>
      *
-     * @return the Proj.4 release string, or {@code null} if no installation has been found.
+     * @return the Proj.4 release string, or {@code null} if the native library has been found.
      */
     public static String version() {
         try {
@@ -98,6 +105,7 @@ public final class Proj4 extends Static
     /**
      * Infers a {@literal Proj.4} definition from the given projected, geographic or geocentric coordinate reference system.
      * This method does not need the Proj.4 native library; it can be used in a pure Java application.
+     * However the returned definition string may differ depending on whether the Proj.4 library is available or not.
      *
      * @param  crs  the coordinate reference system for which to create a Proj.4 definition.
      * @return the definition of the given CRS in a Proj.4 format.
@@ -154,21 +162,40 @@ public final class Proj4 extends Static
          * Append the map projection parameters. Those parameters may include axis lengths (a and b),
          * but not necessarily. If axis lengths are specified, then we will ignore the Ellipsoid instance
          * associated to the CRS.
+         *
+         * The "+over" option is for disabling the default wrapping of output longitudes in the -180 to 180 range.
+         * We do that for having the same behavior between Proj.4 and Apache SIS. No wrapping reduce discontinuity
+         * problems with geometries that cross the anti-meridian.
+         *
+         * The "+no_defs" option is for ensuring that no defaults are read from "/usr/share/proj/proj_def.dat" file.
+         * That file contains default values for various map projections, for example "+lat_1=29.5" and "+lat_2=45.5"
+         * for the "aea" projection. Those defaults are assuming that users want Conterminous U.S. map.
+         * This may cause surprising behavior for users outside USA.
          */
         final StringBuilder definition = new StringBuilder(100);
-        definition.append("+proj=").append(method);
+        definition.append(Proj4Factory.PROJ_PARAM).append(method);
         boolean hasSemiMajor = false;
         boolean hasSemiMinor = false;
         if (parameters != null) {
+            definition.append(" +over +no_defs");                                       // See above comment
             for (final GeneralParameterValue parameter : parameters.values()) {
-                if (parameter instanceof ParameterValue) {
-                    final Object value = ((ParameterValue) parameter).getValue();
-                    if (value != null) {
-                        final String pn = name(parameter.getDescriptor());
-                        if (pn.equals("+a")) hasSemiMajor = true;
-                        if (pn.equals("+b")) hasSemiMinor = true;
-                        definition.append(' ').append(pn).append('=').append(value);
+                if (parameter instanceof ParameterValue<?>) {
+                    final ParameterValue<?> pv = (ParameterValue<?>) parameter;
+                    final Object value;
+                    Unit<?> unit = pv.getUnit();
+                    if (unit != null) {
+                        unit = Units.isAngular(unit) ? Units.DEGREE : unit.getSystemUnit();
+                        value = pv.doubleValue(unit);       // Always in metres or degrees.
+                    } else {
+                        value = pv.getValue();
+                        if (value == null) {
+                            continue;
+                        }
                     }
+                    final String pn = name(parameter.getDescriptor());
+                    hasSemiMajor |= pn.equals("+a");
+                    hasSemiMinor |= pn.equals("+b");
+                    definition.append(' ').append(pn).append('=').append(value);
                 }
             }
         }
@@ -190,24 +217,59 @@ public final class Proj4 extends Static
         /*
          * Appends axis directions. This method always format a vertical direction (up or down)
          * even if the coordinate system is two-dimensional, because Proj.4 seems to require it.
+         * Also extract axis units in the process.
          */
+        final Unit<?>[] units = new Unit<?>[2];     // Horizontal at index 0, vertical at index 1.
+        boolean validCS = true;
         definition.append(' ').append(Proj4Factory.AXIS_ORDER_PARAM);
         final int dimension = Math.min(cs.getDimension(), 3);
         boolean hasVertical = false;
         for (int i=0; i<dimension; i++) {
-            final AxisDirection dir = cs.getAxis(i).getDirection();
+            final CoordinateSystemAxis axis = cs.getAxis(i);
+            final AxisDirection dir = axis.getDirection();
+            int unitIndex = 0;
             if (!AxisDirections.isCardinal(dir)) {
                 if (!AxisDirections.isVertical(dir)) {
                     throw new FactoryException(Errors.format(Errors.Keys.UnsupportedAxisDirection_1, dir));
                 }
                 hasVertical = true;
+                unitIndex = 1;
             }
+            final Unit<?> old  = units[unitIndex];
+            units[unitIndex]   = axis.getUnit();
+            validCS &= (old == null || old.equals(units[unitIndex]));
             definition.appendCodePoint(Character.toLowerCase(dir.name().codePointAt(0)));
         }
         if (!hasVertical && dimension < 3) {
             definition.append('u');                    // Add a UP direction if not already present.
         }
-        return definition.toString();
+        /*
+         * Append units of measurement, then verify the coordinate system validity.
+         */
+        for (int i=0; i<units.length; i++) {
+            final Unit<?> unit = units[i];
+            if (unit != null && !unit.equals(Units.DEGREE) && !unit.equals(Units.METRE)) {
+                validCS &= Units.isLinear(unit);
+                definition.append(" +");
+                if (i == 1) definition.append('v');     // "+vto_meter" parameter.
+                definition.append("to_meter=").append(Units.toStandardUnit(unit));
+            }
+        }
+        if (validCS) {
+            return definition.toString();
+        }
+        /*
+         * If we reach this point, we detected a coordinate system that we can not format as a
+         * Proj.4 definition string. Format an error message with axis directions and units.
+         */
+        definition.setLength(0);
+        definition.append('(');
+        for (int i=0; i<units.length; i++) {
+            final CoordinateSystemAxis axis = cs.getAxis(i);
+            if (i != 0) definition.append(", ");
+            definition.append(axis.getUnit()).append(' ').append(Types.getCodeName(axis.getDirection()));
+        }
+        throw new FactoryException(Errors.format(Errors.Keys.IllegalCoordinateSystem_1, definition.append(')')));
     }
 
     /**
@@ -224,49 +286,105 @@ public final class Proj4 extends Static
 
     /**
      * Creates a new CRS from the given {@literal Proj.4} definition string.
+     * Some examples of definition strings are:
+     * <ul>
+     *   <li>{@code "+init=epsg:3395"} (see warning below)</li>
+     *   <li>{@code "+proj=latlong +datum=WGS84 +ellps=WGS84 +towgs84=0,0,0"}</li>
+     *   <li>{@code "+proj=merc +lon_0=0 +k=1 +x_0=0 +y_0=0 +datum=WGS84 +units=m +ellps=WGS84 +towgs84=0,0,0"}</li>
+     * </ul>
+     *
+     * <b>Warning:</b> despite the {@code "epsg"} word, coordinate reference systems created by {@code "+init=epsg:"}
+     * syntax are not necessarily compliant with EPSG definitions. In particular, the axis order is often different.
+     * Units of measurement may also differ.
      *
      * @param  definition  the Proj.4 definition string.
      * @param  dimension   the number of dimension of the CRS to create (2 or 3).
      * @return a CRS created from the given definition string and number of dimensions.
      * @throws NullPointerException if the definition string is {@code null}.
-     * @throws FactoryException if one of the given argument has an invalid value.
+     * @throws IllegalArgumentException if the definition string is empty or the dimension argument is out of range.
+     * @throws UnavailableFactoryException if the Proj.4 native library is not available.
+     * @throws FactoryException if the CRS creation failed for another reason.
      *
      * @see Proj4Factory#createCoordinateReferenceSystem(String)
      */
     public static CoordinateReferenceSystem createCRS(String definition, final int dimension) throws FactoryException {
-        definition = definition.trim();
-        ArgumentChecks.ensureNonEmpty(definition, definition);
+        ArgumentChecks.ensureNonEmpty("definition", definition);
         ArgumentChecks.ensureBetween("dimension", 2, 3, dimension);
+        definition = definition.trim();
         try {
             return Proj4Factory.INSTANCE.createCRS(definition, dimension >= 3);
+        } catch (IllegalArgumentException | ParserException e) {
+            throw new InvalidGeodeticParameterException(canNotParse(definition), e);
         } catch (UnsatisfiedLinkError | NoClassDefFoundError e) {
-            throw new UnavailableFactoryException(Errors.format(Errors.Keys.NativeInterfacesNotFound_2, OS.uname(), "libproj"), e);
+            throw new UnavailableFactoryException(unavailable(e), e);
         }
     }
 
     /**
      * Creates an operation for conversion or transformation between two coordinate reference systems.
-     * This implementation always uses Proj.4 for performing the coordinate operations, regardless if
-     * the given CRS were created from a Proj.4 definition string or not. This method fails if it can
-     * not map the given CRS to Proj.4 structures.
-     *
-     * @param  sourceCRS   the source coordinate reference system.
-     * @param  targetCRS   the target coordinate reference system.
-     * @return a coordinate operation for transforming coordinates from the given source CRS to the given target CRS.
-     * @throws FactoryException if an error occurred while creating the coordinate operation.
+     * The given CRSs should be instances created by this package. If not, then there is a choice:
+     *
+     * <ul>
+     *   <li>If {@code force} is {@code false}, then this method returns {@code null}.</li>
+     *   <li>Otherwise this method always uses Proj.4 for performing the coordinate operations,
+     *       regardless if the given CRS were created from Proj.4 definition strings or not.
+     *       This method fails if it can not map the given CRS to Proj.4 data structures.</li>
+     * </ul>
      *
-     * @see Proj4Factory#createOperation(CoordinateReferenceSystem, CoordinateReferenceSystem)
+     * <p><b>Recommended alternative</b></p>
+     * Provided that an <a href="http://sis.apache.org/epsg.html">EPSG database is available</a>,
+     * Apache SIS {@link CRS#findOperation CRS.findOperation(…)} method produces results that are closer
+     * to the authoritative definitions of coordinate operations (technically, Apache SIS referencing
+     * engine is a <cite>late-binding</cite> implementation while Proj.4 is an <cite>early-binding</cite>
+     * implementation — see EPSG guidance notes for a definition of late versus early-binding approaches).
+     * Apache SIS also attaches metadata about
+     * {@linkplain AbstractCoordinateOperation#getCoordinateOperationAccuracy() coordinate operation accuracy} and
+     * {@linkplain AbstractCoordinateOperation#getDomainOfValidity() domain of validity}, have extended support of
+     * multi-dimensional CRS and provides transform derivatives. This {@code Proj4.createOperation(…)} method should
+     * be reserved to situations where an application needs to reproduce the same numerical results than Proj.4.
+     *
+     * @param  sourceCRS  the source coordinate reference system.
+     * @param  targetCRS  the target coordinate reference system.
+     * @param  force      whether to force the creation of a Proj.4 transform
+     *                    even if the given CRS are not wrappers around Proj.4 data structures.
+     * @return a coordinate operation for transforming coordinates from the given source CRS to the given target CRS, or
+     *         {@code null} if the given CRS are not wrappers around Proj.4 data structures and {@code force} is false.
+     * @throws UnavailableFactoryException if {@code force} is {@code true} and the Proj.4 native library is not available.
+     * @throws FactoryException if {@code force} is {@code true} and this method can not create Proj.4 transform
+     *         for the given pair of coordinate reference systems for another reason.
+     *
+     * @see Proj4Factory#createOperation(CoordinateReferenceSystem, CoordinateReferenceSystem, boolean)
+     * @see CRS#findOperation(CoordinateReferenceSystem, CoordinateReferenceSystem, GeographicBoundingBox)
      */
     public static CoordinateOperation createOperation(final CoordinateReferenceSystem sourceCRS,
-                                                      final CoordinateReferenceSystem targetCRS)
+                                                      final CoordinateReferenceSystem targetCRS,
+                                                      final boolean force)
             throws FactoryException
     {
         ArgumentChecks.ensureNonNull("sourceCRS", sourceCRS);
         ArgumentChecks.ensureNonNull("targetCRS", targetCRS);
         try {
-            return Proj4Factory.INSTANCE.createOperation(sourceCRS, targetCRS);
+            return Proj4Factory.INSTANCE.createOperation(sourceCRS, targetCRS, force);
         } catch (UnsatisfiedLinkError | NoClassDefFoundError e) {
-            throw new UnavailableFactoryException(Errors.format(Errors.Keys.NativeInterfacesNotFound_2, OS.uname(), "libproj"), e);
+            throw new UnavailableFactoryException(unavailable(e), e);
         }
     }
+
+    /**
+     * Returns the error message for a {@literal Proj.4} not found.
+     */
+    static String unavailable(final Throwable e) {
+        String message = e.getLocalizedMessage();
+        if (message == null || message.indexOf(' ') < 0) {      // Keep existing message if it is a sentence.
+            message = Errors.format(Errors.Keys.NativeInterfacesNotFound_2, OS.uname(), "libproj");
+        }
+        return message;
+    }
+
+    /**
+     * Returns the error message for a {@literal Proj.4} string that can not be parsed.
+     */
+    static String canNotParse(final String code) {
+        return Errors.format(Errors.Keys.CanNotParse_1, code);
+    }
 }

Modified: sis/trunk/storage/sis-gdal/src/main/java/org/apache/sis/storage/gdal/Proj4Factory.java
URL: http://svn.apache.org/viewvc/sis/trunk/storage/sis-gdal/src/main/java/org/apache/sis/storage/gdal/Proj4Factory.java?rev=1802945&r1=1802944&r2=1802945&view=diff
==============================================================================
--- sis/trunk/storage/sis-gdal/src/main/java/org/apache/sis/storage/gdal/Proj4Factory.java [UTF-8] (original)
+++ sis/trunk/storage/sis-gdal/src/main/java/org/apache/sis/storage/gdal/Proj4Factory.java [UTF-8] Tue Jul 25 14:07:13 2017
@@ -22,36 +22,48 @@ import java.util.HashMap;
 import java.util.LinkedHashSet;
 import java.util.Collections;
 import javax.measure.Unit;
+import javax.measure.format.ParserException;
 import org.opengis.util.FactoryException;
+import org.opengis.util.NoSuchIdentifierException;
 import org.opengis.metadata.Identifier;
 import org.opengis.metadata.citation.Citation;
+import org.opengis.parameter.GeneralParameterValue;
+import org.opengis.parameter.ParameterValue;
 import org.opengis.parameter.ParameterValueGroup;
 import org.opengis.referencing.cs.*;
 import org.opengis.referencing.crs.*;
 import org.opengis.referencing.datum.*;
+import org.opengis.referencing.operation.*;
 import org.opengis.referencing.IdentifiedObject;
-import org.opengis.referencing.operation.OperationMethod;
-import org.opengis.referencing.operation.Conversion;
-import org.opengis.referencing.operation.CoordinateOperation;
-import org.opengis.referencing.operation.CoordinateOperationFactory;
+import org.apache.sis.referencing.IdentifiedObjects;
 import org.apache.sis.referencing.operation.DefaultConversion;
 import org.apache.sis.referencing.operation.DefaultCoordinateOperationFactory;
+import org.apache.sis.referencing.factory.InvalidGeodeticParameterException;
+import org.apache.sis.referencing.factory.UnavailableFactoryException;
 import org.apache.sis.referencing.factory.GeodeticAuthorityFactory;
 import org.apache.sis.referencing.CommonCRS;
 import org.apache.sis.internal.util.Constants;
 import org.apache.sis.metadata.iso.citation.Citations;
+import org.apache.sis.metadata.iso.citation.DefaultCitation;
 import org.apache.sis.metadata.iso.ImmutableIdentifier;
 import org.apache.sis.internal.metadata.AxisDirections;
+import org.apache.sis.internal.metadata.ReferencingServices;
 import org.apache.sis.internal.system.DefaultFactories;
 import org.apache.sis.internal.system.Modules;
+import org.apache.sis.internal.util.CollectionsExt;
+import org.apache.sis.internal.util.LazySet;
+import org.apache.sis.util.iso.SimpleInternationalString;
 import org.apache.sis.util.collection.WeakValueHashMap;
 import org.apache.sis.util.resources.Vocabulary;
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.util.logging.Logging;
-import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.util.CharSequences;
+import org.apache.sis.util.Classes;
 import org.apache.sis.measure.Units;
 
+// Branch-dependent imports
+import org.apache.sis.internal.jdk8.Predicate;
+
 
 /**
  * A factory for Coordinate Reference Systems created from {@literal Proj.4} definitions.
@@ -60,16 +72,11 @@ import org.apache.sis.measure.Units;
  * <ul>
  *   <li>{@link #getAuthority()}</li>
  *   <li>{@link #createCoordinateReferenceSystem(String)}</li>
+ *   <li>{@link #createOperation(CoordinateReferenceSystem, CoordinateReferenceSystem, boolean)}</li>
+ *   <li>{@link #createParameterizedTransform(ParameterValueGroup)}</li>
  * </ul>
  *
- * The following methods delegate to {@link #createCoordinateReferenceSystem(String)} and cast
- * the result if possible, or throw a {@link FactoryException} otherwise.
- * <ul>
- *   <li>{@link #createGeographicCRS(String)}</li>
- *   <li>{@link #createGeocentricCRS(String)}</li>
- *   <li>{@link #createProjectedCRS(String)}</li>
- *   <li>{@link #createObject(String)}</li>
- * </ul>
+ * Other methods delegate to one of above-cited methods if possible, or throw a {@link FactoryException} otherwise.
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @version 0.8
@@ -77,6 +84,19 @@ import org.apache.sis.measure.Units;
  * @module
  */
 public class Proj4Factory extends GeodeticAuthorityFactory implements CRSAuthorityFactory {
+    /*
+     * NOTE: Proj4Factory could implement CoordinateOperationFactory or MathTransformFactory.
+     *       But we don't do that yet because we are not sure about exposing large amount of
+     *       methods that are not really supported by Proj.4 wrappers. However this approach
+     *       is experimented in the MTFactory class in the test directory. MTFactory methods
+     *       could be refactored here if experience shows us that it would be useful.
+     */
+
+    /**
+     * The {@literal Proj.4} parameter used for projection name.
+     */
+    static final String PROJ_PARAM = '+' + Proj4Parser.PROJ + '=';
+
     /**
      * The {@literal Proj.4} parameter used for declaration of axis order.  Proj.4 expects the axis parameter
      * to be exactly 3 characters long, but Apache SIS accepts 2 characters as well. We relax the Proj.4 rule
@@ -86,11 +106,30 @@ public class Proj4Factory extends Geodet
     static final String AXIS_ORDER_PARAM = "+axis=";
 
     /**
+     * The name to use when no specific name were found for an object.
+     */
+    static final String UNNAMED = "Unnamed";
+
+    /**
      * The default factory instance.
      */
     static final Proj4Factory INSTANCE = new Proj4Factory();
 
     /**
+     * The {@literal Proj.4} authority completed by the version string. Created when first needed.
+     *
+     * @see #getAuthority()
+     */
+    private volatile Citation authority;
+
+    /**
+     * The default properties, or an empty map if none. This map shall not change after construction in
+     * order to allow usage without synchronization in multi-thread context. But we do not need to wrap
+     * in a unmodifiable map since {@code Proj4Factory} does not provide public access to it.
+     */
+    private final Map<String,?> defaultProperties;
+
+    /**
      * The factory for coordinate reference system objects.
      */
     private final CRSFactory crsFactory;
@@ -106,10 +145,18 @@ public class Proj4Factory extends Geodet
     private final DatumFactory datumFactory;
 
     /**
-     * The factory for coordinate operation objects.
-     * Currently restricted to Apache SIS implementation because we use a method not yet available in GeoAPI.
+     * The {@code MathTransform} factory on which to delegate operations that are not supported by {@code Proj4Factory}.
+     */
+    private final MathTransformFactory mtFactory;
+
+    /**
+     * The factory for coordinate operation objects, created when first needed.
+     * Currently restricted to Apache SIS implementation because we use a method not yet available in GeoAPI and
+     * because we configure it for using the {@link MathTransformFactory} provided by this {@code Proj4Factory}.
+     *
+     * @see #opFactory()
      */
-    private final DefaultCoordinateOperationFactory opFactory;
+    private volatile DefaultCoordinateOperationFactory opFactory;
 
     /**
      * Poll of identifiers created by this factory.
@@ -124,38 +171,62 @@ public class Proj4Factory extends Geodet
     private final WeakValueHashMap<String,PJ> pool = new WeakValueHashMap<>(String.class);
 
     /**
-     * Creates a new {@literal Proj.4} factory using the default CRS, CS and datum factories.
+     * Creates a default factory.
      */
     public Proj4Factory() {
-        crsFactory   = DefaultFactories.forBuildin(CRSFactory.class);
-        csFactory    = DefaultFactories.forBuildin(CSFactory.class);
-        datumFactory = DefaultFactories.forBuildin(DatumFactory.class);
-        opFactory    = DefaultFactories.forBuildin(CoordinateOperationFactory.class, DefaultCoordinateOperationFactory.class);
+        this(null);
     }
 
     /**
-     * Creates a new {@literal Proj.4} factory using the specified CRS, CS and datum factories.
-     *
-     * @param crsFactory    the factory to use for creating Coordinate Reference Systems.
-     * @param csFactory     the factory to use for creating Coordinate Systems.
-     * @param datumFactory  the factory to use for creating Geodetic Datums.
-     * @param opFactory     the factory to use for creating Coordinate Operations.
-     *                      Current implementation requires an instance of {@link DefaultCoordinateOperationFactory},
-     *                      but this may be related in a future Apache SIS version.
-     */
-    public Proj4Factory(final CRSFactory   crsFactory,
-                        final CSFactory    csFactory,
-                        final DatumFactory datumFactory,
-                        final CoordinateOperationFactory opFactory)
-    {
-        ArgumentChecks.ensureNonNull("crsFactory",   crsFactory);
-        ArgumentChecks.ensureNonNull("csFactory",    csFactory);
-        ArgumentChecks.ensureNonNull("datumFactory", datumFactory);
-        ArgumentChecks.ensureNonNull("opFactory",    opFactory);
-        this.crsFactory   = crsFactory;
-        this.csFactory    = csFactory;
-        this.datumFactory = datumFactory;
-        this.opFactory    = (DefaultCoordinateOperationFactory) opFactory;
+     * Creates a new {@literal Proj.4} factory. The {@code properties} argument is an optional map
+     * for specifying common properties shared by the objects to create. Some available properties are
+     * {@linkplain org.apache.sis.referencing.AbstractIdentifiedObject#AbstractIdentifiedObject(Map) listed there}.
+     * Unknown properties, or properties that do not apply, or properties for which {@code Proj4Factory} supplies
+     * itself a value, are ignored.
+     *
+     * @param properties  common properties for the objects to create, or {@code null} if none.
+     */
+    public Proj4Factory(Map<String,?> properties) {
+        properties   = new HashMap(properties != null ? properties : Collections.emptyMap());
+        crsFactory   = factory(CRSFactory.class,           properties, ReferencingServices.CRS_FACTORY);
+        csFactory    = factory(CSFactory.class,            properties, ReferencingServices.CS_FACTORY);
+        datumFactory = factory(DatumFactory.class,         properties, ReferencingServices.DATUM_FACTORY);
+        mtFactory    = factory(MathTransformFactory.class, properties, ReferencingServices.MT_FACTORY);
+        defaultProperties = CollectionsExt.compact(properties);
+    }
+
+    /**
+     * Returns the factory to use, using the instance specified in the properties map if it exists,
+     * or the system-wide default instance otherwise.
+     */
+    @SuppressWarnings("unchecked")
+    private static <T> T factory(final Class<T> type, final Map<String,?> properties, final String key) {
+        final Object value = properties.remove(key);
+        if (value == null) {
+            return DefaultFactories.forBuildin(type);
+        }
+        if (type.isInstance(value)) {
+            return (T) value;
+        }
+        throw new IllegalArgumentException(Errors.getResources(properties)
+                .getString(Errors.Keys.IllegalPropertyValueClass_2, key, Classes.getClass(value)));
+    }
+
+    /**
+     * Returns the factory for coordinate operation objects.
+     * The factory is backed by this {@code Proj4Factory} as the {@code MathTransformFactory} implementation.
+     */
+    final DefaultCoordinateOperationFactory opFactory() {
+        DefaultCoordinateOperationFactory factory = opFactory;
+        if (factory == null) {
+            final Map<String,Object> properties = new HashMap<String,Object>(defaultProperties);
+            properties.put(ReferencingServices.CRS_FACTORY,   crsFactory);
+            properties.put(ReferencingServices.CS_FACTORY,    csFactory);
+            properties.put(ReferencingServices.DATUM_FACTORY, datumFactory);
+            factory = new DefaultCoordinateOperationFactory(properties, mtFactory);
+            opFactory = factory;
+        }
+        return factory;
     }
 
     /**
@@ -166,7 +237,19 @@ public class Proj4Factory extends Geodet
      */
     @Override
     public Citation getAuthority() {
-        return Citations.PROJ4;
+        Citation c = authority;
+        if (c == null) {
+            c = Citations.PROJ4;
+            final String release = Proj4.version();
+            if (release != null) {
+                final DefaultCitation df = new DefaultCitation(c);
+                df.setEdition(new SimpleInternationalString(release));
+                df.freeze();
+                c = df;
+            }
+            authority = c;
+        }
+        return c;
     }
 
     /**
@@ -211,11 +294,165 @@ public class Proj4Factory extends Geodet
         }
         final Set<String> codes = new LinkedHashSet<>(4);
         codes.add("+init=");
-        codes.add("+proj=".concat(method));
+        codes.add(PROJ_PARAM.concat(method));
         return codes;
     }
 
     /**
+     * Returns some map projection methods supported by {@literal Proj.4}.
+     * Current implementation can not return the complete list of Proj.4 method, but returns the main ones.
+     * For each operation method in the returned set, the Proj.4 projection name can be obtained as below:
+     *
+     * {@preformat java
+     *     String proj = IdentifiedObjects.getName(method, Citations.PROJ4);
+     * }
+     *
+     * The {@code proj} names obtained as above can be given in argument to the
+     * {@link #getOperationMethod(String)} and {@link #getDefaultParameters(String)} methods.
+     *
+     * @param  type <code>{@linkplain SingleOperation}.class</code> for fetching all operation methods, or
+     *              <code>{@linkplain Projection}.class</code> for fetching only map projection methods.
+     * @return methods available in this factory for coordinate operations of the given type.
+     *
+     * @see org.apache.sis.referencing.operation.transform.DefaultMathTransformFactory#getAvailableMethods(Class)
+     */
+    public Set<OperationMethod> getAvailableMethods(final Class<? extends SingleOperation> type) {
+        return new LazySet<>(CollectionsExt.filter(mtFactory.getAvailableMethods(type).iterator(), new Predicate<IdentifiedObject>() {
+            @Override public boolean test(final IdentifiedObject method) {
+                return isSupported(method);
+            }
+        }));
+    }
+
+    /**
+     * Returns the operation method of the given name. The given argument can be a Proj.4 projection name,
+     * but other authorities (OGC, EPSG…) are also accepted. A partial list of supported projection names
+     * can be obtained by {@link #getAvailableMethods(Class)}. Some examples of Proj.4 projection names
+     * are given below (not all of them are supported by this Proj.4 wrapper).
+     *
+     * <table class="sis">
+     *   <caption>Some Proj.4 projection names</caption>
+     *   <tr><th>Name</th>            <th>Meaning</th></tr>
+     *   <tr><td>{@code aea}</td>     <td>Albers Equal-Area Conic</td></tr>
+     *   <tr><td>{@code aeqd}</td>    <td>Azimuthal Equidistant</td></tr>
+     *   <tr><td>{@code cass}</td>    <td>Cassini-Soldner</td></tr>
+     *   <tr><td>{@code cea}</td>     <td>Cylindrical Equal Area</td></tr>
+     *   <tr><td>{@code eck4}</td>    <td>Eckert IV</td></tr>
+     *   <tr><td>{@code eck6}</td>    <td>Eckert VI</td></tr>
+     *   <tr><td>{@code eqdc}</td>    <td>Equidistant Conic</td></tr>
+     *   <tr><td>{@code gall}</td>    <td>Gall Stereograpic</td></tr>
+     *   <tr><td>{@code geos}</td>    <td>Geostationary Satellite View</td></tr>
+     *   <tr><td>{@code gnom}</td>    <td>Gnomonic</td></tr>
+     *   <tr><td>{@code krovak}</td>  <td>Krovak Oblique Conic Conformal</td></tr>
+     *   <tr><td>{@code laea}</td>    <td>Lambert Azimuthal Equal Area</td></tr>
+     *   <tr><td>{@code lcc}</td>     <td>Lambert Conic Conformal</td></tr>
+     *   <tr><td>{@code merc}</td>    <td>Mercator</td></tr>
+     *   <tr><td>{@code mill}</td>    <td>Miller Cylindrical</td></tr>
+     *   <tr><td>{@code moll}</td>    <td>Mollweide</td></tr>
+     *   <tr><td>{@code nzmg}</td>    <td>New Zealand Map Grid</td></tr>
+     *   <tr><td>{@code omerc}</td>   <td>Oblique Mercator</td></tr>
+     *   <tr><td>{@code ortho}</td>   <td>Orthographic</td></tr>
+     *   <tr><td>{@code sterea}</td>  <td>Oblique Stereographic</td></tr>
+     *   <tr><td>{@code stere}</td>   <td>Stereographic</td></tr>
+     *   <tr><td>{@code robin}</td>   <td>Robinson</td></tr>
+     *   <tr><td>{@code sinu}</td>    <td>Sinusoidal</td></tr>
+     *   <tr><td>{@code tmerc}</td>   <td>Transverse Mercator</td></tr>
+     *   <tr><td>{@code vandg}</td>   <td>VanDerGrinten</td></tr>
+     * </table>
+     *
+     * The default implementation delegates to a {@code DefaultCoordinateOperationFactory} instance.
+     * It works because the Apache SIS operation methods declare the Proj.4 projection names as
+     * {@linkplain org.apache.sis.referencing.AbstractIdentifiedObject#getAlias() aliases}.
+     *
+     * @param  name  the name of the operation method to fetch.
+     * @return the operation method of the given name.
+     * @throws FactoryException if the requested operation method can not be fetched.
+     *
+     * @see org.apache.sis.referencing.operation.DefaultCoordinateOperationFactory#getOperationMethod(String)
+     */
+    public OperationMethod getOperationMethod(final String name) throws FactoryException {
+        final OperationMethod method = opFactory().getOperationMethod(name);
+        if (isSupported(method)) {
+            return method;
+        }
+        throw new NoSuchIdentifierException(Errors.getResources(defaultProperties)
+                .getString(Errors.Keys.UnsupportedOperation_1, name), name);
+    }
+
+    /**
+     * Returns the default parameter values for a math transform using the given operation method.
+     * The given argument can be a Proj.4 projection name, but other authorities (OGC, EPSG…) are also accepted.
+     * A partial list of supported projection names can be obtained by {@link #getAvailableMethods(Class)}.
+     * The returned parameters can be given to {@link #createParameterizedTransform(ParameterValueGroup)}.
+     *
+     * @param  method  the case insensitive name of the coordinate operation method to search for.
+     * @return a new group of parameter values for the {@code OperationMethod} identified by the given name.
+     * @throws NoSuchIdentifierException if there is no method registered for the given name or identifier.
+     */
+    public ParameterValueGroup getDefaultParameters(final String method) throws NoSuchIdentifierException {
+        final ParameterValueGroup parameters = mtFactory.getDefaultParameters(method);
+        if (isSupported(parameters.getDescriptor())) {
+            return parameters;
+        }
+        throw new NoSuchIdentifierException(Errors.getResources(defaultProperties)
+                .getString(Errors.Keys.UnsupportedOperation_1, method), method);
+    }
+
+    /**
+     * Creates a transform from a group of parameters. The {@link OperationMethod} name is inferred from
+     * the {@linkplain org.opengis.parameter.ParameterDescriptorGroup#getName() parameter group name}.
+     * Each parameter value is formatted as a Proj.4 parameter in a definition string.
+     *
+     * <div class="note"><b>Example:</b>
+     * {@preformat java
+     *     ParameterValueGroup p = factory.getDefaultParameters("Mercator");
+     *     p.parameter("semi_major").setValue(6378137.000);
+     *     p.parameter("semi_minor").setValue(6356752.314);
+     *     MathTransform mt = factory.createParameterizedTransform(p);
+     * }
+     *
+     * The corresponding Proj.4 definition string is:
+     *
+     * {@preformat text
+     *     +proj=merc +a=6378137.0 +b=6356752.314
+     * }
+     * </div>
+     *
+     * @param  parameters  the parameter values.
+     * @return the parameterized transform.
+     * @throws FactoryException if the object creation failed. This exception is thrown
+     *         if some required parameter has not been supplied, or has illegal value.
+     *
+     * @see #getDefaultParameters(String)
+     * @see #getAvailableMethods(Class)
+     */
+    public MathTransform createParameterizedTransform(final ParameterValueGroup parameters) throws FactoryException {
+        final String proj = name(parameters.getDescriptor(), Errors.Keys.UnsupportedOperation_1);
+        final StringBuilder buffer = new StringBuilder(100).append(PROJ_PARAM).append(proj);
+        for (final GeneralParameterValue p : parameters.values()) {
+            /*
+             * Unconditionally ask the parameter name in order to throw an exception
+             * with better error message in case of unrecognized parameter.
+             */
+            final String name = name(p.getDescriptor(), Errors.Keys.UnexpectedParameter_1);
+            if (p instanceof ParameterValue) {
+                final Object value = ((ParameterValue) p).getValue();
+                if (value != null) {
+                    buffer.append(" +").append(name).append('=').append(value);
+                }
+            }
+        }
+        final String definition = buffer.toString();
+        try {
+            final PJ pj = unique(new PJ(definition));
+            final PJ base = unique(new PJ(pj));
+            return new Transform(base, false, pj, false);
+        } catch (UnsatisfiedLinkError | NoClassDefFoundError e) {
+            throw new UnavailableFactoryException(Proj4.unavailable(e), e);
+        }
+    }
+
+    /**
      * Creates a new geodetic object from the given {@literal Proj.4} definition.
      * The default implementation delegates to {@link #createCoordinateReferenceSystem(String)}.
      *
@@ -283,69 +520,54 @@ public class Proj4Factory extends Geodet
                 }
             }
         }
-        return createCRS(code, hasHeight);
-    }
-
-    /**
-     * Returns the value of the given parameter as an identifier, or {@code null} if none.
-     * The given parameter key shall include the {@code '+'} prefix and {@code '='} suffix,
-     * for example {@code "+proj="}. This is a helper method for providing the {@code name}
-     * property value in constructors.
-     *
-     * @param  definition  the Proj.4 definition string to parse.
-     * @param  keyword     the parameter name.
-     * @return the parameter value as an identifier.
-     */
-    private Map<String,Identifier> identifier(final String definition, final String keyword) {
-        String value = "";
-        if (keyword != null) {
-            int i = definition.indexOf(keyword);
-            if (i >= 0) {
-                i += keyword.length();
-                final int stop = definition.indexOf(' ', i);
-                value = (stop >= 0) ? definition.substring(i, stop) : definition.substring(i);
-                value = value.trim();
-            }
-        }
-        if (value.isEmpty()) {
-            value = "Unnamed";
+        try {
+            return createCRS(code, hasHeight);
+        } catch (IllegalArgumentException | ParserException e) {
+            throw new InvalidGeodeticParameterException(Proj4.canNotParse(code), e);
+        } catch (UnsatisfiedLinkError | NoClassDefFoundError e) {
+            throw new UnavailableFactoryException(Proj4.unavailable(e), e);
         }
-        return identifier(value);
     }
 
     /**
      * Returns the identifier for the given code in {@literal Proj.4} namespace.
      */
-    private Map<String,Identifier> identifier(final String code) {
+    private Map<String,Object> identifier(final String code) {
         Identifier id = identifiers.get(code);
         if (id == null) {
             short i18n = 0;
-            if (code.equalsIgnoreCase("Unnamed")) i18n = Vocabulary.Keys.Unnamed;
+            if (code.equalsIgnoreCase( UNNAMED )) i18n = Vocabulary.Keys.Unnamed;
             if (code.equalsIgnoreCase("Unknown")) i18n = Vocabulary.Keys.Unknown;
             id = new ImmutableIdentifier(Citations.PROJ4, Constants.PROJ4, code, null,
                     (i18n != 0) ? Vocabulary.formatInternational(i18n) : null);
             identifiers.put(code, id);
         }
-        return Collections.singletonMap(IdentifiedObject.NAME_KEY, id);
+        final Map<String,Object> properties = new HashMap<String,Object>(defaultProperties);
+        properties.put(IdentifiedObject.NAME_KEY, id);
+        return properties;
     }
 
     /**
      * Creates a geodetic datum from the given {@literal Proj.4} wrapper.
      *
-     * @param  pj  the Proj.4 object to wrap.
+     * @param  pj      the Proj.4 object to wrap.
+     * @param  parser  the parameter values of the Proj.4 object to wrap.
+     * @throws NumberFormatException if a Proj.4 parameter value can not be parsed.
      */
-    private GeodeticDatum createDatum(final PJ pj) throws FactoryException {
+    private GeodeticDatum createDatum(final PJ pj, final Proj4Parser parser) throws FactoryException {
         final PrimeMeridian pm;
-        final double greenwichLongitude = pj.getGreenwichLongitude();
+        final double greenwichLongitude = Double.parseDouble(parser.value("pm", "0"));
         if (greenwichLongitude == 0) {
             pm = CommonCRS.WGS84.datum().getPrimeMeridian();
         } else {
-            pm = datumFactory.createPrimeMeridian(identifier("Unnamed"), greenwichLongitude, Units.DEGREE);
+            pm = datumFactory.createPrimeMeridian(identifier(UNNAMED), greenwichLongitude, Units.DEGREE);
         }
-        final String definition = pj.getCode();
-        return datumFactory.createGeodeticDatum(identifier(definition, "+datum="),
-               datumFactory.createEllipsoid    (identifier(definition, "+ellps="),
-                    pj.getSemiMajorAxis(), pj.getSemiMinorAxis(), Units.METRE), pm);
+        final double[] def = pj.getEllipsoidDefinition();
+        return datumFactory.createGeodeticDatum(identifier(parser.value("datum", UNNAMED)),
+               datumFactory.createEllipsoid    (identifier(parser.value("ellps", UNNAMED)),
+                    def[0],                             // Semi-major axis length
+                    def[0] * Math.sqrt(1 - def[1]),     // Semi-minor axis length
+                    Units.METRE), pm);
     }
 
     /**
@@ -354,14 +576,17 @@ public class Proj4Factory extends Geodet
      *
      * @param  pj          the Proj.4 object to wrap.
      * @param  withHeight  whether to include a height axis.
+     * @throws IllegalArgumentException if a Proj.4 parameter value can not be parsed or assigned.
+     * @throws ParserException if a unit symbol can not be parsed.
      */
     private CoordinateReferenceSystem createCRS(final PJ pj, final boolean withHeight) throws FactoryException {
         final PJ.Type type = pj.getType();
         final boolean geographic = PJ.Type.GEOGRAPHIC.equals(type);
-        final char[] dir = pj.getAxisDirections();
-        final CoordinateSystemAxis[] axes = new CoordinateSystemAxis[withHeight ? dir.length : 2];
+        final Proj4Parser parser = new Proj4Parser(pj.getCode());
+        final String dir = parser.value("axis", "enu");
+        final CoordinateSystemAxis[] axes = new CoordinateSystemAxis[withHeight ? dir.length() : 2];
         for (int i=0; i<axes.length; i++) {
-            final char d = Character.toLowerCase(dir[i]);
+            final char d = Character.toLowerCase(dir.charAt(i));
             char abbreviation = Character.toUpperCase(d);
             boolean vertical = false;
             final AxisDirection c;
@@ -378,7 +603,7 @@ public class Proj4Factory extends Geodet
             if (geographic && AxisDirections.isCardinal(c)) {
                 abbreviation = (d == 'e' || d == 'w') ? 'λ' : 'φ';
             }
-            final Unit<?> unit = (vertical || !geographic) ? Units.METRE.divide(pj.getLinearUnitToMetre(vertical)) : Units.DEGREE;
+            final Unit<?> unit = (vertical || !geographic) ? parser.unit(vertical) : Units.DEGREE;
             axes[i] = csFactory.createCoordinateSystemAxis(identifier(name), String.valueOf(abbreviation).intern(), c, unit);
         }
         /*
@@ -386,17 +611,17 @@ public class Proj4Factory extends Geodet
          * will be stored as the CRS identifier for allowing OperationFactory to get it back before to
          * attempt to create a new one for a given CRS.
          */
-        final Map<String,Identifier> csName = identifier("Unnamed");
-        final Map<String,Identifier> name = new HashMap<>(identifier(String.valueOf(pj.getDescription())));
+        final Map<String,Object> csName = identifier(UNNAMED);
+        final Map<String,Object> name = new HashMap<>(identifier(parser.name(type == PJ.Type.PROJECTED)));
         name.put(CoordinateReferenceSystem.IDENTIFIERS_KEY, pj);
         switch (type) {
             case GEOGRAPHIC: {
-                return crsFactory.createGeographicCRS(name, createDatum(pj), withHeight ?
+                return crsFactory.createGeographicCRS(name, createDatum(pj, parser), withHeight ?
                         csFactory.createEllipsoidalCS(csName, axes[0], axes[1], axes[2]) :
                         csFactory.createEllipsoidalCS(csName, axes[0], axes[1]));
             }
             case GEOCENTRIC: {
-                return crsFactory.createGeocentricCRS(name, createDatum(pj),
+                return crsFactory.createGeocentricCRS(name, createDatum(pj, parser),
                         csFactory.createCartesianCS(csName, axes[0], axes[1], axes[2]));
             }
             case PROJECTED: {
@@ -413,8 +638,7 @@ public class Proj4Factory extends Geodet
                 OperationMethod method;
                 ParameterValueGroup parameters;
                 try {
-                    final Proj4Parser parser = new Proj4Parser(pj.getCode());
-                    method = parser.method(opFactory);
+                    method = parser.method(opFactory());
                     parameters = parser.parameters();
                 } catch (IllegalArgumentException | FactoryException e) {
                     Logging.recoverableException(Logging.getLogger(Modules.GDAL), Proj4Factory.class, "createProjectedCRS", e);
@@ -427,23 +651,25 @@ public class Proj4Factory extends Geodet
                         csFactory.createCartesianCS(csName, axes[0], axes[1]));
             }
             default: {
-                throw new FactoryException(Errors.format(Errors.Keys.UnknownEnumValue_2, type, PJ.Type.class));
+                throw new FactoryException(Errors.getResources(defaultProperties)
+                        .getString(Errors.Keys.UnknownEnumValue_2, type, PJ.Type.class));
             }
         }
     }
 
     /**
      * Gets the {@literal Proj.4} object from the given coordinate reference system. If an existing {@code PJ}
-     * instance is found, returns it. Otherwise creates a new {@code PJ} instance from a Proj.4 definition
-     * inferred from the given CRS. This method is the converse of {@link #createCRS(PJ, boolean)}.
+     * instance is found, returns it. Otherwise if {@code force} is {@code true}, creates a new {@code PJ}
+     * instance from a Proj.4 definition inferred from the given CRS.
+     * This method is the converse of {@link #createCRS(PJ, boolean)}.
      */
-    private PJ unwrapOrCreate(final CoordinateReferenceSystem crs) throws FactoryException {
+    private PJ unwrapOrCreate(final CoordinateReferenceSystem crs, final boolean force) throws FactoryException {
         for (final Identifier id : crs.getIdentifiers()) {
             if (id instanceof PJ) {
                 return (PJ) id;
             }
         }
-        return unique(new PJ(Proj4.definition(crs)));
+        return force ? unique(new PJ(Proj4.definition(crs))) : null;
     }
 
     /**
@@ -480,23 +706,63 @@ public class Proj4Factory extends Geodet
 
     /**
      * Creates an operation for conversion or transformation between two coordinate reference systems.
-     * This implementation always uses Proj.4 for performing the coordinate operations, regardless if
-     * the given CRS were created from a Proj.4 definition string or not. This method fails if it can
-     * not map the given CRS to Proj.4 structures.
+     * The given CRSs should be instances {@linkplain #createCoordinateReferenceSystem created by this factory}.
+     * If not, then there is a choice:
+     *
+     * <ul>
+     *   <li>If {@code force} is {@code false}, then this method returns {@code null}.</li>
+     *   <li>Otherwise this method always uses Proj.4 for performing the coordinate operations,
+     *       regardless if the given CRS were created from Proj.4 definition strings or not.
+     *       This method fails if it can not map the given CRS to Proj.4 data structures.</li>
+     * </ul>
      *
      * @param  sourceCRS  the source coordinate reference system.
      * @param  targetCRS  the target coordinate reference system.
-     * @return a coordinate operation for transforming coordinates from the given source CRS to the given target CRS.
-     * @throws FactoryException if the given CRS are not instances recognized by this class.
+     * @param  force      whether to force the creation of a Proj.4 transform
+     *                    even if the given CRS are not wrappers around Proj.4 data structures.
+     * @return a coordinate operation for transforming coordinates from the given source CRS to the given target CRS, or
+     *         {@code null} if the given CRS are not wrappers around Proj.4 data structures and {@code force} is false.
+     * @throws FactoryException if {@code force} is {@code true} and this method can not create Proj.4 transform
+     *         for the given pair of coordinate reference systems.
      *
-     * @see Proj4#createOperation(CoordinateReferenceSystem, CoordinateReferenceSystem)
+     * @see Proj4#createOperation(CoordinateReferenceSystem, CoordinateReferenceSystem, boolean)
+     * @see DefaultCoordinateOperationFactory#createOperation(CoordinateReferenceSystem, CoordinateReferenceSystem)
      */
     public CoordinateOperation createOperation(final CoordinateReferenceSystem sourceCRS,
-                                               final CoordinateReferenceSystem targetCRS)
+                                               final CoordinateReferenceSystem targetCRS,
+                                               final boolean force)
             throws FactoryException
     {
+        final PJ source, target;
+        try {
+            if ((source = unwrapOrCreate(sourceCRS, force)) == null ||
+                (target = unwrapOrCreate(targetCRS, force)) == null)
+            {
+                return null;            // At least one CRS is not a Proj.4 wrapper and 'force' is false.
+            }
+        } catch (UnsatisfiedLinkError | NoClassDefFoundError e) {
+            throw new UnavailableFactoryException(Proj4.unavailable(e), e);
+        }
+        /*
+         * Before to create a transform, verify if the target CRS already contains a suitable transform.
+         * In such case, returning the existing operation is preferable since it usually contains better
+         * parameter description than what this method build.
+         */
+        if (targetCRS instanceof GeneralDerivedCRS) {
+            final CoordinateOperation op = ((GeneralDerivedCRS) targetCRS).getConversionFromBase();
+            final MathTransform tr = op.getMathTransform();
+            if (tr instanceof Transform && ((Transform) tr).isFor(sourceCRS, source, targetCRS, target)) {
+                return op;
+            }
+        }
+        /*
+         * The 'Transform' construction implies parameter validation, so we do it first before to
+         * construct other objects.
+         */
+        final Transform tr = new Transform(source, is3D("sourceCRS", sourceCRS),
+                                           target, is3D("targetCRS", targetCRS));
         Identifier id;
-        String src = null, tgt = null, name = "Unnamed";
+        String src = null, tgt = null, name = UNNAMED;
         if ((id = sourceCRS.getName()) != null) src = id.getCode();
         if ((id = targetCRS.getName()) != null) tgt = id.getCode();
         if (src != null || tgt != null) {
@@ -505,21 +771,51 @@ public class Proj4Factory extends Geodet
             if (tgt != null) buffer.append(buffer.length() == 0 ? "To " : " to ").append(tgt);
             name = buffer.toString();
         }
-        final Transform tr = new Transform(unwrapOrCreate(sourceCRS), is3D("sourceCRS", sourceCRS),
-                                           unwrapOrCreate(targetCRS), is3D("targetCRS", targetCRS));
-        return opFactory.createSingleOperation(identifier(name), sourceCRS, targetCRS, null, Transform.METHOD, tr);
+        return opFactory().createSingleOperation(identifier(name), sourceCRS, targetCRS, null, Transform.METHOD, tr);
     }
 
     /**
      * Returns whether the given CRS is three-dimensional.
      * Thrown an exception if the number of dimension is unsupported.
      */
-    private static boolean is3D(final String arg, final CoordinateReferenceSystem crs) throws FactoryException {
+    private boolean is3D(final String arg, final CoordinateReferenceSystem crs) throws FactoryException {
         final int dim = crs.getCoordinateSystem().getDimension();
         final boolean is3D = (dim >= 3);
         if (dim < 2 || dim > 3) {
-            throw new FactoryException(Errors.format(Errors.Keys.MismatchedDimension_3, arg, is3D ? 3 : 2, dim));
+            throw new FactoryException(Errors.getResources(defaultProperties)
+                    .getString(Errors.Keys.MismatchedDimension_3, arg, is3D ? 3 : 2, dim));
         }
         return is3D;
     }
+
+    /**
+     * Returns {@code true} if the given coordinate operation method or parameter group is supported.
+     */
+    private static boolean isSupported(final IdentifiedObject method) {
+        return IdentifiedObjects.getName(method, Citations.PROJ4) != null;
+    }
+
+    /**
+     * Returns the {@literal Proj.4} name of the given parameter value or parameter group.
+     *
+     * @param  param    the parameter value or parameter group for which to get the Proj.4 name.
+     * @param  errorKey the resource key of the error message to produce if no Proj.4 name has been found.
+     *                  The message shall expect exactly one argument. This error key can be
+     *                  {@link Errors.Keys#UnsupportedOperation_1} or {@link Errors.Keys#UnexpectedParameter_1}.
+     * @return the Proj.4 name of the given object (never null).
+     * @throws FactoryException if the Proj.4 name has not been found.
+     */
+    private String name(final IdentifiedObject param, final short errorKey) throws FactoryException {
+        String name = IdentifiedObjects.getName(param, Citations.PROJ4);
+        if (name == null) {
+            name = param.getName().getCode();
+            final String message = Errors.getResources(defaultProperties).getString(errorKey, name);
+            if (errorKey == Errors.Keys.UnsupportedOperation_1) {
+                throw new NoSuchIdentifierException(message, name);
+            } else {
+                throw new InvalidGeodeticParameterException(message);
+            }
+        }
+        return name;
+    }
 }

Modified: sis/trunk/storage/sis-gdal/src/main/java/org/apache/sis/storage/gdal/Proj4Parser.java
URL: http://svn.apache.org/viewvc/sis/trunk/storage/sis-gdal/src/main/java/org/apache/sis/storage/gdal/Proj4Parser.java?rev=1802945&r1=1802944&r2=1802945&view=diff
==============================================================================
--- sis/trunk/storage/sis-gdal/src/main/java/org/apache/sis/storage/gdal/Proj4Parser.java [UTF-8] (original)
+++ sis/trunk/storage/sis-gdal/src/main/java/org/apache/sis/storage/gdal/Proj4Parser.java [UTF-8] Tue Jul 25 14:07:13 2017
@@ -21,6 +21,9 @@ import java.util.Map;
 import java.util.HashSet;
 import java.util.LinkedHashMap;
 import java.util.Locale;
+import javax.measure.Unit;
+import javax.measure.format.ParserException;
+import org.apache.sis.measure.Units;
 import org.opengis.util.FactoryException;
 import org.opengis.parameter.ParameterValue;
 import org.opengis.parameter.ParameterValueGroup;
@@ -45,18 +48,26 @@ final class Proj4Parser {
      * We ignore those parameters because they define for example the datum, prime meridian, <i>etc</i>.
      * Those information will be fetched by other classes than this {@code Proj4Parser}.
      */
-    private static final Set<String> EXCLUDES = new HashSet<>(12);
+    private static final Set<String> EXCLUDES = new HashSet<>(16);
     static {
         EXCLUDES.add("init");           // Authority code
         EXCLUDES.add("datum");          // Geodetic datum name
         EXCLUDES.add("ellps");          // Ellipsoid name
         EXCLUDES.add("pm");             // Prime meridian
         EXCLUDES.add("units");          // Axis units
+        EXCLUDES.add("vunits");         // Vertical axis units
+        EXCLUDES.add("to_meter");       // Axis units conversion factor
+        EXCLUDES.add("vto_meter");      // Vertical axis units conversion factor
         EXCLUDES.add("towgs84");        // Datum shift
         EXCLUDES.add("axis");           // Axis order
     }
 
     /**
+     * The {@value} keyword used for projection name in {@literal Proj.4} definition strings.
+     */
+    static final String PROJ = "proj";
+
+    /**
      * The values extracted from the {@literal Proj.4} definition strings.
      * Keys are Proj.4 keywords like {@code "a"} or {@code "ts"}.
      */
@@ -78,8 +89,8 @@ final class Proj4Parser {
             final int next = definition.indexOf('+',   start);
             if (end >= 0 && (next < 0 || end < next)) {
                 final String keyword = CharSequences.trimWhitespaces(definition, start, end).toString().toLowerCase(Locale.US);
-                if (!EXCLUDES.contains(keyword)) {
-                    final String value = CharSequences.trimWhitespaces(definition, end+1, (next >= 0) ? next : length).toString();
+                final String value   = CharSequences.trimWhitespaces(definition, end+1, (next >= 0) ? next : length).toString();
+                if (!value.isEmpty()) {
                     final String old = parameters.put(keyword, value);
                     if (old != null && !old.equals(value)) {
                         throw new InvalidGeodeticParameterException(Errors.format(Errors.Keys.DuplicatedElement_1, keyword));
@@ -91,14 +102,43 @@ final class Proj4Parser {
     }
 
     /**
+     * Returns a suggested name for the coordinate reference system object to build.
+     */
+    final String name(final boolean isProjected) {
+        String name = parameters.get("datum");
+        if (name == null) {
+            name = parameters.get("ellps");
+            if (name == null) {
+                name = Proj4Factory.UNNAMED;
+            }
+        }
+        if (isProjected) {
+            final String proj = parameters.get(PROJ);
+            if (proj != null) {
+                name = name + ' ' + Proj4Factory.PROJ_PARAM + proj;
+            }
+        }
+        return name;
+    }
+
+    /**
+     * Returns the parameter value for the given keyword, or the given default value if none.
+     * The parameter value is removed from the map.
+     */
+    final String value(final String keyword, final String defaultValue) {
+        final String value = parameters.remove(keyword);
+        return (value != null) ? value : defaultValue;
+    }
+
+    /**
      * Returns the operation method inferred from the {@code "proj"} parameter value.
      * This method must be invoked at least once before {@link #parameters()}.
      */
     final OperationMethod method(final DefaultCoordinateOperationFactory opFactory) throws FactoryException {
         if (method == null) {
-            final String name = parameters.remove("proj");
+            final String name = parameters.remove(PROJ);
             if (name == null) {
-                throw new InvalidGeodeticParameterException(Errors.format(Errors.Keys.ElementNotFound_1, "proj"));
+                throw new InvalidGeodeticParameterException(Errors.format(Errors.Keys.ElementNotFound_1, PROJ));
             }
             method = opFactory.getOperationMethod(name);
         }
@@ -113,9 +153,33 @@ final class Proj4Parser {
     final ParameterValueGroup parameters() throws IllegalArgumentException {
         final ParameterValueGroup pg = method.getParameters().createValue();
         for (final Map.Entry<String,String> entry : parameters.entrySet()) {
-            final ParameterValue<?> value = pg.parameter(entry.getKey());
-            value.setValue(Double.parseDouble(entry.getValue()));
+            final String keyword = entry.getKey();
+            if (!EXCLUDES.contains(keyword)) {
+                final ParameterValue<?> value = pg.parameter(keyword);
+                value.setValue(Double.parseDouble(entry.getValue()));
+            }
         }
         return pg;
     }
+
+    /**
+     * Returns the vertical or horizontal axis unit.
+     * This unit applies only to linear axes, not angular axes neither parameters.
+     *
+     * @param  vertical  {@code true} for querying the vertical unit, or {@code false} for the horizontal one.
+     * @return the vertical or horizontal axis unit of measurement, or {@code Units.METRE} if unspecified.
+     * @throws NumberFormatException if the unit conversion factor can not be parsed.
+     * @throws ParserException if the unit symbol can not be parsed.
+     */
+    final Unit<?> unit(final boolean vertical) throws ParserException {
+        String v = parameters.remove(vertical ? "vto_meter" : "to_meter");
+        if (v != null) {
+            return Units.METRE.divide(Double.parseDouble(v));
+        }
+        v = parameters.remove(vertical ? "vunits" : "units");
+        if (v != null) {
+            return Units.valueOf(v);
+        }
+        return Units.METRE;
+    }
 }



Mime
View raw message