You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sis.apache.org by de...@apache.org on 2019/04/30 14:35:16 UTC

[sis] 01/01: Merge branch 'geoapi-3.1'. Contains work on netCDF reader and addition of two map projections.

This is an automated email from the ASF dual-hosted git repository.

desruisseaux pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sis.git

commit 4610eabf6db804db9aa428d9977f8140f8e76e52
Merge: e55b128 2751173
Author: Martin Desruisseaux <ma...@geomatys.com>
AuthorDate: Tue Apr 30 16:34:42 2019 +0200

    Merge branch 'geoapi-3.1'. Contains work on netCDF reader and addition of two map projections.

 .../sis/internal/metadata/ReferencingServices.java |   2 +-
 .../sis/internal/simple/SimpleIdentifier.java      |  31 +-
 .../apache/sis/metadata/PropertyInformation.java   |   5 +-
 .../org/apache/sis/metadata/TreeTableView.java     |   2 +-
 .../iso/extent/DefaultSpatialTemporalExtent.java   |   4 +-
 .../sis/internal/simple/SimpleIdentifierTest.java  |  12 -
 .../org/apache/sis/internal/map/MapContext.java    |   2 +-
 .../org/apache/sis/coverage/grid/GridGeometry.java |  32 +-
 .../apache/sis/coverage/grid/PixelTranslation.java |  24 +-
 .../apache/sis/coverage/grid/GridGeometryTest.java |  33 +-
 .../internal/referencing/ReferencingUtilities.java |  18 +
 .../referencing/provider/MercatorSpherical.java    |   2 +-
 .../internal/referencing/provider/Polyconic.java   | 117 +++++++
 .../referencing/provider/PseudoPlateCarree.java    |  13 +-
 .../internal/referencing/provider/Sinusoidal.java  | 103 ++++++
 .../org/apache/sis/parameter/ParameterFormat.java  |   2 +-
 .../java/org/apache/sis/referencing/CommonCRS.java |  46 +--
 .../sis/referencing/EPSGFactoryFallback.java       | 186 +++++++---
 .../sis/referencing/StandardDefinitions.java       |  85 +++--
 .../java/org/apache/sis/referencing/cs/Codes.java  |   3 +-
 .../referencing/factory/sql/EPSGDataAccess.java    |   2 +-
 .../operation/CoordinateOperationContext.java      |  12 +-
 .../operation/CoordinateOperationRegistry.java     |   2 +-
 .../referencing/operation/matrix/package-info.java |   4 +-
 .../operation/projection/AlbersEqualArea.java      |   4 +-
 .../operation/projection/ConformalProjection.java  | 200 ++++-------
 .../operation/projection/CylindricalEqualArea.java |   6 +-
 .../operation/projection/EqualAreaProjection.java  | 206 +++++------
 .../operation/projection/Initializer.java          |  12 +-
 .../projection/LambertConicConformal.java          |   4 +-
 .../referencing/operation/projection/Mercator.java |   4 +-
 .../operation/projection/MeridianArcBased.java     | 222 ++++++++++++
 .../operation/projection/NormalizedProjection.java |   2 +-
 .../operation/projection/ObliqueMercator.java      |   1 -
 .../operation/projection/ObliqueStereographic.java |   3 +-
 .../operation/projection/PolarStereographic.java   |   4 +-
 .../operation/projection/Polyconic.java            | 382 +++++++++++++++++++++
 .../operation/projection/Sinusoidal.java           | 240 +++++++++++++
 .../operation/projection/TransverseMercator.java   | 183 ++++++----
 .../operation/transform/MathTransforms.java        |  36 +-
 .../operation/transform/TranslationTransform.java  |   9 +
 ...g.opengis.referencing.operation.OperationMethod |   2 +
 .../org/apache/sis/geometry/TransformTestCase.java |   2 +-
 .../referencing/provider/ProvidersTest.java        |   4 +-
 .../java/org/apache/sis/referencing/CRSTest.java   |   4 +-
 .../sis/referencing/EPSGFactoryFallbackTest.java   |  38 +-
 .../sis/referencing/StandardDefinitionsTest.java   |  33 +-
 .../sis/referencing/cs/DefaultCartesianCSTest.java |   7 +-
 .../operation/CoordinateOperationFinderTest.java   |   4 +-
 .../operation/projection/AlbersEqualAreaTest.java  |  10 +-
 .../projection/ConformalProjectionTest.java        | 117 +++----
 .../projection/EqualAreaProjectionTest.java        | 127 +++++++
 .../projection/LambertConicConformalTest.java      |   8 +-
 .../projection/MapProjectionTestCase.java          |  27 +-
 .../projection/MercatorMethodComparison.java       | 107 ++----
 .../operation/projection/MercatorTest.java         |   8 +-
 .../operation/projection/MeridianArcTest.java      | 263 ++++++++++++++
 .../sis/referencing/operation/projection/NoOp.java |   1 -
 .../projection/ObliqueStereographicTest.java       |   2 +-
 .../operation/projection/PolyconicTest.java        | 143 ++++++++
 .../operation/projection/SinusoidalTest.java       | 131 +++++++
 .../projection/TransverseMercatorTest.java         |   6 +-
 .../operation/projection/ZonedGridSystemTest.java  |   6 +-
 .../sis/test/suite/ReferencingTestSuite.java       |   4 +
 .../org/apache/sis/internal/util/Constants.java    |   6 -
 .../sis/internal/util/StandardDateFormat.java      |   2 +-
 .../java/org/apache/sis/util/ArgumentChecks.java   |   2 +-
 .../java/org/apache/sis/util/collection/Cache.java |   2 +-
 .../java/org/apache/sis/util/package-info.java     |   2 +-
 ide-project/NetBeans/nbproject/genfiles.properties |   2 +-
 ide-project/NetBeans/nbproject/project.xml         |   1 +
 profiles/sis-french-profile/pom.xml                |   2 +-
 .../apache/sis/internal/earth/netcdf/GCOM_C.java   | 266 +++++++++++---
 .../apache/sis/internal/earth/netcdf/GCOM_W.java   |  36 +-
 .../org/apache/sis/storage/geotiff/CRSBuilder.java |   2 +-
 .../org/apache/sis/internal/netcdf/CRSBuilder.java |  64 ++--
 .../org/apache/sis/internal/netcdf/Convention.java | 253 +++++++++++++-
 .../org/apache/sis/internal/netcdf/Decoder.java    |   8 +
 .../java/org/apache/sis/internal/netcdf/Grid.java  |   2 +-
 .../apache/sis/internal/netcdf/GridMapping.java    | 245 +++++++++++--
 .../apache/sis/internal/netcdf/NamedElement.java   |   2 +-
 .../java/org/apache/sis/internal/netcdf/Node.java  | 190 ++++++++++
 .../apache/sis/internal/netcdf/RasterResource.java |  41 ++-
 .../org/apache/sis/internal/netcdf/Resources.java  |   6 +
 .../sis/internal/netcdf/Resources.properties       |   1 +
 .../sis/internal/netcdf/Resources_fr.properties    |   1 +
 .../org/apache/sis/internal/netcdf/Variable.java   | 153 +--------
 .../sis/internal/netcdf/impl/ChannelDecoder.java   |  14 +-
 .../sis/internal/netcdf/impl/VariableInfo.java     |  18 +-
 .../sis/internal/netcdf/ucar/DecoderWrapper.java   |  17 +
 .../sis/internal/netcdf/ucar/GroupWrapper.java     |  78 +++++
 .../sis/internal/netcdf/ucar/VariableWrapper.java  |  26 +-
 .../sis/internal/storage/AbstractResource.java     |   4 +-
 .../sis/internal/storage/StoreUtilities.java       |   4 +-
 .../org/apache/sis/internal/storage/csv/Store.java |   6 +-
 .../sis/internal/storage/xml/package-info.java     |   2 +-
 .../main/java/org/apache/sis/storage/DataSet.java  |   4 +-
 .../apache/sis/storage/GridCoverageResource.java   |  12 +
 .../main/java/org/apache/sis/storage/Resource.java |   2 +-
 .../org/apache/sis/internal/storage/gpx/Store.java |   4 +-
 100 files changed, 3705 insertions(+), 1089 deletions(-)

