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 2014/01/30 13:37:11 UTC

svn commit: r1562800 [2/3] - in /sis/trunk: ./ core/sis-referencing/src/main/java/org/apache/sis/geometry/ core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/ core/sis-referencing/src/main/java/org/apache/sis/io/wkt/ core/sis-refere...

Modified: sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/io/wkt/WKTFormat.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/io/wkt/WKTFormat.java?rev=1562800&r1=1562799&r2=1562800&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/io/wkt/WKTFormat.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/io/wkt/WKTFormat.java [UTF-8] Thu Jan 30 12:37:10 2014
@@ -16,8 +16,71 @@
  */
 package org.apache.sis.io.wkt;
 
+import java.text.Format;
+import java.text.FieldPosition;
+import java.text.ParsePosition;
+import org.opengis.metadata.citation.Citation;
+import org.opengis.parameter.GeneralParameterValue;
+import org.opengis.referencing.IdentifiedObject;
+import org.opengis.referencing.operation.Matrix;
+import org.opengis.referencing.operation.MathTransform;
+import org.apache.sis.util.ArgumentChecks;
+import org.apache.sis.util.resources.Errors;
+
+
+/**
+ * Parser and formatter for <cite>Well Known Text</cite> (WKT) objects.
+ * This format handles a pair of {@link Parser} and {@link Formatter},
+ * to be used by {@code parse} and {@code format} methods respectively.
+ * {@code WKTFormat} objects allow the following configuration:
+ *
+ * <ul>
+ *   <li>The {@linkplain Symbols symbols} to use (curly braces or brackets, <i>etc.</i>)</li>
+ *   <li>The preferred authority of {@linkplain IdentifiedObject#getName() object name} to
+ *       format (see {@link Formatter#getName(IdentifiedObject)} for more information)</li>
+ *   <li>Whatever ANSI X3.64 colors are allowed or not (default is not)</li>
+ *   <li>The indentation</li>
+ * </ul>
+ *
+ * {@section String expansion}
+ * Because the strings to be parsed by this class are long and tend to contain repetitive substrings,
+ * {@code WKTFormat} provides a mechanism for performing string substitutions before the parsing take place.
+ * Long strings can be assigned short names by calls to the
+ * <code>{@linkplain #definitions()}.put(<var>key</var>,<var>value</var>)</code> method.
+ * After definitions have been added, any call to a parsing method will replace all occurrences
+ * of a short name by the associated long string.
+ *
+ * <p>The short names must comply with the rules of Java identifiers. It is recommended, but not
+ * required, to prefix the names by some symbol like {@code "$"} in order to avoid ambiguity.
+ * Note however that this class doesn't replace occurrences between quoted text, so string
+ * expansion still relatively safe even when used with non-prefixed identifiers.</p>
+ *
+ * <blockquote><font size="-1"><b>Example:</b>
+ * In the example below, the {@code $WGS84} substring which appear in the argument given to the
+ * {@code parseObject(…)} method will be expanded into the full {@code GEOGCS["WGS84", …]} string
+ * before the parsing proceed.
+ *
+ * <blockquote><code>{@linkplain #definitions()}.put("$WGS84", "GEOGCS[\"WGS84\", DATUM[</code> <i>…etc…</i> <code>]]);<br>
+ * Object crs = {@linkplain #parseObject(String) parseObject}("PROJCS[\"Mercator_1SP\", <strong>$WGS84</strong>,
+ * PROJECTION[</code> <i>…etc…</i> <code>]]");</code></blockquote>
+ * </font></blockquote>
+ *
+ * {@section Thread safety}
+ * {@code WKTFormat}s are not synchronized. It is recommended to create separated format instances for each thread.
+ * If multiple threads access a {@code WKTFormat} concurrently, it must be synchronized externally.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @author  Rémi Eve (IRD)
+ * @since   0.4 (derived from geotk-3.20)
+ * @version 0.4
+ * @module
+ */
+public class WKTFormat extends Format {
+    /**
+     * For cross-version compatibility.
+     */
+    private static final long serialVersionUID = -2909110214650709560L;
 
-public class WKTFormat {
     /**
      * The indentation value to give to the {@link #setIndentation(int)}
      * method for formatting the complete object on a single line.
@@ -28,4 +91,270 @@ public class WKTFormat {
      * The default indentation value.
      */
     static final byte DEFAULT_INDENTATION = 2;
+
+    /**
+     * The symbols to use for this formatter.
+     * The same object is also referenced in the {@linkplain #parser} and {@linkplain #formatter}.
+     * It appears here for serialization purpose.
+     */
+    private Symbols symbols = Symbols.DEFAULT;
+
+    /**
+     * The colors to use for this formatter, or {@code null} for no syntax coloring.
+     * The same object is also referenced in the {@linkplain #formatter}.
+     * It appears here for serialization purpose.
+     */
+    private Colors colors;
+
+    /**
+     * The convention to use. The same object is also referenced in the {@linkplain #formatter}.
+     * It appears here for serialization purpose.
+     */
+    private Convention convention = Convention.DEFAULT;
+
+    /**
+     * The preferred authority for objects or parameter names. A {@code null} value
+     * means that the authority shall be inferred from the {@linkplain #convention}.
+     */
+    private Citation authority;
+
+    /**
+     * The amount of spaces to use in indentation, or {@value #SINGLE_LINE} if indentation is disabled.
+     * The same value is also stored in the {@linkplain #formatter}.
+     * It appears here for serialization purpose.
+     */
+    private byte indentation = DEFAULT_INDENTATION;
+
+    /**
+     * A formatter using the same symbols than the {@linkplain #parser}.
+     * Will be created by the {@link #format} method when first needed.
+     */
+    private transient Formatter formatter;
+
+    /**
+     * Constructs a format using the default factories.
+     */
+    public WKTFormat() {
+    }
+
+    /**
+     * Returns the symbols used for parsing and formatting WKT.
+     *
+     * @return The current set of symbols used for parsing and formatting WKT.
+     */
+    public Symbols getSymbols() {
+        return symbols;
+    }
+
+    /**
+     * Sets the symbols used for parsing and formatting WKT.
+     *
+     * @param symbols The new set of symbols to use for parsing and formatting WKT.
+     */
+    public void setSymbols(final Symbols symbols) {
+        ArgumentChecks.ensureNonNull("symbols", symbols);
+        if (!symbols.equals(this.symbols)) {
+            this.symbols = symbols;
+            formatter = null;
+        }
+    }
+
+    /**
+     * Returns the colors to use for syntax coloring, or {@code null} if none.
+     * By default there is no syntax coloring.
+     *
+     * @return The colors for syntax coloring, or {@code null} if none.
+     */
+    public Colors getColors() {
+        return colors;
+    }
+
+    /**
+     * Sets the colors to use for syntax coloring.
+     * This property applies only when formatting text.
+     *
+     * <p>Newly created {@code WKTFormat}s have no syntax coloring. If a non-null argument like
+     * {@link Colors#CONSOLE} is given to this method, then the {@link #format(Object) format(…)}
+     * method tries to highlight most of the elements that are relevant to
+     * {@link org.apache.sis.util.Utilities#equalsIgnoreMetadata(Object, Object)}.</p>
+     *
+     * @param colors The colors for syntax coloring, or {@code null} if none.
+     */
+    public void setColors(final Colors colors) {
+        this.colors = colors;
+        if (formatter != null) {
+            formatter.colors = colors;
+        }
+    }
+
+    /**
+     * Returns the convention for parsing and formatting WKT elements.
+     * The default value is {@link Convention#WKT2}.
+     *
+     * @return The convention to use for formatting WKT elements (never {@code null}).
+     */
+    public Convention getConvention() {
+        return convention;
+    }
+
+    /**
+     * Sets the convention for parsing and formatting WKT elements.
+     *
+     * @param convention The new convention to use for parsing and formatting WKT elements.
+     */
+    public void setConvention(final Convention convention) {
+        ArgumentChecks.ensureNonNull("convention", convention);
+        this.convention = convention;
+        updateFormatter(formatter);
+    }
+
+    /**
+     * Returns the preferred authority for choosing the projection and parameter names.
+     * If no authority has been {@link #setNameAuthority(Citation) explicitly set}, then this
+     * method returns the authority associated to the {@linkplain #getConvention() convention}.
+     *
+     * @return The authority for projection and parameter names.
+     *
+     * @see Convention#getNameAuthority()
+     * @see Formatter#getName(IdentifiedObject)
+     */
+    public Citation getNameAuthority() {
+        Citation result = authority;
+        if (result == null) {
+            result = convention.getNameAuthority();
+        }
+        return result;
+    }
+
+    /**
+     * Sets the preferred authority for choosing the projection and parameter names.
+     * If non-null, the given priority will have precedence over the authority usually
+     * associated to the {@linkplain #getConvention() convention}. A {@code null} value
+     * restore the default behavior.
+     *
+     * @param authority The new authority, or {@code null} for inferring it from the convention.
+     *
+     * @see Formatter#getName(IdentifiedObject)
+     */
+    public void setNameAuthority(final Citation authority) {
+        this.authority = authority;
+        updateFormatter(formatter);
+        // No need to update the parser.
+    }
+
+    /**
+     * Updates the formatter convention and authority according the current state of this
+     * {@code WKTFormat}. The authority may be null, in which case it will be inferred from
+     * the convention when first needed.
+     */
+    private void updateFormatter(final Formatter formatter) {
+        if (formatter != null) {
+            formatter.setConvention(convention, authority);
+        }
+    }
+
+    /**
+     * Returns the current indentation to be used for formatting objects.
+     * The {@value #SINGLE_LINE} value means that the whole WKT is to be formatted on a single line.
+     *
+     * @return The current indentation.
+     */
+    public int getIndentation() {
+        return indentation;
+    }
+
+    /**
+     * Sets a new indentation to be used for formatting objects.
+     * The {@value #SINGLE_LINE} value means that the whole WKT is to be formatted on a single line.
+     *
+     * @param indentation The new indentation to use.
+     */
+    public void setIndentation(final int indentation) {
+        ArgumentChecks.ensureBetween("indentation", WKTFormat.SINGLE_LINE, Byte.MAX_VALUE, indentation);
+        this.indentation = (byte) indentation;
+        if (formatter != null) {
+            formatter.indentation = this.indentation;
+        }
+    }
+
+    /**
+     * Not yet supported.
+     *
+     * @param  text The text to parse.
+     * @param  position The index of the first character to parse.
+     * @return The parsed object, or {@code null} in case of failure.
+     */
+    @Override
+    public Object parseObject(final String text, final ParsePosition position) {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+    /**
+     * Returns the formatter, creating it if needed.
+     */
+    private Formatter getFormatter() {
+        Formatter formatter = this.formatter;
+        if (formatter == null) {
+            formatter = new Formatter(Convention.DEFAULT, symbols, colors, indentation);
+            updateFormatter(formatter);
+            this.formatter = formatter;
+        }
+        return formatter;
+    }
+
+    /**
+     * Formats the specified object as a Well Know Text. The given object shall be an instance of one of
+     * {@link FormattableObject}, {@link IdentifiedObject}, {@link MathTransform}, {@link GeneralParameterValue}
+     * or {@link Matrix}.
+     *
+     * @param  object     The object to format.
+     * @param  toAppendTo Where the text is to be appended.
+     * @param  pos        An identification of a field in the formatted text.
+     * @return The given {@code toAppendTo} buffer.
+     *
+     * @see #getWarning()
+     */
+    @Override
+    public StringBuffer format(final Object        object,
+                               final StringBuffer  toAppendTo,
+                               final FieldPosition pos)
+    {
+        final Formatter formatter = getFormatter();
+        try {
+            formatter.clear();
+            formatter.buffer = toAppendTo;
+            formatter.bufferBase = toAppendTo.length();
+            if (object instanceof FormattableObject) {
+                formatter.append((FormattableObject) object);
+            } else if (object instanceof IdentifiedObject) {
+                formatter.append((IdentifiedObject) object);
+            } else if (object instanceof MathTransform) {
+                formatter.append((MathTransform) object);
+            } else if (object instanceof GeneralParameterValue) {
+                /*
+                 * Special processing for parameter values, which is formatted directly in 'Formatter'.
+                 * Note that this interface doesn't share the same parent interface than other interfaces.
+                 */
+                formatter.append((GeneralParameterValue) object);
+            } else if (object instanceof Matrix) {
+                formatter.append((Matrix) object);
+            } else {
+                throw new ClassCastException(Errors.format(
+                        Errors.Keys.IllegalArgumentClass_2, "object", object.getClass()));
+            }
+            return toAppendTo;
+        } finally {
+            formatter.buffer = null;
+        }
+    }
+
+    /**
+     * If a warning occurred during the last WKT {@linkplain #format formatting}, returns the warning.
+     * Otherwise returns {@code null}. The warning is cleared every time a new object is formatted.
+     *
+     * @return The last warning, or {@code null} if none.
+     */
+    public String getWarning() {
+        return (formatter != null) ? formatter.getErrorMessage() : null;
+    }
 }

Modified: sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/io/wkt/package-info.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/io/wkt/package-info.java?rev=1562800&r1=1562799&r2=1562800&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/io/wkt/package-info.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/io/wkt/package-info.java [UTF-8] Thu Jan 30 12:37:10 2014
@@ -17,49 +17,43 @@
 
 /**
  * <cite>Well Known Text</cite> (WKT) parsing and formatting.
- * This package provides the internal mechanism used by SIS implementation. Most users do not need to know about it,
- * except if they want to customize the parsing process or the WKT output. For example this package allows to:
+ * This package implements the services provided by the {@link org.apache.sis.referencing.CRS#parseWKT(String)}
+ * and {@link org.opengis.referencing.IdentifiedObject#toWKT()} convenience methods, with more control.
+ * For example this package allows to:
  *
  * <ul>
- *   <li>{@linkplain org.apache.sis.io.wkt.WKTFormat#setConvention Format the parameters using the names
- *       of an other authority than OGC}. For example a user may want to format map projections using the
- *       {@linkplain org.apache.sis.io.wkt.Convention#GEOTIFF GeoTIFF} parameter names.</li>
- *   <li>{@linkplain org.apache.sis.io.wkt.WKTFormat#setSymbols Use curly brackets instead than square ones},
- *       for example {@code DATUM("WGS84")} instead than {@code DATUM["WGS84"]}.
- *       The former is legal WKT, while less frequently used than the later one.</li>
- *   <li>{@linkplain org.apache.sis.io.wkt.WKTFormat#setColors Apply syntactic coloring} for output
- *       on terminal supporting <cite>ANSI escape codes</cite> (a.k.a. ECMA-48, ISO/IEC 6429 and X3.64).</li>
- *   <li>{@linkplain org.apache.sis.io.wkt.WKTFormat#setIndentation Use a different indentation}, or
- *       format the whole WKT on a {@linkplain org.apache.sis.io.wkt.WKTFormat#SINGLE_LINE single line}.</li>
+ *   <li>Format projection and parameters using the names of a chosen authority. For example the
+ *       "<cite>Mercator (variant A)</cite>" projection is named {@code "Mercator_1SP"} by OGC 01-009
+ *       and {@code "CT_Mercator"} by GeoTIFF.</li>
+ *   <li>Format the elements with curly brackets instead than square ones.
+ *       For example both {@code ID["EPSG",4326]} and {@code ID("EPSG",4326)} are legal WKT.</li>
+ *   <li>Format with a different indentation or format the whole WKT on a single line.</li>
+ *   <li>Apply syntactic coloring on terminal supporting <cite>ANSI escape codes</cite>
+ *       (a.k.a. ECMA-48, ISO/IEC 6429 and X3.64).</li>
+ *   <li>Ignore the {@code AXIS[…]} elements at parsing time. This approach can be used as a way to force
+ *       the (<var>longitude</var>, <var>latitude</var>) axes order.</li>
  * </ul>
  *
- * Current implementation is primarily designed for parsing and formatting referencing objects.
- * However other objects (especially the one for geometric objects) are expected to be provided
- * here in future versions.
- *
  * {@section Referencing WKT}
- * Parsing of {@linkplain org.apache.sis.referencing.crs.AbstractCRS Coordinate Reference System}
- * and {@linkplain org.apache.sis.referencing.operation.transform.AbstractMathTransform Math Transform} objects
- * are performed by the {@link org.apache.sis.io.wkt.ReferencingParser} class. The parser provides methods for:
- *
+ * Referencing WKT is defined using Extended Backus Naur Form (EBNF) in two versions:
  * <ul>
- *   <li>Specifying whatever the default axis names shall be ISO identifiers or the
- *       legacy identifiers specified in the WKT specification.</li>
- *   <li>Ignoring the {@code AXIS[...]} elements. This approach can be used as a way to force
- *       the (<var>longitude</var>, <var>latitude</var>) axes order.</li>
+ *   <li>ISO 19162 defines the current format, also known as “WKT 2”.</li>
+ *   <li>The previous format — “WKT 1” — was defined in the <a href="http://www.opengeospatial.org/standards/ct">OGC
+ *       document 01-009</a>. This definition is
+ *       <a href="http://www.geoapi.org/3.0/javadoc/org/opengis/referencing/doc-files/WKT.html">shown on GeoAPI</a>.</li>
  * </ul>
  *
- * {@section Geometry WKT}
- * The {@link org.apache.sis.geometry.GeneralEnvelope} and
- * {@link org.apache.sis.geometry.GeneralDirectPosition} classes provide their own, limited,
- * WKT parsing and formatting services for the {@code BOX} and {@code POINT} elements.
+ * The WKT 1 format has been interpreted differently by various implementors.
+ * One noticeable difference is the unit of measurement of prime meridians and projection parameters.
+ * The WKT 2 format aims to solve the inter-operability problem caused by such mismatches,
+ * but not all softwares support this new format. Consequently importing or exporting data from/to a software with
+ * the WKT syntax require knowledge of the WKT variant used by that software. This variant can be specified by the
+ * {@link org.apache.sis.io.wkt.Convention} enumeration.
  *
- * {@section References}
- * <ul>
- *   <li><a href="http://www.geoapi.org/3.0/javadoc/org/opengis/referencing/doc-files/WKT.html">Well Known Text specification</a></li>
- *   <li><a href="http://home.gdal.org/projects/opengis/wktproblems.html">OGC WKT Coordinate System Issues</a></li>
- *   <li><a href="http://en.wikipedia.org/wiki/Well-known_text">Wikipedia: Well Known Text</a></li>
- * </ul>
+ * {@section Geometry WKT}
+ * The {@link org.apache.sis.geometry.GeneralEnvelope} and {@link org.apache.sis.geometry.GeneralDirectPosition} classes
+ * provide their own, limited, WKT parsing and formatting services for the {@code BOX} and {@code POINT} elements.
+ * A description for this WKT format can be found on <a href="http://en.wikipedia.org/wiki/Well-known_text">Wikipedia</a>.
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
  * @author  Rémi Eve (IRD)

Modified: sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/AbstractIdentifiedObject.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/AbstractIdentifiedObject.java?rev=1562800&r1=1562799&r2=1562800&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/AbstractIdentifiedObject.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/AbstractIdentifiedObject.java [UTF-8] Thu Jan 30 12:37:10 2014
@@ -86,10 +86,10 @@ import org.apache.sis.internal.jdk7.Obje
  * Applications should instead instantiate the most specific subclass having a name starting by {@code Default}.
  * However exceptions to this rule may occur when it is not possible to identify the exact type.
  *
- * {@example It is sometime not possible to infer the exact coordinate system from version 1 of
- *           <a href="http://www.geoapi.org/3.0/javadoc/org/opengis/referencing/doc-files/WKT.html"><cite>Well
- *           Known Text</cite></a>, for example when parsing a <code>LOCAL_CS</code> element. In such exceptional
- *           situation, a plain <code>AbstractCS</code> object may be instantiated.}
+ * {@example It is sometime not possible to infer the exact coordinate system type from
+ *           <a href="http://www.geoapi.org/3.0/javadoc/org/opengis/referencing/doc-files/WKT.html">version 1 of
+ *           <cite>Well Known Text format</cite></a>, for example when parsing a <code>LOCAL_CS</code> element.
+ *           In such exceptional situation, a plain <code>AbstractCS</code> object may be instantiated.}
  *
  * {@code IdentifiedObject} instances are created in two main ways:
  *

Modified: sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/CRS.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/CRS.java?rev=1562800&r1=1562799&r2=1562800&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/CRS.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/CRS.java [UTF-8] Thu Jan 30 12:37:10 2014
@@ -16,28 +16,66 @@
  */
 package org.apache.sis.referencing;
 
+import java.util.Map;
 import java.util.List;
 import java.util.ArrayList;
 import java.util.Collections;
 import org.opengis.util.FactoryException;
 import org.opengis.util.NoSuchIdentifierException;
 import org.opengis.referencing.NoSuchAuthorityCodeException;
+import org.opengis.referencing.cs.EllipsoidalCS;
+import org.opengis.referencing.cs.AxisDirection;
+import org.opengis.referencing.cs.CoordinateSystem;
+import org.opengis.referencing.cs.CoordinateSystemAxis;
 import org.opengis.referencing.crs.SingleCRS;
 import org.opengis.referencing.crs.CompoundCRS;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
 import org.opengis.referencing.crs.CRSAuthorityFactory;
+import org.opengis.referencing.crs.GeographicCRS;
+import org.opengis.referencing.crs.ProjectedCRS;
+import org.opengis.referencing.crs.TemporalCRS;
+import org.opengis.referencing.crs.VerticalCRS;
+import org.opengis.metadata.extent.Extent;
+import org.opengis.metadata.extent.GeographicBoundingBox;
 import org.apache.sis.internal.util.DefinitionURI;
+import org.apache.sis.internal.referencing.AxisDirections;
 import org.apache.sis.internal.referencing.ReferencingUtilities;
+import org.apache.sis.referencing.cs.DefaultVerticalCS;
+import org.apache.sis.referencing.cs.DefaultEllipsoidalCS;
+import org.apache.sis.referencing.crs.DefaultGeographicCRS;
+import org.apache.sis.referencing.crs.DefaultVerticalCRS;
 import org.apache.sis.referencing.crs.DefaultCompoundCRS;
+import org.apache.sis.metadata.iso.extent.Extents;
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.util.CharSequences;
+import org.apache.sis.util.Utilities;
 import org.apache.sis.util.Static;
 
+import static java.util.Collections.singletonMap;
 import static org.apache.sis.util.ArgumentChecks.ensureNonNull;
 
 
 /**
  * Static methods working on {@linkplain CoordinateReferenceSystem Coordinate Reference Systems}.
+ * The methods defined in this class can be grouped in two categories:
+ *
+ * <ul>
+ *   <li>Factory methods, the most notable one being {@link #forCode(String)}.</li>
+ *   <li>Methods providing information, like {@link #isHorizontalCRS(CoordinateReferenceSystem)}.</li>
+ * </ul>
+ *
+ * {@section Note on kinds of CRS}
+ * The {@link #getSingleComponents(CoordinateReferenceSystem)} method decomposes an arbitrary CRS into a flat
+ * list of single components. In such flat list, vertical and temporal components can easily be identified by
+ * {@code instanceof} checks. But identifying the horizontal component is not as easy. The list below suggests
+ * ways to classify the components:
+ *
+ * <ul>
+ *   <li><code>if (crs instanceof TemporalCRS)</code> determines if the CRS is for the temporal component.</li>
+ *   <li><code>if (crs instanceof VerticalCRS)</code> determines if the CRS is for the vertical component.</li>
+ *   <li><code>if (CRS.{@linkplain #isHorizontalCRS(CoordinateReferenceSystem) isHorizontalCRS}(crs))</code>
+ *       determines if the CRS is for the horizontal component.</li>
+ * </ul>
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
  * @since   0.3 (derived from geotk-2.1)
@@ -59,17 +97,23 @@ public final class CRS extends Static {
      * on any installation of Apache SIS version 0.4 or above:
      *
      * <blockquote><table class="sis">
-     *   <tr><th>Code</th>      <th>Enum</th>                                  <th>CRS Type</th>   <th>Description</th></tr>
-     *   <tr><td>CRS:27</td>    <td>{@link CommonCRS#NAD27  NAD27}</td>  <td>Geographic</td> <td>Like EPSG:4267 except for (<var>longitude</var>, <var>latitude</var>) axis order</td></tr>
-     *   <tr><td>CRS:83</td>    <td>{@link CommonCRS#NAD83  NAD83}</td>  <td>Geographic</td> <td>Like EPSG:4269 except for (<var>longitude</var>, <var>latitude</var>) axis order</td></tr>
-     *   <tr><td>CRS:84</td>    <td>{@link CommonCRS#WGS84  WGS84}</td>  <td>Geographic</td> <td>Like EPSG:4326 except for (<var>longitude</var>, <var>latitude</var>) axis order</td></tr>
-     *   <tr><td>EPSG:4047</td> <td>{@link CommonCRS#SPHERE SPHERE}</td> <td>Geographic</td> <td>GRS 1980 Authalic Sphere</td></tr>
-     *   <tr><td>EPSG:4230</td> <td>{@link CommonCRS#ED50   ED50}</td>   <td>Geographic</td> <td>European Datum 1950</td></tr>
-     *   <tr><td>EPSG:4258</td> <td>{@link CommonCRS#ETRS89 ETRS89}</td> <td>Geographic</td> <td>European Terrestrial Reference Frame 1989</td></tr>
-     *   <tr><td>EPSG:4267</td> <td>{@link CommonCRS#NAD27  NAD27}</td>  <td>Geographic</td> <td>North American Datum 1927</td></tr>
-     *   <tr><td>EPSG:4269</td> <td>{@link CommonCRS#NAD83  NAD83}</td>  <td>Geographic</td> <td>North American Datum 1983</td></tr>
-     *   <tr><td>EPSG:4322</td> <td>{@link CommonCRS#WGS72  WGS72}</td>  <td>Geographic</td> <td>World Geodetic System 1972</td></tr>
-     *   <tr><td>EPSG:4326</td> <td>{@link CommonCRS#WGS84  WGS84}</td>  <td>Geographic</td> <td>World Geodetic System 1984</td></tr>
+     *   <tr><th>Code</th>      <th>Enum</th>                            <th>CRS Type</th>      <th>Description</th></tr>
+     *   <tr><td>CRS:27</td>    <td>{@link CommonCRS#NAD27  NAD27}</td>  <td>Geographic</td>    <td>Like EPSG:4267 except for (<var>longitude</var>, <var>latitude</var>) axis order</td></tr>
+     *   <tr><td>CRS:83</td>    <td>{@link CommonCRS#NAD83  NAD83}</td>  <td>Geographic</td>    <td>Like EPSG:4269 except for (<var>longitude</var>, <var>latitude</var>) axis order</td></tr>
+     *   <tr><td>CRS:84</td>    <td>{@link CommonCRS#WGS84  WGS84}</td>  <td>Geographic</td>    <td>Like EPSG:4326 except for (<var>longitude</var>, <var>latitude</var>) axis order</td></tr>
+     *   <tr><td>EPSG:4047</td> <td>{@link CommonCRS#SPHERE SPHERE}</td> <td>Geographic</td>    <td>GRS 1980 Authalic Sphere</td></tr>
+     *   <tr><td>EPSG:4230</td> <td>{@link CommonCRS#ED50   ED50}</td>   <td>Geographic</td>    <td>European Datum 1950</td></tr>
+     *   <tr><td>EPSG:4258</td> <td>{@link CommonCRS#ETRS89 ETRS89}</td> <td>Geographic</td>    <td>European Terrestrial Reference Frame 1989</td></tr>
+     *   <tr><td>EPSG:4267</td> <td>{@link CommonCRS#NAD27  NAD27}</td>  <td>Geographic</td>    <td>North American Datum 1927</td></tr>
+     *   <tr><td>EPSG:4269</td> <td>{@link CommonCRS#NAD83  NAD83}</td>  <td>Geographic</td>    <td>North American Datum 1983</td></tr>
+     *   <tr><td>EPSG:4322</td> <td>{@link CommonCRS#WGS72  WGS72}</td>  <td>Geographic</td>    <td>World Geodetic System 1972</td></tr>
+     *   <tr><td>EPSG:4326</td> <td>{@link CommonCRS#WGS84  WGS84}</td>  <td>Geographic</td>    <td>World Geodetic System 1984</td></tr>
+     *   <tr><td>EPSG:4936</td> <td>{@link CommonCRS#ETRS89 ETRS89}</td> <td>Geocentric</td>    <td>European Terrestrial Reference Frame 1989</td></tr>
+     *   <tr><td>EPSG:4937</td> <td>{@link CommonCRS#ETRS89 ETRS89}</td> <td>Geographic 3D</td> <td>European Terrestrial Reference Frame 1989</td></tr>
+     *   <tr><td>EPSG:4978</td> <td>{@link CommonCRS#WGS84  WGS84}</td>  <td>Geocentric</td>    <td>World Geodetic System 1984</td></tr>
+     *   <tr><td>EPSG:4979</td> <td>{@link CommonCRS#WGS84  WGS84}</td>  <td>Geographic 3D</td> <td>World Geodetic System 1984</td></tr>
+     *   <tr><td>EPSG:4984</td> <td>{@link CommonCRS#WGS72  WGS72}</td>  <td>Geocentric</td>    <td>World Geodetic System 1972</td></tr>
+     *   <tr><td>EPSG:4985</td> <td>{@link CommonCRS#WGS72  WGS72}</td>  <td>Geographic 3D</td> <td>World Geodetic System 1972</td></tr>
      *   <tr><td>EPSG:5715</td> <td>{@link CommonCRS.Vertical#DEPTH  DEPTH}</td> <td>Vertical</td> <td>Mean Sea Level depth</td></tr>
      *   <tr><td>EPSG:5714</td> <td>{@link CommonCRS.Vertical#MEAN_SEA_LEVEL MEAN_SEA_LEVEL}</td> <td>Vertical</td> <td>Mean Sea Level height</td></tr>
      * </table></blockquote>
@@ -85,6 +129,8 @@ public final class CRS extends Static {
      * @return The Coordinate Reference System for the given authority code.
      * @throws NoSuchAuthorityCodeException If there is no known CRS associated to the given code.
      * @throws FactoryException if the CRS creation failed for an other reason.
+     *
+     * @category factory
      */
     public static CoordinateReferenceSystem forCode(final String code)
             throws NoSuchAuthorityCodeException, FactoryException
@@ -122,14 +168,16 @@ public final class CRS extends Static {
                 }
             } else if (authority.equalsIgnoreCase("EPSG")) {
                 final int n = Integer.parseInt(value);
-                for (final CommonCRS candidate : CommonCRS.values()) {
-                    if (candidate.geographic == n) {
-                        return candidate.geographic();
+                if (n != 0) { // CommonCRS uses 0 as a sentinel value for "no EPSG code".
+                    for (final CommonCRS candidate : CommonCRS.values()) {
+                        if (candidate.geographic == n) return candidate.geographic();
+                        if (candidate.geocentric == n) return candidate.geocentric();
+                        if (candidate.geo3D      == n) return candidate.geographic3D();
                     }
-                }
-                for (final CommonCRS.Vertical candidate : CommonCRS.Vertical.values()) {
-                    if (candidate.isEPSG && candidate.crs == n) {
-                        return candidate.crs();
+                    for (final CommonCRS.Vertical candidate : CommonCRS.Vertical.values()) {
+                        if (candidate.isEPSG && candidate.crs == n) {
+                            return candidate.crs();
+                        }
                     }
                 }
             } else {
@@ -146,6 +194,194 @@ public final class CRS extends Static {
     }
 
     /**
+     * Returns the valid geographic area for the given coordinate reference system, or {@code null} if unknown.
+     * This method explores the {@linkplain CoordinateReferenceSystem#getDomainOfValidity() domain of validity}
+     * associated with the given CRS. If more than one geographic bounding box is found, then they will be
+     * {@linkplain org.apache.sis.metadata.iso.extent.DefaultGeographicBoundingBox#add(GeographicBoundingBox)
+     * added} together.
+     *
+     * @param  crs The coordinate reference system, or {@code null}.
+     * @return The geographic area, or {@code null} if none.
+     *
+     * @see #getEnvelope(CoordinateReferenceSystem)
+     * @see Extents#getGeographicBoundingBox(Extent)
+     *
+     * @category information
+     */
+    public static GeographicBoundingBox getGeographicBoundingBox(final CoordinateReferenceSystem crs) {
+        return (crs != null) ? Extents.getGeographicBoundingBox(crs.getDomainOfValidity()) : null;
+    }
+
+    /**
+     * Returns {@code true} if the given CRS is horizontal. The current implementation considers a
+     * CRS as horizontal if it is two-dimensional and comply with one of the following conditions:
+     *
+     * <ul>
+     *   <li>It is an instance of {@link GeographicCRS}.</li>
+     *   <li>It is an instance of {@link ProjectedCRS}.</li>
+     * </ul>
+     *
+     * In case of doubt, this method conservatively returns {@code false}.
+     *
+     * @todo Future SIS implementation may extend the above condition list. For example a radar station could
+     *       use a polar coordinate system in a <code>DerivedCRS</code> instance based on a projected CRS.
+     *       See <a href="http://issues.apache.org/jira/browse/SIS-161">SIS-161</a>.
+     *
+     * @param  crs The coordinate reference system, or {@code null}.
+     * @return {@code true} if the given CRS is non-null and comply with one of the above conditions,
+     *         or {@code false} otherwise.
+     *
+     * @see #getHorizontalComponent(CoordinateReferenceSystem)
+     *
+     * @category information
+     */
+    public static boolean isHorizontalCRS(final CoordinateReferenceSystem crs) {
+        if (crs instanceof GeographicCRS || crs instanceof ProjectedCRS) {
+            return crs.getCoordinateSystem().getDimension() == 2;
+        }
+        return false;
+    }
+
+    /**
+     * Returns the first horizontal coordinate reference system found in the given CRS, or {@code null} if there is
+     * none. If the given CRS is already horizontal according {@link #isHorizontalCRS(CoordinateReferenceSystem)},
+     * then this method returns it as-is. Otherwise if the given CRS is compound, then this method searches for the
+     * first horizontal component in the order of the {@linkplain #getSingleComponents(CoordinateReferenceSystem)
+     * single components list}.
+     *
+     * <p>In the special case where a three-dimensional geographic CRS is found, this method will create a
+     * two-dimensional geographic CRS without the vertical axis.</p>
+     *
+     * @param  crs The coordinate reference system, or {@code null}.
+     * @return The first horizontal CRS, or {@code null} if none.
+     *
+     * @category information
+     */
+    public static SingleCRS getHorizontalComponent(final CoordinateReferenceSystem crs) {
+        if (isHorizontalCRS(crs)) {
+            return (SingleCRS) crs;
+        }
+        if (crs instanceof GeographicCRS) {
+            EllipsoidalCS cs = ((GeographicCRS) crs).getCoordinateSystem();
+            final int i = AxisDirections.indexOfColinear(cs, AxisDirection.UP);
+            if (i >= 0) {
+                final CoordinateSystemAxis xAxis = cs.getAxis(i > 0 ? 0 : 1);
+                final CoordinateSystemAxis yAxis = cs.getAxis(i > 1 ? 1 : 2);
+                cs = CommonCRS.DEFAULT.geographic().getCoordinateSystem();
+                if (!Utilities.equalsIgnoreMetadata(cs.getAxis(0), xAxis) ||
+                    !Utilities.equalsIgnoreMetadata(cs.getAxis(1), yAxis))
+                {
+                    // We can not reuse the name of the existing CS, because it typically
+                    // contains text about axes including the axis that we just dropped.
+                    cs = new DefaultEllipsoidalCS(singletonMap(EllipsoidalCS.NAME_KEY, "Ellipsoidal 2D"), xAxis, yAxis);
+                }
+                return new DefaultGeographicCRS(IdentifiedObjects.getProperties(crs), ((GeographicCRS) crs).getDatum(), cs);
+            }
+        }
+        if (crs instanceof CompoundCRS) {
+            final CompoundCRS cp = (CompoundCRS) crs;
+            for (final CoordinateReferenceSystem c : cp.getComponents()) {
+                final SingleCRS candidate = getHorizontalComponent(c);
+                if (candidate != null) {
+                    return candidate;
+                }
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Returns the first vertical coordinate reference system found in the given CRS, or {@code null} if there is none.
+     * If the given CRS is already an instance of {@code VerticalCRS}, then this method returns it as-is.
+     * Otherwise if the given CRS is compound, then this method searches for the first vertical component
+     * in the order of the {@linkplain #getSingleComponents(CoordinateReferenceSystem) single components list}.
+     *
+     * {@section Height in a three-dimensional geographic CRS}
+     * In ISO 19111 model, ellipsoidal heights are indissociable from geographic CRS because such heights
+     * without their (<var>latitude</var>, <var>longitude</var>) locations make little sense. Consequently
+     * a standard-conformant library should return {@code null} when asked for the {@code VerticalCRS}
+     * component of a geographic CRS. This is what {@code getVerticalComponent(…)} does when the
+     * {@code allowCreateEllipsoidal} argument is {@code false}.
+     *
+     * <p>However in some exceptional cases, handling ellipsoidal heights like any other kind of heights
+     * may simplify the task. For example when computing <em>difference</em> between heights above the
+     * same datum, the impact of ignoring locations may be smaller (but not necessarily canceled).
+     * Orphan {@code VerticalCRS} may also be useful for information purpose like labeling a plot axis.
+     * If the caller feels confident that ellipsoidal heights are safe for his task, he can set the
+     * {@code allowCreateEllipsoidal} argument to {@code true}. In such case, this {@code getVerticalComponent(…)}
+     * method will create a temporary {@code VerticalCRS} from the first three-dimensional {@code GeographicCRS}
+     * <em>in last resort</em>, only if it can not find an existing {@code VerticalCRS} instance.
+     * <strong>Note that this is not a valid CRS according ISO 19111</strong> — use with care.</p>
+     *
+     * @param  crs The coordinate reference system, or {@code null}.
+     * @param  allowCreateEllipsoidal {@code true} for allowing the creation of orphan CRS for ellipsoidal heights.
+     *         The recommended value is {@code false}.
+     * @return The first vertical CRS, or {@code null} if none.
+     *
+     * @category information
+     */
+    public static VerticalCRS getVerticalComponent(final CoordinateReferenceSystem crs,
+            final boolean allowCreateEllipsoidal)
+    {
+        if (crs instanceof VerticalCRS) {
+            return (VerticalCRS) crs;
+        }
+        if (crs instanceof CompoundCRS) {
+            final CompoundCRS cp = (CompoundCRS) crs;
+            boolean a = false;
+            do { // Executed at most twice.
+                for (final CoordinateReferenceSystem c : cp.getComponents()) {
+                    final VerticalCRS candidate = getVerticalComponent(c, a);
+                    if (candidate != null) {
+                        return candidate;
+                    }
+                }
+            } while ((a = !a) == allowCreateEllipsoidal);
+        }
+        if (allowCreateEllipsoidal && crs instanceof GeographicCRS) {
+            final CoordinateSystem cs = crs.getCoordinateSystem();
+            final int i = AxisDirections.indexOfColinear(cs, AxisDirection.UP);
+            if (i >= 0) {
+                final CoordinateSystemAxis axis = cs.getAxis(i);
+                VerticalCRS c = CommonCRS.Vertical.ELLIPSOIDAL.crs();
+                if (!c.getCoordinateSystem().getAxis(0).equals(axis)) {
+                    final Map<String,?> properties = IdentifiedObjects.getProperties(c);
+                    c = new DefaultVerticalCRS(properties, c.getDatum(), new DefaultVerticalCS(properties, axis));
+                }
+                return c;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Returns the first temporal coordinate reference system found in the given CRS, or {@code null} if there is none.
+     * If the given CRS is already an instance of {@code TemporalCRS}, then this method returns it as-is.
+     * Otherwise if the given CRS is compound, then this method searches for the first temporal component
+     * in the order of the {@linkplain #getSingleComponents(CoordinateReferenceSystem) single components list}.
+     *
+     * @param  crs The coordinate reference system, or {@code null}.
+     * @return The first temporal CRS, or {@code null} if none.
+     *
+     * @category information
+     */
+    public static TemporalCRS getTemporalComponent(final CoordinateReferenceSystem crs) {
+        if (crs instanceof TemporalCRS) {
+            return (TemporalCRS) crs;
+        }
+        if (crs instanceof CompoundCRS) {
+            final CompoundCRS cp = (CompoundCRS) crs;
+            for (final CoordinateReferenceSystem c : cp.getComponents()) {
+                final TemporalCRS candidate = getTemporalComponent(c);
+                if (candidate != null) {
+                    return candidate;
+                }
+            }
+        }
+        return null;
+    }
+
+    /**
      * Returns the ordered list of single coordinate reference systems for the specified CRS.
      * This method performs the following choices:
      *

Modified: sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/CommonCRS.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/CommonCRS.java?rev=1562800&r1=1562799&r2=1562800&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/CommonCRS.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/CommonCRS.java [UTF-8] Thu Jan 30 12:37:10 2014
@@ -124,7 +124,7 @@ public enum CommonCRS {
      *   <tr><th>Ellipsoid axes unit:</th>     <td>{@link SI#METRE}</td></tr>
      * </table></blockquote>
      */
-    WGS84((short) 4326, (short) 0, (short) 4978, (short) 6326, (short) 7030),
+    WGS84((short) 4326, (short) 4979, (short) 4978, (short) 6326, (short) 7030),
 
     /**
      * World Geodetic System 1972.
@@ -140,7 +140,7 @@ public enum CommonCRS {
      *   <tr><th>Ellipsoid axes unit:</th>     <td>{@link SI#METRE}</td></tr>
      * </table></blockquote>
      */
-    WGS72((short) 4322, (short) 0, (short) 4984, (short) 6322, (short) 7043),
+    WGS72((short) 4322, (short) 4985, (short) 4984, (short) 6322, (short) 7043),
 
     /**
      * European Terrestrial Reference System 1989.
@@ -162,7 +162,7 @@ public enum CommonCRS {
      *        The <cite>Web Map Server</cite> <code>"CRS:83"</code> authority code uses the NAD83 datum,
      *        while the <code>"IGNF:MILLER"</code> authority code uses the GRS80 datum.}
      */
-    ETRS89((short) 4258, (short) 0, (short) 4936, (short) 6258, (short) 7019),
+    ETRS89((short) 4258, (short) 4937, (short) 4936, (short) 6258, (short) 7019),
 
     /**
      * North American Datum 1983.
@@ -238,6 +238,13 @@ public enum CommonCRS {
     SPHERE((short) 4047, (short) 0, (short) 0, (short) 6047, (short) 7048);
 
     /**
+     * The enum for the default CRS.
+     *
+     * @see #defaultGeographic()
+     */
+    static final CommonCRS DEFAULT = WGS84;
+
+    /**
      * The EPSG code of the two-dimensional geographic CRS.
      */
     final short geographic;
@@ -358,7 +365,7 @@ public enum CommonCRS {
      * @return The default two-dimensional geographic CRS with (<var>longitude</var>, <var>latitude</var>) axis order.
      */
     public static GeographicCRS defaultGeographic() {
-        return WGS84.normalizedGeographic();
+        return DEFAULT.normalizedGeographic();
     }
 
     /**
@@ -438,14 +445,14 @@ public enum CommonCRS {
                     }
                     /*
                      * All constants defined in this enumeration use the same coordinate system, EPSG:6422.
-                     * We will arbitrarily create this CS only for WGS84 (the most frequently created CRS),
+                     * We will arbitrarily create this CS only for the most frequently created CRS,
                      * and share that CS instance for all other constants.
                      */
                     final EllipsoidalCS cs;
-                    if (this == WGS84) {
+                    if (this == DEFAULT) {
                         cs = (EllipsoidalCS) StandardDefinitions.createCoordinateSystem((short) 6422);
                     } else {
-                        cs = WGS84.geographic().getCoordinateSystem();
+                        cs = DEFAULT.geographic().getCoordinateSystem();
                     }
                     object = StandardDefinitions.createGeographicCRS(geographic, datum(), cs);
                     cached = object;
@@ -494,14 +501,14 @@ public enum CommonCRS {
                     }
                     /*
                      * All constants defined in this enumeration use the same coordinate system, EPSG:6423.
-                     * We will arbitrarily create this CS only for WGS84 (the most frequently created CRS),
+                     * We will arbitrarily create this CS only for the most frequently created CRS,
                      * and share that CS instance for all other constants.
                      */
                     final EllipsoidalCS cs;
-                    if (this == WGS84) {
+                    if (this == DEFAULT) {
                         cs = (EllipsoidalCS) StandardDefinitions.createCoordinateSystem((short) 6423);
                     } else {
-                        cs = WGS84.geographic3D().getCoordinateSystem();
+                        cs = DEFAULT.geographic3D().getCoordinateSystem();
                     }
                     // Use same name and datum than the geographic CRS.
                     final GeographicCRS base = geographic();
@@ -551,14 +558,14 @@ public enum CommonCRS {
                     }
                     /*
                      * All constants defined in this enumeration use the same coordinate system, EPSG:6500.
-                     * We will arbitrarily create this CS only for WGS84 (the most frequently created CRS),
+                     * We will arbitrarily create this CS only for the most frequently created CRS,
                      * and share that CS instance for all other constants.
                      */
                     final CartesianCS cs;
-                    if (this == WGS84) {
+                    if (this == DEFAULT) {
                         cs = (CartesianCS) StandardDefinitions.createCoordinateSystem((short) 6500);
                     } else {
-                        cs = (CartesianCS) WGS84.geocentric().getCoordinateSystem();
+                        cs = (CartesianCS) DEFAULT.geocentric().getCoordinateSystem();
                     }
                     // Use same name and datum than the geographic CRS.
                     final GeographicCRS base = geographic();
@@ -675,8 +682,8 @@ public enum CommonCRS {
             synchronized (this) {
                 object = primeMeridian(cached);
                 if (object == null) {
-                    if (this != WGS84) {
-                        object = WGS84.primeMeridian(); // Share the same instance for all constants.
+                    if (this != DEFAULT) {
+                        object = DEFAULT.primeMeridian(); // Share the same instance for all constants.
                     } else {
                         final DatumAuthorityFactory factory = datumFactory();
                         if (factory != null) try {
@@ -822,12 +829,11 @@ public enum CommonCRS {
          * Height measured along the normal to the ellipsoid used in the definition of horizontal datum.
          * The unit of measurement is metres.
          *
-         * <p><b>This datum is not part of ISO 19111 international standard.</b>
-         * Usage of this datum is generally not recommended since ellipsoidal heights make little sense without
+         * <p><b>Ellipsoidal height is not part of ISO 19111 international standard.</b>
+         * Such vertical CRS is usually not recommended since ellipsoidal heights make little sense without
          * their (<var>latitude</var>, <var>longitude</var>) locations. The ISO specification defines instead
-         * three-dimensional {@code GeographicCRS} for that reason. However Apache SIS provides this value
-         * because it is sometime useful to temporarily express ellipsoidal heights independently from other
-         * ordinate values.</p>
+         * three-dimensional {@code GeographicCRS} for that reason. Users are encouraged to avoid this orphan
+         * ellipsoidal height as much as possible.</p>
          */
         ELLIPSOIDAL(false, Vocabulary.Keys.EllipsoidalHeight, Vocabulary.Keys.Ellipsoid),
 

Modified: sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/AbstractCRS.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/AbstractCRS.java?rev=1562800&r1=1562799&r2=1562800&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/AbstractCRS.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/AbstractCRS.java [UTF-8] Thu Jan 30 12:37:10 2014
@@ -101,7 +101,7 @@ public class AbstractCRS extends Abstrac
     private CoordinateSystem coordinateSystem;
 
     /**
-     * Other coordinate systems derived from this coordinate systems for other axes conventions.
+     * Other coordinate reference systems derived from this CRS.
      * Created only when first needed.
      *
      * @see #forConvention(AxesConvention)

Modified: sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultGeocentricCRS.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultGeocentricCRS.java?rev=1562800&r1=1562799&r2=1562800&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultGeocentricCRS.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultGeocentricCRS.java [UTF-8] Thu Jan 30 12:37:10 2014
@@ -25,6 +25,7 @@ import org.opengis.referencing.cs.Coordi
 import org.opengis.referencing.crs.GeocentricCRS;
 import org.opengis.referencing.datum.GeodeticDatum;
 import org.apache.sis.io.wkt.Formatter;
+import org.apache.sis.internal.referencing.Legacy;
 import org.apache.sis.referencing.cs.AxesConvention;
 import org.apache.sis.referencing.AbstractReferenceSystem;
 
@@ -222,9 +223,13 @@ public class DefaultGeocentricCRS extend
         formatter.append(datum);
         formatter.append(datum.getPrimeMeridian());
         formatter.append(unit);
-        final CoordinateSystem cs = formatter.getConvention().toConformCS(getCoordinateSystem());
-        if (!(cs instanceof CartesianCS)) {
-            formatter.setInvalidWKT(cs);
+        CoordinateSystem cs = getCoordinateSystem();
+        if (formatter.getConvention().isWKT1()) {
+            if (cs instanceof CartesianCS) {
+                cs = Legacy.forGeocentricCRS((CartesianCS) cs, true);
+            } else {
+                formatter.setInvalidWKT(cs);
+            }
         }
         final int dimension = cs.getDimension();
         for (int i=0; i<dimension; i++) {

Modified: sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/AxesConvention.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/AxesConvention.java?rev=1562800&r1=1562799&r2=1562800&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/AxesConvention.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/AxesConvention.java [UTF-8] Thu Jan 30 12:37:10 2014
@@ -49,8 +49,9 @@ import org.opengis.referencing.cs.Coordi
  * {@link #RIGHT_HANDED} or {@link #NORMALIZED} enumeration value.</p>
  *
  * {@section Normalized coordinate systems}
- * The definition of <cite>normalized coordinate systems</cite> is somewhat fuzzy.
- * This concept appears in the Web Map Services (WMS) 1.3 specification, quoted here:
+ * <cite>Right-handed</cite> coordinate systems have a precise meaning in Apache SIS.
+ * However SIS defines also <cite>normalized</cite> coordinate systems in a more heuristic way.
+ * A similar concept appears in the Web Map Services (WMS) 1.3 specification, quoted here:
  *
  * <blockquote><font size="-1"><b>6.7.2 Map CS</b> —
  * The usual orientation of the Map CS shall be such that the <var>i</var> axis is parallel to the East-to-West axis
@@ -59,18 +60,9 @@ import org.opengis.referencing.cs.Coordi
  * orthographic projection over the South Pole. The convention to be followed is that, wherever possible, East shall
  * be to the right edge and North shall be toward the upper edge of the Map CS.</font></blockquote>
  *
- * In addition to WMS, this method is used together with
- * {@link org.apache.sis.referencing.cs.CoordinateSystems#swapAndScaleAxes CoordinateSystems.swapAndScaleAxes(…)}
- * for the creation of transformation steps, as in the example below:
- *
- * {@preformat java
- *     Matrix step1 = swapAndScaleAxes(sourceCS, sourceCS.forConvention(NORMALIZED));
- *     Matrix step2 = ... some coordinate operation working on normalized axes ...
- *     Matrix step3 = swapAndScaleAxes(targetCS.forConvention(NORMALIZED), targetCS);
- * }
- *
- * A rational for normalized axes order and units is explained in the <cite>Axis units and directions</cite>
- * section of {@linkplain org.apache.sis.referencing.operation.projection map projection package description}.
+ * In addition to WMS, Apache SIS uses normalized coordinate systems in map projections.
+ * More information are provided in the <cite>Axis units and directions</cite> section of
+ * {@linkplain org.apache.sis.referencing.operation.projection map projection package}.
  *
  * {@section Range of longitude values}
  * Most geographic CRS have a longitude axis defined in the [-180 … +180]° range. All map projections in Apache SIS are

Modified: sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/CoordinateSystems.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/CoordinateSystems.java?rev=1562800&r1=1562799&r2=1562800&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/CoordinateSystems.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/CoordinateSystems.java [UTF-8] Thu Jan 30 12:37:10 2014
@@ -206,12 +206,12 @@ public final class CoordinateSystems ext
 
     /**
      * Returns an affine transform between two coordinate systems.
-     * The two coordinate systems must implement the same GeoAPI coordinate system interface
-     * (for example both of them shall implement {@link org.opengis.referencing.cs.CartesianCS},
-     * or both of them shall implement {@link org.opengis.referencing.cs.EllipsoidalCS}).
+     * The two coordinate systems must implement the same GeoAPI coordinate system interface.
+     * For example if {@code sourceCRS} is a {@link org.opengis.referencing.cs.CartesianCS},
+     * then {@code targetCRS} must be a {@code CartesianCS} too.
      * Only units and axes order (e.g. transforming from
-     * ({@linkplain AxisDirection#NORTH NORTH},{@linkplain AxisDirection#WEST WEST}) to
-     * ({@linkplain AxisDirection#EAST EAST},{@linkplain AxisDirection#NORTH NORTH})
+     * ({@linkplain AxisDirection#NORTH North}, {@linkplain AxisDirection#WEST West}) to
+     * ({@linkplain AxisDirection#EAST East}, {@linkplain AxisDirection#NORTH North})
      * are taken in account by this method.
      *
      * <blockquote><font size="-1"><b>Example:</b>

Modified: sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/geometry/GeneralDirectPositionTest.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/geometry/GeneralDirectPositionTest.java?rev=1562800&r1=1562799&r2=1562800&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/geometry/GeneralDirectPositionTest.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/geometry/GeneralDirectPositionTest.java [UTF-8] Thu Jan 30 12:37:10 2014
@@ -82,8 +82,8 @@ public final strictfp class GeneralDirec
         } catch (IllegalArgumentException e) {
             // This is the expected exception.
             final String message = e.getMessage();
-            assertTrue(message.contains("POINT(6 10 2"));
-            assertTrue(message.contains("‘)’"));
+            assertTrue(message, message.contains("POINT(6 10 2"));
+            assertTrue(message, message.contains("‘)’"));
         }
         try {
             new GeneralDirectPosition("POINT 6 10 2)");
@@ -97,8 +97,9 @@ public final strictfp class GeneralDirec
         } catch (IllegalArgumentException e) {
             // This is the expected exception.
             final String message = e.getMessage();
-            assertTrue(message.contains("POINT(6 10 2) x"));
-            assertTrue(message.contains("“x”"));
+            assertTrue(message, message.contains("POINT(6 10 2) x"));
+            assertTrue(message, message.contains("“x”") ||  // English locale
+                                message.contains("« x »")); // French locale
         }
     }
 

Modified: sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/geometry/GeneralEnvelopeTest.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/geometry/GeneralEnvelopeTest.java?rev=1562800&r1=1562799&r2=1562800&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/geometry/GeneralEnvelopeTest.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/geometry/GeneralEnvelopeTest.java [UTF-8] Thu Jan 30 12:37:10 2014
@@ -426,17 +426,95 @@ public strictfp class GeneralEnvelopeTes
     }
 
     /**
+     * Tests {@link GeneralEnvelope#setEnvelope(double...)} with valid ranges,
+     * then with a range which is known to be invalid.
+     */
+    @Test
+    public void testSetEnvelope() {
+        final GeneralEnvelope e = create(2, -4, 3, -3);
+        e.setEnvelope(3, -5, -8, 2);
+        try {
+            e.setEnvelope(1, -10, 2, -20);
+            fail("Invalid range shall not be allowed.");
+        } catch (IllegalArgumentException ex) {
+            // This is the expected exception.
+            final String message = ex.getMessage();
+            assertTrue(message, message.contains("Geodetic latitude"));
+        }
+        // Verify that the envelope still have the old values.
+        assertEnvelopeEquals(e, 3, -5, -8, 2);
+        verifyInvariants(e);
+    }
+
+    /**
+     * Tests {@link GeneralEnvelope#setRange(int, double, double)} with a valid range,
+     * then with a range which is known to be invalid.
+     */
+    @Test
+    public void testSetRange() {
+        final GeneralEnvelope e = create(2, -4, 3, -3);
+        e.setRange(1, -5, 2);
+        try {
+            e.setRange(1, -10, -20);
+            fail("Invalid range shall not be allowed.");
+        } catch (IllegalArgumentException ex) {
+            // This is the expected exception.
+            final String message = ex.getMessage();
+            assertTrue(message, message.contains("Geodetic latitude"));
+        }
+        // Verify that the envelope still have the old values.
+        assertEnvelopeEquals(e, 2, -5, 3, 2);
+        verifyInvariants(e);
+    }
+
+    /**
+     * Tests {@link GeneralEnvelope#setCoordinateReferenceSystem(CoordinateReferenceSystem)}.
+     */
+    @Test
+    @DependsOnMethod("testSetRange")
+    public void testSetCoordinateReferenceSystem() {
+        final GeneralEnvelope e = create(2, -4, 3, -3);
+        e.setCoordinateReferenceSystem(null);
+        /*
+         * Set an invalid latitude range, but the Envelope can not known that fact without CRS.
+         * Only when we will specify the CRS, the envelope will realize that it contains an
+         * invalid range.
+         */
+        e.setRange(1, -10, -20);
+        try {
+            e.setCoordinateReferenceSystem(WGS84);
+            fail("Invalid range shall not be allowed.");
+        } catch (IllegalStateException ex) {
+            // This is the expected exception.
+            final String message = ex.getMessage();
+            assertTrue(message, message.contains("Geodetic latitude"));
+        }
+        /*
+         * Verify that the envelope values are unchanged.
+         * Then fix the range and try again to set the CRS.
+         */
+        assertEquals(  2, e.getLower(0), STRICT);
+        assertEquals(-10, e.getLower(1), STRICT);
+        assertEquals(  3, e.getUpper(0), STRICT);
+        assertEquals(-20, e.getUpper(1), STRICT);
+        e.setRange(1, -20, -10);
+        e.setCoordinateReferenceSystem(WGS84);
+        assertEnvelopeEquals(e, 2, -20, 3, -10);
+        verifyInvariants(e);
+    }
+
+    /**
      * Tests modifying the corner of an envelope.
      */
     @Test
     public void testCornerModifications() {
-        final GeneralEnvelope e = create(2, -2, 3, -3);
+        final GeneralEnvelope e = create(2, -4, 3, -3);
         e.getLowerCorner().setOrdinate(0,  1);
         e.getUpperCorner().setOrdinate(1, -1);
-        assertEquals( 1.0, e.getLower(0), 0.0);
-        assertEquals(-2.0, e.getLower(1), 0.0);
-        assertEquals( 3.0, e.getUpper(0), 0.0);
-        assertEquals(-1.0, e.getUpper(1), 0.0);
+        assertEquals( 1, e.getLower(0), STRICT);
+        assertEquals(-4, e.getLower(1), STRICT);
+        assertEquals( 3, e.getUpper(0), STRICT);
+        assertEquals(-1, e.getUpper(1), STRICT);
         verifyInvariants(e);
     }
 

Modified: sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/AxisDirectionsTest.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/AxisDirectionsTest.java?rev=1562800&r1=1562799&r2=1562800&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/AxisDirectionsTest.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/AxisDirectionsTest.java [UTF-8] Thu Jan 30 12:37:10 2014
@@ -312,11 +312,11 @@ public final strictfp class AxisDirectio
     }
 
     /**
-     * Tests {@link AxisDirections#indexOf(CoordinateSystem, AxisDirection)}.
+     * Tests {@link AxisDirections#indexOfColinear(CoordinateSystem, AxisDirection)}.
      */
     @Test
-    public void testIndexOf() {
-        assertEquals(1, AxisDirections.indexOf(HardCodedCS.GEODETIC_3D, AxisDirection.NORTH));
-        assertEquals(1, AxisDirections.indexOf(HardCodedCS.GEODETIC_3D, AxisDirection.SOUTH));
+    public void testIndexOfColinear() {
+        assertEquals(1, AxisDirections.indexOfColinear(HardCodedCS.GEODETIC_3D, AxisDirection.NORTH));
+        assertEquals(1, AxisDirections.indexOfColinear(HardCodedCS.GEODETIC_3D, AxisDirection.SOUTH));
     }
 }

Modified: sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/io/wkt/ConventionTest.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/io/wkt/ConventionTest.java?rev=1562800&r1=1562799&r2=1562800&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/io/wkt/ConventionTest.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/io/wkt/ConventionTest.java [UTF-8] Thu Jan 30 12:37:10 2014
@@ -16,8 +16,9 @@
  */
 package org.apache.sis.io.wkt;
 
-import org.opengis.metadata.citation.Citation;
-import org.apache.sis.metadata.iso.citation.Citations;
+import javax.measure.unit.NonSI;
+import javax.measure.quantity.Angle;
+import javax.measure.quantity.Duration;
 import org.apache.sis.test.TestCase;
 import org.junit.Test;
 
@@ -34,43 +35,13 @@ import static org.junit.Assert.*;
  */
 public final strictfp class ConventionTest extends TestCase {
     /**
-     * Tests all citations associated with enum values.
+     * Tests {@link Convention#getForcedUnit(Class)}.
      */
     @Test
-    public void testGetCitation() {
-        for (final Convention convention : Convention.values()) {
-            final Citation citation = convention.getAuthority();
-            if (convention != Convention.PROJ4 && convention != Convention.INTERNAL) {
-                assertTrue(convention.name(), convention.name().equalsIgnoreCase(Citations.getIdentifier(citation)));
-            }
-        }
-    }
-
-    /**
-     * Tests {@link Convention#forCitation(Citation, Convention)}.
-     */
-    @Test
-    public void testForCitation() {
-        assertSame(Convention.OGC,     Convention.forCitation(Citations.OGC,     null));
-        assertSame(Convention.EPSG,    Convention.forCitation(Citations.EPSG,    null));
-        assertSame(Convention.ESRI,    Convention.forCitation(Citations.ESRI,    null));
-        assertSame(Convention.ORACLE,  Convention.forCitation(Citations.ORACLE,  null));
-        assertSame(Convention.NETCDF,  Convention.forCitation(Citations.NETCDF,  null));
-        assertSame(Convention.GEOTIFF, Convention.forCitation(Citations.GEOTIFF, null));
-        assertSame(Convention.PROJ4,   Convention.forCitation(Citations.PROJ4,   null));
-    }
-
-    /**
-     * Tests {@link Convention#forIdentifier(String, Convention)}.
-     */
-    @Test
-    public void testForIdentifier() {
-        assertSame(Convention.OGC,     Convention.forIdentifier("OGC",     null));
-        assertSame(Convention.EPSG,    Convention.forIdentifier("EPSG",    null));
-        assertSame(Convention.ESRI,    Convention.forIdentifier("ESRI",    null));
-        assertSame(Convention.ORACLE,  Convention.forIdentifier("ORACLE",  null));
-        assertSame(Convention.NETCDF,  Convention.forIdentifier("NETCDF",  null));
-        assertSame(Convention.GEOTIFF, Convention.forIdentifier("GEOTIFF", null));
-        assertSame(Convention.PROJ4,   Convention.forIdentifier("PROJ4",   null));
+    public void testGetForcedUnit() {
+        assertNull(Convention.WKT2.getForcedUnit(Angle.class));
+        assertNull(Convention.WKT1.getForcedUnit(Angle.class));
+        assertEquals(NonSI.DEGREE_ANGLE, Convention.WKT1_COMMON_UNITS.getForcedUnit(Angle.class));
+        assertNull(Convention.WKT1_COMMON_UNITS.getForcedUnit(Duration.class));
     }
 }

Modified: sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/io/wkt/FormatterTest.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/io/wkt/FormatterTest.java?rev=1562800&r1=1562799&r2=1562800&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/io/wkt/FormatterTest.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/io/wkt/FormatterTest.java [UTF-8] Thu Jan 30 12:37:10 2014
@@ -50,16 +50,16 @@ public final strictfp class FormatterTes
      */
     @Test
     public void testAppendMatrix() {
-        final Formatter formatter = new Formatter();
-        formatter.append(new Matrix4(1, 0, 4, 0,
-                                    -2, 1, 0, 0,
-                                     0, 0, 1, 7,
-                                     0, 0, 0, 1));
+        final Matrix m = new Matrix4(
+                1, 0, 4, 0,
+               -2, 1, 0, 0,
+                0, 0, 1, 7,
+                0, 0, 0, 1);
         assertWktEquals(
                 "PARAMETER[“num_row”, 4],\n"    +
                 "PARAMETER[“num_col”, 4],\n"    +
                 "PARAMETER[“elt_0_2”, 4.0],\n"  +
                 "PARAMETER[“elt_1_0”, -2.0],\n"  +
-                "PARAMETER[“elt_2_3”, 7.0]", formatter.toString());
+                "PARAMETER[“elt_2_3”, 7.0]", m);
     }
 }

Modified: sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/io/wkt/SymbolsTest.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/io/wkt/SymbolsTest.java?rev=1562800&r1=1562799&r2=1562800&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/io/wkt/SymbolsTest.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/io/wkt/SymbolsTest.java [UTF-8] Thu Jan 30 12:37:10 2014
@@ -17,6 +17,7 @@
 package org.apache.sis.io.wkt;
 
 import org.apache.sis.test.TestCase;
+import org.apache.sis.util.StringBuilders;
 import org.junit.Test;
 
 import static org.junit.Assert.*;
@@ -36,13 +37,26 @@ public final strictfp class SymbolsTest 
      */
     @Test
     public void testContainsAxis() {
-        final Symbols s = Symbols.DEFAULT;
-        assertTrue("At beginning of a line.",   s.containsAxis(                  "AXIS[\"Long\", EAST]"));
-        assertTrue("Embeded in GEOGCS.",        s.containsAxis("GEOGCS[\"WGS84\", AXIS[\"Long\", EAST]]"));
-        assertTrue("Using different brackets.", s.containsAxis("GEOGCS[\"WGS84\", AXIS (\"Long\", EAST)]"));
-        assertTrue("Mixed cases.",              s.containsAxis("GEOGCS[\"WGS84\", aXis[\"Long\", EAST]]"));
-        assertFalse("AXIS in quoted text.",     s.containsAxis("GEOGCS[\"AXIS\"]"));
-        assertFalse("Without opening bracket.", s.containsAxis("GEOGCS[\"WGS84\", AXIS]"));
-        assertFalse("No AXIS.",                 s.containsAxis("GEOGCS[\"WGS84\"]"));
+        assertContainsAxis("At beginning of a line.",   true,                  "AXIS[“Long”, EAST]");
+        assertContainsAxis("Embeded in GEOGCS.",        true,  "GEOGCS[“WGS84”, AXIS[“Long”, EAST]]");
+        assertContainsAxis("Using different brackets.", true,  "GEOGCS[“WGS84”, AXIS (“Long”, EAST)]");
+        assertContainsAxis("Mixed cases.",              true,  "GEOGCS[“WGS84”, aXis[“Long”, EAST]]");
+        assertContainsAxis("AXIS in quoted text.",      false, "GEOGCS[“AXIS”]");
+        assertContainsAxis("Without opening bracket.",  false, "GEOGCS[“WGS84”, AXIS]");
+        assertContainsAxis("No AXIS.",                  false, "GEOGCS[“WGS84”]");
+    }
+
+    /**
+     * Asserts that the call to {@link Symbols#containsAxis(CharSequence)} produce the given result.
+     * This method expects an array using the {@code “…”} quotation marks, which will be replaced by
+     * the standard {@code '"'} quotation mark after we tested the given string.
+     */
+    private static void assertContainsAxis(final String message, final boolean expected, final String wkt) {
+        assertEquals(message, expected, Symbols.DEFAULT.containsAxis(wkt));
+        final StringBuilder buffer = new StringBuilder(wkt);
+        StringBuilders.replace(buffer, '“', '"');
+        StringBuilders.replace(buffer, '”', '"');
+        assertFalse(wkt.contentEquals(buffer));
+        assertEquals(message, expected, Symbols.DEFAULT.containsAxis(buffer));
     }
 }

Modified: sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/Assert.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/Assert.java?rev=1562800&r1=1562799&r2=1562800&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/Assert.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/Assert.java [UTF-8] Thu Jan 30 12:37:10 2014
@@ -30,6 +30,8 @@ import org.opengis.referencing.operation
 import org.opengis.referencing.cs.AxisDirection;
 import org.opengis.referencing.cs.CoordinateSystemAxis;
 import org.opengis.referencing.cs.RangeMeaning;
+import org.apache.sis.io.wkt.Symbols;
+import org.apache.sis.io.wkt.WKTFormat;
 import org.apache.sis.geometry.AbstractEnvelope;
 import org.apache.sis.geometry.GeneralDirectPosition;
 
@@ -52,6 +54,18 @@ public strictfp class Assert extends org
     private static final double STRICT = 0;
 
     /**
+     * The formatter to be used by {@link #assertWktEquals(Object, String)}.
+     * This formatter uses the {@code “…”} quotation marks instead of {@code "…"}
+     * for easier readability of {@link String} constants in Java code.
+     */
+    private static final WKTFormat WKT_FORMAT = new WKTFormat();
+    static {
+        final Symbols s = new Symbols();
+        s.setPairedQuotes("“”", "\"\"");
+        WKT_FORMAT.setSymbols(s);
+    }
+
+    /**
      * For subclass constructor only.
      */
     protected Assert() {
@@ -318,38 +332,24 @@ public strictfp class Assert extends org
     }
 
     /**
-     * Asserts that the WKT of the given object is equal to the expected one. If the given string contains
-     * {@code '“'} and {@code '”'} characters (for easier reading), then those characters will be replaced
-     * by "ordinary" quote characters ({@code '"'}).
+     * Asserts that the WKT of the given object is equal to the expected one.
+     * This method expected the {@code “…”} quotation marks instead of {@code "…"}
+     * for easier readability of {@link String} constants in Java code.
      *
-     * @param object The object to format in <cite>Well Known Text</cite> format.
      * @param expected The expected text, or {@code null} if {@code object} is expected to be null.
+     * @param object The object to format in <cite>Well Known Text</cite> format, or {@code null}.
      */
-    public static void assertWktEquals(final IdentifiedObject object, String expected) {
+    public static void assertWktEquals(final String expected, final Object object) {
         if (expected == null) {
             assertNull(object);
         } else {
             assertNotNull(object);
-            expected = expected.replace('“', '"').replace('”', '"');
-            assertMultilinesEquals(object.getName().getCode(), expected, object.toWKT());
-        }
-    }
-
-    /**
-     * Asserts that the given WKT is equal to the expected one. If the given expected string contains
-     * {@code '“'} and {@code '”'} characters (for easier reading), then those characters will be replaced
-     * by "ordinary" quote characters ({@code '"'}).
-     *
-     * @param expected The expected text, or {@code null} if {@code actual} is expected to be null.
-     * @param actual   The actual <cite>Well Known Text</cite> to compare.
-     */
-    public static void assertWktEquals(String expected, final String actual) {
-        if (expected == null) {
-            assertNull(actual);
-        } else {
-            assertNotNull(actual);
-            expected = expected.replace('“', '"').replace('”', '"');
-            assertMultilinesEquals(expected, actual);
+            final String wkt;
+            synchronized (WKT_FORMAT) {
+                wkt = WKT_FORMAT.format(object);
+            }
+            assertMultilinesEquals((object instanceof IdentifiedObject) ?
+                    ((IdentifiedObject) object).getName().getCode() : object.getClass().getSimpleName(), expected, wkt);
         }
     }
 }

Modified: sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/CRSTest.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/CRSTest.java?rev=1562800&r1=1562799&r2=1562800&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/CRSTest.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/CRSTest.java [UTF-8] Thu Jan 30 12:37:10 2014
@@ -16,14 +16,16 @@
  */
 package org.apache.sis.referencing;
 
-import org.opengis.referencing.crs.GeographicCRS;
+import org.opengis.referencing.crs.CoordinateReferenceSystem;
+import org.opengis.referencing.crs.SingleCRS;
 import org.opengis.util.FactoryException;
+import org.apache.sis.referencing.crs.HardCodedCRS;
 import org.apache.sis.test.DependsOnMethod;
 import org.apache.sis.test.DependsOn;
 import org.apache.sis.test.TestCase;
 import org.junit.Test;
 
-import static org.junit.Assert.*;
+import static org.apache.sis.test.Assert.*;
 
 
 /**
@@ -41,7 +43,7 @@ public final strictfp class CRSTest exte
     /**
      * Asserts that the result of {@link CRS#forCode(String)} is the given CRS.
      */
-    private static void verifyForCode(final GeographicCRS expected, final String code) throws FactoryException {
+    private static void verifyForCode(final SingleCRS expected, final String code) throws FactoryException {
         assertSame(code, expected, CRS.forCode(code));
     }
 
@@ -52,17 +54,23 @@ public final strictfp class CRSTest exte
      */
     @Test
     public void testForEpsgCode() throws FactoryException {
-        verifyForCode(CommonCRS.WGS84 .geographic(), "EPSG:4326");
-        verifyForCode(CommonCRS.WGS84 .geographic(), "urn:ogc:def:crs:EPSG::4326");
-        verifyForCode(CommonCRS.WGS84 .geographic(), "http://www.opengis.net/gml/srs/epsg.xml#4326");
-        verifyForCode(CommonCRS.WGS72 .geographic(), "EPSG:4322");
-        verifyForCode(CommonCRS.SPHERE.geographic(), "EPSG:4047");
-        verifyForCode(CommonCRS.NAD83 .geographic(), "EPSG:4269");
-        verifyForCode(CommonCRS.NAD27 .geographic(), "EPSG:4267");
-        verifyForCode(CommonCRS.ETRS89.geographic(), "EPSG:4258");
-        verifyForCode(CommonCRS.ED50  .geographic(), "EPSG:4230");
-        assertSame("EPSG:5714", CommonCRS.Vertical.MEAN_SEA_LEVEL.crs(), CRS.forCode("EPSG:5714"));
-        assertSame("EPSG:5715", CommonCRS.Vertical.DEPTH.crs(), CRS.forCode("EPSG:5715"));
+        verifyForCode(CommonCRS.WGS84 .geographic(),   "EPSG:4326");
+        verifyForCode(CommonCRS.WGS84 .geographic(),   "urn:ogc:def:crs:EPSG::4326");
+        verifyForCode(CommonCRS.WGS84 .geographic(),   "http://www.opengis.net/gml/srs/epsg.xml#4326");
+        verifyForCode(CommonCRS.WGS72 .geographic(),   "EPSG:4322");
+        verifyForCode(CommonCRS.SPHERE.geographic(),   "EPSG:4047");
+        verifyForCode(CommonCRS.NAD83 .geographic(),   "EPSG:4269");
+        verifyForCode(CommonCRS.NAD27 .geographic(),   "EPSG:4267");
+        verifyForCode(CommonCRS.ETRS89.geographic(),   "EPSG:4258");
+        verifyForCode(CommonCRS.ED50  .geographic(),   "EPSG:4230");
+        verifyForCode(CommonCRS.WGS84 .geocentric(),   "EPSG:4978");
+        verifyForCode(CommonCRS.WGS72 .geocentric(),   "EPSG:4984");
+        verifyForCode(CommonCRS.ETRS89.geocentric(),   "EPSG:4936");
+        verifyForCode(CommonCRS.WGS84 .geographic3D(), "EPSG:4979");
+        verifyForCode(CommonCRS.WGS72 .geographic3D(), "EPSG:4985");
+        verifyForCode(CommonCRS.ETRS89.geographic3D(), "EPSG:4937");
+        verifyForCode(CommonCRS.Vertical.MEAN_SEA_LEVEL.crs(), "EPSG:5714");
+        verifyForCode(CommonCRS.Vertical.DEPTH.crs(), "EPSG:5715");
     }
 
     /**
@@ -77,4 +85,68 @@ public final strictfp class CRSTest exte
         verifyForCode(CommonCRS.NAD83.normalizedGeographic(), "CRS:83");
         verifyForCode(CommonCRS.NAD27.normalizedGeographic(), "CRS:27");
     }
+
+    /**
+     * Tests {@link CRS#isHorizontalCRS(CoordinateReferenceSystem)}.
+     */
+    @Test
+    public void testIsHorizontalCRS() {
+        assertFalse(CRS.isHorizontalCRS(HardCodedCRS.TIME));
+        assertFalse(CRS.isHorizontalCRS(HardCodedCRS.ELLIPSOIDAL_HEIGHT));
+        assertTrue (CRS.isHorizontalCRS(HardCodedCRS.WGS84));
+        assertTrue (CRS.isHorizontalCRS(HardCodedCRS.WGS84_φλ));
+        assertFalse(CRS.isHorizontalCRS(HardCodedCRS.WGS84_3D));
+        assertFalse(CRS.isHorizontalCRS(HardCodedCRS.GEOID_4D));
+        assertFalse(CRS.isHorizontalCRS(HardCodedCRS.GEOCENTRIC));
+    }
+
+    /**
+     * Tests {@link CRS#getHorizontalComponent(CoordinateReferenceSystem)}.
+     */
+    @Test
+    @DependsOnMethod("testIsHorizontalCRS")
+    public void testGetHorizontalComponent() {
+        assertNull(CRS.getHorizontalComponent(HardCodedCRS.TIME));
+        assertNull(CRS.getHorizontalComponent(HardCodedCRS.ELLIPSOIDAL_HEIGHT));
+        assertNull(CRS.getHorizontalComponent(HardCodedCRS.GEOCENTRIC));
+
+        assertSame(HardCodedCRS.WGS84,    CRS.getHorizontalComponent(HardCodedCRS.WGS84));
+        assertSame(HardCodedCRS.WGS84_φλ, CRS.getHorizontalComponent(HardCodedCRS.WGS84_φλ));
+
+        assertEqualsIgnoreMetadata(HardCodedCRS.WGS84, CRS.getHorizontalComponent(HardCodedCRS.WGS84_3D));
+    }
+
+    /**
+     * Tests {@link CRS#getVerticalComponent(CoordinateReferenceSystem, boolean)}.
+     */
+    @Test
+    public void testGetVerticalComponent() {
+        assertNull(CRS.getVerticalComponent(HardCodedCRS.TIME,  false));
+        assertNull(CRS.getVerticalComponent(HardCodedCRS.TIME,  true));
+        assertNull(CRS.getVerticalComponent(HardCodedCRS.WGS84, false));
+        assertNull(CRS.getVerticalComponent(HardCodedCRS.WGS84, true));
+
+        assertSame(HardCodedCRS.ELLIPSOIDAL_HEIGHT,     CRS.getVerticalComponent(HardCodedCRS.ELLIPSOIDAL_HEIGHT, false));
+        assertSame(HardCodedCRS.ELLIPSOIDAL_HEIGHT,     CRS.getVerticalComponent(HardCodedCRS.ELLIPSOIDAL_HEIGHT, true));
+        assertSame(HardCodedCRS.GRAVITY_RELATED_HEIGHT, CRS.getVerticalComponent(HardCodedCRS.GEOID_4D, false));
+        assertSame(HardCodedCRS.GRAVITY_RELATED_HEIGHT, CRS.getVerticalComponent(HardCodedCRS.GEOID_4D, true));
+
+        assertNull(CRS.getVerticalComponent(HardCodedCRS.WGS84_3D, false));
+        assertEqualsIgnoreMetadata(HardCodedCRS.ELLIPSOIDAL_HEIGHT,
+                CRS.getVerticalComponent(HardCodedCRS.WGS84_3D, true));
+    }
+
+    /**
+     * Tests {@link CRS#getTemporalComponent(CoordinateReferenceSystem)}.
+     */
+    @Test
+    public void testGetTemporalComponent() {
+        assertNull(CRS.getTemporalComponent(HardCodedCRS.ELLIPSOIDAL_HEIGHT));
+        assertNull(CRS.getTemporalComponent(HardCodedCRS.WGS84));
+        assertNull(CRS.getTemporalComponent(HardCodedCRS.WGS84_φλ));
+        assertNull(CRS.getTemporalComponent(HardCodedCRS.WGS84_3D));
+
+        assertSame(HardCodedCRS.TIME, CRS.getTemporalComponent(HardCodedCRS.TIME));
+        assertSame(HardCodedCRS.TIME, CRS.getTemporalComponent(HardCodedCRS.GEOID_4D));
+    }
 }

Modified: sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/crs/HardCodedCRS.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/crs/HardCodedCRS.java?rev=1562800&r1=1562799&r2=1562800&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/crs/HardCodedCRS.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/crs/HardCodedCRS.java [UTF-8] Thu Jan 30 12:37:10 2014
@@ -137,6 +137,13 @@ public final strictfp class HardCodedCRS
             getProperties(HardCodedCS.DAYS), HardCodedDatum.UNIX, HardCodedCS.DAYS);
 
     /**
+     * A (λ,φ,h,t) CRS where <var>h</var> is the {@link #GRAVITY_RELATED_HEIGHT}.
+     * This constant uses the "geoid" term as an approximation for the gravity related height.
+     */
+    public static final DefaultCompoundCRS GEOID_4D = new DefaultCompoundCRS(
+            properties("WGS 84 + height + time"), GRAVITY_RELATED_HEIGHT, TIME);
+
+    /**
      * A two-dimensional Cartesian coordinate reference system with (column, row) axes.
      * By default, this CRS has no transformation path to any other CRS (i.e. a map using
      * this CS can't be reprojected to a geographic coordinate reference system for example).

Modified: sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/crs/HardCodedCRSTest.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/crs/HardCodedCRSTest.java?rev=1562800&r1=1562799&r2=1562800&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/crs/HardCodedCRSTest.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/crs/HardCodedCRSTest.java [UTF-8] Thu Jan 30 12:37:10 2014
@@ -72,7 +72,7 @@ public final strictfp class HardCodedCRS
      */
     @Test
     public void testWKT() {
-        assertWktEquals(WGS84,
+        assertWktEquals(
                 "GEOGCS[“WGS 84”,\n" +
                 "  DATUM[“World Geodetic System 1984”,\n" +
                 "    SPHEROID[“WGS84”, 6378137.0, 298.257223563],\n" +
@@ -80,7 +80,8 @@ public final strictfp class HardCodedCRS
                 "  PRIMEM[“Greenwich”, 0.0, AUTHORITY[“EPSG”, “8901”]],\n" +
                 "  UNIT[“degree”, 0.017453292519943295],\n" +
                 "  AXIS[“Geodetic longitude”, EAST],\n" +
-                "  AXIS[“Geodetic latitude”, NORTH]]");
+                "  AXIS[“Geodetic latitude”, NORTH]]",
+                WGS84);
     }
 
     /**