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 2015/06/30 18:58:21 UTC

svn commit: r1688485 [1/2] - in /sis/branches/JDK6: ./ core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/ core/sis-metadata/src/main/java/org/apache/sis/io/wkt/ core/sis-metadata/src/test/java/org/apache/sis/internal/metadata/ core/sis-m...

Author: desruisseaux
Date: Tue Jun 30 16:58:20 2015
New Revision: 1688485

URL: http://svn.apache.org/r1688485
Log:
Merge WKT 2 work from the JDK7 branch.

Added:
    sis/branches/JDK6/core/sis-metadata/src/test/java/org/apache/sis/internal/metadata/AxisNamesTest.java
      - copied unchanged from r1688483, sis/branches/JDK7/core/sis-metadata/src/test/java/org/apache/sis/internal/metadata/AxisNamesTest.java
Modified:
    sis/branches/JDK6/   (props changed)
    sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/AxisNames.java
    sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/ReferencingServices.java
    sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/VerticalDatumTypes.java
    sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/WKTKeywords.java
    sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/AbstractParser.java
    sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/Element.java
    sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/Formatter.java
    sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/GeodeticObjectParser.java
    sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/MathTransformParser.java
    sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/Transliterator.java
    sis/branches/JDK6/core/sis-metadata/src/test/java/org/apache/sis/io/wkt/TransliteratorTest.java
    sis/branches/JDK6/core/sis-metadata/src/test/java/org/apache/sis/test/suite/MetadataTestSuite.java
    sis/branches/JDK6/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/ServicesForMetadata.java
    sis/branches/JDK6/core/sis-referencing/src/main/java/org/apache/sis/parameter/MatrixParametersAlphaNum.java
    sis/branches/JDK6/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/DefaultVerticalDatum.java
    sis/branches/JDK6/core/sis-referencing/src/test/java/org/apache/sis/io/wkt/GeodeticObjectParserTest.java
    sis/branches/JDK6/core/sis-referencing/src/test/java/org/apache/sis/io/wkt/WKTParserTest.java
    sis/branches/JDK6/core/sis-referencing/src/test/java/org/apache/sis/parameter/DefaultParameterDescriptorGroupTest.java
    sis/branches/JDK6/core/sis-referencing/src/test/java/org/apache/sis/parameter/DefaultParameterDescriptorTest.java
    sis/branches/JDK6/core/sis-referencing/src/test/java/org/apache/sis/parameter/DefaultParameterValueTest.java
    sis/branches/JDK6/core/sis-utility/src/main/java/org/apache/sis/internal/util/Constants.java
    sis/branches/JDK6/core/sis-utility/src/main/java/org/apache/sis/internal/util/PatchedUnitFormat.java
    sis/branches/JDK6/core/sis-utility/src/main/java/org/apache/sis/internal/util/StandardDateFormat.java
    sis/branches/JDK6/core/sis-utility/src/main/java/org/apache/sis/measure/Units.java
    sis/branches/JDK6/src/main/javadoc/stylesheet.css

Propchange: sis/branches/JDK6/
------------------------------------------------------------------------------
--- svn:mergeinfo (original)
+++ svn:mergeinfo Tue Jun 30 16:58:20 2015
@@ -1,4 +1,4 @@
 /sis/branches/Android:1430670-1480699
-/sis/branches/JDK7:1394913-1687818
-/sis/branches/JDK8:1584960-1687814
+/sis/branches/JDK7:1394913-1688483
+/sis/branches/JDK8:1584960-1688481
 /sis/trunk:1394364-1508466,1519089-1519674

Modified: sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/AxisNames.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/AxisNames.java?rev=1688485&r1=1688484&r2=1688485&view=diff
==============================================================================
--- sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/AxisNames.java [UTF-8] (original)
+++ sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/AxisNames.java [UTF-8] Tue Jun 30 16:58:20 2015
@@ -16,6 +16,14 @@
  */
 package org.apache.sis.internal.metadata;
 
+import java.util.Map;
+import java.util.HashMap;
+import java.lang.reflect.Field;
+import org.apache.sis.util.logging.Logging;
+
+// Branch-specific imports
+import org.apache.sis.internal.jdk8.JDK8;
+
 
 /**
  * Constants for axis names specified by ISO 19111 and ISO 19162.
@@ -120,8 +128,75 @@ public final class AxisNames {
     public static final String SOUTHING = "Southing";
 
     /**
+     * A ISO 19162 frequently used name.
+     */
+    public static final String TIME = "Time";
+
+    /**
+     * The map of all of the above values, used for fixing the case.
+     * Shall not be modified after construction.
+     */
+    private static final Map<String,String> VALUES;
+    static {
+        final Map<String,String> values = new HashMap<String,String>(22);
+        final StringBuilder buffer = new StringBuilder(22);     // Length of the longuest string: "Gravity-related height"
+        try {
+            for (final Field f : AxisNames.class.getFields()) {
+                final String name = (String) f.get(null);
+                values.put(toUpperCase(name, buffer).intern(), name);
+                buffer.setLength(0);
+                /*
+                 * The call to 'intern()' is because many upper-case strings match the field name
+                 * (e.g. "LATITUDE", "NORTHING", "DEPTH", etc.), so we use the same String instance.
+                 */
+            }
+        } catch (IllegalAccessException e) {
+            /*
+             * Should never happen. But if it happen anyway, do not kill the application for that.
+             * We will take the values that we have been able to map so far. The other values will
+             * just not have their case fixed.
+             */
+            Logging.unexpectedException(AxisNames.class, "<cinit>", e);
+        }
+        VALUES = values;
+    }
+
+    /**
      * Do not allow instantiation of this class.
      */
     private AxisNames() {
     }
+
+    /**
+     * Returns the given axis name in upper case without punctuation characters.
+     *
+     * @param  name   The axis name to return in upper-case.
+     * @param  buffer A temporary buffer to use. Must be initially empty.
+     * @return The given name converted to upper-case.
+     */
+    private static String toUpperCase(final String name, final StringBuilder buffer) {
+        for (int i=0; i<name.length(); i++) {
+            final char c = name.charAt(i);
+            if (Character.isLetterOrDigit(c)) {
+                buffer.append(Character.toUpperCase(c));
+            }
+        }
+        return buffer.toString();
+    }
+
+    /**
+     * Returns the given name in camel case if it is one of the know names.
+     * This method canonicalizes also the use of {@code '_'}, {@code '-'} and {@code ' '}.
+     *
+     * <div class="note"><b>Rational:</b>
+     * Axis names are not really free text. They are specified by ISO 19111 and ISO 19162.
+     * SIS does not put restriction on axis names, but we nevertheless try to use a unique
+     * name when we recognize it.</div>
+     *
+     * @param  name The name in any case.
+     * @return The given name in camel case.
+     */
+    public static String toCamelCase(final String name) {
+        return JDK8.getOrDefault(VALUES, toUpperCase(name, new StringBuilder(name.length())), name);
+    }
 }

Modified: sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/ReferencingServices.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/ReferencingServices.java?rev=1688485&r1=1688484&r2=1688485&view=diff
==============================================================================
--- sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/ReferencingServices.java [UTF-8] (original)
+++ sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/ReferencingServices.java [UTF-8] Tue Jun 30 16:58:20 2015
@@ -17,6 +17,7 @@
 package org.apache.sis.internal.metadata;
 
 import java.util.Map;
+import java.util.Collections;
 import javax.measure.unit.Unit;
 import javax.measure.quantity.Length;
 import org.opengis.geometry.Envelope;
@@ -446,6 +447,18 @@ public class ReferencingServices extends
     }
 
     /**
+     * Returns the properties of the given object.
+     *
+     * @param  object The object from which to get the properties.
+     * @return The properties of the given object.
+     *
+     * @since 0.6
+     */
+    public Map<String,?> getProperties(final IdentifiedObject object) {
+        return Collections.singletonMap(IdentifiedObject.NAME_KEY, object.getName());
+    }
+
+    /**
      * Returns {@code true} if the {@linkplain org.apache.sis.referencing.AbstractIdentifiedObject#getName()
      * primary name} or an aliases of the given object matches the given name. The comparison ignores case,
      * some Latin diacritical signs and any characters that are not letters or digits.

Modified: sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/VerticalDatumTypes.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/VerticalDatumTypes.java?rev=1688485&r1=1688484&r2=1688485&view=diff
==============================================================================
--- sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/VerticalDatumTypes.java [UTF-8] (original)
+++ sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/VerticalDatumTypes.java [UTF-8] Tue Jun 30 16:58:20 2015
@@ -16,12 +16,15 @@
  */
 package org.apache.sis.internal.metadata;
 
+import java.util.Collection;
+import javax.measure.unit.Unit;
 import org.opengis.util.CodeList;
 import org.opengis.util.GenericName;
-import org.opengis.metadata.Identifier;
-import org.opengis.referencing.datum.VerticalDatum;
 import org.opengis.referencing.datum.VerticalDatumType;
+import org.opengis.referencing.cs.CoordinateSystemAxis;
+import org.opengis.referencing.cs.AxisDirection;
 import org.apache.sis.util.StringBuilders;
