sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject svn commit: r1802942 [3/3] - in /sis/branches/JDK7: ./ 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/...
Date Tue, 25 Jul 2017 13:54:42 GMT
Modified: sis/branches/JDK7/storage/sis-gdal/src/main/java/org/apache/sis/storage/gdal/Proj4Factory.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/storage/sis-gdal/src/main/java/org/apache/sis/storage/gdal/Proj4Factory.java?rev=1802942&r1=1802941&r2=1802942&view=diff
==============================================================================
--- sis/branches/JDK7/storage/sis-gdal/src/main/java/org/apache/sis/storage/gdal/Proj4Factory.java [UTF-8] (original)
+++ sis/branches/JDK7/storage/sis-gdal/src/main/java/org/apache/sis/storage/gdal/Proj4Factory.java [UTF-8] Tue Jul 25 13:54:41 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/branches/JDK7/storage/sis-gdal/src/main/java/org/apache/sis/storage/gdal/Proj4Parser.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/storage/sis-gdal/src/main/java/org/apache/sis/storage/gdal/Proj4Parser.java?rev=1802942&r1=1802941&r2=1802942&view=diff
==============================================================================
--- sis/branches/JDK7/storage/sis-gdal/src/main/java/org/apache/sis/storage/gdal/Proj4Parser.java [UTF-8] (original)
+++ sis/branches/JDK7/storage/sis-gdal/src/main/java/org/apache/sis/storage/gdal/Proj4Parser.java [UTF-8] Tue Jul 25 13:54:41 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;
+    }
 }

Modified: sis/branches/JDK7/storage/sis-gdal/src/main/java/org/apache/sis/storage/gdal/Transform.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/storage/sis-gdal/src/main/java/org/apache/sis/storage/gdal/Transform.java?rev=1802942&r1=1802941&r2=1802942&view=diff
==============================================================================
--- sis/branches/JDK7/storage/sis-gdal/src/main/java/org/apache/sis/storage/gdal/Transform.java [UTF-8] (original)
+++ sis/branches/JDK7/storage/sis-gdal/src/main/java/org/apache/sis/storage/gdal/Transform.java [UTF-8] Tue Jul 25 13:54:41 2017
@@ -16,7 +16,9 @@
  */
 package org.apache.sis.storage.gdal;
 
+import java.io.Serializable;
 import java.util.Collections;
+import org.opengis.util.FactoryException;
 import org.opengis.parameter.ParameterValueGroup;
 import org.opengis.parameter.ParameterDescriptor;
 import org.opengis.parameter.ParameterDescriptorGroup;
@@ -24,11 +26,14 @@ import org.opengis.referencing.operation
 import org.opengis.referencing.operation.Matrix;
 import org.opengis.referencing.operation.MathTransform;
 import org.opengis.referencing.operation.TransformException;
+import org.opengis.referencing.operation.MathTransformFactory;
+import org.opengis.referencing.crs.CoordinateReferenceSystem;
 import org.apache.sis.internal.util.Constants;
 import org.apache.sis.metadata.iso.citation.Citations;
 import org.apache.sis.parameter.ParameterBuilder;
 import org.apache.sis.referencing.operation.DefaultOperationMethod;
 import org.apache.sis.referencing.operation.transform.AbstractMathTransform;
+import org.apache.sis.util.ComparisonMode;
 
 
 /**
@@ -39,7 +44,12 @@ import org.apache.sis.referencing.operat
  * @since   0.8
  * @module
  */
