sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject svn commit: r1688265 - in /sis/branches/JDK8/core: sis-metadata/src/main/java/org/apache/sis/internal/metadata/ sis-metadata/src/main/java/org/apache/sis/io/wkt/ sis-referencing/src/main/java/org/apache/sis/parameter/ sis-referencing/src/test/java/org/...
Date Mon, 29 Jun 2015 17:01:51 GMT
Author: desruisseaux
Date: Mon Jun 29 17:01:50 2015
New Revision: 1688265

URL: http://svn.apache.org/r1688265
Log:
WKT 2: initial support of "ProjectedCRS" element in the WKT 2 way.

Modified:
    sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/WKTKeywords.java
    sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/AbstractParser.java
    sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/Formatter.java
    sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/GeodeticObjectParser.java
    sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/MathTransformParser.java
    sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/parameter/MatrixParametersAlphaNum.java
    sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/parameter/DefaultParameterDescriptorGroupTest.java
    sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/parameter/DefaultParameterDescriptorTest.java
    sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/parameter/DefaultParameterValueTest.java
    sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/internal/util/Constants.java
    sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/internal/util/PatchedUnitFormat.java
    sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/Units.java

Modified: sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/WKTKeywords.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/WKTKeywords.java?rev=1688265&r1=1688264&r2=1688265&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/WKTKeywords.java
[UTF-8] (original)
+++ sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/WKTKeywords.java
[UTF-8] Mon Jun 29 17:01:50 2015
@@ -150,6 +150,7 @@ public final class WKTKeywords extends S
     public static final String
             ProjectedCRS = "ProjectedCRS",
             BaseProjCRS  = "BaseProjCRS",
+            ProjCRS      = "ProjCRS",
             ProjCS       = "ProjCS";
 
     /**

Modified: sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/AbstractParser.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/AbstractParser.java?rev=1688265&r1=1688264&r2=1688265&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/AbstractParser.java
[UTF-8] (original)
+++ sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/AbstractParser.java
[UTF-8] Mon Jun 29 17:01:50 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<>();

Modified: sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/Formatter.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/Formatter.java?rev=1688265&r1=1688264&r2=1688265&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/Formatter.java
[UTF-8] (original)
+++ sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/Formatter.java
[UTF-8] Mon Jun 29 17:01:50 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.usesCommonUnits = 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/JDK8/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/GeodeticObjectParser.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/GeodeticObjectParser.java?rev=1688265&r1=1688264&r2=1688265&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/GeodeticObjectParser.java
[UTF-8] (original)
+++ sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/GeodeticObjectParser.java
[UTF-8] Mon Jun 29 17:01:50 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}
@@ -327,7 +311,7 @@ final class GeodeticObjectParser extends
             (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 = parseConversion       (FIRST, element, false, SI.METRE, NonSI.DEGREE_ANGLE))
== null)
         {
             throw element.missingOrUnknownComponent(WKTKeywords.GeodeticCRS);
         }
@@ -362,6 +346,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:
@@ -380,8 +375,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);
@@ -425,6 +420,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)'.
             }
         }
         /*
@@ -536,6 +532,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
     {
@@ -545,32 +542,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);
     }
 
     /**
@@ -1100,37 +1081,99 @@ 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);
+            }
             throw element.parseFailed(exception);
         }
     }
@@ -1311,7 +1354,7 @@ final class GeodeticObjectParser extends
      *
      * @param  mode {@link #FIRST}, {@link #OPTIONAL} or {@link #MANDATORY}.
      * @param  parent The parent element.
-     * @return The {@code "GeodeticCRS"} element as a {@link GeocentricCRS} object.
+     * @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)
@@ -1448,8 +1491,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>,}*,
@@ -1458,14 +1503,19 @@ 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.BaseProjCRS,    // [2]  WKT 2
+                WKTKeywords.ProjCS);        // [3]  WKT 1
         if (element == null) {
             return null;
         }
+        final boolean     isWKT1 = element.getKeywordIndex() == 3;  // Index of "ProjCS"
above.
         final String      name   = element.pullString("name");
         final GeodeticCRS geoCRS = parseGeodeticCRS(MANDATORY, element);
         if (!(geoCRS instanceof GeographicCRS)) {
@@ -1474,7 +1524,7 @@ final class GeodeticObjectParser extends
         }
         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,
+        final Conversion conversion = parseConversion(MANDATORY, element, isWKT1, usesCommonUnits
? SI.METRE : linearUnit,
                 usesCommonUnits ? NonSI.DEGREE_ANGLE : geoCRS.getCoordinateSystem().getAxis(0).getUnit().asType(Angle.class));
         try {
             final CoordinateSystem cs = parseCoordinateSystem(element, WKTKeywords.Cartesian,
2, linearUnit, geoCRS.getDatum());

Modified: sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/MathTransformParser.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/MathTransformParser.java?rev=1688265&r1=1688264&r2=1688265&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/MathTransformParser.java
[UTF-8] (original)
+++ sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/MathTransformParser.java
[UTF-8] Mon Jun 29 17:01:50 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/JDK8/core/sis-referencing/src/main/java/org/apache/sis/parameter/MatrixParametersAlphaNum.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/parameter/MatrixParametersAlphaNum.java?rev=1688265&r1=1688264&r2=1688265&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/parameter/MatrixParametersAlphaNum.java
[UTF-8] (original)
+++ sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/parameter/MatrixParametersAlphaNum.java
[UTF-8] Mon Jun 29 17:01:50 2015
@@ -109,7 +109,7 @@ final class MatrixParametersAlphaNum ext
          */
         if (isEPSG(indices)) {
             name = EPSGName.create(name.tip().toString()); // Put the name in EPSG namespace.
-            final int code = (indices[0] == 0 ? Constants.A0 : Constants.B0) + indices[1];
+            final int code = (indices[0] == 0 ? Constants.EPSG_A0 : Constants.EPSG_B0) +
indices[1];
             properties.put(ParameterDescriptor.IDENTIFIERS_KEY, EPSGName.identifier(code));
         }
         properties.put(ParameterDescriptor.NAME_KEY, name);