+import org.apache.sis.measure.Units;
 
 
 /**
@@ -112,27 +115,51 @@ public final class VerticalDatumTypes im
     }
 
     /**
-     * Guesses the type of the given datum using its name or identifiers. This is sometime needed
-     * after XML unmarshalling, since GML 3.2 does not contain any attribute for the datum type.
+     * Guesses the type of a datum from its name, aliases or a given vertical axis. This is sometime needed
+     * after XML unmarshalling or WKT parsing, since GML 3.2 and ISO 19162 do not contain any attribute for
+     * the datum type.
      *
      * <p>This method uses heuristic rules and may be changed in any future SIS version.
-     * If the type can not be determined, defaults on {@link VerticalDatumType#OTHER_SURFACE}.</p>
+     * If the type can not be determined, defaults to {@link VerticalDatumType#OTHER_SURFACE}.</p>
      *
-     * @param  datum The datum for which to guess a type.
+     * @param  name    The name of the datum for which to guess a type, or {@code null} if unknown.
+     * @param  aliases The aliases of the datum for which to guess a type, or {@code null} if unknown.
+     * @param  axis    The vertical axis for which to guess a type, or {@code null} if unknown.
      * @return A datum type, or {@link VerticalDatumType#OTHER_SURFACE} if none can be guessed.
      */