-final class Transform extends AbstractMathTransform {
+final class Transform extends AbstractMathTransform implements Serializable {
+    /**
+     * For cross-version compatibility.
+     */
+    private static final long serialVersionUID = -8593638007439108805L;
+
     /**
      * The operation method for a transformation between two {@link PJ} instances.
      * The parameter names are taken from
@@ -170,4 +180,61 @@ final class Transform extends AbstractMa
         }
         return inverse;
     }
+
+    /**
+     * If both transforms are {@literal Proj.4} wrappers, returns a single transform without intermediate step.
+     */
+    @Override
+    protected MathTransform tryConcatenate(final boolean applyOtherFirst, final MathTransform other,
+            final MathTransformFactory factory) throws FactoryException
+    {
+        if (other instanceof Transform) {
+            final Transform tr = (Transform) other;
+            if (applyOtherFirst) {
+                return new Transform(tr.source, tr.source3D, target, target3D);
+            } else {
+                return new Transform(source, source3D, tr.target, tr.target3D);
+            }
+        }
+        return super.tryConcatenate(applyOtherFirst, other, factory);
+    }
+
+    /**
+     * Returns {@code true} if this math transform is for the given pair of source and target CRS.
+     */
+    final boolean isFor(final CoordinateReferenceSystem sourceCRS, final PJ sourcePJ,
+                        final CoordinateReferenceSystem targetCRS, final PJ targetPJ)
+    {
+        return source.equals(sourcePJ) && dimensionMatches(sourceCRS, source3D) &&
+               target.equals(targetPJ) && dimensionMatches(targetCRS, target3D);
+    }
+
+    /**
+     * Tests whether the given CRS matches the expected number of dimensions.
+     */
+    private static boolean dimensionMatches(final CoordinateReferenceSystem crs, final boolean is3D) {
+        return crs.getCoordinateSystem().getDimension() == (is3D ? 3 : 2);
+    }
+
+    /**
+     * Computes a hash value for this transform. This method is invoked by {@link #hashCode()} when first needed.
+     */
+    @Override
+    protected int computeHashCode() {
+        return super.computeHashCode() + source.hashCode() + 31*target.hashCode();
+    }
+
+    /**
+     * Compares the given object with this transform for equality.
+     * This implementation can not ignore metadata or rounding errors.
+     */
+    @Override
+    public boolean equals(final Object object, final ComparisonMode mode) {
+        if (object instanceof Transform) {
+            final Transform that = (Transform) object;
+            return source.equals(that.source) && source3D == that.source3D
+                && target.equals(that.target) && target3D == that.target3D;
+        }
+        return false;
+    }
 }

Modified: sis/branches/JDK7/storage/sis-gdal/src/main/java/org/apache/sis/storage/gdal/package-info.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/storage/sis-gdal/src/main/java/org/apache/sis/storage/gdal/package-info.java?rev=1802942&r1=1802941&r2=1802942&view=diff
==============================================================================
--- sis/branches/JDK7/storage/sis-gdal/src/main/java/org/apache/sis/storage/gdal/package-info.java [UTF-8] (original)
+++ sis/branches/JDK7/storage/sis-gdal/src/main/java/org/apache/sis/storage/gdal/package-info.java [UTF-8] Tue Jul 25 13:54:41 2017
@@ -16,7 +16,65 @@
  */
 
 /**
- * Referencing services as wrapper around the C/C++ <a href="http://proj.osgeo.org/">{@literal Proj.4}</a> library.
+ * Extensions to referencing services as wrapper around the C/C++ {@literal Proj.4} library.
+ * Current version wraps only referencing services, but future versions are expected to wrap more GDAL functionalities.
+ * Unless otherwise specified, this optional module requires the native (C/C++) <a href="http://proj.osgeo.org/">Proj.4</a>
+ * library to be installed on the local machine. This package allows to:
+ * <ul>
+ *   <li>{@linkplain org.apache.sis.storage.gdal.Proj4#createCRS Create a Coordinate Reference System instance from a Proj.4 definition string}.</li>
+ *   <li>Conversely, {@linkplain org.apache.sis.storage.gdal.Proj4#definition get a Proj.4 definition string from a Coordinate Reference System}.</li>
+ *   <li>{@linkplain org.apache.sis.storage.gdal.Proj4#createOperation Create a coordinate operation backed by Proj.4 between two arbitrary CRS}.</li>
+ * </ul>
+ *
+ * Note that Apache SIS provides its own map projection engine in pure Java, so this module is usually not needed.
+ * This module is useful when a map projection is not yet available in Apache SIS, or when an application needs to
+ * reproduce the exact same numerical results than Proj.4. But some Apache SIS features like
+ * {@linkplain org.apache.sis.referencing.operation.transform.AbstractMathTransform#derivative transform derivatives}
+ * are not available through the Proj.4 wrappers.
+ *
+ * <p>When this optional module is available, the {@link org.apache.sis.referencing.CRS#forCode CRS.forCode(String)}
+ * method accepts Proj.4 definition strings prefixed by {@code "Proj4::"}. Example:</p>
+ *
+ * {@preformat java
+ *     CoordinateReferenceSystem crs = CRS.forCode("Proj4::+init=epsg:3395");
+ * }
+ *
+ * Calls to {@link org.apache.sis.referencing.CRS#findOperation CRS.findOperation(…)} will delegate the coordinate
+ * transformation to Proj.4 if an only if {@code sourceCRS} and {@code targetCRS} were both obtained from a code
+ * in {@code "Proj4"} namespace or by a method in this package. If at least one CRS were obtained by another way,
+ * then Apache SIS will use its own referencing engine. The backing referencing engine can be seen by printing
+ * the {@code CoordinateOperation}.
+ *
+ * <div class="section">Note on Proj.4 definition strings</div>
+ * Proj.4 unconditionally requires 3 letters for the {@code "+axis="} parameter — for example {@code "neu"} for
+ * <cite>North</cite>, <cite>East</cite> and <cite>Up</cite> respectively — regardless the number of dimensions
+ * in the CRS to create. Apache SIS makes the vertical direction optional in order to specify whether the CRS to
+ * create shall contain a vertical axis or not:
+ *
+ * <ul>
+ *   <li>If the vertical direction is present (as in {@code "neu"}), a three-dimensional CRS is created.</li>
+ *   <li>If the vertical direction is absent (as in {@code "ne"}), a two-dimensional CRS is created.</li>
+ * </ul>
+ *
+ * <div class="note"><b>Examples:</b>
+ * <ul>
+ *   <li>{@code "+init=epsg:4326"} (<strong>not</strong> equivalent to the standard EPSG::4326 definition)</li>
+ *   <li>{@code "+proj=latlong +datum=WGS84 +ellps=WGS84 +towgs84=0,0,0"} (default to two-dimensional CRS)</li>
+ *   <li>{@code "+proj=latlon +a=6378137.0 +b=6356752.314245179 +pm=0.0 +axis=ne"} (explicitely two-dimensional)</li>
+ *   <li>{@code "+proj=latlon +a=6378137.0 +b=6356752.314245179 +pm=0.0 +axis=neu"} (three-dimensional)</li>
+ * </ul>
+ * </div>
+ *
+ * <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.
+ *
+ * <div class="section">Installation</div>
+ * The Proj.4 library needs to be reachable on a platform-dependent library path.
+ * For example the operating system may search in {@code /usr/lib}, {@code /opt/local/lib} and other directories.
+ * One of those directories shall contain the {@code proj} or {@code libproj} file with platform-specific suffix
+ * (e.g. {@code .so}, {@code .dylib} or {@code .dll}).
+ * An easy way to install the library in appropriate directory is to use the package manager provided by the platform.
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @version 0.8

Modified: sis/branches/JDK7/storage/sis-gdal/src/test/java/org/apache/sis/storage/gdal/PJTest.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/storage/sis-gdal/src/test/java/org/apache/sis/storage/gdal/PJTest.java?rev=1802942&r1=1802941&r2=1802942&view=diff
==============================================================================
--- sis/branches/JDK7/storage/sis-gdal/src/test/java/org/apache/sis/storage/gdal/PJTest.java [UTF-8] (original)
+++ sis/branches/JDK7/storage/sis-gdal/src/test/java/org/apache/sis/storage/gdal/PJTest.java [UTF-8] Tue Jul 25 13:54:41 2017
@@ -23,8 +23,8 @@ import org.apache.sis.test.TestCase;
 import org.junit.BeforeClass;
 import org.junit.Test;
 
-import static org.junit.Assert.*;
 import static org.junit.Assume.*;
+import static org.apache.sis.test.Assert.*;
 
 
 /**
@@ -60,18 +60,15 @@ public final strictfp class PJTest exten
      * Ensures that the given object is the WGS84 definition.
      */
     private static void assertIsWGS84(final PJ pj) {
-        assertEquals("Lat/long (Geodetic alias)", pj.getName().trim());
         assertEquals("+proj=latlong +datum=WGS84 +ellps=WGS84 +towgs84=0,0,0", pj.getCode().trim());
         assertEquals("Proj4",            pj.getCodeSpace());
         assertSame  (Citations.PROJ4,    pj.getAuthority());
         assertTrue  (Character.isDigit(  pj.getVersion().codePointAt(0)));
         assertEquals(PJ.Type.GEOGRAPHIC, pj.getType());
-        assertEquals(6378137.0,          pj.getSemiMajorAxis(),         1E-9);
-        assertEquals(6356752.314245179,  pj.getSemiMinorAxis(),         1E-9);
-        assertEquals(298.257223563,      pj.getInverseFlattening(),     1E-9);
-        assertEquals(0.0,                pj.getGreenwichLongitude(),    STRICT);
-        assertEquals(1.0,                pj.getLinearUnitToMetre(true), STRICT);
-        assertArrayEquals(new char[] {'e', 'n', 'u'}, pj.getAxisDirections());
+        final double[] ellps = pj.getEllipsoidDefinition();
+        assertEquals(2, ellps.length);
+        ellps[1] = 1 / (1 - StrictMath.sqrt(1 - ellps[1]));     // Replace eccentricity squared by inverse flattening.
+        assertArrayEquals(new double[] {6378137.0, 298.257223563}, ellps, 1E-9);
     }
 
     /**
@@ -94,13 +91,15 @@ public final strictfp class PJTest exten
     public void testEPSG3395() throws FactoryException {
         final PJ pj = new PJ("+init=epsg:3395");
         assertEquals(PJ.Type.PROJECTED, pj.getType());
-        assertArrayEquals(new char[] {'e', 'n', 'u'}, pj.getAxisDirections());
-        assertEquals(1.0, pj.getLinearUnitToMetre(true), STRICT);
+        final String definition = pj.getCode();
+        assertTrue(definition, definition.contains("+proj=merc"));
+        assertTrue(definition, definition.contains("+datum=WGS84"));
+        assertTrue(definition, definition.contains("+units=m"));
         assertIsWGS84(new PJ(pj));
     }
 
     /**
-     * Tests the {@link PJ#getLinearUnitToMetre(boolean)} method.
+     * Tests the units of measurement.
      *
      * @throws FactoryException if the Proj.4 definition string used in this test is invalid.
      */
@@ -108,9 +107,11 @@ public final strictfp class PJTest exten
     public void testGetLinearUnit() throws FactoryException {
         PJ pj;
         pj = new PJ("+proj=merc +to_meter=1");
-        assertEquals(1, pj.getLinearUnitToMetre(false), STRICT);
+        String definition = pj.getCode();
+        assertTrue(definition, definition.contains("+to_meter=1"));
         pj = new PJ("+proj=merc +to_meter=0.001");
-        assertEquals(0.001, pj.getLinearUnitToMetre(false), STRICT);
+        definition = pj.getCode();
+        assertTrue(definition, definition.contains("+to_meter=0.001"));
     }
 
     /**
@@ -161,20 +162,20 @@ public final strictfp class PJTest exten
     public void testNaN() throws FactoryException {
         final PJ pj = new PJ("+proj=latlong +datum=WGS84");
         pj.finalize();              // This cause the disposal of the internal PJ structure.
+        assertNull(pj.getCode());
         assertNull(pj.getType());
-        assertNaN(pj.getSemiMajorAxis());
-        assertNaN(pj.getSemiMinorAxis());
-        assertNaN(pj.getEccentricitySquared());
-        assertNaN(pj.getGreenwichLongitude());
-        assertNaN(pj.getLinearUnitToMetre(false));
-        assertNaN(pj.getLinearUnitToMetre(true));
+        assertNull(pj.getEllipsoidDefinition());
     }
 
     /**
-     * Asserts that the bits pattern of the given value is strictly identical to the bits
-     * pattern of the {@link java.lang.Double#NaN} constant.
+     * Tests serialization. Since we can not serialize native resources, {@link PJ} is expected
+     * to serialize the Proj.4 definition string instead.
+     *
+     * @throws FactoryException if the Proj.4 definition string used in this test is invalid.
      */
-    private static void assertNaN(final double value) {
-        assertEquals(Double.doubleToRawLongBits(Double.NaN), Double.doubleToRawLongBits(value));
+    @Test
+    public void testSerialization() throws FactoryException {
+        final PJ pj = new PJ("+proj=latlong +datum=WGS84");
+        assertNotSame(pj, assertSerializedEquals(pj));
     }
 }

Modified: sis/branches/JDK7/storage/sis-gdal/src/test/java/org/apache/sis/storage/gdal/Proj4FactoryTest.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/storage/sis-gdal/src/test/java/org/apache/sis/storage/gdal/Proj4FactoryTest.java?rev=1802942&r1=1802941&r2=1802942&view=diff
==============================================================================
--- sis/branches/JDK7/storage/sis-gdal/src/test/java/org/apache/sis/storage/gdal/Proj4FactoryTest.java [UTF-8] (original)
+++ sis/branches/JDK7/storage/sis-gdal/src/test/java/org/apache/sis/storage/gdal/Proj4FactoryTest.java [UTF-8] Tue Jul 25 13:54:41 2017
@@ -19,6 +19,7 @@ package org.apache.sis.storage.gdal;
 import java.util.Arrays;
 import org.opengis.util.FactoryException;
 import org.opengis.geometry.DirectPosition;
+import org.opengis.referencing.cs.AxisDirection;
 import org.opengis.referencing.crs.ProjectedCRS;
 import org.opengis.referencing.crs.GeographicCRS;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
@@ -26,6 +27,7 @@ import org.opengis.referencing.operation
 import org.opengis.referencing.operation.Conversion;
 import org.opengis.referencing.operation.MathTransform;
 import org.opengis.referencing.operation.TransformException;
+import org.opengis.parameter.ParameterValueGroup;
 import org.apache.sis.geometry.DirectPosition2D;
 import org.apache.sis.test.DependsOn;
 import org.apache.sis.test.TestCase;
@@ -76,12 +78,12 @@ public final strictfp class Proj4Factory
     public void test4326() throws FactoryException {
         final Proj4Factory factory = Proj4Factory.INSTANCE;
         final GeographicCRS crs = factory.createGeographicCRS("+init=epsg:4326");
-        /*
-         * Use Proj.4 specific API to check axis order.
-         */
         final PJ pj = (PJ) TestUtilities.getSingleton(crs.getIdentifiers());
         assertEquals(PJ.Type.GEOGRAPHIC, pj.getType());
-        assertArrayEquals(new char[] {'e', 'n', 'u'}, pj.getAxisDirections());
+        final String definition = pj.getCode();
+        assertTrue(definition, definition.contains("+proj=longlat"));
+        assertTrue(definition, definition.contains("+datum=WGS84"));
+        assertAxisDirectionsEqual(definition, crs.getCoordinateSystem(), AxisDirection.EAST, AxisDirection.NORTH);
     }
 
     /**
@@ -93,12 +95,12 @@ public final strictfp class Proj4Factory
     public void test3395() throws FactoryException {
         final Proj4Factory factory = Proj4Factory.INSTANCE;
         final ProjectedCRS crs = factory.createProjectedCRS("+init=epsg:3395");
-        /*
-         * Use Proj.4 specific API to check axis order.
-         */
         final PJ pj = (PJ) TestUtilities.getSingleton(crs.getIdentifiers());
         assertEquals(PJ.Type.PROJECTED, pj.getType());
-        assertArrayEquals(new char[] {'e', 'n', 'u'}, pj.getAxisDirections());
+        final String definition = pj.getCode();
+        assertTrue(definition, definition.contains("+proj=merc"));
+        assertTrue(definition, definition.contains("+datum=WGS84"));
+        assertAxisDirectionsEqual(definition, crs.getCoordinateSystem(), AxisDirection.EAST, AxisDirection.NORTH);
     }
 
     /**
@@ -112,13 +114,33 @@ public final strictfp class Proj4Factory
         final Proj4Factory  factory   = Proj4Factory.INSTANCE;
         final GeographicCRS sourceCRS = factory.createGeographicCRS("+init=epsg:4326");
         final ProjectedCRS  targetCRS = factory.createProjectedCRS("+init=epsg:3395");
-        final CoordinateOperation op  = factory.createOperation(sourceCRS, targetCRS);
+        final CoordinateOperation op  = factory.createOperation(sourceCRS, targetCRS, true);
         assertInstanceOf("createOperation", Conversion.class, op);
+        testMercatorProjection(op.getMathTransform());
+    }
 
-        final MathTransform mt = op.getMathTransform();
+    /**
+     * Tests EPSG:3395 on a point.
+     */
+    static void testMercatorProjection(final MathTransform mt) throws TransformException {
         DirectPosition pt = new DirectPosition2D(20, 40);
         pt = mt.transform(pt, pt);
         assertEquals("Easting",  2226389.816, pt.getOrdinate(0), 0.01);
         assertEquals("Northing", 4838471.398, pt.getOrdinate(1), 0.01);
     }