diff --cc core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/ConformalProjectionTest.java
index 76bb7b7,651a031..edc64d7
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/ConformalProjectionTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/ConformalProjectionTest.java
@@@ -195,9 -191,16 +191,9 @@@ public final strictfp class ConformalPr
              }
              @Override public double derivative(final double φ) {
                  final double sinφ = sin(φ);
-                 return projection.dy_dφ(sinφ, cos(φ)) * expOfNorthing(projection, φ);
+                 return projection.dy_dφ(sinφ, cos(φ)) * transform(φ);
              }
          };
 -        isInverseTransformSupported = false;
 -        derivativeDeltas = new double[] {2E-8};
 -        tolerance = 1E-7;
 -        verifyInDomain(new double[] {-89 * (PI/180)},                  // Minimal value to test.
 -                       new double[] {+89 * (PI/180)},                  // Maximal value to test.
 -                       new int[]    {100},                             // Number of points to test.
 -                       TestUtilities.createRandomNumberGenerator());
      }
  
      /**
diff --cc core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/PolyconicTest.java
index 0000000,7ffe0d5..51a8849
mode 000000,100644..100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/PolyconicTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/PolyconicTest.java
@@@ -1,0 -1,155 +1,143 @@@
+ /*
+  * Licensed to the Apache Software Foundation (ASF) under one or more
+  * contributor license agreements.  See the NOTICE file distributed with
+  * this work for additional information regarding copyright ownership.
+  * The ASF licenses this file to You under the Apache License, Version 2.0
+  * (the "License"); you may not use this file except in compliance with
+  * the License.  You may obtain a copy of the License at
+  *
+  *     http://www.apache.org/licenses/LICENSE-2.0
+  *
+  * Unless required by applicable law or agreed to in writing, software
+  * distributed under the License is distributed on an "AS IS" BASIS,
+  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  * See the License for the specific language governing permissions and
+  * limitations under the License.
+  */
+ package org.apache.sis.referencing.operation.projection;
+ 
+ import org.opengis.util.FactoryException;
+ import org.opengis.referencing.operation.TransformException;
+ import org.apache.sis.internal.referencing.Formulas;
+ import org.apache.sis.test.DependsOnMethod;
+ import org.apache.sis.test.DependsOn;
+ import org.junit.Test;
+ 
+ 
+ /**
+  * Tests the {@link Polyconic} class.
+  *
+  * @author  Simon Reynard (Geomatys)
+  * @author  Martin Desruisseaux (Geomatys)
+  * @author  Rémi Maréchal (Geomatys)
+  * @version 1.0
+  * @since   1.0
+  * @module
+  */
+ @DependsOn(MeridianArcTest.class)
+ public final strictfp class PolyconicTest extends MapProjectionTestCase {
+     /**
+      * Creates a new instance of {@link Polyconic} concatenated with the (de)normalization matrices.
+      * The new instance is stored in the inherited {@link #transform} field.
+      *
+      * @param  ellipsoidal  {@code false} for a sphere, or {@code true} for WGS84 ellipsoid.
+      */
+     private void createProjection(final boolean ellipsoidal) throws FactoryException {
+         createCompleteProjection(new org.apache.sis.internal.referencing.provider.Polyconic(),
+                 ellipsoidal ? CLARKE_A : RADIUS,        // Semi-major axis (Clarke 1866)
+                 ellipsoidal ? CLARKE_B : RADIUS,        // Semi-minor axis (Clarke 1866)
+                 -96,                                    // Central meridian
+                  30,                                    // Latitude of origin
+                 Double.NaN, Double.NaN, Double.NaN, Double.NaN, Double.NaN);
+         tolerance = Formulas.LINEAR_TOLERANCE;  // Not NORMALIZED_TOLERANCE since this is not a NormalizedProjection.
+     }
+ 
+     /**
+      * Tests the projection of a few points on a sphere. The first point in this test is provided
+      * by Snyder at page 304. The Snyder example gives intermediate values at different step,
+      * which may be verified by executing this code in the debugger.
+      *
+      * @throws FactoryException if an error occurred while creating the map projection.
+      * @throws TransformException if an error occurred while projecting a point.
+      */
+     @Test
+     public void testSpherical() throws FactoryException, TransformException {
+         createProjection(false);
+         verifyTransform(
+             new double[] {                  // (λ,φ) coordinates in degrees to project.
+                 -75, 40,                    // Snyder example is relative to λ₀ = 96°W.
+                 -75,  0
+             },
+             new double[] {                  // Expected (x,y) results in metres.
+                 1780350.84,  1327706.12,    // Values derived from Snyder page 303 with R=6400000 metres.
+                 2345722.51, -3351032.16
+             });
+     }
+ 
+     /**
+      * Tests the projection of a few points on an ellipsoid. The first point in this test is provided
+      * by Snyder at page 304. The Snyder example gives intermediate values at different step, which may
+      * be verified by executing this code in the debugger. In particular during inverse projection,
+      * values of φ should be as below during each iteration steps:
+      *
+      * <ol>
+      *   <li>0.6967280</li>
+      *   <li>0.6981286</li>
+      *   <li>0.6981317</li>
+      * </ol>
+      *
+      * @throws FactoryException if an error occurred while creating the map projection.
+      * @throws TransformException if an error occurred while projecting a point.
+      */
+     @Test
+     public void testEllipsoidal() throws FactoryException, TransformException {
+         createProjection(true);
+         verifyTransform(
+             new double[] {                  // (λ,φ) coordinates in degrees to project.
+                 -75, 40,                    // Snyder example is relative to λ₀ = 96°W.
+                 -75,  0
+             },
+             new double[] {                  // Expected (x,y) results in metres.
+                 1776774.54,  1319657.78,    // Values derived from Snyder page 304.
+                 2337734.74, -3319933.30
+             });
+     }
+ 
+     /**
+      * Tests the derivatives at a few points on a sphere. This method compares the derivatives computed
+      * by the projection with an estimation of derivatives computed by the finite differences method.
+      *
+      * @throws FactoryException if an error occurred while creating the map projection.
+      * @throws TransformException if an error occurred while projecting a point.
+      */
+     @Test
+     @DependsOnMethod("testInverseDerivative")
+     public void testDerivativeOnSphere() throws FactoryException, TransformException {
+         createProjection(false);
+         final double delta = (1.0 / 60) / 1852;                 // Approximatively 1 metre.
+         derivativeDeltas = new double[] {delta, delta};
+         tolerance = Formulas.LINEAR_TOLERANCE / 10;
+         verifyDerivative(-100,  3);
+         verifyDerivative( -56, 50);
+         verifyDerivative( -20, 47);
+     }
+ 
+     /**
+      * Tests the derivatives at a few points on an ellipsoid. This method compares the derivatives computed
+      * by the projection with an estimation of derivatives computed by the finite differences method.
+      *
+      * @throws FactoryException if an error occurred while creating the map projection.
+      * @throws TransformException if an error occurred while projecting a point.
+      */
+     @Test
+     @DependsOnMethod("testInverseDerivative")
+     public void testDerivativeOnEllipsoid() throws FactoryException, TransformException {
+         createProjection(true);
+         final double delta = (1.0 / 60) / 1852;                 // Approximatively 1 metre.
+         derivativeDeltas = new double[] {delta, delta};
+         tolerance = Formulas.LINEAR_TOLERANCE / 10;
+         verifyDerivative(-100,  3);
+         verifyDerivative( -56, 50);
+         verifyDerivative( -20, 47);
+     }
 -
 -    /**
 -     * Runs the test defined in the GeoAPI-conformance module.
 -     *
 -     * @throws FactoryException   if the transform can not be created.
 -     * @throws TransformException if an error occurred while projecting a point.
 -     */
 -    @Test
 -    @DependsOnMethod("testEllipsoidal")
 -    public void runGeoapiTest() throws FactoryException, TransformException {
 -        createGeoApiTest(new org.apache.sis.internal.referencing.provider.Polyconic()).testPolyconic();
 -    }
+ }
diff --cc ide-project/NetBeans/nbproject/genfiles.properties
index fd1d952,cb2fa3c..adb45e3
--- a/ide-project/NetBeans/nbproject/genfiles.properties
+++ b/ide-project/NetBeans/nbproject/genfiles.properties
@@@ -3,6 -3,6 +3,6 @@@
  build.xml.data.CRC32=58e6b21c
  build.xml.script.CRC32=462eaba0
  build.xml.stylesheet.CRC32=28e38971@1.53.1.46