-    public static VerticalDatumType guess(final VerticalDatum datum) {
-        final Identifier identifier = datum.getName();
-        if (identifier != null) {
-            final VerticalDatumType type = guess(identifier.getCode());
-            if (type != null) {
-                return type;
+    public static VerticalDatumType guess(final String name, final Collection<? extends GenericName> aliases,
+            final CoordinateSystemAxis axis)
+    {
+        VerticalDatumType type = guess(name);
+        if (type != null) {
+            return type;
+        }
+        if (aliases != null) {
+            for (final GenericName alias : aliases) {
+                type = guess(alias.tip().toString());
+                if (type != null) {
+                    return type;
+                }
             }
         }
-        for (final GenericName alias : datum.getAlias()) {
-            final VerticalDatumType type = guess(alias.tip().toString());
-            if (type != null) {
-                return type;
+        if (axis != null) {
+            final Unit<?> unit = axis.getUnit();
+            if (Units.isLinear(unit)) {
+                final String abbreviation = axis.getAbbreviation();
+                if (abbreviation.length() == 1) {
+                    AxisDirection dir = AxisDirection.UP;   // Expected direction for accepting the type.
+                    switch (abbreviation.charAt(0)) {
+                        case 'h': type = ELLIPSOIDAL; break;
+                        case 'H': type = VerticalDatumType.GEOIDAL; break;
+                        case 'D': type = VerticalDatumType.DEPTH; dir = AxisDirection.DOWN; break;
+                        default:  return VerticalDatumType.OTHER_SURFACE;
+                    }
+                    if (dir.equals(axis.getDirection())) {
+                        return type;
+                    }
+                }
+            } else if (Units.isPressure(unit)) {
+                return VerticalDatumType.BAROMETRIC;
             }
         }
         return VerticalDatumType.OTHER_SURFACE;

Modified: sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/WKTKeywords.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/WKTKeywords.java?rev=1688485&r1=1688484&r2=1688485&view=diff
==============================================================================
--- sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/WKTKeywords.java [UTF-8] (original)
+++ sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/WKTKeywords.java [UTF-8] Tue Jun 30 16:58:20 2015
@@ -110,7 +110,9 @@ public final class WKTKeywords extends S
             VerticalDatum  = "VerticalDatum",
             VerticalCRS    = "VerticalCRS",
             BaseVertCRS    = "BaseVertCRS",
+            VDatum         = "VDatum",
             Vert_Datum     = "Vert_Datum",
+            VertCRS        = "VertCRS",
             Vert_CS        = "Vert_CS";
 
     /**
@@ -120,6 +122,7 @@ public final class WKTKeywords extends S
             TimeExtent  = "TimeExtent",
             TimeOrigin  = "TimeOrigin",
             TimeDatum   = "TimeDatum",
+            TDatum      = "TDatum",
             TimeCRS     = "TimeCRS",
             BaseTimeCRS = "BaseTimeCRS";
 
@@ -134,6 +137,8 @@ public final class WKTKeywords extends S
             EngineeringDatum = "EngineeringDatum",
             EngineeringCRS   = "EngineeringCRS",
             BaseEngCRS       = "BaseEngCRS",
+            EngCRS           = "EngCRS",
+            EDatum           = "EDatum",
             Local_Datum      = "Local_Datum",
             Local_CS         = "Local_CS";
 
@@ -150,6 +155,7 @@ public final class WKTKeywords extends S
     public static final String
             ProjectedCRS = "ProjectedCRS",
             BaseProjCRS  = "BaseProjCRS",
+            ProjCRS      = "ProjCRS",
             ProjCS       = "ProjCS";
 
     /**

Modified: sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/AbstractParser.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/AbstractParser.java?rev=1688485&r1=1688484&r2=1688485&view=diff
==============================================================================
--- sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/AbstractParser.java [UTF-8] (original)
+++ sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/AbstractParser.java [UTF-8] Tue Jun 30 16:58:20 2015
@@ -151,13 +151,12 @@ abstract class AbstractParser implements
         if (numberFormat == null) {
             numberFormat = symbols.createNumberFormat();
         }
-        this.symbols      = symbols;
-        this.numberFormat = numberFormat;
-        this.dateFormat   = dateFormat;
-        this.unitFormat   = unitFormat;
-        this.errorLocale  = errorLocale;
+        this.symbols     = symbols;
+        this.dateFormat  = dateFormat;
+        this.unitFormat  = unitFormat;
+        this.errorLocale = errorLocale;
         if (SCIENTIFIC_NOTATION && numberFormat instanceof DecimalFormat) {
-            final DecimalFormat decimalFormat = (DecimalFormat) numberFormat;
+            final DecimalFormat decimalFormat = (DecimalFormat) ((DecimalFormat) numberFormat).clone();
             exponentSymbol = decimalFormat.getDecimalFormatSymbols().getExponentSeparator();
             String pattern = decimalFormat.toPattern();
             if (!pattern.contains("E0")) {
@@ -169,7 +168,9 @@ abstract class AbstractParser implements
                 buffer.append("E0");
                 decimalFormat.applyPattern(buffer.toString());
             }
+            this.numberFormat = decimalFormat;
         } else {
+            this.numberFormat = numberFormat;
             exponentSymbol = null;
         }
         ignoredElements = new LinkedHashMap<String, List<String>>();
@@ -195,7 +196,7 @@ abstract class AbstractParser implements
             if (cause instanceof FactoryException) {
                 throw (FactoryException) cause;
             }
-            throw new FactoryException(exception);
+            throw new FactoryException(exception.getMessage(), exception);
         }
     }
 

Modified: sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/Element.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/Element.java?rev=1688485&r1=1688484&r2=1688485&view=diff
==============================================================================
--- sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/Element.java [UTF-8] (original)
+++ sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/Element.java [UTF-8] Tue Jun 30 16:58:20 2015
@@ -393,6 +393,22 @@ final class Element {
     //////////////////////////////////////////////////////////////////////////////////////
 
     /**
+     * Returns the next value (not a child element) without removing it.
+     *
+     * @return The next value, or {@code null} if none.
+     */
+    public Object peekValue() {
+        final Iterator<Object> iterator = list.iterator();
+        while (iterator.hasNext()) {
+            final Object object = iterator.next();
+            if (!(object instanceof Element)) {
+                return object;
+            }
+        }
+        return null;
+    }
+
+    /**
      * Removes the next {@link Date} from the list and returns it.
      *
      * @param  key The parameter name. Used for formatting an error message if no date is found.

Modified: sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/Formatter.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/Formatter.java?rev=1688485&r1=1688484&r2=1688485&view=diff
==============================================================================
--- sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/Formatter.java [UTF-8] (original)
+++ sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/Formatter.java [UTF-8] Tue Jun 30 16:58:20 2015
@@ -31,7 +31,6 @@ import java.text.FieldPosition;
 import java.lang.reflect.Array;
 import java.math.RoundingMode;
 import javax.measure.unit.SI;
-import javax.measure.unit.NonSI;
 import javax.measure.unit.Unit;
 import javax.measure.unit.UnitFormat;
 import javax.measure.quantity.Quantity;
@@ -64,6 +63,7 @@ import org.apache.sis.util.CharSequences
 import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.internal.util.Citations;
+import org.apache.sis.internal.util.Constants;
 import org.apache.sis.internal.util.PatchedUnitFormat;
 import org.apache.sis.internal.util.StandardDateFormat;
 import org.apache.sis.internal.simple.SimpleExtent;
@@ -389,6 +389,7 @@ public class Formatter implements Locali
         this.toUpperCase    = toUpperCase;
         this.indentation    = indentation;
         this.transliterator = (convention == Convention.INTERNAL) ? Transliterator.IDENTITY : Transliterator.DEFAULT;
+        unitFormat.isLocaleUS = convention.usesCommonUnits;
     }
 
     /**
@@ -947,7 +948,7 @@ public class Formatter implements Locali
      * The {@linkplain Symbols#getSeparator() element separator} will be written before the text if needed.
      *
      * @param text The string to format to the WKT, or {@code null} if none.
-     * @param type The key of the colors to apply if syntax coloring is enabled.
+     * @param type The key of the colors to apply if syntax coloring is enabled, or {@code null} if none.
      */
     public void append(final String text, final ElementKind type) {
         if (text != null) {
@@ -1129,6 +1130,7 @@ public class Formatter implements Locali
     public void append(final Unit<?> unit) {
         if (unit != null) {
             final boolean isSimplified = convention.isSimplified();
+            final boolean isWKT1 = convention.majorVersion() == 1;
             final Unit<?> base = unit.toSI();
             final String keyword;
             if (base.equals(SI.METRE)) {
@@ -1145,27 +1147,39 @@ public class Formatter implements Locali
             openElement(false, keyword);
             setColor(ElementKind.UNIT);
             final int fromIndex = buffer.appendCodePoint(symbols.getOpeningQuote(0)).length();
-            if (Unit.ONE.equals(unit)) {
-                buffer.append("unity");
-            } else if (NonSI.DEGREE_ANGLE.equals(unit)) {
-                buffer.append("degree");
-            } else if (SI.METRE.equals(unit)) {
-                buffer.append(convention.usesCommonUnits ? "meter" : "metre");
-            } else if (Units.PPM.equals(unit)) {
-                buffer.append("parts per million");
-            } else {
-                unitFormat.format(unit, buffer, dummy);
-            }
+            unitFormat.format(unit, buffer, dummy);
             closeQuote(fromIndex);
             resetColor();
             final double conversion = Units.toStandardUnit(unit);
             appendExact(conversion);
+            /*
+             * The EPSG code in UNIT elements is generally not recommended.
+             * But we make an exception for unit that have no exact representation in WKT.
+             */
+            final Integer code = Units.getEpsgCode(unit, false);
+            if (code != null) {
+                final int n = code;
+                switch (n) {
+                    case Constants.EPSG_DM:
+                    case Constants.EPSG_DMS:
+                    case Constants.EPSG_DMSH: {
+                        openElement(false, isWKT1 ? WKTKeywords.Authority : WKTKeywords.Id);
+                        append(Constants.EPSG, null);
+                        if (isWKT1) {
+                            append(Integer.toString(n), null);
+                        } else {
+                            append(n);
+                        }
+                        closeElement(false);
+                    }
+                }
+            }
             closeElement(false);
             /*
              * ISO 19162 requires the conversion factor to be positive.
              * In addition, keywords other than "Unit" are not valid in WKt 1.
              */
-            if (!(conversion > 0) || (keyword != WKTKeywords.Unit && convention.majorVersion() == 1)) {
+            if (!(conversion > 0) || (keyword != WKTKeywords.Unit && isWKT1)) {
                 setInvalidWKT(Unit.class, null);
             }
         }

Modified: sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/GeodeticObjectParser.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/GeodeticObjectParser.java?rev=1688485&r1=1688484&r2=1688485&view=diff
==============================================================================
--- sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/GeodeticObjectParser.java [UTF-8] (original)
+++ sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/GeodeticObjectParser.java [UTF-8] Tue Jun 30 16:58:20 2015
@@ -70,6 +70,7 @@ import org.apache.sis.internal.util.Loca
 import org.apache.sis.internal.system.DefaultFactories;
 import org.apache.sis.util.CharSequences;
 import org.apache.sis.util.resources.Errors;
+import org.apache.sis.util.iso.DefaultNameSpace;
 import org.apache.sis.util.iso.Types;
 
 import static java.util.Collections.singletonMap;
@@ -89,23 +90,6 @@ import static java.util.Collections.sing
  */
 final class GeodeticObjectParser extends MathTransformParser implements Comparator<CoordinateSystemAxis> {
     /**
-     * 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}
@@ -140,18 +124,25 @@ final class GeodeticObjectParser extends
     private final ReferencingServices referencing;
 
     /**
-     * The WKT convention to assume for parsing.
+     * During WKT 1 parsing, {@code true} 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
      *
-     * <ul>
-     *   <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>
+     * <p>This flag is ignored during WKT 2 parsing.</p>
      *
-     *   <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>
+     * @see Convention#WKT1_COMMON_UNITS
      */
-    private final Convention convention;
+    private final boolean usesCommonUnits;
+
+    /**
+     * During WKT 1 parsing, {@code true} means that axes should be parsed only for verifying the syntax,
+     * but otherwise parsing should behave as if axes were not declared.
+     *
+     * <p>This flag is ignored during WKT 2 parsing.</p>
+     *
+     * @see Convention#WKT1_IGNORE_AXES
+     */
+    private final boolean ignoreAxes;
 
     /**
      * The object to use for replacing WKT axis names and abbreviations by ISO 19111 names and abbreviations.
@@ -204,13 +195,14 @@ final class GeodeticObjectParser extends
             final ObjectFactory factories, final MathTransformFactory mtFactory)
     {
         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;
-        transliterator = Transliterator.DEFAULT;
+        crsFactory      = (CRSFactory)   factories;
+        csFactory       = (CSFactory)    factories;
+        datumFactory    = (DatumFactory) factories;
+        referencing     = ReferencingServices.getInstance();
+        opFactory       = referencing.getCoordinateOperationFactory(defaultProperties, mtFactory);
+        transliterator  = Transliterator.DEFAULT;
+        usesCommonUnits = false;
+        ignoreAxes      = false;
     }
 
     /**
@@ -230,13 +222,14 @@ final class GeodeticObjectParser extends
             final Locale errorLocale, final Map<Class<?>,Factory> factories)
     {
         super(symbols, numberFormat, dateFormat, unitFormat, getFactory(MathTransformFactory.class, factories), errorLocale);
-        this.convention = convention;
         this.transliterator = transliterator;
-        crsFactory   = getFactory(CRSFactory.class,   factories);
-        csFactory    = getFactory(CSFactory.class,    factories);
-        datumFactory = getFactory(DatumFactory.class, factories);
-        referencing  = ReferencingServices.getInstance();
-        opFactory    = referencing.getCoordinateOperationFactory(null, mtFactory);
+        crsFactory      = getFactory(CRSFactory.class,   factories);
+        csFactory       = getFactory(CSFactory.class,    factories);
+        datumFactory    = getFactory(DatumFactory.class, factories);
+        referencing     = ReferencingServices.getInstance();
+        opFactory       = referencing.getCoordinateOperationFactory(null, mtFactory);
+        usesCommonUnits = convention.usesCommonUnits;
+        ignoreAxes      = convention == Convention.WKT1_IGNORE_AXES;
     }
 
     /**
@@ -319,14 +312,14 @@ final class GeodeticObjectParser extends
         }
         Object object;
         if ((object = parseAxis             (FIRST, element, null, 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)
+            (object = parsePrimeMeridian    (FIRST, element, false, NonSI.DEGREE_ANGLE)) == null &&
+            (object = parseDatum            (FIRST, element, null )) == null &&
+            (object = parseEllipsoid        (FIRST, element       )) == null &&
+            (object = parseToWGS84          (FIRST, element       )) == null &&
+            (object = parseVerticalDatum    (FIRST, element, false)) == null &&
+            (object = parseTimeDatum        (FIRST, element       )) == null &&
+            (object = parseEngineeringDatum (FIRST, element, false)) == null &&
+            (object = parseConversion       (FIRST, element, false, SI.METRE, NonSI.DEGREE_ANGLE)) == null)
         {
             throw element.missingOrUnknownComponent(WKTKeywords.GeodeticCRS);
         }
@@ -345,7 +338,7 @@ final class GeodeticObjectParser extends
             throws ParseException
     {
         CoordinateReferenceSystem crs;
-        if ((crs = parseGeodeticCRS    (FIRST, element)) == null &&
+        if ((crs = parseGeodeticCRS    (FIRST, element, null)) == null &&
             (crs = parseProjectedCRS   (FIRST, element)) == null &&
             (crs = parseVerticalCRS    (FIRST, element)) == null &&
             (crs = parseTimeCRS        (FIRST, element)) == null &&
@@ -361,6 +354,17 @@ final class GeodeticObjectParser extends
     }
 
     /**
+     * Returns the value associated to {@link IdentifiedObject#IDENTIFIERS_KEY} as an {@code Identifier} object.
+     * This method shall accept all value types that {@link #parseMetadataAndClose(Element, Object)} may store.
+     *
+     * @param  identifier The {@link #properties} value, or {@code null}.
+     * @return The identifier, or {@code null} if the given value was null.
+     */
+    private static Identifier toIdentifier(final Object identifier) {
+        return (identifier instanceof Identifier[]) ? ((Identifier[]) identifier)[0] : (Identifier) identifier;
+    }
+
+    /**
      * 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:
@@ -379,8 +383,8 @@ final class GeodeticObjectParser extends
         properties.clear();
         properties.put(IdentifiedObject.NAME_KEY, name);
         Element element;
-        while ((element = parent.pullElement(OPTIONAL, WKTKeywords.Id, WKTKeywords.Authority)) != null) {
-            final String   codeSpace = element.pullString("name");
+        while ((element = parent.pullElement(OPTIONAL, ID_KEYWORDS)) != null) {
+            final String   codeSpace = element.pullString("codeSpace");
             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);
@@ -424,6 +428,7 @@ final class GeodeticObjectParser extends
                     identifiers[n] = id;
                 }
                 properties.put(IdentifiedObject.IDENTIFIERS_KEY, identifiers);
+                // REMINDER: values associated to IDENTIFIERS_KEY shall be recognized by 'toIdentifier(Object)'.
             }
         }
         /*
@@ -490,16 +495,23 @@ final class GeodeticObjectParser extends
              * 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);
+                if (element.peekValue() instanceof String) {
+                    element.pullString("startTime");
+                    element.pullString("endTime");
+                    element.close(ignoredElements);
+                    warning(Errors.formatInternational(Errors.Keys.UnsupportedType_1, "TimeExtent[String,String]"), null);
+                } else {
+                    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);
+                    }
                 }
             }
             /*
@@ -516,6 +528,20 @@ final class GeodeticObjectParser extends
     }
 
     /**
+     * Parses the datum {@code ANCHOR[]} element and invoke {@link #parseMetadataAndClose(Element, Object)}.
+     * If an anchor has been found, its value is stored in the returned map.
+     */
+    private Map<String,Object> parseAnchorAndClose(final Element element, final String name) throws ParseException {
+        final Element anchor = element.pullElement(OPTIONAL, WKTKeywords.Anchor);
+        final Map<String,Object> properties = parseMetadataAndClose(element, name);
+        if (anchor != null) {
+            properties.put(Datum.ANCHOR_POINT_KEY, anchor.pullString("anchorDefinition"));
+            anchor.close(ignoredElements);
+        }
+        return properties;
+    }
+
+    /**
      * Parses an optional {@code "UNIT"} element of a known dimension.
      * This element has the following pattern:
      *
@@ -535,6 +561,7 @@ final class GeodeticObjectParser extends
      * @todo Authority code is currently ignored. We may consider to create a subclass of
      *       {@link Unit} which implements {@link IdentifiedObject} in a future version.
      */
+    @SuppressWarnings("unchecked")
     private <Q extends Quantity> Unit<Q> parseScaledUnit(final Element parent,
             final String keyword, final Unit<Q> baseUnit) throws ParseException
     {
@@ -544,32 +571,16 @@ final class GeodeticObjectParser extends
         }
         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], factor);
+        final Unit<?> unit  = parseUnitID(element);
+        element.close(ignoredElements);
+        if (unit != null) {
+            if (baseUnit.toSI().equals(unit.toSI())) {
+                return (Unit<Q>) unit;
+            } else {
+                warning(Errors.formatInternational(Errors.Keys.IllegalUnitFor_2, keyword, unit), null);
+            }
         }
-        // If we can not infer the base type, we have to rely on the name.
-        return parseUnit(name);
+        return Units.multiply(baseUnit, factor);
     }
 
     /**
@@ -605,6 +616,7 @@ final class GeodeticObjectParser extends
      * @param  parent      The parent element.
      * @param  type        The expected type (Cartesian | ellipsoidal | vertical | etc…), or null if unknown.
      * @param  dimension   The minimal number of dimensions. Can be 1 if unknown.
+     * @param  isWKT1      {@code true} if the parent element is an element from the WKT 1 standard.
      * @param  defaultUnit The contextual unit (usually {@code SI.METRE} or {@code SI.RADIAN}), or {@code null} if unknown.
      * @param  datum       The datum of the enclosing CRS.
      * @return The {@code "CS"}, {@code "UNIT"} and/or {@code "AXIS"} elements as a Coordinate System, or {@code null}.
@@ -612,7 +624,7 @@ final class GeodeticObjectParser extends
      * @throws FactoryException if the factory can not create the coordinate system.
      */
     private CoordinateSystem parseCoordinateSystem(final Element parent, String type, int dimension,
-            final Unit<?> defaultUnit, final Datum datum) throws ParseException, FactoryException
+            final boolean isWKT1, final Unit<?> defaultUnit, final Datum datum) throws ParseException, FactoryException
     {
         axisOrder.clear();
         final boolean is3D = (dimension >= 3);
@@ -663,7 +675,7 @@ final class GeodeticObjectParser extends
                 list.add(axis);
                 axis = parseAxis(list.size() < dimension ? MANDATORY : OPTIONAL, parent, type, defaultUnit);
             } while (axis != null);
-            if (convention != Convention.WKT1_IGNORE_AXES) {
+            if (!isWKT1 || !ignoreAxes) {
                 axes = list.toArray(new CoordinateSystemAxis[list.size()]);
                 Arrays.sort(axes, this);  // Take ORDER[n] elements in account.
             }
@@ -996,18 +1008,19 @@ final class GeodeticObjectParser extends
      *     PRIMEM["<name>", <longitude> {,<authority>}]
      * }
      *
-     * @param  mode {@link #FIRST}, {@link #OPTIONAL} or {@link #MANDATORY}.
-     * @param  parent The parent element.
+     * @param  mode        {@link #FIRST}, {@link #OPTIONAL} or {@link #MANDATORY}.
+     * @param  parent      The parent element.
+     * @param  isWKT1      {@code true} if this method is invoked while parsing a WKT 1 element.
      * @param  angularUnit The contextual unit.
      * @return The {@code "PrimeMeridian"} element as a {@link PrimeMeridian} object.
      * @throws ParseException if the {@code "PrimeMeridian"} element can not be parsed.
      *
      * @see org.apache.sis.referencing.datum.DefaultPrimeMeridian#formatTo(Formatter)
      */
-    private PrimeMeridian parsePrimeMeridian(final int mode, final Element parent, Unit<Angle> angularUnit)
+    private PrimeMeridian parsePrimeMeridian(final int mode, final Element parent, final boolean isWKT1, Unit<Angle> angularUnit)
             throws ParseException
     {
-        if (convention.usesCommonUnits) {
+        if (isWKT1 && usesCommonUnits) {
             angularUnit = NonSI.DEGREE_ANGLE;
         }
         final Element element = parent.pullElement(mode, WKTKeywords.PrimeMeridian, WKTKeywords.PrimeM);
@@ -1016,8 +1029,11 @@ final class GeodeticObjectParser extends
         }
         final String name      = element.pullString("name");
         final double longitude = element.pullDouble("longitude");
-        if (angularUnit == null) {
-            throw element.missingComponent(WKTKeywords.AngleUnit);
+        final Unit<Angle> unit = parseScaledUnit(element, WKTKeywords.AngleUnit, SI.RADIAN);
+        if (unit != null) {
+            angularUnit = unit;
+        } else if (angularUnit == null) {
+            throw parent.missingComponent(WKTKeywords.AngleUnit);
         }
         try {
             return datumFactory.createPrimeMeridian(parseMetadataAndClose(element, name), longitude, angularUnit);
@@ -1098,37 +1114,98 @@ final class GeodeticObjectParser extends
     }
 
     /**
-     * Parses a {@code "PROJECTION"} element. This element has the following pattern:
+     * Parses a {@code "Method"} (WKT 2) element, followed by parameter values. The syntax is given by
+     * <a href="http://docs.opengeospatial.org/is/12-063r5/12-063r5.html#62">WKT 2 specification §9.3</a>.
+     *
+     * The legacy WKT 1 specification was:
      *
      * {@preformat text
      *     PROJECTION["<name>" {,<authority>}]
      * }
      *
+     * Note that in WKT 2, this element is wrapped inside a {@code Conversion} element which is itself inside
+     * the {@code ProjectedCRS} element. This is different than WKT 1, which puts this element right into the
+     * the {@code ProjectedCRS} element without {@code Conversion} wrapper.
+     *
      * @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.
+     * @param  isWKT1      {@code false} if we expect a {@code Conversion} element wrapping the method and parameters.
+     * @param  linearUnit  The linear unit of the parent {@code ProjectedCRS} element, or {@code null}.
+     * @param  angularUnit The angular unit of the sibling {@code GeographicCRS} element, or {@code null}.
+     * @return The {@code "Method"} element and its parameters as a defining conversion.
+     * @throws ParseException if the {@code "Method"} element can not be parsed.
      */
-    private Conversion parseProjection(final int mode, final Element parent,
+    private Conversion parseConversion(final int mode, Element parent, final boolean isWKT1,
             final Unit<Length> linearUnit, final Unit<Angle> angularUnit) throws ParseException
     {
-        final Element element = parent.pullElement(mode, WKTKeywords.Projection);
-        if (element == null) {
-            return null;
+        final String name;
+        if (isWKT1) {
+            name = null;  // Will actually be ignored. WKT 1 does not provide name for Conversion objects.
+        } else {
+            /*
+             * If we are parsing WKT 2, then there is an additional "Conversion" element between
+             * the parent (usually a ProjectedCRS) and the other elements parsed by this method.
+             */
+            parent = parent.pullElement(mode, WKTKeywords.Conversion);
+            if (parent == null) {
+                return null;
+            }
+            name = parent.pullString("name");
+        }
+        final Element element    = parent.pullElement(MANDATORY, WKTKeywords.Method, WKTKeywords.Projection);
+        final String  methodName = element.pullString("method");
+        Map<String,?> properties = parseMetadataAndClose(element, methodName);
+        final Identifier id      = toIdentifier(properties.remove(IdentifiedObject.IDENTIFIERS_KEY)); // See NOTE 2.
+        /*
+         * The map projection method may be specified by an EPSG identifier (or any other authority),
+         * which is preferred to the method name since the later is potentially ambiguous. However not
+         * all CoordinateOperationFactory may accept identifier as an argument to 'getOperationMethod'.
+         * So if an identifier is present, we will try to use it but fallback on the name if we can
+         * not use the identifier.
+         */
+        OperationMethod  method     = null;
+        FactoryException suppressed = null;
+        if (id != null) try {
+            // CodeSpace is a mandatory attribute in ID[…] elements, so we do not test for null values.
+            method = opFactory.getOperationMethod(id.getCodeSpace() + DefaultNameSpace.DEFAULT_SEPARATOR + id.getCode());
+        } catch (FactoryException e) {
+            suppressed = e;
         }
-        final String classification = element.pullString("name");
         /*
-         * Set the list of parameters. NOTE: Parameters are defined in the parent
-         * Element (usually a "PROJCS" element), not in this "PROJECTION" element.
+         * Set the list of parameters.
+         *
+         * NOTE 1: Parameters are defined in the parent element (usually a "ProjectedCRS" element
+         *         in WKT 1 or a "Conversion" element in WKT 2), not in this "Method" element.
+         *
+         * NOTE 2: We may inherit the OperationMethod name if there is no Conversion wrapper with its own name,
+         *         but we shall not inherit the OperationMethod identifier. This is the reason why we invoked
+         *         properties.remove(IdentifiedObject.IDENTIFIERS_KEY)) above.
          */
         try {
-            final OperationMethod method = opFactory.getOperationMethod(classification);
+            if (method == null) {
+                method = opFactory.getOperationMethod(methodName);
+            }
             final ParameterValueGroup parameters = method.getParameters().createValue();
             parseParameters(parent, parameters, linearUnit, angularUnit);
-            return opFactory.createDefiningConversion(parseMetadataAndClose(element, classification), method, parameters);
+            if (!isWKT1) {
+                properties = parseMetadataAndClose(parent, name);
+                /*
+                 * DEPARTURE FROM ISO 19162: the specification in §9.3.2 said:
+                 *
+                 *     "If an identifier is provided as an attribute within the <map projection conversion> object,
+                 *     because it is expected to describe a complete collection of zone name, method, parameters and
+                 *     parameter values, it shall override any identifiers given within the map projection method and
+                 *     map projection parameter objects."
+                 *
+                 * However this would require this GeodeticObjectParser to hold a CoordinateOperationAuthorityFactory,
+                 * which we do not yet implement. See https://issues.apache.org/jira/browse/SIS-163
+                 */
+            }
+            return opFactory.createDefiningConversion(properties, method, parameters);
         } catch (FactoryException exception) {
+            if (suppressed != null) {
+//              exception.addSuppressed(suppressed);    // Not available on JDK6.
+            }
             throw element.parseFailed(exception);
         }
     }
@@ -1158,16 +1235,11 @@ final class GeodeticObjectParser extends
         }
         final String             name       = element.pullString("name");
         final Ellipsoid          ellipsoid  = parseEllipsoid(MANDATORY, element);
-        final Element            anchor     = element.pullElement(OPTIONAL, WKTKeywords.Anchor);
         final Object             toWGS84    = parseToWGS84(OPTIONAL, element);
-        final Map<String,Object> properties = parseMetadataAndClose(element, name);
+        final Map<String,Object> properties = parseAnchorAndClose(element, name);
         if (meridian == null) {
             meridian = referencing.getGreenwich();
         }
-        if (anchor != null) {
-            properties.put(GeodeticDatum.ANCHOR_POINT_KEY, anchor.pullString("anchorDefinition"));
-            anchor.close(ignoredElements);
-        }
         if (toWGS84 != null) {
             properties.put(ReferencingServices.BURSA_WOLF_KEY, toWGS84);
         }
@@ -1179,28 +1251,41 @@ final class GeodeticObjectParser extends
     }
 
     /**
-     * Parses a {@code "VERT_DATUM"} element. This element has the following pattern:
+     * Parses a {@code "VerticalDatum"} (WKT 2) element. The syntax is given by
+     * <a href="http://docs.opengeospatial.org/is/12-063r5/12-063r5.html#71">WKT 2 specification §10.2</a>.
+     *
+     * The legacy WKT 1 pattern was:
      *
      * {@preformat text
      *     VERT_DATUM["<name>", <datum type> {,<authority>}]
      * }
      *
-     * @param  mode {@link #FIRST}, {@link #OPTIONAL} or {@link #MANDATORY}.
+     * @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.
+     * @param  isWKT1 {@code true} if the parent is a WKT 1 element.
+     * @return The {@code "VerticalDatum"} element as a {@link VerticalDatum} object.
+     * @throws ParseException if the {@code "VerticalDatum"} element can not be parsed.
      */
-    private VerticalDatum parseVerticalDatum(final int mode, final Element parent) throws ParseException {
-        final Element element = parent.pullElement(mode, WKTKeywords.VerticalDatum, WKTKeywords.Vert_Datum);
+    private VerticalDatum parseVerticalDatum(final int mode, final Element parent, final boolean isWKT1)
+            throws ParseException
+    {
+        final Element element = parent.pullElement(mode,
+                WKTKeywords.VerticalDatum,
+                WKTKeywords.VDatum,
+                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.
+        final String name = element.pullString("name");
+        VerticalDatumType type = null;
+        if (isWKT1) {
+            type = VerticalDatumTypes.fromLegacy(element.pullInteger("datum"));
+        }
+        if (type == null) {
+            type = VerticalDatumTypes.guess(name, null, null);
+        }
         try {
-            return datumFactory.createVerticalDatum(parseMetadataAndClose(element, name), type);
+            return datumFactory.createVerticalDatum(parseAnchorAndClose(element, name), type);
         } catch (FactoryException exception) {
             throw element.parseFailed(exception);
         }
@@ -1219,7 +1304,7 @@ final class GeodeticObjectParser extends
      * @throws ParseException if the {@code "TimeDatum"} element can not be parsed.
      */
     private TemporalDatum parseTimeDatum(final int mode, final Element parent) throws ParseException {
-        final Element element = parent.pullElement(mode, WKTKeywords.TimeDatum);
+        final Element element = parent.pullElement(mode, WKTKeywords.TimeDatum, WKTKeywords.TDatum);
         if (element == null) {
             return null;
         }
@@ -1235,26 +1320,35 @@ final class GeodeticObjectParser extends
     }
 
     /**
-     * Parses a {@code "LOCAL_DATUM"} element. This element has the following pattern:
+     * Parses a {@code "EngineeringDatum"} (WKT 2) element. The syntax is given by
+     * <a href="http://docs.opengeospatial.org/is/12-063r5/12-063r5.html#76">WKT 2 specification §11.2</a>.
+     *
+     * The legacy WKT 1 pattern was:
      *
      * {@preformat text
      *     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.
+     * The datum type (WKT 1 only) is currently ignored.
      *
-     * @todo The vertical datum type is currently ignored.
+     * @param  mode   {@link #FIRST}, {@link #OPTIONAL} or {@link #MANDATORY}.
+     * @param  parent The parent element.
+     * @param  isWKT1 {@code true} if the parent is a WKT 1 element.
+     * @return The {@code "EngineeringDatum"} element as an {@link EngineeringDatum} object.
+     * @throws ParseException if the {@code "EngineeringDatum"} element can not be parsed.
      */
-    private EngineeringDatum parseEngineeringDatum(final int mode, final Element parent) throws ParseException {
-        final Element element = parent.pullElement(mode, WKTKeywords.EngineeringDatum, WKTKeywords.Local_Datum);
+    private EngineeringDatum parseEngineeringDatum(final int mode, final Element parent, final boolean isWKT1) throws ParseException {
+        final Element element = parent.pullElement(mode,
+                WKTKeywords.EngineeringDatum,
+                WKTKeywords.EDatum,
+                WKTKeywords.Local_Datum);
         if (element == null) {
             return null;
         }
         final String name  = element.pullString ("name");
-        final int    datum = element.pullInteger("datum");   // Ignored for now.
+        if (isWKT1) {
+            element.pullInteger("datum");   // Ignored for now.
+        }
         try {
             return datumFactory.createEngineeringDatum(parseMetadataAndClose(element, name));
         } catch (FactoryException exception) {
@@ -1263,8 +1357,10 @@ final class GeodeticObjectParser extends
     }
 
     /**
-     * Parses a {@code "LOCAL_CS"} element.
-     * This element has the following pattern:
+     * Parses a {@code "EngineeringCRS"} (WKT 2) element. The syntax is given by
+     * <a href="http://docs.opengeospatial.org/is/12-063r5/12-063r5.html#74">WKT 2 specification §11</a>.
+     *
+     * The legacy WKT 1 pattern was:
      *
      * {@preformat text
      *     LOCAL_CS["<name>", <local datum>, <unit>, <axis>, {,<axis>}* {,<authority>}]
@@ -1272,19 +1368,24 @@ final class GeodeticObjectParser extends
      *
      * @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.
+     * @return The {@code "EngineeringCRS"} element as an {@link EngineeringCRS} object.
+     * @throws ParseException if the {@code "EngineeringCRS"} element can not be parsed.
      */
     private EngineeringCRS parseEngineeringCRS(final int mode, final Element parent) throws ParseException {
-        final Element element = parent.pullElement(mode, WKTKeywords.EngineeringCRS, WKTKeywords.Local_CS);
+        final Element element = parent.pullElement(mode,
+                WKTKeywords.EngineeringCRS,   // [0]  WKT 2
+                WKTKeywords.EngCRS,           // [1]  WKT 2
+                WKTKeywords.Local_CS,         // [2]  WKT 1
+                WKTKeywords.BaseEngCRS);      // [3]  WKT 2 in DerivedCRS
         if (element == null) {
             return null;
         }
-        final String           name  = element.pullString("name");
-        final EngineeringDatum datum = parseEngineeringDatum(MANDATORY, element);
-        final Unit<?>          unit  = parseUnit(element);
+        final boolean          isWKT1 = element.getKeywordIndex() == 2;  // Index of "Local_CS" above.
+        final String           name   = element.pullString("name");
+        final EngineeringDatum datum  = parseEngineeringDatum(MANDATORY, element, isWKT1);
+        final Unit<?>          unit   = parseUnit(element);
         try {
-            final CoordinateSystem cs = parseCoordinateSystem(element, null, 1, unit, datum);
+            final CoordinateSystem cs = parseCoordinateSystem(element, null, 1, isWKT1, unit, datum);
             return crsFactory.createEngineeringCRS(parseMetadataAndClose(element, name), datum, cs);
         } catch (FactoryException exception) {
             throw element.parseFailed(exception);
@@ -1307,44 +1408,59 @@ final class GeodeticObjectParser extends
      *     GEOCCS["<name>", <datum>, <prime meridian>, <linear unit> {,<axis> ,<axis> ,<axis>} {,<authority>}]
      * }
      *
-     * @param  mode {@link #FIRST}, {@link #OPTIONAL} or {@link #MANDATORY}.
+     * @param  mode   {@link #FIRST}, {@link #OPTIONAL} or {@link #MANDATORY}.
      * @param  parent The parent element.
-     * @return The {@code "GeodeticCRS"} element as a {@link GeocentricCRS} object.
+     * @param  csType The default coordinate system type, or {@code null} if unknown.
+     *                Should be non-null only when parsing a {@link GeneralDerivedCRS#getBaseCRS()} component.
+     * @return The {@code "GeodeticCRS"} element as a {@link GeographicCRS} or {@link GeocentricCRS} object.
      * @throws ParseException if the {@code "GeodeticCRS"} element can not be parsed.
      *
      * @see org.apache.sis.referencing.crs.DefaultGeographicCRS#formatTo(Formatter)
      * @see org.apache.sis.referencing.crs.DefaultGeocentricCRS#formatTo(Formatter)
      */
-    private GeodeticCRS parseGeodeticCRS(final int mode, final Element parent) throws ParseException {
+    private GeodeticCRS parseGeodeticCRS(final int mode, final Element parent, String csType) throws ParseException {
         final Element element = parent.pullElement(mode,
                 WKTKeywords.GeodeticCRS,    // [0]  WKT 2
                 WKTKeywords.GeodCRS,        // [1]  WKT 2
-                WKTKeywords.BaseGeodCRS,    // [2]  WKT 2
-                WKTKeywords.GeogCS,         // [3]  WKT 1
-                WKTKeywords.GeocCS);        // [4]  WKT 1
+                WKTKeywords.GeogCS,         // [2]  WKT 1
+                WKTKeywords.GeocCS,         // [3]  WKT 1
+                WKTKeywords.BaseGeodCRS);   // [4]  WKT 2 in ProjectedCRS or DerivedCRS
         if (element == null) {
             return null;
         }
-        final String      csType;
+        final boolean     isWKT1;
         final Unit<Angle> angularUnit;
-        final Unit<?>     defaultUnit;
+              Unit<?>     defaultUnit;
         final int         dimension;
         switch (element.getKeywordIndex()) {
             default: {      // WKT2 element.
-                csType      = null;
+                isWKT1      = false;
                 defaultUnit = parseUnit(element);
                 angularUnit = Units.isAngular(defaultUnit) ? defaultUnit.asType(Angle.class) : NonSI.DEGREE_ANGLE;
                 dimension   = 2;
+                if (defaultUnit == null) {
+                    /*
+                     * A UNIT[…] is mandatory either in the CoordinateSystem as a whole (defaultUnit != null),
+                     * or inside each AXIS[…] component (defaultUnit == null). An exception to this rule is when
+                     * parsing a BaseGeodCRS inside a ProjectedCRS or DerivedCRS, in which case axes are omitted.
+                     * We recognize those cases by a non-null 'csType' given in argument to this method.
+                     */
+                    if (WKTKeywords.ellipsoidal.equals(csType)) {
+                        defaultUnit = NonSI.DEGREE_ANGLE;   // For BaseGeodCRS in ProjectedCRS.
+                    }
+                }
                 break;
             }
-            case 3: {       // WKT1 "GeogCS" element.
+            case 2: {       // WKT1 "GeogCS" element.
+                isWKT1      = true;
                 csType      = WKTKeywords.ellipsoidal;
                 angularUnit = parseScaledUnit(element, WKTKeywords.AngleUnit, SI.RADIAN);
                 defaultUnit = angularUnit;
                 dimension   = 2;
                 break;
             }
-            case 4: {       // WKT1 "GeocCS" element.
+            case 3: {       // WKT1 "GeocCS" element.
+                isWKT1      = true;
                 csType      = WKTKeywords.Cartesian;
                 angularUnit = NonSI.DEGREE_ANGLE;
                 defaultUnit = parseScaledUnit(element, WKTKeywords.LengthUnit, SI.METRE);
@@ -1353,7 +1469,7 @@ final class GeodeticObjectParser extends
             }
         }
         Object              name     = element.pullString("name");
-        final PrimeMeridian meridian = parsePrimeMeridian(OPTIONAL, element, angularUnit);
+        final PrimeMeridian meridian = parsePrimeMeridian(OPTIONAL, element, isWKT1, angularUnit);
         final GeodeticDatum datum    = parseDatum(MANDATORY, element, meridian);
         if (((String) name).isEmpty()) {
             /*
@@ -1365,7 +1481,7 @@ final class GeodeticObjectParser extends
         }
         final CoordinateSystem cs;
         try {
-            cs = parseCoordinateSystem(element, csType, dimension, defaultUnit, datum);
+            cs = parseCoordinateSystem(element, csType, dimension, isWKT1, defaultUnit, datum);
             final Map<String,?> properties = parseMetadataAndClose(element, name);
             if (cs instanceof EllipsoidalCS) {
                 return crsFactory.createGeographicCRS(properties, datum, (EllipsoidalCS) cs);
@@ -1385,8 +1501,10 @@ final class GeodeticObjectParser extends
     }
 
     /**
-     * Parses an <strong>optional</strong> {@code "VERT_CS"} element.
-     * This element has the following pattern:
+     * Parses a {@code "VerticalCRS"} (WKT 2) element. The syntax is given by
+     * <a href="http://docs.opengeospatial.org/is/12-063r5/12-063r5.html#69">WKT 2 specification §10</a>.
+     *
+     * The legacy WKT 1 pattern was:
      *
      * {@preformat text
      *     VERT_CS["<name>", <vert datum>, <linear unit>, {<axis>,} {,<authority>}]
@@ -1394,19 +1512,35 @@ final class GeodeticObjectParser extends
      *
      * @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.
+     * @return The {@code "VerticalCRS"} element as a {@link VerticalCRS} object.
+     * @throws ParseException if the {@code "VerticalCRS"} element can not be parsed.
      */
     private VerticalCRS parseVerticalCRS(final int mode, final Element parent) throws ParseException {
-        final Element element = parent.pullElement(mode, WKTKeywords.VerticalCRS, WKTKeywords.Vert_CS);
+        final Element element = parent.pullElement(mode,
+                WKTKeywords.VerticalCRS,    // [0]  WKT 2
+                WKTKeywords.VertCRS,        // [1]  WKT 2
+                WKTKeywords.Vert_CS,        // [2]  WKT 1
+                WKTKeywords.BaseVertCRS);   // [3]  WKT 2 in DerivedCRS
         if (element == null) {
             return null;
         }
-        final String        name       = element.pullString("name");
-        final VerticalDatum datum      = parseVerticalDatum(MANDATORY, element);
-        final Unit<Length>  linearUnit = parseScaledUnit(element, WKTKeywords.LengthUnit, SI.METRE);
+        final boolean isWKT1 = element.getKeywordIndex() == 2;  // Index of "Vert_CS" above.
+        final String  name   = element.pullString("name");
+        VerticalDatum datum  = parseVerticalDatum(MANDATORY, element, isWKT1);
+        final Unit<?> unit   = parseUnit(element);
         try {
-            final CoordinateSystem cs = parseCoordinateSystem(element, WKTKeywords.vertical, 1, linearUnit, datum);
+            final CoordinateSystem cs = parseCoordinateSystem(element, WKTKeywords.vertical, 1, isWKT1, unit, datum);
+            /*
+             * The 'parseVerticalDatum(…)' method may have been unable to resolve the datum type.
+             * But sometime the axis (which was not available when we created the datum) provides
+             * more information. Verify if we can have a better type now, and if so rebuild the datum.
+             */
+            if (VerticalDatumType.OTHER_SURFACE.equals(datum.getVerticalDatumType())) {
+                final VerticalDatumType type = VerticalDatumTypes.guess(datum.getName().getCode(), datum.getAlias(), cs.getAxis(0));
+                if (!VerticalDatumType.OTHER_SURFACE.equals(type)) {
+                    datum = datumFactory.createVerticalDatum(referencing.getProperties(datum), type);
+                }
+            }
             verticalCRS = crsFactory.createVerticalCRS(parseMetadataAndClose(element, name), datum, (VerticalCS) cs);
             /*
              * Some DefaultVerticalExtent objects may be waiting for the VerticalCRS before to complete
@@ -1422,15 +1556,15 @@ final class GeodeticObjectParser extends
     }
 
     /**
-     * Parses an <strong>optional</strong> {@code "TIMECRS"} element.
+     * Parses {@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.
+     * @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 int mode, final Element parent) throws ParseException {
-        final Element element = parent.pullElement(mode, WKTKeywords.TimeCRS);
+        final Element element = parent.pullElement(mode, WKTKeywords.TimeCRS, WKTKeywords.BaseTimeCRS);
         if (element == null) {
             return null;
         }
@@ -1438,7 +1572,7 @@ final class GeodeticObjectParser extends
         final TemporalDatum  datum    = parseTimeDatum(MANDATORY, element);
         final Unit<Duration> timeUnit = parseScaledUnit(element, WKTKeywords.TimeUnit, SI.SECOND);
         try {
-            final CoordinateSystem cs = parseCoordinateSystem(element, WKTKeywords.temporal, 1, timeUnit, datum);
+            final CoordinateSystem cs = parseCoordinateSystem(element, WKTKeywords.temporal, 1, false, timeUnit, datum);
             return crsFactory.createTemporalCRS(parseMetadataAndClose(element, name), datum, (TimeCS) cs);
         } catch (FactoryException exception) {
             throw element.parseFailed(exception);
@@ -1446,8 +1580,10 @@ final class GeodeticObjectParser extends
     }
 
     /**
-     * Parses a {@code "PROJCS"} element.
-     * This element has the following pattern:
+     * Parses a {@code "ProjectedCRS"} (WKT 2) element. The syntax is given by
+     * <a href="http://docs.opengeospatial.org/is/12-063r5/12-063r5.html#57">WKT 2 specification §9</a>.
+     *
+     * The legacy WKT 1 specification was:
      *
      * {@preformat text
      *     PROJCS["<name>", <geographic cs>, <projection>, {<parameter>,}*,
@@ -1456,26 +1592,31 @@ final class GeodeticObjectParser extends
      *
      * @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.
+     * @return The {@code "ProjectedCRS"} element as a {@link ProjectedCRS} object.
+     * @throws ParseException if the {@code "ProjectedCRS"} element can not be parsed.
      */
     private ProjectedCRS parseProjectedCRS(final int mode, final Element parent) throws ParseException {
-        final Element element = parent.pullElement(mode, WKTKeywords.ProjCS);
+        final Element element = parent.pullElement(mode,
+                WKTKeywords.ProjectedCRS,   // [0]  WKT 2
+                WKTKeywords.ProjCRS,        // [1]  WKT 2
+                WKTKeywords.ProjCS,         // [2]  WKT 1
+                WKTKeywords.BaseProjCRS);   // [3]  WKT 2 in DerivedCRS
         if (element == null) {
             return null;
         }
+        final boolean     isWKT1 = element.getKeywordIndex() == 2;  // Index of "ProjCS" above.
         final String      name   = element.pullString("name");
-        final GeodeticCRS geoCRS = parseGeodeticCRS(MANDATORY, element);
+        final GeodeticCRS geoCRS = parseGeodeticCRS(MANDATORY, element, WKTKeywords.ellipsoidal);
         if (!(geoCRS instanceof GeographicCRS)) {
             throw new LocalizedParseException(errorLocale, Errors.Keys.IllegalCRSType_1,
                     new Object[] {geoCRS.getClass()}, element.offset);
         }
         final Unit<Length> linearUnit = parseScaledUnit(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));
+        final boolean ignoreUnits = isWKT1 && usesCommonUnits;
+        final Conversion conversion = parseConversion(MANDATORY, element, isWKT1, ignoreUnits ? SI.METRE : linearUnit,
+                ignoreUnits ? NonSI.DEGREE_ANGLE : geoCRS.getCoordinateSystem().getAxis(0).getUnit().asType(Angle.class));
         try {
-            final CoordinateSystem cs = parseCoordinateSystem(element, WKTKeywords.Cartesian, 2, linearUnit, geoCRS.getDatum());
+            final CoordinateSystem cs = parseCoordinateSystem(element, WKTKeywords.Cartesian, 2, isWKT1, linearUnit, geoCRS.getDatum());
             return crsFactory.createProjectedCRS(parseMetadataAndClose(element, name), (GeographicCRS) geoCRS, conversion, (CartesianCS) cs);
         } catch (FactoryException exception) {
             throw element.parseFailed(exception);

Modified: sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/MathTransformParser.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/MathTransformParser.java?rev=1688485&r1=1688484&r2=1688485&view=diff
==============================================================================
--- sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/MathTransformParser.java [UTF-8] (original)
+++ sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/MathTransformParser.java [UTF-8] Tue Jun 30 16:58:20 2015
@@ -20,6 +20,7 @@ import java.util.Locale;
 import java.text.DateFormat;
 import java.text.NumberFormat;
 import java.text.ParseException;
+import javax.measure.unit.SI;
 import javax.measure.unit.Unit;
 import javax.measure.unit.UnitFormat;
 import javax.measure.quantity.Angle;
@@ -30,6 +31,7 @@ import org.opengis.parameter.ParameterVa
 import org.opengis.parameter.ParameterValueGroup;
 import org.opengis.parameter.ParameterDescriptor;
 import org.opengis.parameter.ParameterNotFoundException;
+import org.opengis.parameter.InvalidParameterValueException;
 import org.opengis.referencing.operation.SingleOperation;
 import org.opengis.referencing.operation.MathTransform;
 import org.opengis.referencing.operation.MathTransformFactory;
@@ -39,6 +41,7 @@ import org.apache.sis.internal.system.De
 import org.apache.sis.internal.metadata.WKTKeywords;
 import org.apache.sis.internal.metadata.ReferencingServices;
 import org.apache.sis.internal.util.LocalizedParseException;
+import org.apache.sis.internal.util.Constants;
 import org.apache.sis.measure.Units;
 import org.apache.sis.util.Numbers;
 import org.apache.sis.util.resources.Errors;
@@ -61,6 +64,28 @@ import static org.apache.sis.util.Argume
  */
 class MathTransformParser extends AbstractParser {
     /**
+     * The keywords for {@code ID} or {@code AUTHORITY} elements, as a static array because frequently used.
+     */
+    static final String[] ID_KEYWORDS = {WKTKeywords.Id, WKTKeywords.Authority};
+
+    /**
+     * 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 factory to use for creating math transforms.
      */
     final MathTransformFactory mtFactory;
@@ -153,6 +178,73 @@ class MathTransformParser extends Abstra
     }
 
     /**
+     * Parses the {@code ID["authority", "code"]} element inside a {@code UNIT} element.
+     * If such element is found, the authority is {@code "EPSG"} and the code is one of
+     * the codes known to the {@link Units#valueOfEPSG(int)}, then that unit is returned.
+     * Otherwise this method returns null.
+     *
+     * <div class="note"><b>Note:</b>
+     * this method is a slight departure of ISO 19162, which said <cite>"Should any attributes or values given
+     * in the cited identifier be in conflict with attributes or values given explicitly in the WKT description,
+     * the WKT values shall prevail."</cite> But some units can hardly be expressed by the {@code UNIT} element,
+     * because the later can contain only a conversion factor. For example sexagesimal units (EPSG:9108, 9110
+     * and 9111) can hardly be expressed in an other way than by their EPSG code. Thankfully, identifiers in
+     * {@code UNIT} elements are rare, so risk of conflicts should be low.</div>
+     *
+     * @param  parent The parent {@code "UNIT"} element.
+     * @return The unit from the identifier code, or {@code null} if none.
+     * @throws ParseException if the {@code "ID"} can not be parsed.
+     */
+    final Unit<?> parseUnitID(final Element parent) throws ParseException {
+        final Element element = parent.pullElement(OPTIONAL, ID_KEYWORDS);
+        if (element != null) {
+            final String codeSpace = element.pullString("codeSpace");
+            final Object code      = element.pullObject("code");   // Accepts Integer as well as String.
+            element.close(ignoredElements);
+            if (Constants.EPSG.equalsIgnoreCase(codeSpace)) try {
+                final int n;
+                if (Numbers.isInteger(code.getClass())) {
+                    n = ((Number) code).intValue();
+                } else {
+                    n = Integer.parseInt(code.toString());
+                }
+                return Units.valueOfEPSG(n);
+            } catch (NumberFormatException e) {
+                warning(parent, element, e);
+            }
+        }
+        return null;
+    }
+
+    /**
+     * 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.
+     */
+    final 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;
+        final Unit<?> unit   = parseUnitID(element);
+        element.close(ignoredElements);
+        if (unit != null) {
+            return unit;
+        }
+        if (index >= 0 && index < BASE_UNITS.length) {
+            return Units.multiply(BASE_UNITS[index], factor);
+        }
+        // If we can not infer the base type, we have to rely on the name.
+        return parseUnit(name);
+    }
+
+    /**
      * Parses a sequence of {@code "PARAMETER"} elements.
      *
      * @param  element     The parent element containing the parameters to parse.
@@ -167,34 +259,48 @@ class MathTransformParser extends Abstra
         Element param = element;
         try {
             while ((param = element.pullElement(OPTIONAL, WKTKeywords.Parameter)) != null) {
-                final String                 name       = param.pullString("name");
+                final String name = param.pullString("name");
+                Unit<?> unit = parseUnit(param);
+                param.pullElement(OPTIONAL, ID_KEYWORDS);
+                /*
+                 * DEPARTURE FROM ISO 19162: the specification recommends that we use the identifier instead
+                 * than the parameter name. However we do not yet have a "get parameter by ID" in Apache SIS
+                 * or in GeoAPI interfaces. This was not considered necessary since SIS is lenient (hopefully
+                 * without introducing ambiguity) regarding parameter names, but we may revisit in a future
+                 * version if it become no longer the case. See https://issues.apache.org/jira/browse/SIS-163
+                 */
                 final ParameterValue<?>      parameter  = parameters.parameter(name);
                 final ParameterDescriptor<?> descriptor = parameter.getDescriptor();
                 final Class<?>               valueClass = descriptor.getValueClass();
-                if (Number.class.isAssignableFrom(valueClass)) {
-                    Unit<?> unit = descriptor.getUnit();
+                final boolean                isNumeric  = Number.class.isAssignableFrom(valueClass);
+                if (isNumeric && unit == null) {
+                    unit = descriptor.getUnit();
                     if (Units.isLinear(unit)) {
                         unit = linearUnit;
                     } else if (Units.isAngular(unit)) {
                         unit = angularUnit;
                     }
-                    if (unit != null) {
-                        parameter.setValue(param.pullDouble("value"), unit);
-                    } else if (Numbers.isInteger(valueClass)) {
-                        parameter.setValue(param.pullInteger("value"));
+                }
+                if (unit != null) {
+                    parameter.setValue(param.pullDouble("doubleValue"), unit);
+                } else if (isNumeric) {
+                    if (Numbers.isInteger(valueClass)) {
+                        parameter.setValue(param.pullInteger("intValue"));
                     } else {
-                        parameter.setValue(param.pullDouble("value"));
+                        parameter.setValue(param.pullDouble("doubleValue"));
                     }
                 } else if (valueClass == Boolean.class) {
-                    parameter.setValue(param.pullBoolean("value"));
+                    parameter.setValue(param.pullBoolean("booleanValue"));
                 } else {
-                    parameter.setValue(param.pullString("value"));
+                    parameter.setValue(param.pullString("stringValue"));
                 }
                 param.close(ignoredElements);
             }
-        } catch (ParameterNotFoundException exception) {
+        } catch (ParameterNotFoundException e) {
             throw (ParseException) new LocalizedParseException(errorLocale, Errors.Keys.UnexpectedParameter_1,
-                    new String[] {exception.getParameterName()}, param.offset).initCause(exception);
+                    new String[] {e.getParameterName()}, param.offset).initCause(e);
+        } catch (InvalidParameterValueException e) {
+            throw (ParseException) new ParseException(e.getLocalizedMessage(), param.offset).initCause(e);
         }
     }
 

Modified: sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/Transliterator.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/Transliterator.java?rev=1688485&r1=1688484&r2=1688485&view=diff
==============================================================================
--- sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/Transliterator.java [UTF-8] (original)
+++ sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/Transliterator.java [UTF-8] Tue Jun 30 16:58:20 2015
@@ -16,6 +16,8 @@
  */
 package org.apache.sis.io.wkt;
 
+import java.util.Map;
+import java.util.HashMap;
 import java.io.Serializable;
 import org.opengis.referencing.cs.PolarCS;
 import org.opengis.referencing.cs.SphericalCS;
@@ -99,6 +101,25 @@ public abstract class Transliterator imp
     private static final long serialVersionUID = 7115456393795045932L;
 
     /**
+     * Default names to associate to axis directions in a Cartesian coordinate system.
+     * Those names do not apply to other kind of coordinate systems.
+     *
+     * <p>For thread safety reasons, this map shall not be modified after construction.</p>
+     */
+    private static final Map<AxisDirection,String> CARTESIAN;
+    static {
+        final Map<AxisDirection,String> m = new HashMap<AxisDirection,String>(12);
+        m.put(AxisDirection.EAST,         AxisNames.EASTING);
+        m.put(AxisDirection.WEST,         AxisNames.WESTING);
+        m.put(AxisDirection.NORTH,        AxisNames.NORTHING);
+        m.put(AxisDirection.SOUTH,        AxisNames.SOUTHING);
+        m.put(AxisDirection.GEOCENTRIC_X, AxisNames.GEOCENTRIC_X);
+        m.put(AxisDirection.GEOCENTRIC_Y, AxisNames.GEOCENTRIC_Y);
+        m.put(AxisDirection.GEOCENTRIC_Z, AxisNames.GEOCENTRIC_Z);
+        CARTESIAN = m;
+    }
+
+    /**
      * A transliterator compliant with ISO 19162 on a <cite>"best effort"</cite> basis.
      * All methods perform the default implementation documented in this {@code Transliterator} class.
      */
@@ -193,10 +214,18 @@ public abstract class Transliterator imp
      *       (case insensitive) by <cite>“Geodetic longitude”</cite> or <cite>“Spherical longitude”</cite>,
      *       depending on whether the axis is part of an ellipsoidal or spherical CS respectively.</li>
      *   <li>Return <cite>“Geocentric X”</cite>, <cite>“Geocentric Y”</cite> and <cite>“Geocentric Z”</cite>
-     *       for {@link AxisDirection#GEOCENTRIC_X}, {@code GEOCENTRIC_Y} and {@code GEOCENTRIC_Z} respectively
-     *       in a Cartesian CS, if the given axis name is only an abbreviation.</li>
+     *       for {@link AxisDirection#GEOCENTRIC_X}, {@link AxisDirection#GEOCENTRIC_Y GEOCENTRIC_Y}
+     *       and {@link AxisDirection#GEOCENTRIC_Z GEOCENTRIC_Z} respectively in a Cartesian CS,
+     *       if the given axis name is only an abbreviation.</li>
+     *   <li>Use unique camel-case names for axis names defined by ISO 19111 and ISO 19162. For example this method
+     *       replaces <cite>“<b>e</b>llipsoidal height”</cite> by <cite>“<b>E</b>llipsoidal height”</cite>.</li>
      * </ul>
      *
+     * <div class="note"><b>Rational:</b>
+     * Axis names are not really free text. They are specified by ISO 19111 and ISO 19162.
+     * SIS does not put restriction on axis names, but we nevertheless try to use a unique
+     * name when we recognize it.</div>
+     *
      * @param  csType    The type of the coordinate system, or {@code null} if unknown.
      * @param  direction The parsed axis direction.
      * @param  name      The parsed axis abbreviation, to be eventually replaced by this method.
@@ -214,13 +243,14 @@ public abstract class Transliterator imp
             }
             else if (csType.equals(WKTKeywords.Cartesian)) {
                 if (name.length() <= 1) {
-                    if      (direction.equals(AxisDirection.GEOCENTRIC_X)) return AxisNames.GEOCENTRIC_X;
-                    else if (direction.equals(AxisDirection.GEOCENTRIC_Y)) return AxisNames.GEOCENTRIC_Y;
-                    else if (direction.equals(AxisDirection.GEOCENTRIC_Z)) return AxisNames.GEOCENTRIC_Z;
+                    final String c = CARTESIAN.get(direction);
+                    if (c != null) {
+                        return c;
+                    }
                 }
             }
         }
-        return name;
+        return AxisNames.toCamelCase(name);
     }
 
     /**
@@ -345,6 +375,9 @@ public abstract class Transliterator imp
             if (condition.equals(csType)) {
                 return replacement;
             }
+        } else {
+            if (isLatLong(AxisNames.LATITUDE,  abbreviation)) return "φ";
+            if (isLatLong(AxisNames.LONGITUDE, abbreviation)) return "λ";
         }
         return abbreviation;
     }

Modified: sis/branches/JDK6/core/sis-metadata/src/test/java/org/apache/sis/io/wkt/TransliteratorTest.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK6/core/sis-metadata/src/test/java/org/apache/sis/io/wkt/TransliteratorTest.java?rev=1688485&r1=1688484&r2=1688485&view=diff
==============================================================================
--- sis/branches/JDK6/core/sis-metadata/src/test/java/org/apache/sis/io/wkt/TransliteratorTest.java [UTF-8] (original)
+++ sis/branches/JDK6/core/sis-metadata/src/test/java/org/apache/sis/io/wkt/TransliteratorTest.java [UTF-8] Tue Jun 30 16:58:20 2015
@@ -49,6 +49,8 @@ public final strictfp class Transliterat
         assertEquals("Geodetic longitude",  t.toLongAxisName("ellipsoidal", AxisDirection.EAST,  "longitude"));
         assertEquals("Spherical latitude",  t.toLongAxisName("spherical",   AxisDirection.NORTH, "lat"));
         assertEquals("Spherical longitude", t.toLongAxisName("spherical",   AxisDirection.EAST,  "long"));
+        assertEquals("Ellipsoidal height",  t.toLongAxisName("ellipsoidal", AxisDirection.UP,    "ellipsoidal_height"));
+        assertEquals("Unknown name",        t.toLongAxisName("ellipsoidal", AxisDirection.UP,    "Unknown name"));
     }
 
     /**