+
+    /**
+     * Tests {@link Proj4Factory#createParameterizedTransform(ParameterValueGroup)}.
+     *
+     * @throws FactoryException if an error occurred while creating the CRS objects.
+     * @throws TransformException if an error occurred while projecting a test point.
+     */
+    @Test
+    public void testParameterizedTransform() throws FactoryException, TransformException {
+        final Proj4Factory factory   = Proj4Factory.INSTANCE;
+        final ParameterValueGroup pg = factory.getDefaultParameters("merc");
+        pg.parameter("a").setValue(6378137.0);
+        pg.parameter("b").setValue(6356752.314245179);
+        testMercatorProjection(factory.createParameterizedTransform(pg));
+    }
 }

Modified: sis/branches/JDK7/storage/sis-gdal/src/test/java/org/apache/sis/storage/gdal/Proj4ParserTest.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/storage/sis-gdal/src/test/java/org/apache/sis/storage/gdal/Proj4ParserTest.java?rev=1802942&r1=1802941&r2=1802942&view=diff
==============================================================================
--- sis/branches/JDK7/storage/sis-gdal/src/test/java/org/apache/sis/storage/gdal/Proj4ParserTest.java [UTF-8] (original)
+++ sis/branches/JDK7/storage/sis-gdal/src/test/java/org/apache/sis/storage/gdal/Proj4ParserTest.java [UTF-8] Tue Jul 25 13:54:41 2017
@@ -21,6 +21,7 @@ import org.opengis.referencing.operation
 import org.apache.sis.referencing.operation.DefaultCoordinateOperationFactory;
 import org.apache.sis.internal.referencing.provider.Mercator1SP;
 import org.apache.sis.internal.system.DefaultFactories;