- nbproject/build-impl.xml.data.CRC32=5c88b452
 -nbproject/build-impl.xml.data.CRC32=86faa61f
 -nbproject/build-impl.xml.script.CRC32=aa8f5386
++nbproject/build-impl.xml.data.CRC32=d72b1d8e
 +nbproject/build-impl.xml.script.CRC32=3a1dc6ad
  nbproject/build-impl.xml.stylesheet.CRC32=3a2fa800@1.89.1.48
diff --cc storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/GridMapping.java
index ea7a691,b6b9d6f..5ba3333
--- a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/GridMapping.java
+++ b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/GridMapping.java
@@@ -22,9 -24,23 +24,22 @@@ import java.util.logging.Level
  import java.util.logging.LogRecord;
  import java.text.ParseException;
  import org.opengis.util.FactoryException;
+ import org.opengis.parameter.ParameterValueGroup;
+ import org.opengis.parameter.ParameterNotFoundException;
+ import org.opengis.referencing.IdentifiedObject;
+ import org.opengis.referencing.cs.CartesianCS;
  import org.opengis.referencing.cs.CoordinateSystem;
+ import org.opengis.referencing.crs.ProjectedCRS;
+ import org.opengis.referencing.crs.GeographicCRS;
  import org.opengis.referencing.crs.CoordinateReferenceSystem;
