sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject svn commit: r1686282 [2/4] - in /sis/trunk: ./ core/sis-feature/src/main/java/org/apache/sis/feature/ core/sis-feature/src/test/java/org/apache/sis/feature/ core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/ core/sis-metadata/src/main/ja...
Date Thu, 18 Jun 2015 17:38:17 GMT
Modified: sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/GeodeticObjectParser.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/GeodeticObjectParser.java?rev=1686282&r1=1686281&r2=1686282&view=diff
==============================================================================
--- sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/GeodeticObjectParser.java [UTF-8] (original)
+++ sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/GeodeticObjectParser.java [UTF-8] Thu Jun 18 17:38:16 2015
@@ -21,12 +21,14 @@ import java.util.List;
 import java.util.Locale;
 import java.util.HashMap;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Date;
 import java.text.DateFormat;
 import java.text.NumberFormat;
 import java.text.ParsePosition;
 import java.text.ParseException;
 import javax.measure.unit.Unit;
+import javax.measure.unit.UnitFormat;
 import javax.measure.unit.SI;
 import javax.measure.unit.NonSI;
 import javax.measure.quantity.Angle;
@@ -36,9 +38,9 @@ import javax.measure.quantity.Duration;
 
 import org.opengis.util.Factory;
 import org.opengis.metadata.Identifier;
-import org.opengis.metadata.citation.Citation;
 import org.opengis.parameter.ParameterValueGroup;
 import org.opengis.referencing.IdentifiedObject;
+import org.opengis.referencing.ReferenceSystem;
 import org.opengis.referencing.ObjectFactory;
 import org.opengis.util.FactoryException;
 
@@ -52,6 +54,11 @@ import org.opengis.referencing.operation
 import org.apache.sis.measure.Units;
 import org.apache.sis.metadata.iso.ImmutableIdentifier;
 import org.apache.sis.metadata.iso.citation.Citations;
+import org.apache.sis.metadata.iso.extent.DefaultExtent;
+import org.apache.sis.metadata.iso.extent.DefaultGeographicBoundingBox;
+import org.apache.sis.metadata.iso.extent.DefaultGeographicDescription;
+import org.apache.sis.metadata.iso.extent.DefaultVerticalExtent;
+import org.apache.sis.metadata.iso.extent.DefaultTemporalExtent;
 import org.apache.sis.internal.metadata.AxisNames;
 import org.apache.sis.internal.metadata.WKTKeywords;
 import org.apache.sis.internal.metadata.VerticalDatumTypes;
@@ -71,21 +78,6 @@ import static java.util.Collections.sing
  * {@linkplain org.apache.sis.referencing.operation.transform.AbstractMathTransform Math Transform} objects.
  * Note that math transforms are part of the WKT 1 {@code "FITTED_CS"} element.
  *
