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"));
}
/**