+ import org.opengis.referencing.operation.TransformException;
 -import org.opengis.referencing.operation.CoordinateOperationFactory;
+ import org.opengis.referencing.operation.OperationMethod;
  import org.opengis.referencing.operation.MathTransform;
+ import org.opengis.referencing.operation.Conversion;
+ import org.opengis.referencing.datum.DatumFactory;
+ import org.opengis.referencing.datum.GeodeticDatum;
+ import org.opengis.referencing.datum.PrimeMeridian;
+ import org.opengis.referencing.datum.Ellipsoid;
  import org.opengis.referencing.datum.PixelInCell;
  import org.apache.sis.referencing.IdentifiedObjects;
  import org.apache.sis.referencing.CRS;
@@@ -44,8 -67,9 +66,12 @@@ import org.apache.sis.util.CharSequence
  import org.apache.sis.util.ArraysExt;
  import org.apache.sis.io.wkt.WKTFormat;
  import org.apache.sis.io.wkt.Warnings;
+ import org.apache.sis.measure.Units;
  import ucar.nc2.constants.CF;
  
++// Branch-dependent imports
++import org.apache.sis.referencing.operation.DefaultCoordinateOperationFactory;
++
  
  /**
   * Temporary objects for creating a {@link GridGeometry} instance defined by attributes on a variable.
@@@ -132,6 -158,169 +160,169 @@@ final class GridMapping 
      }
  
      /**
+      * If the netCDF variable defines explicitly the map projection method and its parameters, returns those parameters.
+      * Otherwise returns {@code null}. The given {@code node} argument is typically a dummy variable referenced by value
+      * of the {@value CF#GRID_MAPPING} attribute on the real data variable (as required by CF-conventions), but may also
+      * be something else (the data variable itself, or a group, <i>etc.</i>). That node, together with the attributes to
+      * be parsed, depends on the {@link Convention} instance.
+      *
+      * @see <a href="http://cfconventions.org/cf-conventions/cf-conventions.html#grid-mappings-and-projections">CF-conventions</a>
+      */
+     private static GridMapping parseProjectionParameters(final Node node) {
+         final Map<String,Object> definition = node.decoder.convention().projection(node);
+         if (definition != null) try {
+             /*
+              * Fetch now numerical values that are not map projection parameters.
+              * This step needs to be done before to try to set parameter values.
+              */
+             final Object greenwichLongitude = definition.remove(Convention.LONGITUDE_OF_PRIME_MERIDIAN);
+             /*
+              * Prepare the block of projection parameters. The set of legal parameter depends on the map projection.
+              * We assume that all numerical values are map projection parameters; character sequences (assumed to be
+              * component names) are handled later. The CF-conventions use parameter names that are slightly different
+              * than OGC names, but Apache SIS implementations of map projections know how to handle them, including
+              * the redundant parameters like "inverse_flattening" and "earth_radius".
+              */
 -            final CoordinateOperationFactory opFactory = node.decoder.getCoordinateOperationFactory();
++            final DefaultCoordinateOperationFactory opFactory = node.decoder.getCoordinateOperationFactory();
+             final OperationMethod method = opFactory.getOperationMethod((String) definition.remove(CF.GRID_MAPPING_NAME));
+             final ParameterValueGroup parameters = method.getParameters().createValue();
+             for (final Map.Entry<String,Object> entry : definition.entrySet()) {
+                 final String name  = entry.getKey();
+                 final Object value = entry.getValue();
+                 if (value instanceof Number || value instanceof double[]) try {
+                     parameters.parameter(name).setValue(value);
+                 } catch (IllegalArgumentException ex) {
+                     warning(node, ex, Resources.Keys.CanNotSetProjectionParameter_5, node.decoder.getFilename(),
+                             node.getName(), name, value, ex.getLocalizedMessage());
+                 }
+             }
+             /*
+              * In principle, projection parameters do not include the semi-major and semi-minor axis lengths.
+              * But if those information are provided, then we use them for building the geodetic reference frame.
+              * Otherwise a default reference frame will be used.
+              */
+             final GeographicCRS baseCRS = createBaseCRS(node.decoder, parameters, definition, greenwichLongitude);
+             final MathTransform baseToCRS;
+             final CoordinateReferenceSystem crs;
+             if (method instanceof PseudoPlateCarree) {
+                 // Only swap axis order from (latitude, longitude) to (longitude, latitude).
+                 baseToCRS = MathTransforms.linear(new Matrix3(0, 1, 0, 1, 0, 0, 0, 0, 1));
+                 crs = baseCRS;
+             } else {
+                 Map<String,?> properties = properties(definition, Convention.CONVERSION_NAME, node.getName());
+                 final Conversion conversion = opFactory.createDefiningConversion(properties, method, parameters);
+                 final CartesianCS cs = ReferencingUtilities.standardProjectedCS(node.decoder.getCSAuthorityFactory());
+                 properties = properties(definition, Convention.PROJECTED_CRS_NAME, conversion);
+                 final ProjectedCRS p = node.decoder.getCRSFactory().createProjectedCRS(properties, baseCRS, conversion, cs);
+                 baseToCRS = p.getConversionFromBase().getMathTransform();
+                 crs = p;
+             }
+             /*
+              * Build the "grid to CRS" if present. This is not defined by CF-convention,
+              * but may be present in some non-CF conventions.
+              */
+             final MathTransform gridToCRS = node.decoder.convention().gridToCRS(node, baseToCRS);
+             return new GridMapping(crs, gridToCRS, false);
+         } catch (ClassCastException | IllegalArgumentException | FactoryException | TransformException e) {
+             canNotCreate(node, Resources.Keys.CanNotCreateCRS_3, e);
+         }
+         return null;
+     }
+ 
+     /**
+      * Creates the geographic CRS from axis length specified in the given map projection parameters.
+      * The returned CRS will always have (latitude, longitude) axes in that order and in degrees.
+      */
+     private static GeographicCRS createBaseCRS(final Decoder decoder, final ParameterValueGroup parameters,
+             final Map<String,Object> definition, final Object greenwichLongitude) throws FactoryException
+     {
+         final DatumFactory datumFactory = decoder.getDatumFactory();
+         final CommonCRS defaultDefinitions = decoder.convention().defaultHorizontalCRS(false);
+         boolean isSpecified = false;
+         /*
+          * Prime meridian built from "longitude_of_prime_meridian".
+          */
+         final PrimeMeridian meridian;
+         if (greenwichLongitude instanceof Number) {
+             final double longitude = ((Number) greenwichLongitude).doubleValue();
+             final Map<String,?> properties = properties(definition, Convention.PRIME_MERIDIAN_NAME, null);
+             meridian = datumFactory.createPrimeMeridian(properties, longitude, Units.DEGREE);
+             isSpecified = true;
+         } else {
+             meridian = defaultDefinitions.primeMeridian();
+         }
+         /*
+          * Ellipsoid built from "semi_major_axis", "semi_minor_axis", etc.
+          */
+         Ellipsoid ellipsoid;
+         try {
+             final double semiMajor = parameters.parameter(Constants.SEMI_MAJOR).doubleValue();
+             final Map<String,?> properties = properties(definition, Convention.ELLIPSOID_NAME, null);
+             if (parameters.parameter(Constants.IS_IVF_DEFINITIVE).booleanValue()) {
+                 final double ivf = parameters.parameter(Constants.INVERSE_FLATTENING).doubleValue();
+                 ellipsoid = datumFactory.createFlattenedSphere(properties, semiMajor, ivf, Units.METRE);
+             } else {
+                 final double semiMinor = parameters.parameter(Constants.SEMI_MINOR).doubleValue();
+                 ellipsoid = datumFactory.createEllipsoid(properties, semiMajor, semiMinor, Units.METRE);
+             }
+             isSpecified = true;
+         } catch (ParameterNotFoundException | IllegalStateException e) {
+             // Ignore - may be normal if the map projection is not an Apache SIS implementation.
+             ellipsoid = defaultDefinitions.ellipsoid();
+         }
+         /*
+          * Geodetic datum built from "towgs84" and above properties.
+          */
+         final Object bursaWolf = definition.remove(Convention.TOWGS84);
+         final GeodeticDatum datum;
+         if (isSpecified | bursaWolf != null) {
+             Map<String,Object> properties = properties(definition, Convention.GEODETIC_DATUM_NAME, ellipsoid);
+             if (bursaWolf instanceof BursaWolfParameters) {
+                 properties = new HashMap<>(properties);
+                 properties.put(DefaultGeodeticDatum.BURSA_WOLF_KEY, bursaWolf);
+                 isSpecified = true;
+             }
+             datum = datumFactory.createGeodeticDatum(properties, ellipsoid, meridian);
+         } else {
+             datum = defaultDefinitions.datum();
+         }
+         /*
+          * Geographic CRS from all above properties.
+          */
+         if (isSpecified) {
+             final Map<String,?> properties = properties(definition, Convention.GEOGRAPHIC_CRS_NAME, datum);
+             return decoder.getCRSFactory().createGeographicCRS(properties, datum,
+                     defaultDefinitions.geographic().getCoordinateSystem());
+         } else {
+             return defaultDefinitions.geographic();
+         }
+     }
+ 
+     /**
+      * Returns the {@code properties} argument value to give to the factory methods of geodetic objects.
+      * The returned map contains at least an entry for {@value IdentifiedObject#NAME_KEY} with the name
+      * fetched from the value of the attribute named {@code nameAttribute}.
+      *
+      * @param definition     map containing the attribute values.
+      * @param nameAttribute  name of the attribute from which to get the name.
+      * @param fallback       fallback as an {@link IdentifiedObject} (from which the name will be copied),
+      *                       or a character sequence, or {@code null} for "Unnamed" localized string.
+      */
+     private static Map<String,Object> properties(final Map<String,Object> definition, final String nameAttribute, final Object fallback) {
+         Object name = definition.remove(nameAttribute);
+         if (name == null) {
+             if (fallback instanceof IdentifiedObject) {
+                 name = ((IdentifiedObject) fallback).getName();
+             } else if (fallback != null) {
+                 name = fallback;
+             } else {
+                 name = Vocabulary.formatInternational(Vocabulary.Keys.Unnamed);
+             }
+         }
+         return Collections.singletonMap(IdentifiedObject.NAME_KEY, name);
+     }
+ 
+     /**
       * Tries to parse a CRS and affine transform from GDAL GeoTransform coefficients.
       * Those coefficients are not in the usual order expected by matrix, affine
       * transforms or TFW files. The relationship from pixel/line (P,L) coordinates