- * <div class="section">Default axis names</div>
- * The WKT 1 specification defined axis names different than the ISO 19111 ones.
- * This parser replaces the WKT 1 names by the ISO names and abbreviations when possible.
- *
- * <table class="sis">
- *   <tr><th>CRS type</th>   <th>WKT1 names</th> <th>ISO abbreviations</th></tr>
- *   <tr><td>Geographic</td> <td>Lon, Lat</td>   <td>λ, φ</td></tr>
- *   <tr><td>Vertical</td>   <td>H</td>          <td>h</td></tr>
- *   <tr><td>Projected</td>  <td>X, Y</td>       <td>x, y</td></tr>
- *   <tr><td>Geocentric</td> <td>X, Y, Z</td>    <td>X, Y, Z</td></tr>
- * </table>
- *
- * The default behavior is to use the ISO identifiers.
- * This behavior can be changed by setting the parsing conventions to {@link Convention#WKT1}.
- *
  * @author  Rémi Eve (IRD)
  * @author  Martin Desruisseaux (IRD, Geomatys)
  * @since   0.6
@@ -94,6 +86,23 @@ import static java.util.Collections.sing
  */
 final class GeodeticObjectParser extends MathTransformParser {
     /**
+     * The keywords of unit elements. Most frequently used keywords should be first.
+     */
+    private static final String[] UNIT_KEYWORDS = {
+        WKTKeywords.Unit,   // Ignored since it does not allow us to know the quantity dimension.
+        WKTKeywords.LengthUnit, WKTKeywords.AngleUnit, WKTKeywords.ScaleUnit, WKTKeywords.TimeUnit,
+        WKTKeywords.ParametricUnit  // Ignored for the same reason than "Unit".
+    };
+
+    /**
+     * The base unit associated to the {@link #UNIT_KEYWORDS}, ignoring {@link WKTKeywords#Unit}.
+     * For each {@code UNIT_KEYWORDS[i]} element, the associated base unit is {@code BASE_UNIT[i]}.
+     */
+    private static final Unit<?>[] BASE_UNITS = {
+        SI.METRE, SI.RADIAN, Unit.ONE, SI.SECOND
+    };
+
+    /**
      * The names of the 7 parameters in a {@code TOWGS84[…]} element.
      * Those names are derived from the <cite>Well Known Text</cite> (WKT) version 1 specification.
      * They are not the same than the {@link org.apache.sis.referencing.datum.BursaWolfParameters}
@@ -134,31 +143,41 @@ final class GeodeticObjectParser extends
      *   <li>{@link Convention#WKT1_COMMON_UNITS} means that {@code PRIMEM} and {@code PARAMETER} angular units
      *       need to be forced to {@code NonSI.DEGREE_ANGLE} instead than inferred from the context.
      *       Note that this rule does not apply to {@code AXIS} elements.</li>
+     *
+     *   <li>{@link Convention#WKT1_IGNORE_AXES} means that axes should be parsed only for verifying the syntax,
+     *       but otherwise parsing should behave as if axes were not declared.</li>
      * </ul>
      */
     private final Convention convention;
 
     /**
-     * {@code true} if {@code AXIS[...]} elements should be ignored.
-     * This is sometime used for simulating a "force longitude first axis order" behavior.
-     * This is also used for compatibility with softwares that ignore axis elements.
+     * A map of properties to be given to the factory constructor methods.
+     * This map will be recycled for each object to be parsed.
+     */
+    private final Map<String,Object> properties = new HashMap<String,Object>(4);
+
+    /**
+     * The last vertical CRS found during the parsing, or {@code null} if none.
+     * This information is needed for creating {@link DefaultVerticalExtent} instances.
      *
-     * <p>Note that {@code AXIS} elements still need to be well formed even when this flag is set to {@code true}.
-     * Malformed axis elements will continue to cause a {@link ParseException} despite their content being ignored.</p>
+     * <p>ISO 19162 said that we should have at most one vertical CRS per WKT. Apache SIS does
+     * not enforce this constraint, but if a WKT contains more than one vertical CRS then the
+     * instance used for completing the {@link DefaultVerticalExtent} instances is unspecified.</p>
      */
-    private final boolean isAxisIgnored;
+    private transient VerticalCRS verticalCRS;
 
     /**
-     * A map of properties to be given the factory constructor methods.
-     * This map will be recycled for each object to be parsed.
+     * A chained list of temporary information needed for completing the construction of {@link DefaultVerticalExtent}
+     * instances. In particular, stores the unit of measurement until the {@link VerticalCRS} instance to associate to
+     * the extents become known.
      */
-    private final Map<String,Object> properties = new HashMap<String,Object>(4);
+    private transient VerticalInfo verticalElements;
 
     /**
      * Creates a parser using the default set of symbols and factories.
      */
     public GeodeticObjectParser() {
-        this(Symbols.getDefault(), null, null, Convention.DEFAULT, false, null, null);
+        this(Symbols.getDefault(), null, null, null, Convention.DEFAULT, null, null);
     }
 
     /**
@@ -178,14 +197,13 @@ final class GeodeticObjectParser extends
     public GeodeticObjectParser(final Map<String,?> defaultProperties,
             final ObjectFactory factories, final MathTransformFactory mtFactory)
     {
-        super(Symbols.getDefault(), null, null, mtFactory, (Locale) defaultProperties.get(Errors.LOCALE_KEY));
-        crsFactory    = (CRSFactory)   factories;
-        csFactory     = (CSFactory)    factories;
-        datumFactory  = (DatumFactory) factories;
-        referencing   = ReferencingServices.getInstance();
-        opFactory     = referencing.getCoordinateOperationFactory(defaultProperties, mtFactory);
-        convention    = Convention.DEFAULT;
-        isAxisIgnored = false;
+        super(Symbols.getDefault(), null, null, null, mtFactory, (Locale) defaultProperties.get(Errors.LOCALE_KEY));
+        crsFactory   = (CRSFactory)   factories;
+        csFactory    = (CSFactory)    factories;
+        datumFactory = (DatumFactory) factories;
+        referencing  = ReferencingServices.getInstance();
+        opFactory    = referencing.getCoordinateOperationFactory(defaultProperties, mtFactory);
+        convention   = Convention.DEFAULT;
     }
 
     /**
@@ -195,23 +213,22 @@ final class GeodeticObjectParser extends
      * @param symbols       The set of symbols to use.
      * @param numberFormat  The number format provided by {@link WKTFormat}, or {@code null} for a default format.
      * @param dateFormat    The date format provided by {@link WKTFormat}, or {@code null} for a default format.
+     * @param unitFormat    The unit format provided by {@link WKTFormat}, or {@code null} for a default format.
      * @param convention    The WKT convention to use.
-     * @param isAxisIgnored {@code true} if {@code AXIS} elements should be ignored.
      * @param errorLocale   The locale for error messages (not for parsing), or {@code null} for the system default.
      * @param factories     On input, the factories to use. On output, the factories used. Can be null.
      */
     GeodeticObjectParser(final Symbols symbols, final NumberFormat numberFormat, final DateFormat dateFormat,
-            final Convention convention, final boolean isAxisIgnored, final Locale errorLocale,
+            final UnitFormat unitFormat, final Convention convention, final Locale errorLocale,
             final Map<Class<?>,Factory> factories)
     {
-        super(symbols, numberFormat, dateFormat, getFactory(MathTransformFactory.class, factories), errorLocale);
+        super(symbols, numberFormat, dateFormat, unitFormat, getFactory(MathTransformFactory.class, factories), errorLocale);
         crsFactory   = getFactory(CRSFactory.class,   factories);
         csFactory    = getFactory(CSFactory.class,    factories);
         datumFactory = getFactory(DatumFactory.class, factories);
         referencing  = ReferencingServices.getInstance();
         opFactory    = referencing.getCoordinateOperationFactory(null, mtFactory);
         this.convention = convention;
-        this.isAxisIgnored = isAxisIgnored;
     }
 
     /**
@@ -240,11 +257,38 @@ final class GeodeticObjectParser extends
      */
     @Override
     public Object parseObject(final String text, final ParsePosition position) throws ParseException {
+        final Object object;
         try {
-            return super.parseObject(text, position);
+            object = super.parseObject(text, position);
+            /*
+             * After parsing the object, we may have been unable to set the VerticalCRS of VerticalExtent instances.
+             * First, try to set a default VerticalCRS for Mean Sea Level Height in metres. In the majority of cases
+             * that should be enough. If not (typically because the vertical extent uses other unit than metre), try
+             * to create a new CRS using the unit declared in the WKT.
+             */
+            if (verticalElements != null) {
+                Exception ex = null;
+                try {
+                    verticalElements = verticalElements.resolve(referencing.getMSLH());     // Optional operation.
+                } catch (UnsupportedOperationException e) {
+                    ex = e;
+                }
+                if (verticalElements != null) try {
+                    verticalElements = verticalElements.complete(crsFactory, csFactory);
+                } catch (FactoryException e) {
+                    if (ex == null) ex = e;
+                }
+                if (verticalElements != null) {
+                    warning(Errors.formatInternational(Errors.Keys.CanNotAssignUnitToDimension_2,
+                            WKTKeywords.VerticalExtent, verticalElements.unit), ex);
+                }
+            }
         } finally {
+            verticalCRS = null;
+            verticalElements = null;
             properties.clear();     // for letting the garbage collector do its work.
         }
+        return object;
     }
 
     /**
@@ -264,21 +308,20 @@ final class GeodeticObjectParser extends
         if (value != null) {
             return value;
         }
-        String keyword = WKTKeywords.GeogCS;
-        final Object child = element.peek();
-        if (child instanceof Element) {
-            keyword = ((Element) child).keyword;
-            if (keyword != null) {
-                if (keyword.equalsIgnoreCase(WKTKeywords.Axis))        return parseAxis      (element, false, SI.METRE, true);
-                if (keyword.equalsIgnoreCase(WKTKeywords.PrimeM))      return parsePrimem    (element, NonSI.DEGREE_ANGLE);
-                if (keyword.equalsIgnoreCase(WKTKeywords.ToWGS84))     return parseToWGS84   (element);
-                if (keyword.equalsIgnoreCase(WKTKeywords.Spheroid))    return parseSpheroid  (element);
-                if (keyword.equalsIgnoreCase(WKTKeywords.Vert_Datum))  return parseVertDatum (element);
-                if (keyword.equalsIgnoreCase(WKTKeywords.Local_Datum)) return parseLocalDatum(element);
-                if (keyword.equalsIgnoreCase(WKTKeywords.Datum))       return parseDatum     (element, referencing.getGreenwich());
-            }
+        Object object;
+        if ((object = parseAxis             (FIRST, element, false, SI.METRE)) == null &&
+            (object = parsePrimeMeridian    (FIRST, element, NonSI.DEGREE_ANGLE)) == null &&
+            (object = parseDatum            (FIRST, element, null)) == null &&
+            (object = parseEllipsoid        (FIRST, element)) == null &&
+            (object = parseToWGS84          (FIRST, element)) == null &&
+            (object = parseVerticalDatum    (FIRST, element)) == null &&
+            (object = parseTimeDatum        (FIRST, element)) == null &&
+            (object = parseEngineeringDatum (FIRST, element)) == null &&
+            (object = parseProjection       (FIRST, element, SI.METRE, NonSI.DEGREE_ANGLE)) == null)
+        {
+            throw element.missingOrUnknownComponent(WKTKeywords.GeodeticCRS);
         }
-        throw element.keywordNotFound(keyword, keyword == WKTKeywords.GeogCS);
+        return object;
     }
 
     /**
@@ -292,30 +335,27 @@ final class GeodeticObjectParser extends
     private CoordinateReferenceSystem parseCoordinateReferenceSystem(final Element element, final boolean mandatory)
             throws ParseException
     {
-        String keyword = WKTKeywords.GeogCS;
-        final Object child = element.peek();
-        if (child instanceof Element) {
-            keyword = ((Element) child).keyword;
-            if (keyword != null) {
-                if (keyword.equalsIgnoreCase(WKTKeywords.GeogCS))    return parseGeoGCS  (element);
-                if (keyword.equalsIgnoreCase(WKTKeywords.ProjCS))    return parseProjCS  (element);
-                if (keyword.equalsIgnoreCase(WKTKeywords.GeocCS))    return parseGeoCCS  (element);
-                if (keyword.equalsIgnoreCase(WKTKeywords.Vert_CS))   return parseVertCS  (element);
-                if (keyword.equalsIgnoreCase(WKTKeywords.TimeCRS))   return parseTimeCRS (element);
-                if (keyword.equalsIgnoreCase(WKTKeywords.Local_CS))  return parseLocalCS (element);
-                if (keyword.equalsIgnoreCase(WKTKeywords.Compd_CS))  return parseCompdCS (element);
-                if (keyword.equalsIgnoreCase(WKTKeywords.Fitted_CS)) return parseFittedCS(element);
+        CoordinateReferenceSystem crs;
+        if ((crs = parseGeographicCRS  (FIRST, element)) == null &&
+            (crs = parseProjectedCRS   (FIRST, element)) == null &&
+            (crs = parseGeocentricCRS  (FIRST, element)) == null &&
+            (crs = parseVerticalCRS    (FIRST, element)) == null &&
+            (crs = parseTimeCRS        (FIRST, element)) == null &&
+            (crs = parseEngineeringCRS (FIRST, element)) == null &&
+            (crs = parseCompoundCRS    (FIRST, element)) == null &&
+            (crs = parseFittedCS       (FIRST, element)) == null)
+        {
+            if (mandatory) {
+                throw element.missingOrUnknownComponent(WKTKeywords.GeodeticCRS);
             }
         }
-        if (mandatory) {
-            throw element.keywordNotFound(keyword, keyword == WKTKeywords.GeogCS);
-        }
-        return null;
+        return crs;
     }
 
     /**
-     * Parses an <strong>optional</strong> {@code "AUTHORITY"} element.
-     * This element has the following pattern:
+     * Parses an <strong>optional</strong> metadata elements and close.
+     * This include elements like {@code "SCOPE"}, {@code "ID"} (WKT 2) or {@code "AUTHORITY"} (WKT 1).
+     * This WKT 1 element has the following pattern:
      *
      * {@preformat text
      *     AUTHORITY["<name>", "<code>"]
@@ -324,19 +364,31 @@ final class GeodeticObjectParser extends
      * @param  parent The parent element.
      * @param  name The name of the parent object being parsed, either a {@link String} or {@link Identifier} instance.
      * @return A properties map with the parent name and the optional authority code.
-     * @throws ParseException if the {@code "AUTHORITY"} can not be parsed.
+     * @throws ParseException if an element can not be parsed.
      */
-    private Map<String,Object> parseAuthorityAndClose(final Element parent, Object name) throws ParseException {
+    private Map<String,Object> parseMetadataAndClose(final Element parent, Object name) throws ParseException {
         assert (name instanceof String) || (name instanceof Identifier);
         properties.clear();
         properties.put(IdentifiedObject.NAME_KEY, name);
-        final Element element = parent.pullOptionalElement(WKTKeywords.Authority);
-        if (element != null) {
-            final String auth = element.pullString("name");
-            final String code = element.pullObject("code").toString();  // Accepts Integer as well as String.
+        Element element;
+        while ((element = parent.pullElement(OPTIONAL, WKTKeywords.Id, WKTKeywords.Authority)) != null) {
+            final String   codeSpace = element.pullString("name");
+            final String   code      = element.pullObject("code").toString();   // Accepts Integer as well as String.
+            final Object   version   = element.pullOptional(Object.class);      // Accepts Number as well as String.
+            final Element  citation  = element.pullElement(OPTIONAL, WKTKeywords.Citation);
+            final String   authority;
+            if (citation != null) {
+                authority = citation.pullString("authority");
+                citation.close(ignoredElements);
+            } else {
+                authority = codeSpace;
+            }
+            final Element uri = element.pullElement(OPTIONAL, WKTKeywords.URI);
+            if (uri != null) {
+                uri.pullString("URI");      // TODO: not yet stored, since often redundant with other informations.
+                uri.close(ignoredElements);
+            }
             element.close(ignoredElements);
-            final Citation authority = Citations.fromName(auth);
-            properties.put(IdentifiedObject.IDENTIFIERS_KEY, new ImmutableIdentifier(authority, auth, code));
             /*
              * Note: we could be tempted to assign the authority to the name as well, like below:
              *
@@ -350,35 +402,166 @@ final class GeodeticObjectParser extends
              * (for example "WGS84" for the datum instead than "World Geodetic System 1984"),
              * so the name in WKT is often not compliant with the name actually defined by the authority.
              */
+            final ImmutableIdentifier id = new ImmutableIdentifier(Citations.fromName(authority),
+                    codeSpace, code, (version != null) ? version.toString() : null, null);
+            final Object previous = properties.put(IdentifiedObject.IDENTIFIERS_KEY, id);
+            if (previous != null) {
+                Identifier[] identifiers;
+                if (previous instanceof Identifier) {
+                    identifiers = new Identifier[] {(Identifier) previous, id};
+                } else {
+                    identifiers = (Identifier[]) previous;
+                    final int n = identifiers.length;
+                    identifiers = Arrays.copyOf(identifiers, n + 1);
+                    identifiers[n] = id;
+                }
+                properties.put(IdentifiedObject.IDENTIFIERS_KEY, identifiers);
+            }
+        }
+        /*
+         * Other metadata (SCOPE, AREA, etc.).  ISO 19162 said that at most one of each type shall be present,
+         * but our parser accepts an arbitrary amount of some kinds of metadata. They can be recognized by the
+         * 'while' loop.
+         *
+         * Most WKT do not contain any of those metadata, so we perform an 'isEmpty()' check as an optimization
+         * for those common cases.
+         */
+        if (!parent.isEmpty()) {
+            /*
+             * Example: SCOPE["Large scale topographic mapping and cadastre."]
+             */
+            element = parent.pullElement(OPTIONAL, WKTKeywords.Scope);
+            if (element != null) {
+                properties.put(ReferenceSystem.SCOPE_KEY, element.pullString("scope"));  // Other types like Datum use the same key.
+                element.close(ignoredElements);
+            }
+            /*
+             * Example: AREA["Netherlands offshore."]
+             */
+            DefaultExtent extent = null;
+            while ((element = parent.pullElement(OPTIONAL, WKTKeywords.Area)) != null) {
+                final String area = element.pullString("area");
+                element.close(ignoredElements);
+                if (extent == null) extent = new DefaultExtent();
+                extent.getGeographicElements().add(new DefaultGeographicDescription(area));
+            }
+            /*
+             * Example: BBOX[51.43, 2.54, 55.77, 6.40]
+             */
+            while ((element = parent.pullElement(OPTIONAL, WKTKeywords.BBox)) != null) {
+                final double southBoundLatitude = element.pullDouble("southBoundLatitude");
+                final double westBoundLongitude = element.pullDouble("westBoundLongitude");
+                final double northBoundLatitude = element.pullDouble("northBoundLatitude");
+                final double eastBoundLongitude = element.pullDouble("eastBoundLongitude");
+                element.close(ignoredElements);
+                if (extent == null) extent = new DefaultExtent();
+                extent.getGeographicElements().add(new DefaultGeographicBoundingBox(
+                        westBoundLongitude, eastBoundLongitude, southBoundLatitude, northBoundLatitude));
+            }
+            /*
+             * Example: VERTICALEXTENT[-1000, 0, LENGTHUNIT[“metre”, 1]]
+             *
+             * Units are optional, default to metres (no "contextual units" here).
+             */
+            while ((element = parent.pullElement(OPTIONAL, WKTKeywords.VerticalExtent)) != null) {
+                final double minimum = element.pullDouble("minimum");
+                final double maximum = element.pullDouble("maximum");
+                Unit<Length> unit = parseDerivedUnit(element, WKTKeywords.LengthUnit, SI.METRE);
+                element.close(ignoredElements);
+                if (unit   == null) unit   = SI.METRE;
+                if (extent == null) extent = new DefaultExtent();
+                verticalElements = new VerticalInfo(verticalElements, extent, minimum, maximum, unit).resolve(verticalCRS);
+            }
+            /*
+             * Example: TIMEEXTENT[2013-01-01, 2013-12-31]
+             *
+             * TODO: syntax like TIMEEXTENT[“Jurassic”, “Quaternary”] is not yet supported.
+             * See https://issues.apache.org/jira/browse/SIS-163
+             *
+             * This operation requires the the sis-temporal module. If not available,
+             * we will report a warning and leave the temporal extent missing.
+             */
+            while ((element = parent.pullElement(OPTIONAL, WKTKeywords.TimeExtent)) != null) {
+                final Date startTime = element.pullDate("startTime");
+                final Date endTime   = element.pullDate("endTime");
+                element.close(ignoredElements);
+                try {
+                    final DefaultTemporalExtent t = new DefaultTemporalExtent();
+                    t.setBounds(startTime, endTime);
+                    if (extent == null) extent = new DefaultExtent();
+                    extent.getTemporalElements().add(t);
+                } catch (UnsupportedOperationException e) {
+                    warning(parent, element, e);
+                }
+            }
+            /*
+             * Example: REMARK["Замечание на русском языке"]
+             */
+            element = parent.pullElement(OPTIONAL, WKTKeywords.Remark);
+            if (element != null) {
+                properties.put(IdentifiedObject.REMARKS_KEY, element.pullString("remarks"));
+                element.close(ignoredElements);
+            }
         }
         parent.close(ignoredElements);
         return properties;
     }
 
     /**
-     * Parses a {@code "UNIT"} element.
+     * Parses an optional {@code "UNIT"} element of a known dimension.
      * This element has the following pattern:
      *
      * {@preformat text
      *     UNIT["<name>", <conversion factor> {,<authority>}]
      * }
      *
-     * @param  parent The parent element.
-     * @param  unit The contextual unit, usually {@code SI.METRE} or {@code SI.RADIAN}.
-     * @return The {@code "UNIT"} element as an {@link Unit} object.
+     * Unit was a mandatory element in WKT 1, but became optional in WKT 2 because the unit may be specified
+     * in each {@code AXIS[…]} element instead than for the whole coordinate system.
+     *
+     * @param  parent   The parent element.
+     * @param  keyword  The unit keyword (e.g. {@code "LengthUnit"} or {@code "AngleUnit"}).
+     * @param  baseUnit The base unit, usually {@code SI.METRE} or {@code SI.RADIAN}.
+     * @return The {@code "UNIT"} element as an {@link Unit} object, or {@code null} if none.
      * @throws ParseException if the {@code "UNIT"} can not be parsed.
      *
      * @todo Authority code is currently ignored. We may consider to create a subclass of
      *       {@link Unit} which implements {@link IdentifiedObject} in a future version.
      */
-    private <Q extends Quantity> Unit<Q> parseUnit(final Element parent, final Unit<Q> unit)
-            throws ParseException
+    private <Q extends Quantity> Unit<Q> parseDerivedUnit(final Element parent,
+            final String keyword, final Unit<Q> baseUnit) throws ParseException
     {
-        final Element element = parent.pullElement("Unit");
-        final String  name    = element.pullString("name");
-        final double  factor  = element.pullDouble("factor");
-        parseAuthorityAndClose(element, name);
-        return Units.multiply(unit, factor);
+        final Element element = parent.pullElement(OPTIONAL, keyword, WKTKeywords.Unit);
+        if (element == null) {
+            return null;
+        }
+        final String name   = element.pullString("name");
+        final double factor = element.pullDouble("factor");
+        parseMetadataAndClose(element, name);
+        return Units.multiply(baseUnit, factor);
+    }
+
+    /**
+     * Parses an optional {@code "UNIT"} element of unknown dimension.
+     * This method tries to infer the quantity dimension from the unit keyword.
+     *
+     * @param  parent The parent element.
+     * @return The {@code "UNIT"} element, or {@code null} if none.
+     * @throws ParseException if the {@code "UNIT"} can not be parsed.
+     */
+    private Unit<?> parseUnit(final Element parent) throws ParseException {
+        final Element element = parent.pullElement(OPTIONAL, UNIT_KEYWORDS);
+        if (element == null) {
+            return null;
+        }
+        final String name   = element.pullString("name");
+        final double factor = element.pullDouble("factor");
+        final int    index  = element.getKeywordIndex() - 1;
+        parseMetadataAndClose(element, name);
+        if (index >= 0 && index < BASE_UNITS.length) {
+            return Units.multiply(BASE_UNITS[index - 1], factor);
+        }
+        // If we can not infer the base type, we have to rely on the name.
+        return parseUnit(name);
     }
 
     /**
@@ -394,25 +577,20 @@ final class GeodeticObjectParser extends
      * to make the parser more tolerant to non-100% compliant WKT. Note that AXIS is really the only element without
      * such AUTHORITY clause and the EPSG database provides authority code for all axis.</div>
      *
+     * @param  mode         {@link #FIRST}, {@link #OPTIONAL} or {@link #MANDATORY}.
      * @param  parent       The parent element.
      * @param  isGeographic {@code true} if the parent element is a geodetic CRS having an ellipsoidal coordinate system.
-     * @param  unit         The contextual unit, usually {@code SI.METRE} or {@code SI.RADIAN}.
-     * @param  mandatory    {@code true} if the axis is mandatory, or {@code false} if it is optional.
+     * @param  defaultUnit  The contextual unit (usually {@code SI.METRE} or {@code SI.RADIAN}), or {@code null} if unknown.
      * @return The {@code "AXIS"} element as a {@link CoordinateSystemAxis} object, or {@code null}
      *         if the axis was not required and there is no axis object.
      * @throws ParseException if the {@code "AXIS"} element can not be parsed.
      */
-    private CoordinateSystemAxis parseAxis(final Element parent, final boolean isGeographic,
-            final Unit<?> unit, final boolean mandatory) throws ParseException
+    private CoordinateSystemAxis parseAxis(final int mode, final Element parent, final boolean isGeographic,
+            final Unit<?> defaultUnit) throws ParseException
     {
-        final Element element;
-        if (mandatory) {
-            element = parent.pullElement(WKTKeywords.Axis);
-        } else {
-            element = parent.pullOptionalElement(WKTKeywords.Axis);
-            if (element == null) {
-                return null;
-            }
+        final Element element = parent.pullElement(mode, WKTKeywords.Axis);
+        if (element == null) {
+            return null;
         }
         String name = CharSequences.trimWhitespaces(element.pullString("name"));
         if (isGeographic) {
@@ -429,7 +607,14 @@ final class GeodeticObjectParser extends
             }
         }
         final Element orientation = element.pullVoidElement("orientation");
-        final AxisDirection direction = Types.forCodeName(AxisDirection.class, orientation.keyword, mandatory);
+        Unit<?> unit = parseUnit(element);
+        if (unit == null) {
+            if (defaultUnit == null) {
+                throw element.missingComponent(WKTKeywords.Unit);
+            }
+            unit = defaultUnit;
+        }
+        final AxisDirection direction = Types.forCodeName(AxisDirection.class, orientation.keyword, mode == MANDATORY);
         /*
          * According ISO 19162, the abbreviation should be inserted between parenthesis in the name.
          * Example: "Easting (E)", "Longitude (L)". If we do not find an abbreviation, then we will
@@ -447,7 +632,7 @@ final class GeodeticObjectParser extends
             abbreviation = referencing.suggestAbbreviation(name, direction, unit);
         }
         try {
-            return csFactory.createCoordinateSystemAxis(parseAuthorityAndClose(element, name), abbreviation, direction, unit);
+            return csFactory.createCoordinateSystemAxis(parseMetadataAndClose(element, name), abbreviation, direction, unit);
         } catch (FactoryException exception) {
             throw element.parseFailed(exception);
         }
@@ -464,26 +649,45 @@ final class GeodeticObjectParser extends
     }
 
     /**
+     * Returns {@code true} if axes should be ignored.
+     *
+     * @param  axis The first parsed axis (can be {@code null}).
+     * @return {@code true} for ignoring the given axis and all other ones in the current coordinate system.
+     */
+    private boolean isAxisIgnored(final CoordinateSystemAxis axis) {
+        return (axis == null) || (convention == Convention.WKT1_IGNORE_AXES);
+    }
+
+    /**
      * Parses a {@code "PRIMEM"} element. This element has the following pattern:
      *
      * {@preformat text
      *     PRIMEM["<name>", <longitude> {,<authority>}]
      * }
      *
+     * @param  mode {@link #FIRST}, {@link #OPTIONAL} or {@link #MANDATORY}.
      * @param  parent The parent element.
      * @param  angularUnit The contextual unit.
      * @return The {@code "PRIMEM"} element as a {@link PrimeMeridian} object.
      * @throws ParseException if the {@code "PRIMEM"} element can not be parsed.
      */
-    private PrimeMeridian parsePrimem(final Element parent, Unit<Angle> angularUnit) throws ParseException {
-        if (convention == Convention.WKT1_COMMON_UNITS) {
+    private PrimeMeridian parsePrimeMeridian(final int mode, final Element parent, Unit<Angle> angularUnit)
+            throws ParseException
+    {
+        if (convention.usesCommonUnits) {
             angularUnit = NonSI.DEGREE_ANGLE;
         }
-        final Element element   = parent.pullElement(WKTKeywords.PrimeM);
-        final String  name      = element.pullString("name");
-        final double  longitude = element.pullDouble("longitude");
+        final Element element = parent.pullElement(mode, WKTKeywords.PrimeMeridian, WKTKeywords.PrimeM);
+        if (element == null) {
+            return null;
+        }
+        final String name      = element.pullString("name");
+        final double longitude = element.pullDouble("longitude");
+        if (angularUnit == null) {
+            throw element.missingComponent(WKTKeywords.AngleUnit);
+        }
         try {
-            return datumFactory.createPrimeMeridian(parseAuthorityAndClose(element, name), longitude, angularUnit);
+            return datumFactory.createPrimeMeridian(parseMetadataAndClose(element, name), longitude, angularUnit);
         } catch (FactoryException exception) {
             throw element.parseFailed(exception);
         }
@@ -497,20 +701,21 @@ final class GeodeticObjectParser extends
      *     TOWGS84[<dx>, <dy>, <dz>, <ex>, <ey>, <ez>, <ppm>]
      * }
      *
+     * @param  mode {@link #FIRST}, {@link #OPTIONAL} or {@link #MANDATORY}.
      * @param  parent The parent element.
      * @return The {@code "TOWGS84"} element as a {@link org.apache.sis.referencing.datum.BursaWolfParameters} object,
      *         or {@code null} if no {@code "TOWGS84"} has been found.
      * @throws ParseException if the {@code "TOWGS84"} can not be parsed.
      */
-    private Object parseToWGS84(final Element parent) throws ParseException {
-        final Element element = parent.pullOptionalElement(WKTKeywords.ToWGS84);
+    private Object parseToWGS84(final int mode, final Element parent) throws ParseException {
+        final Element element = parent.pullElement(mode, WKTKeywords.ToWGS84);
         if (element == null) {
             return null;
         }
         final double[] values = new double[ToWGS84.length];
         for (int i=0; i<values.length;) {
             values[i] = element.pullDouble(ToWGS84[i]);
-            if ((++i % 3) == 0 && element.peek() == null) {
+            if ((++i % 3) == 0 && element.isEmpty()) {
                 break;  // It is legal to have only 3 or 6 elements.
             }
         }
@@ -525,12 +730,16 @@ final class GeodeticObjectParser extends
      *     SPHEROID["<name>", <semi-major axis>, <inverse flattening> {,<authority>}]
      * }
      *
+     * @param  mode {@link #FIRST}, {@link #OPTIONAL} or {@link #MANDATORY}.
      * @param  parent The parent element.
      * @return The {@code "SPHEROID"} element as an {@link Ellipsoid} object.
      * @throws ParseException if the {@code "SPHEROID"} element can not be parsed.
      */
-    private Ellipsoid parseSpheroid(final Element parent) throws ParseException {
-        final Element element      = parent.pullElement(WKTKeywords.Spheroid);
+    private Ellipsoid parseEllipsoid(final int mode, final Element parent) throws ParseException {
+        final Element element = parent.pullElement(mode, WKTKeywords.Ellipsoid, WKTKeywords.Spheroid);
+        if (element == null) {
+            return null;
+        }
         final String name          = element.pullString("name");
         final double semiMajorAxis = element.pullDouble("semiMajorAxis");
         double inverseFlattening   = element.pullDouble("inverseFlattening");
@@ -538,7 +747,7 @@ final class GeodeticObjectParser extends
             inverseFlattening = Double.POSITIVE_INFINITY;
         }
         try {
-            return datumFactory.createFlattenedSphere(parseAuthorityAndClose(element, name),
+            return datumFactory.createFlattenedSphere(parseMetadataAndClose(element, name),
                     semiMajorAxis, inverseFlattening, SI.METRE);
         } catch (FactoryException exception) {
             throw element.parseFailed(exception);
@@ -552,18 +761,20 @@ final class GeodeticObjectParser extends
      *     PROJECTION["<name>" {,<authority>}]
      * }
      *
+     * @param  mode        {@link #FIRST}, {@link #OPTIONAL} or {@link #MANDATORY}.
      * @param  parent      The parent element.
      * @param  linearUnit  The linear unit of the parent {@code PROJCS} element, or {@code null}.
      * @param  angularUnit The angular unit of the parent {@code GEOCS} element, or {@code null}.
      * @return The {@code "PROJECTION"} element as a defining conversion.
      * @throws ParseException if the {@code "PROJECTION"} element can not be parsed.
      */
-    private Conversion parseProjection(final Element      parent,
-                                       final Unit<Length> linearUnit,
-                                       final Unit<Angle>  angularUnit)
-            throws ParseException
+    private Conversion parseProjection(final int mode, final Element parent,
+            final Unit<Length> linearUnit, final Unit<Angle> angularUnit) throws ParseException
     {
-        final Element element = parent.pullElement(WKTKeywords.Projection);
+        final Element element = parent.pullElement(mode, WKTKeywords.Projection);
+        if (element == null) {
+            return null;
+        }
         final String classification = element.pullString("name");
         /*
          * Set the list of parameters. NOTE: Parameters are defined in the parent
@@ -573,7 +784,7 @@ final class GeodeticObjectParser extends
             final OperationMethod method = referencing.getOperationMethod(opFactory, mtFactory, classification);
             final ParameterValueGroup parameters = method.getParameters().createValue();
             parseParameters(parent, parameters, linearUnit, angularUnit);
-            return opFactory.createDefiningConversion(parseAuthorityAndClose(element, classification), method, parameters);
+            return opFactory.createDefiningConversion(parseMetadataAndClose(element, classification), method, parameters);
         } catch (FactoryException exception) {
             throw element.parseFailed(exception);
         }
@@ -586,17 +797,24 @@ final class GeodeticObjectParser extends
      *     DATUM["<name>", <spheroid> {,<to wgs84>} {,<authority>}]
      * }
      *
+     * @param  mode {@link #FIRST}, {@link #OPTIONAL} or {@link #MANDATORY}.
      * @param  parent The parent element.
-     * @param  meridian the prime meridian.
+     * @param  meridian the prime meridian, or {@code null} for Greenwich.
      * @return The {@code "DATUM"} element as a {@link GeodeticDatum} object.
      * @throws ParseException if the {@code "DATUM"} element can not be parsed.
      */
-    private GeodeticDatum parseDatum(final Element parent, final PrimeMeridian meridian) throws ParseException {
-        final Element             element    = parent.pullElement(WKTKeywords.Datum);
-        final String              name       = element.pullString("name");
-        final Ellipsoid           ellipsoid  = parseSpheroid(element);
-        final Object              toWGS84    = parseToWGS84(element);     // Optional; may be null.
-        final Map<String,Object>  properties = parseAuthorityAndClose(element, name);
+    private GeodeticDatum parseDatum(final int mode, final Element parent, PrimeMeridian meridian) throws ParseException {
+        final Element element = parent.pullElement(mode, WKTKeywords.Datum);
+        if (element == null) {
+            return null;
+        }
+        final String             name       = element.pullString("name");
+        final Ellipsoid          ellipsoid  = parseEllipsoid(MANDATORY, element);
+        final Object             toWGS84    = parseToWGS84(OPTIONAL, element);
+        final Map<String,Object> properties = parseMetadataAndClose(element, name);
+        if (meridian == null) {
+            meridian = referencing.getGreenwich();
+        }
         if (toWGS84 != null) {
             properties.put(ReferencingServices.BURSA_WOLF_KEY, toWGS84);
         }
@@ -614,18 +832,22 @@ final class GeodeticObjectParser extends
      *     VERT_DATUM["<name>", <datum type> {,<authority>}]
      * }
      *
+     * @param  mode {@link #FIRST}, {@link #OPTIONAL} or {@link #MANDATORY}.
      * @param  parent The parent element.
      * @return The {@code "VERT_DATUM"} element as a {@link VerticalDatum} object.
      * @throws ParseException if the {@code "VERT_DATUM"} element can not be parsed.
      */
-    private VerticalDatum parseVertDatum(final Element parent) throws ParseException {
-        final Element element = parent.pullElement(WKTKeywords.Vert_Datum);
-        final String  name    = element.pullString ("name");
-        final int     datum   = element.pullInteger("datum");
+    private VerticalDatum parseVerticalDatum(final int mode, final Element parent) throws ParseException {
+        final Element element = parent.pullElement(mode, WKTKeywords.VerticalDatum, WKTKeywords.Vert_Datum);
+        if (element == null) {
+            return null;
+        }
+        final String  name  = element.pullString ("name");
+        final int     datum = element.pullInteger("datum");
         final VerticalDatumType type = VerticalDatumTypes.fromLegacy(datum);
         // TODO: need to find a default value if null.
         try {
-            return datumFactory.createVerticalDatum(parseAuthorityAndClose(element, name), type);
+            return datumFactory.createVerticalDatum(parseMetadataAndClose(element, name), type);
         } catch (FactoryException exception) {
             throw element.parseFailed(exception);
         }
@@ -638,18 +860,22 @@ final class GeodeticObjectParser extends
      *     TimeDatum["<name>", TimeOrigin[<time origin>] {,<authority>}]
      * }
      *
+     * @param  mode {@link #FIRST}, {@link #OPTIONAL} or {@link #MANDATORY}.
      * @param  parent The parent element.
      * @return The {@code "TimeDatum"} element as a {@link TemporalDatum} object.
      * @throws ParseException if the {@code "TimeDatum"} element can not be parsed.
      */
-    private TemporalDatum parseTimeDatum(final Element parent) throws ParseException {
-        final Element element = parent.pullElement(WKTKeywords.TimeDatum);
-        final String  name    = element.pullString ("name");
-        final Element origin  = element.pullElement(WKTKeywords.TimeOrigin);
-        final Date    epoch   = origin .pullDate("origin");
+    private TemporalDatum parseTimeDatum(final int mode, final Element parent) throws ParseException {
+        final Element element = parent.pullElement(mode, WKTKeywords.TimeDatum);
+        if (element == null) {
+            return null;
+        }
+        final String  name   = element.pullString ("name");
+        final Element origin = element.pullElement(MANDATORY, WKTKeywords.TimeOrigin);
+        final Date    epoch  = origin .pullDate("origin");
         origin.close(ignoredElements);
         try {
-            return datumFactory.createTemporalDatum(parseAuthorityAndClose(element, name), epoch);
+            return datumFactory.createTemporalDatum(parseMetadataAndClose(element, name), epoch);
         } catch (FactoryException exception) {
             throw element.parseFailed(exception);
         }
@@ -662,18 +888,22 @@ final class GeodeticObjectParser extends
      *     LOCAL_DATUM["<name>", <datum type> {,<authority>}]
      * }
      *
+     * @param  mode {@link #FIRST}, {@link #OPTIONAL} or {@link #MANDATORY}.
      * @param  parent The parent element.
      * @return The {@code "LOCAL_DATUM"} element as an {@link EngineeringDatum} object.
      * @throws ParseException if the {@code "LOCAL_DATUM"} element can not be parsed.
      *
      * @todo The vertical datum type is currently ignored.
      */
-    private EngineeringDatum parseLocalDatum(final Element parent) throws ParseException {
-        final Element element = parent.pullElement(WKTKeywords.Local_Datum);
-        final String  name    = element.pullString ("name");
-        final int     datum   = element.pullInteger("datum");   // Ignored for now.
+    private EngineeringDatum parseEngineeringDatum(final int mode, final Element parent) throws ParseException {
+        final Element element = parent.pullElement(mode, WKTKeywords.EngineeringDatum, WKTKeywords.Local_Datum);
+        if (element == null) {
+            return null;
+        }
+        final String name  = element.pullString ("name");
+        final int    datum = element.pullInteger("datum");   // Ignored for now.
         try {
-            return datumFactory.createEngineeringDatum(parseAuthorityAndClose(element, name));
+            return datumFactory.createEngineeringDatum(parseMetadataAndClose(element, name));
         } catch (FactoryException exception) {
             throw element.parseFailed(exception);
         }
@@ -687,6 +917,7 @@ final class GeodeticObjectParser extends
      *     LOCAL_CS["<name>", <local datum>, <unit>, <axis>, {,<axis>}* {,<authority>}]
      * }
      *
+     * @param  mode {@link #FIRST}, {@link #OPTIONAL} or {@link #MANDATORY}.
      * @param  parent The parent element.
      * @return The {@code "LOCAL_CS"} element as an {@link EngineeringCRS} object.
      * @throws ParseException if the {@code "LOCAL_CS"} element can not be parsed.
@@ -695,20 +926,23 @@ final class GeodeticObjectParser extends
      *       know which method to invokes in the {@link CSFactory} (is it a Cartesian
      *       coordinate system? a spherical one? etc.).
      */
-    private EngineeringCRS parseLocalCS(final Element parent) throws ParseException {
-        final Element          element    = parent.pullElement(WKTKeywords.Local_CS);
+    private EngineeringCRS parseEngineeringCRS(final int mode, final Element parent) throws ParseException {
+        final Element element = parent.pullElement(mode, WKTKeywords.EngineeringCRS, WKTKeywords.Local_CS);
+        if (element == null) {
+            return null;
+        }
         final String           name       = element.pullString("name");
-        final EngineeringDatum datum      = parseLocalDatum(element);
-        final Unit<Length>     linearUnit = parseUnit(element, SI.METRE);
-        CoordinateSystemAxis   axis       = parseAxis(element, false, linearUnit, true);
+        final EngineeringDatum datum      = parseEngineeringDatum(MANDATORY, element);
+        final Unit<Length>     linearUnit = parseDerivedUnit(element, WKTKeywords.LengthUnit, SI.METRE);
+        CoordinateSystemAxis   axis       = parseAxis(MANDATORY, element, false, linearUnit);
         final List<CoordinateSystemAxis> list = new ArrayList<CoordinateSystemAxis>();
         do {
             list.add(axis);
-            axis = parseAxis(element, false, linearUnit, false);
+            axis = parseAxis(OPTIONAL, element, false, linearUnit);
         } while (axis != null);
         final CoordinateSystem cs = referencing.createAbstractCS(list.toArray(new CoordinateSystemAxis[list.size()]));
         try {
-            return crsFactory.createEngineeringCRS(parseAuthorityAndClose(element, name), datum, cs);
+            return crsFactory.createEngineeringCRS(parseMetadataAndClose(element, name), datum, cs);
         } catch (FactoryException exception) {
             throw element.parseFailed(exception);
         }
@@ -723,29 +957,36 @@ final class GeodeticObjectParser extends
      *            {,<axis> ,<axis> ,<axis>} {,<authority>}]
      * }
      *
+     * @param  mode {@link #FIRST}, {@link #OPTIONAL} or {@link #MANDATORY}.
      * @param  parent The parent element.
      * @return The {@code "GEOCCS"} element as a {@link GeocentricCRS} object.
      * @throws ParseException if the {@code "GEOCCS"} element can not be parsed.
      */
-    private GeocentricCRS parseGeoCCS(final Element parent) throws ParseException {
-        final Element       element    = parent.pullElement(WKTKeywords.GeocCS);
+    private GeocentricCRS parseGeocentricCRS(final int mode, final Element parent) throws ParseException {
+        final Element element = parent.pullElement(mode, WKTKeywords.GeocCS);
+        if (element == null) {
+            return null;
+        }
         final String        name       = element.pullString("name");
-        final PrimeMeridian meridian   = parsePrimem(element, NonSI.DEGREE_ANGLE);
-        final GeodeticDatum datum      = parseDatum (element, meridian);
-        final Unit<Length>  linearUnit = parseUnit  (element, SI.METRE);
+        final PrimeMeridian meridian   = parsePrimeMeridian(MANDATORY, element, NonSI.DEGREE_ANGLE);
+        final GeodeticDatum datum      = parseDatum (MANDATORY, element, meridian);
+        final Unit<Length>  linearUnit = parseDerivedUnit(element, WKTKeywords.LengthUnit, SI.METRE);
         CartesianCS cs;
         CoordinateSystemAxis axis0, axis1 = null, axis2 = null;
-        axis0 = parseAxis(element, false, linearUnit, false);
+        axis0 = parseAxis(OPTIONAL, element, false, linearUnit);
         try {
             if (axis0 != null) {
-                axis1 = parseAxis(element, false, linearUnit, true);
-                axis2 = parseAxis(element, false, linearUnit, true);
+                axis1 = parseAxis(MANDATORY, element, false, linearUnit);
+                axis2 = parseAxis(MANDATORY, element, false, linearUnit);
             }
-            final Map<String,?> properties = parseAuthorityAndClose(element, name);
-            if (axis0 != null && !isAxisIgnored) {
+            final Map<String,?> properties = parseMetadataAndClose(element, name);
+            if (!isAxisIgnored(axis0)) {
                 cs = csFactory.createCartesianCS(properties, axis0, axis1, axis2);
                 cs = referencing.upgradeGeocentricCS(cs);
             } else {
+                if (linearUnit == null) {
+                    throw element.missingComponent(WKTKeywords.Unit);
+                }
                 cs = referencing.getGeocentricCS(linearUnit);
             }
             return crsFactory.createGeocentricCRS(properties, datum, cs);
@@ -762,18 +1003,25 @@ final class GeodeticObjectParser extends
      *     VERT_CS["<name>", <vert datum>, <linear unit>, {<axis>,} {,<authority>}]
      * }
      *
+     * @param  mode {@link #FIRST}, {@link #OPTIONAL} or {@link #MANDATORY}.
      * @param  parent The parent element.
      * @return The {@code "VERT_CS"} element as a {@link VerticalCRS} object.
      * @throws ParseException if the {@code "VERT_CS"} element can not be parsed.
      */
-    private VerticalCRS parseVertCS(final Element parent) throws ParseException {
-        final Element        element    = parent.pullElement(WKTKeywords.Vert_CS);
+    private VerticalCRS parseVerticalCRS(final int mode, final Element parent) throws ParseException {
+        final Element element = parent.pullElement(mode, WKTKeywords.VerticalCRS, WKTKeywords.Vert_CS);
+        if (element == null) {
+            return null;
+        }
         final String         name       = element.pullString("name");
-        final VerticalDatum  datum      = parseVertDatum(element);
-        final Unit<Length>   linearUnit = parseUnit(element, SI.METRE);
-        CoordinateSystemAxis axis       = parseAxis(element, false, linearUnit, false);
+        final VerticalDatum  datum      = parseVerticalDatum(MANDATORY, element);
+        final Unit<Length>   linearUnit = parseDerivedUnit(element, WKTKeywords.LengthUnit, SI.METRE);
+        CoordinateSystemAxis axis       = parseAxis(OPTIONAL, element, false, linearUnit);
         try {
-            if (axis == null || isAxisIgnored) {
+            if (isAxisIgnored(axis)) {
+                if (linearUnit == null) {
+                    throw element.missingComponent(WKTKeywords.Unit);
+                }
                 String sn = "Height", abbreviation = "h";
                 AxisDirection direction = AxisDirection.UP;
                 final VerticalDatumType type = datum.getVerticalDatumType();
@@ -789,8 +1037,16 @@ final class GeodeticObjectParser extends
                 }
                 axis = createAxis(sn, abbreviation, direction, linearUnit);
             }
-            return crsFactory.createVerticalCRS(parseAuthorityAndClose(element, name), datum,
-                    csFactory.createVerticalCS(singletonMap("name", name), axis));
+            verticalCRS = crsFactory.createVerticalCRS(parseMetadataAndClose(element, name), datum,
+                           csFactory.createVerticalCS(singletonMap("name", name), axis));
+            /*
+             * Some DefaultVerticalExtent objects may be waiting for the VerticalCRS before to complete
+             * their construction. If this is the case, try to complete them now.
+             */
+            if (verticalElements != null) {
+                verticalElements = verticalElements.resolve(verticalCRS);
+            }
+            return verticalCRS;
         } catch (FactoryException exception) {
             throw element.parseFailed(exception);
         }
@@ -799,21 +1055,28 @@ final class GeodeticObjectParser extends
     /**
      * Parses an <strong>optional</strong> {@code "TIMECRS"} element.
      *
+     * @param  mode {@link #FIRST}, {@link #OPTIONAL} or {@link #MANDATORY}.
      * @param  parent The parent element.
      * @return The {@code "TIMECRS"} element as a {@link TemporalCRS} object.
      * @throws ParseException if the {@code "TIMECRS"} element can not be parsed.
      */
-    private TemporalCRS parseTimeCRS(final Element parent) throws ParseException {
-        final Element        element  = parent.pullElement(WKTKeywords.TimeCRS);
+    private TemporalCRS parseTimeCRS(final int mode, final Element parent) throws ParseException {
+        final Element element = parent.pullElement(mode, WKTKeywords.TimeCRS);
+        if (element == null) {
+            return null;
+        }
         final String         name     = element.pullString("name");
-        final TemporalDatum  datum    = parseTimeDatum(element);
-        final Unit<Duration> timeUnit = parseUnit(element, SI.SECOND);
-        CoordinateSystemAxis axis     = parseAxis(element, false, timeUnit, false);
+        final TemporalDatum  datum    = parseTimeDatum(MANDATORY, element);
+        final Unit<Duration> timeUnit = parseDerivedUnit(element, WKTKeywords.TimeUnit, SI.SECOND);
+        CoordinateSystemAxis axis     = parseAxis(OPTIONAL, element, false, timeUnit);
         try {
-            if (axis == null || isAxisIgnored) {
+            if (isAxisIgnored(axis)) {
+                if (timeUnit == null) {
+                    throw element.missingComponent(WKTKeywords.TimeUnit);
+                }
                 axis = createAxis("Time", "t", AxisDirection.FUTURE, timeUnit);
             }
-            return crsFactory.createTemporalCRS(parseAuthorityAndClose(element, name), datum,
+            return crsFactory.createTemporalCRS(parseMetadataAndClose(element, name), datum,
                     csFactory.createTimeCS(singletonMap("name", name), axis));
         } catch (FactoryException exception) {
             throw element.parseFailed(exception);
@@ -827,16 +1090,20 @@ final class GeodeticObjectParser extends
      *     GEOGCS["<name>", <datum>, <prime meridian>, <angular unit>  {,<twin axes>} {,<authority>}]
      * }
      *
+     * @param  mode {@link #FIRST}, {@link #OPTIONAL} or {@link #MANDATORY}.
      * @param  parent The parent element.
      * @return The {@code "GEOGCS"} element as a {@link GeographicCRS} object.
      * @throws ParseException if the {@code "GEOGCS"} element can not be parsed.
      */
-    private GeographicCRS parseGeoGCS(final Element parent) throws ParseException {
-        final Element       element     = parent.pullElement(WKTKeywords.GeogCS);
-              Object        name        = element.pullString("name");
-        final Unit<Angle>   angularUnit = parseUnit  (element, SI.RADIAN);
-        final PrimeMeridian meridian    = parsePrimem(element, angularUnit);
-        final GeodeticDatum datum       = parseDatum (element, meridian);
+    private GeographicCRS parseGeographicCRS(final int mode, final Element parent) throws ParseException {
+        final Element element = parent.pullElement(mode, WKTKeywords.GeodeticCRS, WKTKeywords.GeodCRS, WKTKeywords.GeogCS);
+        if (element == null) {
+            return null;
+        }
+        Object              name        = element.pullString("name");
+        final Unit<Angle>   angularUnit = parseDerivedUnit(element, WKTKeywords.AngleUnit, SI.RADIAN);
+        final PrimeMeridian meridian    = parsePrimeMeridian(MANDATORY, element, angularUnit);
+        final GeodeticDatum datum       = parseDatum(MANDATORY, element, meridian);
         if (((String) name).isEmpty()) {
             /*
              * GeographicCRS name is a mandatory property, but some invalid WKT with an empty string exist.
@@ -845,17 +1112,20 @@ final class GeodeticObjectParser extends
              */
             name = datum.getName();
         }
-        CoordinateSystemAxis axis0 = parseAxis(element, true, angularUnit, false);
+        CoordinateSystemAxis axis0 = parseAxis(OPTIONAL, element, true, angularUnit);
         CoordinateSystemAxis axis1 = null;
         try {
             if (axis0 != null) {
-                axis1 = parseAxis(element, true, angularUnit, true);
+                axis1 = parseAxis(MANDATORY, element, true, angularUnit);
             }
-            if (axis0 == null || isAxisIgnored) {
+            if (isAxisIgnored(axis0)) {
+                if (angularUnit == null) {
+                    throw element.missingComponent(WKTKeywords.AngleUnit);
+                }
                 axis0 = createAxis(AxisNames.GEODETIC_LONGITUDE, "λ", AxisDirection.EAST,  angularUnit);
                 axis1 = createAxis(AxisNames.GEODETIC_LATITUDE,  "φ", AxisDirection.NORTH, angularUnit);
             }
-            final Map<String,?> properties = parseAuthorityAndClose(element, name);
+            final Map<String,?> properties = parseMetadataAndClose(element, name);
             return crsFactory.createGeographicCRS(properties, datum,
                     csFactory.createEllipsoidalCS(properties, axis0, axis1));
         } catch (FactoryException exception) {
@@ -872,30 +1142,37 @@ final class GeodeticObjectParser extends
      *            <linear unit> {,<twin axes>}{,<authority>}]
      * }
      *
+     * @param  mode {@link #FIRST}, {@link #OPTIONAL} or {@link #MANDATORY}.
      * @param  parent The parent element.
      * @return The {@code "PROJCS"} element as a {@link ProjectedCRS} object.
      * @throws ParseException if the {@code "GEOGCS"} element can not be parsed.
      */
-    private ProjectedCRS parseProjCS(final Element parent) throws ParseException {
-        final Element       element    = parent.pullElement(WKTKeywords.ProjCS);
+    private ProjectedCRS parseProjectedCRS(final int mode, final Element parent) throws ParseException {
+        final Element element = parent.pullElement(mode, WKTKeywords.ProjCS);
+        if (element == null) {
+            return null;
+        }
         final String        name       = element.pullString("name");
-        final GeographicCRS geoCRS     = parseGeoGCS(element);
-        final Unit<Length>  linearUnit = parseUnit(element, SI.METRE);
-        final boolean  usesCommonUnits = convention.usesCommonUnits();
-        final Conversion    conversion = parseProjection(element,
+        final GeographicCRS geoCRS     = parseGeographicCRS(MANDATORY, element);
+        final Unit<Length>  linearUnit = parseDerivedUnit(element, WKTKeywords.LengthUnit, SI.METRE);
+        final boolean  usesCommonUnits = convention.usesCommonUnits;
+        final Conversion    conversion = parseProjection(MANDATORY, element,
                 usesCommonUnits ? SI.METRE : linearUnit,
                 usesCommonUnits ? NonSI.DEGREE_ANGLE : geoCRS.getCoordinateSystem().getAxis(0).getUnit().asType(Angle.class));
-        CoordinateSystemAxis axis0 = parseAxis(element, false, linearUnit, false);
+        CoordinateSystemAxis axis0 = parseAxis(OPTIONAL, element, false, linearUnit);
         CoordinateSystemAxis axis1 = null;
         try {
             if (axis0 != null) {
-                axis1 = parseAxis(element, false, linearUnit, true);
+                axis1 = parseAxis(MANDATORY, element, false, linearUnit);
             }
-            if (axis0 == null || isAxisIgnored) {
+            if (isAxisIgnored(axis0)) {
+                if (linearUnit == null) {
+                    throw element.missingComponent(WKTKeywords.LengthUnit);
+                }
                 axis0 = createAxis(AxisNames.EASTING,  "E", AxisDirection.EAST,  linearUnit);
                 axis1 = createAxis(AxisNames.NORTHING, "N", AxisDirection.NORTH, linearUnit);
             }
-            final Map<String,?> properties = parseAuthorityAndClose(element, name);
+            final Map<String,?> properties = parseMetadataAndClose(element, name);
             return crsFactory.createProjectedCRS(properties, geoCRS, conversion,
                     csFactory.createCartesianCS(properties, axis0, axis1));
         } catch (FactoryException exception) {
@@ -911,20 +1188,24 @@ final class GeodeticObjectParser extends
      *     COMPD_CS["<name>", <head cs>, <tail cs> {,<authority>}]
      * }
      *
+     * @param  mode {@link #FIRST}, {@link #OPTIONAL} or {@link #MANDATORY}.
      * @param  parent The parent element.
      * @return The {@code "COMPD_CS"} element as a {@link CompoundCRS} object.
      * @throws ParseException if the {@code "COMPD_CS"} element can not be parsed.
      */
-    private CompoundCRS parseCompdCS(final Element parent) throws ParseException {
-        final List<CoordinateReferenceSystem> components = new ArrayList<CoordinateReferenceSystem>(4);
-        final Element element = parent.pullElement(WKTKeywords.Compd_CS);
-        final String  name    = element.pullString("name");
+    private CompoundCRS parseCompoundCRS(final int mode, final Element parent) throws ParseException {
+        final Element element = parent.pullElement(mode, WKTKeywords.CompoundCRS, WKTKeywords.Compd_CS);
+        if (element == null) {
+            return null;
+        }
+        final String  name = element.pullString("name");
         CoordinateReferenceSystem crs;
+        final List<CoordinateReferenceSystem> components = new ArrayList<CoordinateReferenceSystem>(4);
         while ((crs = parseCoordinateReferenceSystem(element, components.size() < 2)) != null) {
             components.add(crs);
         }
         try {
-            return crsFactory.createCompoundCRS(parseAuthorityAndClose(element, name),
+            return crsFactory.createCompoundCRS(parseMetadataAndClose(element, name),
                     components.toArray(new CoordinateReferenceSystem[components.size()]));
         } catch (FactoryException exception) {
             throw element.parseFailed(exception);
@@ -939,12 +1220,16 @@ final class GeodeticObjectParser extends
      *     FITTED_CS["<name>", <to base>, <base cs>]
      * }
      *
+     * @param  mode {@link #FIRST}, {@link #OPTIONAL} or {@link #MANDATORY}.
      * @param  parent The parent element.
      * @return The {@code "FITTED_CS"} element as a {@link CompoundCRS} object.
      * @throws ParseException if the {@code "COMPD_CS"} element can not be parsed.
      */
-    private DerivedCRS parseFittedCS(final Element parent) throws ParseException {
-        final Element                   element = parent.pullElement(WKTKeywords.Fitted_CS);
+    private DerivedCRS parseFittedCS(final int mode, final Element parent) throws ParseException {
+        final Element element = parent.pullElement(mode, WKTKeywords.Fitted_CS);
+        if (element == null) {
+            return null;
+        }
         final String                    name    = element.pullString("name");
         final MathTransform             toBase  = parseMathTransform(element, true);
         final OperationMethod           method  = getOperationMethod();
@@ -970,7 +1255,7 @@ final class GeodeticObjectParser extends
                         singletonMap(IdentifiedObject.NAME_KEY, buffer.toString()),
                         number, AxisDirection.OTHER, Unit.ONE);
             }
-            final Map<String,Object> properties = parseAuthorityAndClose(element, name);
+            final Map<String,Object> properties = parseMetadataAndClose(element, name);
             final CoordinateSystem derivedCS = referencing.createAbstractCS(axes);
             /*
              * We do not know which name to give to the conversion method.

Modified: sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/MathTransformParser.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/MathTransformParser.java?rev=1686282&r1=1686281&r2=1686282&view=diff
==============================================================================
--- sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/MathTransformParser.java [UTF-8] (original)
+++ sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/MathTransformParser.java [UTF-8] Thu Jun 18 17:38:16 2015
@@ -21,6 +21,7 @@ import java.text.DateFormat;
 import java.text.NumberFormat;
 import java.text.ParseException;
 import javax.measure.unit.Unit;
+import javax.measure.unit.UnitFormat;
 import javax.measure.quantity.Angle;
 import javax.measure.quantity.Length;
 import org.opengis.util.FactoryException;
@@ -58,7 +59,7 @@ import static org.apache.sis.util.Argume
  *
  * @see <a href="http://www.geoapi.org/snapshot/javadoc/org/opengis/referencing/doc-files/WKT.html">Well Know Text specification</a>
  */
-class MathTransformParser extends Parser {
+class MathTransformParser extends AbstractParser {
     /**
      * The factory to use for creating math transforms.
      */
@@ -94,7 +95,7 @@ class MathTransformParser extends Parser
      * @param mtFactory The factory to use to create {@link MathTransform} objects.
      */
     public MathTransformParser(final MathTransformFactory mtFactory) {
-        this(Symbols.getDefault(), null, null, mtFactory, null);
+        this(Symbols.getDefault(), null, null, null, mtFactory, null);
     }
 
     /**
@@ -103,13 +104,14 @@ class MathTransformParser extends Parser
      * @param symbols       The set of symbols to use.
      * @param numberFormat  The number format provided by {@link WKTFormat}, or {@code null} for a default format.
      * @param dateFormat    The date format provided by {@link WKTFormat}, or {@code null} for a default format.
+     * @param unitFormat    The unit format provided by {@link WKTFormat}, or {@code null} for a default format.
      * @param mtFactory     The factory to use to create {@link MathTransform} objects.
      * @param errorLocale   The locale for error messages (not for parsing), or {@code null} for the system default.
      */
     MathTransformParser(final Symbols symbols, final NumberFormat numberFormat, final DateFormat dateFormat,
-            final MathTransformFactory mtFactory, final Locale errorLocale)
+            final UnitFormat unitFormat, final MathTransformFactory mtFactory, final Locale errorLocale)
     {
-        super(symbols, numberFormat, dateFormat, errorLocale);
+        super(symbols, numberFormat, dateFormat, unitFormat, errorLocale);
         this.mtFactory = mtFactory;
         ensureNonNull("mtFactory", mtFactory);
     }
@@ -137,21 +139,17 @@ class MathTransformParser extends Parser
     final MathTransform parseMathTransform(final Element element, final boolean mandatory) throws ParseException {
         lastMethod = null;
         classification = null;
-        String keyword = WKTKeywords.Param_MT;
-        final Object child = element.peek();
-        if (child instanceof Element) {
-            keyword = ((Element) child).keyword;
-            if (keyword != null) {
-                if (keyword.equalsIgnoreCase(WKTKeywords.Param_MT))       return parseParamMT      (element);
-                if (keyword.equalsIgnoreCase(WKTKeywords.Concat_MT))      return parseConcatMT     (element);
-                if (keyword.equalsIgnoreCase(WKTKeywords.Inverse_MT))     return parseInverseMT    (element);
-                if (keyword.equalsIgnoreCase(WKTKeywords.PassThrough_MT)) return parsePassThroughMT(element);
+        MathTransform tr;
+        if ((tr = parseParamMT       (element)) == null &&
+            (tr = parseConcatMT      (element)) == null &&
+            (tr = parseInverseMT     (element)) == null &&
+            (tr = parsePassThroughMT (element)) == null)
+        {
+            if (mandatory) {
+                throw element.missingOrUnknownComponent(WKTKeywords.Param_MT);
             }
         }
-        if (mandatory) {
-            throw element.keywordNotFound(keyword, keyword == WKTKeywords.Param_MT);
-        }
-        return null;
+        return tr;
     }
 
     /**
@@ -168,7 +166,7 @@ class MathTransformParser extends Parser
     {
         Element param = element;
         try {
-            while ((param = element.pullOptionalElement(WKTKeywords.Parameter)) != null) {
+            while ((param = element.pullElement(OPTIONAL, WKTKeywords.Parameter)) != null) {
                 final String                 name       = param.pullString("name");
                 final ParameterValue<?>      parameter  = parameters.parameter(name);
                 final ParameterDescriptor<?> descriptor = parameter.getDescriptor();
@@ -211,8 +209,11 @@ class MathTransformParser extends Parser
      * @return The {@code "PARAM_MT"} element as an {@link MathTransform} object.
      * @throws ParseException if the {@code "PARAM_MT"} element can not be parsed.
      */
-    final MathTransform parseParamMT(final Element parent) throws ParseException {
-        final Element element = parent.pullElement(WKTKeywords.Param_MT);
+    private MathTransform parseParamMT(final Element parent) throws ParseException {
+        final Element element = parent.pullElement(FIRST, WKTKeywords.Param_MT);
+        if (element == null) {
+            return null;
+        }
         classification = element.pullString("classification");
         final ParameterValueGroup parameters;
         try {
@@ -250,8 +251,11 @@ class MathTransformParser extends Parser
      * @return The {@code "INVERSE_MT"} element as an {@link MathTransform} object.
      * @throws ParseException if the {@code "INVERSE_MT"} element can not be parsed.
      */
-    final MathTransform parseInverseMT(final Element parent) throws ParseException {
-        final Element element = parent.pullElement(WKTKeywords.Inverse_MT);
+    private MathTransform parseInverseMT(final Element parent) throws ParseException {
+        final Element element = parent.pullElement(FIRST, WKTKeywords.Inverse_MT);
+        if (element == null) {
+            return null;
+        }
         MathTransform transform = parseMathTransform(element, true);
         try {
             transform = transform.inverse();
@@ -273,8 +277,11 @@ class MathTransformParser extends Parser
      * @return The {@code "PASSTHROUGH_MT"} element as an {@link MathTransform} object.
      * @throws ParseException if the {@code "PASSTHROUGH_MT"} element can not be parsed.
      */
-    final MathTransform parsePassThroughMT(final Element parent) throws ParseException {
-        final Element element           = parent.pullElement(WKTKeywords.PassThrough_MT);
+    private MathTransform parsePassThroughMT(final Element parent) throws ParseException {
+        final Element element = parent.pullElement(FIRST, WKTKeywords.PassThrough_MT);
+        if (element == null) {
+            return null;
+        }
         final int firstAffectedOrdinate = parent.pullInteger("firstAffectedOrdinate");
         final MathTransform transform   = parseMathTransform(element, true);
         element.close(ignoredElements);
@@ -292,12 +299,16 @@ class MathTransformParser extends Parser
      *     CONCAT_MT[<math transform> {,<math transform>}*]
      * }
      *
+     * @param  mode {@link #FIRST}, {@link #OPTIONAL} or {@link #MANDATORY}.
      * @param  parent The parent element.
      * @return The {@code "CONCAT_MT"} element as an {@link MathTransform} object.
      * @throws ParseException if the {@code "CONCAT_MT"} element can not be parsed.
      */
-    final MathTransform parseConcatMT(final Element parent) throws ParseException {
-        final Element element = parent.pullElement(WKTKeywords.Concat_MT);
+    private MathTransform parseConcatMT(final Element parent) throws ParseException {
+        final Element element = parent.pullElement(FIRST, WKTKeywords.Concat_MT);
+        if (element == null) {
+            return null;
+        }
         MathTransform transform = parseMathTransform(element, true);
         MathTransform optionalTransform;
         while ((optionalTransform = parseMathTransform(element, false)) != null) {
@@ -313,7 +324,7 @@ class MathTransformParser extends Parser
 
     /**
      * Returns the operation method for the last math transform parsed. This is used by
-     * {@link Parser} in order to built {@link org.opengis.referencing.crs.DerivedCRS}.
+     * {@link GeodeticObjectParser} in order to built {@link org.opengis.referencing.crs.DerivedCRS}.
      */
     final OperationMethod getOperationMethod() {
         if (lastMethod == null) {

Modified: sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/WKTFormat.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/WKTFormat.java?rev=1686282&r1=1686281&r2=1686282&view=diff
==============================================================================
--- sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/WKTFormat.java [UTF-8] (original)
+++ sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/WKTFormat.java [UTF-8] Thu Jun 18 17:38:16 2015
@@ -21,6 +21,8 @@ import java.util.Locale;
 import java.util.TimeZone;
 import java.util.Map;
 import java.util.HashMap;
+import java.util.Collections;
+import java.util.List;
 import java.io.IOException;
 import java.text.Format;
 import java.text.NumberFormat;
@@ -31,6 +33,7 @@ import java.text.ParseException;
 import javax.measure.unit.Unit;
 import javax.measure.unit.UnitFormat;
 import org.opengis.util.Factory;
+import org.opengis.util.InternationalString;
 import org.opengis.metadata.citation.Citation;
 import org.opengis.referencing.IdentifiedObject;
 import org.apache.sis.io.CompoundFormat;
@@ -76,9 +79,15 @@ import org.apache.sis.util.resources.Err
  * PROJECTION[</code> <i>…etc…</i> <code>]]");</code></blockquote>
  * </div>
  *
- * <div class="section">Thread safety</div>
- * {@code WKTFormat}s are not synchronized. It is recommended to create separated format instances for each thread.
- * If multiple threads access a {@code WKTFormat} concurrently, it must be synchronized externally.
+ * <div class="section">Limitations</div>
+ * <ul>
+ *   <li>Instances of this class are not synchronized for multi-threading.
+ *       It is recommended to create separated format instances for each thread.
+ *       If multiple threads access a {@code WKTFormat} concurrently, it must be synchronized externally.</li>
+ *   <li>Serialized objects of this class are not guaranteed to be compatible with future Apache SIS releases.
+ *       Serialization support is appropriate for short term storage or RMI between applications running the
+ *       same version of Apache SIS.</li>
+ * </ul>
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @author  Rémi Eve (IRD)
@@ -182,7 +191,7 @@ public class WKTFormat extends CompoundF
     /**
      * The parser. Will be created when first needed.
      */
-    private transient Parser parser;
+    private transient AbstractParser parser;
 
     /**
      * The factories needed by the parser. Will be created when first needed.
@@ -190,9 +199,15 @@ public class WKTFormat extends CompoundF
     private transient Map<Class<?>,Factory> factories;
 
     /**
+     * The warning produced by the last parsing or formatting operation, or {@code null} if none.
+     *
+     * @see #getWarnings()
+     */
+    private transient Warnings warnings;
+
+    /**
      * Creates a format for the given locale and timezone. The given locale will be used for
-     * {@link org.opengis.util.InternationalString} localization; this is <strong>not</strong>
-     * the locale for number format.
+     * {@link InternationalString} localization; this is <strong>not</strong> the locale for number format.
      *
      * @param locale   The locale for the new {@code Format}, or {@code null} for {@code Locale.ROOT}.
      * @param timezone The timezone, or {@code null} for UTC.
@@ -482,16 +497,26 @@ public class WKTFormat extends CompoundF
      */
     @Override
     public Object parse(final CharSequence text, final ParsePosition pos) throws ParseException {
+        warnings = null;
+        ArgumentChecks.ensureNonNull("text", text);
+        ArgumentChecks.ensureNonNull("pos",  pos);
+        AbstractParser parser = this.parser;
         if (parser == null) {
             if (factories == null) {
                 factories = new HashMap<Class<?>,Factory>();
             }
-            parser = new GeodeticObjectParser(symbols,
+            this.parser = parser = new GeodeticObjectParser(symbols,
                     (NumberFormat) getFormat(Number.class),
                     (DateFormat)   getFormat(Date.class),
-                    convention, false, getLocale(), factories);
+                    (UnitFormat)   getFormat(Unit.class),
+                    convention, getLocale(), factories);
+        }
+        Object object = null;
+        try {
+            return object = parser.parseObject(text.toString(), pos);
+        } finally {
+            warnings = parser.getAndClearWarnings(object);
         }
-        return parser.parseObject(text.toString(), pos);
     }
 
     /**
@@ -507,10 +532,11 @@ public class WKTFormat extends CompoundF
      * @param  toAppendTo Where the text is to be appended.
      * @throws IOException If an error occurred while writing to {@code toAppendTo}.
      *
-     * @see #getWarning()
+     * @see FormattableObject#toWKT()
      */
     @Override
     public void format(final Object object, final Appendable toAppendTo) throws IOException {
+        warnings = null;
         ArgumentChecks.ensureNonNull("object",     object);
         ArgumentChecks.ensureNonNull("toAppendTo", toAppendTo);
         /*
@@ -538,13 +564,20 @@ public class WKTFormat extends CompoundF
             this.formatter = formatter;
         }
         final boolean valid;
+        final InternationalString warning;
         try {
             formatter.setBuffer(buffer);
             valid = formatter.appendElement(object) || formatter.appendValue(object);
         } finally {
+            warning = formatter.getErrorMessage();  // Must be saved before formatter.clear() is invoked.
             formatter.setBuffer(null);
             formatter.clear();
         }
+        if (warning != null) {
+            warnings = new Warnings(getLocale(), (byte) 0, Collections.<String, List<String>>emptyMap());
+            warnings.add(warning, formatter.getErrorCause(), null);
+            warnings.setRoot(object);
+        }
         if (!valid) {
             throw new ClassCastException(Errors.format(
                     Errors.Keys.IllegalArgumentClass_2, "object", object.getClass()));
@@ -579,13 +612,34 @@ public class WKTFormat extends CompoundF
     }
 
     /**
-     * If a warning occurred during the last WKT {@linkplain #format(Object, Appendable) formatting}, returns
-     * the warning. Otherwise returns {@code null}. The warning is cleared every time a new object is formatted.
+     * If warnings occurred during the last WKT {@linkplain #parse(CharSequence, ParsePosition) parsing} or
+     * {@linkplain #format(Object, Appendable) formatting}, returns the warnings. Otherwise returns {@code null}.
+     * The warnings are cleared every time a new object is parsed or formatted.
+     *
+     * @return The warnings of the last parsing of formatting operation, or {@code null} if none.
+     *
+     * @since 0.6
+     */
+    public Warnings getWarnings() {
+        final Warnings w = warnings;
+        if (w != null) {
+            w.publish();
+        }
+        return w;
+    }
+
+    /**
+     * If a warning occurred during the last WKT {@linkplain #parse(CharSequence, ParsePosition) parsing} or
+     * {@linkplain #format(Object, Appendable) formatting}, returns the warning. Otherwise returns {@code null}.
+     * The warning is cleared every time a new object is parsed or formatted.
      *
      * @return The last warning, or {@code null} if none.
+     *
+     * @deprecated Replaced by {@link #getWarnings()}.
      */
+    @Deprecated
     public String getWarning() {
-        return (formatter != null) ? formatter.getErrorMessage() : null;
+        return (warnings != null) ? warnings.toString() : null;
     }
 
     /**
@@ -597,6 +651,8 @@ public class WKTFormat extends CompoundF
     public WKTFormat clone() {
         final WKTFormat clone = (WKTFormat) super.clone();
         clone.formatter = null; // Do not share the formatter.
+        clone.parser    = null;
+        clone.warnings  = null;
         return clone;
     }
 }

Modified: sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/package-info.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/package-info.java?rev=1686282&r1=1686281&r2=1686282&view=diff
==============================================================================
--- sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/package-info.java [UTF-8] (original)
+++ sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/package-info.java [UTF-8] Thu Jun 18 17:38:16 2015
@@ -17,21 +17,30 @@
 
 /**
  * <cite>Well Known Text</cite> (WKT) parsing and formatting.
- * This package implements the services provided by the {@link org.apache.sis.referencing.CRS#parseWKT(String)}
- * and {@link org.opengis.referencing.IdentifiedObject#toWKT()} convenience methods, with more control.
+ * This package implements the services provided by various convenience methods:
+ *
+ * <ul>
+ *   <li>{@link org.apache.sis.referencing.CRS#fromWKT(String)} (SIS parsing static method)</li>
+ *   <li>{@link org.opengis.referencing.crs.CRSFactory#createFromWKT(String)} (GeoAPI parsing method)</li>
+ *   <li>{@link org.opengis.referencing.operation.MathTransformFactory#createFromWKT(String)} (GeoAPI parsing method)</li>
+ *   <li>{@link org.opengis.referencing.IdentifiedObject#toWKT()} (GeoAPI formatting method)</li>
+ * </ul>
+ *
+ * However the {@link org.apache.sis.io.wkt.WKTFormat} class provided in this package gives more control.
  * For example this package allows to:
  *
  * <ul>
- *   <li>Format projection and parameters using the names of a chosen authority. For example the
- *       <cite>"Mercator (variant A)"</cite> projection is named {@code "Mercator_1SP"} by OGC 01-009
- *       and {@code "CT_Mercator"} by GeoTIFF.</li>
- *   <li>Format the elements with curly brackets instead than square ones.
+ *   <li>Format projection and parameters using the names of a chosen authority.
+ *       For example the <cite>"Mercator (variant A)"</cite> projection is named
+ *       {@code "Mercator_1SP"} by OGC 01-009 and {@code "CT_Mercator"} by GeoTIFF.</li>
+ *   <li>Format the elements with different quote characters or brackets style.
  *       For example both {@code ID["EPSG",4326]} and {@code ID("EPSG",4326)} are legal WKT.</li>
  *   <li>Format with a different indentation or format the whole WKT on a single line.</li>
  *   <li>Apply syntactic coloring on terminal supporting <cite>ANSI escape codes</cite>
  *       (a.k.a. ECMA-48, ISO/IEC 6429 and X3.64).</li>
- *   <li>Ignore the {@code AXIS[…]} elements at parsing time. This approach can be used as a way to force
- *       the (<var>longitude</var>, <var>latitude</var>) axes order.</li>
+ *   <li>Alter the parsing in a way compatible with non-standard (but commonly used) WKT.
+ *       For example some others softwares ignore the {@code AXIS[…]} elements at parsing time.</li>
+ *   <li>Report warnings that occurred during parsing or formatting.</li>
  * </ul>
  *
  * <div class="section">Referencing WKT</div>
@@ -56,11 +65,25 @@
  * provide their own, limited, WKT parsing and formatting services for the {@code BOX} and {@code POINT} elements.
  * A description for this WKT format can be found on <a href="http://en.wikipedia.org/wiki/Well-known_text">Wikipedia</a>.
  *
+ * <div class="section">Where to find WKT examples</div>
+ * An excellent source of well-formed WKT is the online <cite>EPSG Geodetic Parameter Registry</cite>.
+ * The WKT of many Coordinate Reference System object can be viewed using the pattern below
+ * (replace {@code 3395} by the EPSG code of the desired CRS):
+ *
+ * <blockquote><b>Example</b>: <cite>"WGS 84 / World Mercator"</cite>:
+ * <a href="http://epsg-registry.org/export.htm?wkt=urn:ogc:def:crs:EPSG::3395">http://epsg-registry.org/export.htm?wkt=urn:ogc:def:crs:EPSG::3395</a>
+ * </blockquote>
+ *
+ * Readers should be aware that some popular other sources of WKT are actually invalid,
+ * since many of them do not comply with EPSG definitions (especially on axis order).
+ * The above-cited EPSG registry is <strong>the</strong> authoritative source
+ * of CRS definitions in the EPSG namespace.
+ *
  * @author  Martin Desruisseaux (IRD, Geomatys)
  * @author  Rémi Eve (IRD)
  * @author  Rueben Schulz (UBC)
  * @since   0.4
- * @version 0.4
+ * @version 0.6
  * @module
  *
  * @see <a href="http://docs.opengeospatial.org/is/12-063r5/12-063r5.html">WKT 2 specification</a>



Mime
View raw message