+import org.apache.sis.measure.Units;
 import org.apache.sis.test.TestCase;
 import org.junit.Test;
 import org.opengis.parameter.ParameterValueGroup;
@@ -62,5 +63,21 @@ public final strictfp class Proj4ParserT
         assertInstanceOf("method", Mercator1SP.class, parser.method(opFactory));
         final ParameterValueGroup pg = parser.parameters();
         assertEquals("scale_factor", pg.parameter("scale_factor").getValue(), 1.0);
+        assertEquals(Units.METRE, parser.unit(false));
+    }
+
+    /**
+     * Tests parsing a definition string with axes in kilometres.
+     *
+     * @throws FactoryException if the parsing failed.
+     */
+    @Test
+    public void testKilometres() throws FactoryException {
+        final Proj4Parser parser = new Proj4Parser("+proj=merc +to_meter=0.001");
+
+        assertInstanceOf("method", Mercator1SP.class, parser.method(opFactory));
+        final ParameterValueGroup pg = parser.parameters();
+        assertEquals("scale_factor", pg.parameter("scale_factor").getValue(), 1.0);
+        assertEquals(Units.KILOMETRE, parser.unit(false));
     }
 }

Modified: sis/branches/JDK7/storage/sis-gdal/src/test/java/org/apache/sis/test/suite/GDALTestSuite.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/storage/sis-gdal/src/test/java/org/apache/sis/test/suite/GDALTestSuite.java?rev=1802942&r1=1802941&r2=1802942&view=diff
==============================================================================
--- sis/branches/JDK7/storage/sis-gdal/src/test/java/org/apache/sis/test/suite/GDALTestSuite.java [UTF-8] (original)
+++ sis/branches/JDK7/storage/sis-gdal/src/test/java/org/apache/sis/test/suite/GDALTestSuite.java [UTF-8] Tue Jul 25 13:54:41 2017
@@ -33,7 +33,9 @@ import org.junit.runners.Suite;
     org.apache.sis.storage.gdal.PJTest.class,
     org.apache.sis.storage.gdal.Proj4Test.class,
     org.apache.sis.storage.gdal.Proj4ParserTest.class,
-    org.apache.sis.storage.gdal.Proj4FactoryTest.class
+    org.apache.sis.storage.gdal.Proj4FactoryTest.class,
+    org.apache.sis.storage.gdal.TransformTest.class,
+    org.apache.sis.storage.gdal.IntegrationTest.class
 })
 public final strictfp class GDALTestSuite extends TestSuite {
     /**



Mime
View raw message