Modified: sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/parameter/DefaultParameterDescriptorGroupTest.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/parameter/DefaultParameterDescriptorGroupTest.java?rev=1688265&r1=1688264&r2=1688265&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/parameter/DefaultParameterDescriptorGroupTest.java
[UTF-8] (original)
+++ sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/parameter/DefaultParameterDescriptorGroupTest.java
[UTF-8] Mon Jun 29 17:01:50 2015
@@ -202,7 +202,7 @@ public final strictfp class DefaultParam
          * Test below is identical to DefaultParameterDescriptorTest.testIdentifiedParameterWKT(),
          * but is reproduced here for easier comparison with the test following it.
          */
-        final DefaultParameterDescriptor<Double> descriptor = DefaultParameterDescriptorTest.createEPSG("A0",
Constants.A0);
+        final DefaultParameterDescriptor<Double> descriptor = DefaultParameterDescriptorTest.createEPSG("A0",
Constants.EPSG_A0);
         assertWktEquals("Parameter[“A0”, Id[“EPSG”, 8623, URI[“urn:ogc:def:parameter:EPSG::8623”]]]",
descriptor);
         /*
          * When the parameter is part of a larger element, we expect a simplification.

Modified: sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/parameter/DefaultParameterDescriptorTest.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/parameter/DefaultParameterDescriptorTest.java?rev=1688265&r1=1688264&r2=1688265&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/parameter/DefaultParameterDescriptorTest.java
[UTF-8] (original)
+++ sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/parameter/DefaultParameterDescriptorTest.java
[UTF-8] Mon Jun 29 17:01:50 2015
@@ -322,7 +322,7 @@ public final strictfp class DefaultParam
     @Test
     @DependsOnMethod("testWKT")
     public void testIdentifiedParameterWKT() {
-        final DefaultParameterDescriptor<Double> descriptor = createEPSG("A0", Constants.A0);
+        final DefaultParameterDescriptor<Double> descriptor = createEPSG("A0", Constants.EPSG_A0);
         assertWktEquals("Parameter[“A0”, Id[“EPSG”, 8623, URI[“urn:ogc:def:parameter:EPSG::8623”]]]",
descriptor);
     }
 }

Modified: sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/parameter/DefaultParameterValueTest.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/parameter/DefaultParameterValueTest.java?rev=1688265&r1=1688264&r2=1688265&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/parameter/DefaultParameterValueTest.java
[UTF-8] (original)
+++ sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/parameter/DefaultParameterValueTest.java
[UTF-8] Mon Jun 29 17:01:50 2015
@@ -603,7 +603,7 @@ public final strictfp class DefaultParam
     @Test
     @DependsOnMethod("testWKT")
     public void testIdentifiedParameterWKT() {
-        final Watcher<Double> parameter = new Watcher<>(DefaultParameterDescriptorTest.createEPSG("A0",
Constants.A0));
+        final Watcher<Double> parameter = new Watcher<>(DefaultParameterDescriptorTest.createEPSG("A0",
Constants.EPSG_A0));
         assertWktEquals("Parameter[“A0”, null, Id[“EPSG”, 8623]]", parameter);
     }
 }

Modified: sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/internal/util/Constants.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/internal/util/Constants.java?rev=1688265&r1=1688264&r2=1688265&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/internal/util/Constants.java
[UTF-8] (original)
+++ sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/internal/util/Constants.java
[UTF-8] Mon Jun 29 17:01:50 2015
@@ -142,13 +142,30 @@ public final class Constants extends Sta
      * EPSG code of the {@code A0} coefficient used in affine (general parametric) and polynomial
transformations.
      * Codes for parameters {@code A1} to {@code A8} inclusive follow, but the affine coefficients
stop at {@code A2}.
      */
-    public static final short A0 = 8623;
+    public static final short EPSG_A0 = 8623;
 
     /**
      * EPSG code of the {@code B0} coefficient used in affine (general parametric) and polynomial
transformations.
      * Codes for parameters {@code B1} to {@code B3} inclusive follow, but the affine coefficients
stop at {@code B2}.
      */
-    public static final short B0 = 8639;
+    public static final short EPSG_B0 = 8639;
+
+    /**
+     * The EPSG code of the "degree minute second hemisphere" pseudo-unit.
+     * Different symbol sets (º ' ") may be in use as field separators.
+     * Ends with hemisphere abbreviation (single character N S E or W).
+     */
+    public static final short EPSG_DMSH = 9108;
+
+    /**
+     * The EPSG code of the "sexagesimal DMS" pseudo-unit.
+     */
+    public static final short EPSG_DMS = 9110;
+
+    /**
+     * The EPSG code of the "sexagesimal DM" pseudo-unit.
+     */
+    public static final short EPSG_DM = 9111;
 
     /**
      * Do not allow instantiation of this class.

Modified: sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/internal/util/PatchedUnitFormat.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/internal/util/PatchedUnitFormat.java?rev=1688265&r1=1688264&r2=1688265&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/internal/util/PatchedUnitFormat.java
[UTF-8] (original)
+++ sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/internal/util/PatchedUnitFormat.java
[UTF-8] Mon Jun 29 17:01:50 2015
@@ -21,8 +21,11 @@ import java.util.HashMap;
 import java.text.Format;
 import java.text.FieldPosition;
 import java.text.ParsePosition;
+import javax.measure.unit.NonSI;
+import javax.measure.unit.SI;
 import javax.measure.unit.Unit;
 import javax.measure.unit.UnitFormat;
+import org.apache.sis.measure.Units;
 import org.apache.sis.util.Workaround;
 
 
@@ -78,6 +81,11 @@ public final class PatchedUnitFormat ext
     private final UnitFormat format;
 
     /**
+     * Value of {@link org.apache.sis.io.wkt.Convention#usesCommonUnits}.
+     */
+    public boolean usesCommonUnits;
+
+    /**
      * Creates a new {@code PatchedUnitFormat} instance wrapping the given format.
      *
      * @param format the format to wrap.
@@ -135,6 +143,20 @@ public final class PatchedUnitFormat ext
                 return toAppendTo.append(symbol);
             }
         }
+        /*
+         * Following are specific to the WKT format, which is currently the only user of
this method.
+         * If we invoke this method for other purposes, then we would need to provide more
control on
+         * what kind of formatting is desired.
+         */
+        if (Unit.ONE.equals(unit)) {
+            return toAppendTo.append("unity");
+        } else if (NonSI.DEGREE_ANGLE.equals(unit)) {
+            return toAppendTo.append("degree");
+        } else if (SI.METRE.equals(unit)) {
+            return toAppendTo.append(usesCommonUnits ? "meter" : "metre");
+        } else if (Units.PPM.equals(unit)) {
+            return toAppendTo.append("parts per million");
+        }
         return format.format(unit, toAppendTo, pos);
     }
 

Modified: sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/Units.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/Units.java?rev=1688265&r1=1688264&r2=1688265&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/Units.java [UTF-8]
(original)
+++ sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/Units.java [UTF-8]
Mon Jun 29 17:01:50 2015
@@ -550,11 +550,11 @@ public final class Units extends Static
             case 9103: return NonSI.MINUTE_ANGLE;
             case 9104: return NonSI.SECOND_ANGLE;
             case 9105: return NonSI.GRADE;
-            case 9107: // Fall through
-            case 9108: return SexagesimalConverter.DMS_SCALED;
             case 9109: return SI.MetricPrefix.MICRO(SI.RADIAN);
-            case 9111: return SexagesimalConverter.DM;
-            case 9110: return SexagesimalConverter.DMS;
+            case 9107: // Fall through
+            case Constants.EPSG_DMSH: return SexagesimalConverter.DMS_SCALED;
+            case Constants.EPSG_DM:   return SexagesimalConverter.DM;
+            case Constants.EPSG_DMS:  return SexagesimalConverter.DMS;
             case 9203: // Fall through
             case 9201: return Unit .ONE;
             case 9202: return Units.PPM;



Mime
View raw message