001/*
002 *    GeoAPI - Java interfaces for OGC/ISO standards
003 *    http://www.geoapi.org
004 *
005 *    Copyright (C) 2014-2019 Open Geospatial Consortium, Inc.
006 *    All Rights Reserved. http://www.opengeospatial.org/ogc/legal
007 *
008 *    Permission to use, copy, and modify this software and its documentation, with
009 *    or without modification, for any purpose and without fee or royalty is hereby
010 *    granted, provided that you include the following on ALL copies of the software
011 *    and documentation or portions thereof, including modifications, that you make:
012 *
013 *    1. The full text of this NOTICE in a location viewable to users of the
014 *       redistributed or derivative work.
015 *    2. Notice of any changes or modifications to the OGC files, including the
016 *       date changes were made.
017 *
018 *    THIS SOFTWARE AND DOCUMENTATION IS PROVIDED "AS IS," AND COPYRIGHT HOLDERS MAKE
019 *    NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
020 *    TO, WARRANTIES OF MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR PURPOSE OR THAT
021 *    THE USE OF THE SOFTWARE OR DOCUMENTATION WILL NOT INFRINGE ANY THIRD PARTY
022 *    PATENTS, COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS.
023 *
024 *    COPYRIGHT HOLDERS WILL NOT BE LIABLE FOR ANY DIRECT, INDIRECT, SPECIAL OR
025 *    CONSEQUENTIAL DAMAGES ARISING OUT OF ANY USE OF THE SOFTWARE OR DOCUMENTATION.
026 *
027 *    The name and trademarks of copyright holders may NOT be used in advertising or
028 *    publicity pertaining to the software without specific, written prior permission.
029 *    Title to copyright in this software and any associated documentation will at all
030 *    times remain with copyright holders.
031 */
032package org.opengis.test.wkt;
033
034import java.util.Date;
035import java.util.List;
036import javax.measure.Unit;
037import javax.measure.quantity.Angle;
038import javax.measure.quantity.Length;
039import javax.measure.quantity.Dimensionless;
040
041import org.opengis.util.Factory;
042import org.opengis.util.FactoryException;
043import org.opengis.referencing.IdentifiedObject;
044import org.opengis.referencing.crs.*;
045import org.opengis.referencing.cs.*;
046import org.opengis.referencing.datum.*;
047import org.opengis.parameter.ParameterValueGroup;
048import org.opengis.metadata.extent.Extent;
049import org.opengis.test.referencing.ReferencingTestCase;
050import org.opengis.test.Configuration;
051import org.junit.runner.RunWith;
052import org.junit.runners.Parameterized;
053import org.junit.Test;
054
055import static java.lang.Double.NaN;
056import static org.junit.Assume.assumeTrue;
057import static org.opengis.test.Assert.*;
058import static org.opengis.referencing.cs.AxisDirection.*;
059
060
061/**
062 * Tests the Well-Known Text (WKT) parser of Coordinate Reference System (CRS) objects.
063 * For running this test, vendors need to implement the {@link CRSFactory#createFromWKT(String)} method.
064 * That method will be given various WKT strings from the
065 * <a href="http://docs.opengeospatial.org/is/12-063r5/12-063r5.html">OGC 12-063r5 —
066 * Well-known text representation of coordinate reference systems</a> specification.
067 * The object returned by {@code createFromWKT(String)} will be checked for the following properties:
068 *
069 * <ul>
070 *   <li>{@link IdentifiedObject#getName()} and {@link IdentifiedObject#getIdentifiers() getIdentifiers()} on the CRS and the datum</li>
071 *   <li>{@link Ellipsoid#getSemiMajorAxis()} and {@link Ellipsoid#getInverseFlattening() getInverseFlattening()}</li>
072 *   <li>{@link PrimeMeridian#getGreenwichLongitude()}</li>
073 *   <li>{@link CoordinateSystem#getDimension()}</li>
074 *   <li>{@link CoordinateSystemAxis#getAbbreviation()} when they were explicitly given in the WKT and do not need transliteration.</li>
075 *   <li>{@link CoordinateSystemAxis#getDirection()} and {@link CoordinateSystemAxis#getUnit() getUnit()}</li>
076 *   <li>{@link CoordinateReferenceSystem#getScope()} (optional – null allowed)</li>
077 *   <li>{@link CoordinateReferenceSystem#getDomainOfValidity()} (optional – null allowed)</li>
078 *   <li>{@link CoordinateReferenceSystem#getRemarks()} (optional – null allowed)</li>
079 * </ul>
080 *
081 * <div class="note"><b>Usage example:</b>
082 * in order to specify their factories and run the tests in a JUnit framework, implementors can
083 * define a subclass in their own test suite as in the example below:
084 *
085 * <blockquote><pre>import org.junit.runner.RunWith;
086 *import org.junit.runners.JUnit4;
087 *import org.opengis.test.wkt.CRSParserTest;
088 *
089 *&#64;RunWith(JUnit4.class)
090 *public class MyTest extends CRSParserTest {
091 *    public MyTest() {
092 *        super(new MyCRSFactory());
093 *    }
094 *}</pre></blockquote>
095 * </div>
096 *
097 * @author  Martin Desruisseaux (Geomatys)
098 * @author  Johann Sorel (Geomatys)
099 * @version 3.1
100 * @since   3.1
101 *
102 * @see <a href="http://docs.opengeospatial.org/is/12-063r5/12-063r5.html">WKT 2 specification</a>
103 */
104@RunWith(Parameterized.class)
105public strictfp class CRSParserTest extends ReferencingTestCase {
106    /**
107     * The factory to use for parsing WKT strings. The {@link CRSFactory#createFromWKT(String)} method
108     * of this factory will be invoked for each test defined in this {@code CRSParserTest} class.
109     */
110    protected final CRSFactory crsFactory;
111
112    /**
113     * The instance returned by {@link CRSFactory#createFromWKT(String)} after parsing the WKT.
114     * Subclasses can use this field if they wish to verify additional properties after the
115     * verifications done by this {@code CRSParserTest} class.
116     */
117    protected CoordinateReferenceSystem object;
118
119    /**
120     * {@code true} if the test methods can invoke a <code>{@linkplain #validators validators}.validate(…)}</code>
121     * method after parsing. Implementors can set this flag to {@code false} if their WKT parser is known to create
122     * CRS objects that differ from the ISO 19111 model. One of the main reasons for disabling validation is because
123     * the axis names specified by ISO 19162 differ from the axis names specified by ISO 19111.
124     */
125    protected boolean isValidationEnabled;
126
127    /**
128     * Returns a default set of factories to use for running the tests. Those factories are given
129     * in arguments to the constructor when this test class is instantiated directly by JUnit (for
130     * example as a {@linkplain org.junit.runners.Suite.SuiteClasses suite} element), instead than
131     * sub-classed by the implementor. The factories are fetched as documented in the
132     * {@link #factories(Class[])} javadoc.
133     *
134     * @return the default set of arguments to be given to the {@code ObjectFactoryTest} constructor.
135     */
136    @Parameterized.Parameters
137    @SuppressWarnings("unchecked")
138    public static List<Factory[]> factories() {
139        return factories(CRSFactory.class);
140    }
141
142    /**
143     * Creates a new test using the given factory.
144     *
145     * @param crsFactory  factory for parsing {@link CoordinateReferenceSystem} instances.
146     */
147    public CRSParserTest(final CRSFactory crsFactory) {
148        super(crsFactory);
149        this.crsFactory = crsFactory;
150        @SuppressWarnings("unchecked")
151        final boolean[] isEnabled = getEnabledFlags(
152                Configuration.Key.isValidationEnabled);
153        isValidationEnabled = isEnabled[0];
154    }
155
156    /**
157     * Returns information about the configuration of the test which has been run.
158     * This method returns a map containing:
159     *
160     * <ul>
161     *   <li>All the following values associated to the {@link org.opengis.test.Configuration.Key} of the same name:
162     *     <ul>
163     *       <li>{@link #isValidationEnabled}</li>
164     *       <li>{@link #crsFactory}</li>
165     *     </ul>
166     *   </li>
167     * </ul>
168     *
169     * @return {@inheritDoc}
170     */
171    @Override
172    public Configuration configuration() {
173        final Configuration op = super.configuration();
174        assertNull(op.put(Configuration.Key.isValidationEnabled, isValidationEnabled));
175        assertNull(op.put(Configuration.Key.crsFactory,          crsFactory));
176        return op;
177    }
178
179    /**
180     * Asserts that the given datum has the expected name.
181     *
182     * @param datum  the datum to verify.
183     * @param name   the string representation of the expected name (ignoring code space).
184     */
185    private static void verifyDatum(final Datum datum, final String name) {
186        assertNotNull("SingleCRS.getDatum()", datum);
187        assertEquals("datum.getName().getCode()", name, datum.getName().getCode());
188    }
189
190    /**
191     * Compares the abbreviations of coordinate system axes against the expected values.
192     * The comparison is case-sensitive, e.g. <var>h</var> (ellipsoidal height) is not the same than
193     * <var>H</var> (gravity-related height).
194     *
195     * <p>The GeoAPI conformance tests invoke this method only for abbreviations that should not need transliteration.
196     * For example the GeoAPI tests do not invoke this method for geodetic latitude and longitude axes, because some
197     * implementations may keep the Greek letters φ and λ as specified in ISO 19111 while other implementations may
198     * transliterate those Greek letters to the <var>P</var> and <var>L</var> Latin letters.</p>
199     *
200     * @param cs             the coordinate system to verify.
201     * @param abbreviations  the expected abbreviations. Null elements are considered unrestricted.
202     */
203    private static void verifyAxisAbbreviations(final CoordinateSystem cs, final String... abbreviations) {
204        final int dimension = Math.min(abbreviations.length, cs.getDimension());
205        for (int i=0; i<dimension; i++) {
206            final String expected = abbreviations[i];
207            if (expected != null) {
208                assertEquals("CoordinateSystemAxis.getAbbreviation()", expected, cs.getAxis(i).getAbbreviation());
209            }
210        }
211    }
212
213    /**
214     * Asserts the the given character sequence is either null or equals to the given value.
215     * This is used for optional elements like remarks.
216     *
217     * @param property  the property being tested, for producing a message in case of assertion failure.
218     * @param expected  the expected value.
219     * @param actual    the actual value.
220     */
221    private static void assertNullOrEquals(final String property, final String expected, final CharSequence actual) {
222        if (actual != null) {
223            assertEquals(property, expected, actual.toString());
224        }
225    }
226
227    /**
228     * Pre-process the WKT string before parsing.
229     * The default implementation performs the following changes for strict ISO 19162 compliance:
230     *
231     * <ul>
232     *   <li>Double the straight quotation marks {@code "} (U+0022).</li>
233     *   <li>Replace the left quotation marks {@code “} (U+201C) and right quotation marks {@code ”} (U+201D)
234     *       by straight quotation marks {@code "} (U+0022).</li>
235     * </ul>
236     *
237     * Subclasses can override this method if they wish to perform additional pre-processing.
238     * The use of left and right quotation marks is intended to make easier for subclasses to
239     * identify the beginning and end of quoted texts.
240     *
241     * @param  wkt  the Well-Known Text to pre-process.
242     * @return the Well-Known Text to parse.
243     */
244    protected String preprocessWKT(final String wkt) {
245        final StringBuilder b = new StringBuilder(wkt);
246        for (int i = wkt.lastIndexOf('"'); i >= 0; i = wkt.lastIndexOf('"', i-1)) {
247            b.insert(i, '"');
248        }
249        for (int i=0; i<b.length(); i++) {
250            final char c = b.charAt(i);
251            if (c == '“' || c == '”') {
252                b.setCharAt(i, '"');
253            }
254        }
255        return b.toString();
256    }
257
258    /**
259     * Parses the given WKT.
260     *
261     * @param  type  the expected object type.
262     * @param  text  the WKT string to parse.
263     * @return the parsed object.
264     * @throws FactoryException if an error occurred during the WKT parsing.
265     */
266    private <T extends CoordinateReferenceSystem> T parse(final Class<T> type, final String text) throws FactoryException {
267        assumeTrue("No CRSFactory.", crsFactory != null);
268        object = crsFactory.createFromWKT(preprocessWKT(text));
269        assertInstanceOf("CRSFactory.createFromWKT(String)", type, object);
270        return type.cast(object);
271    }
272
273    /**
274     * Parses a three-dimensional geodetic CRS.
275     * The WKT parsed by this test is (except for quote characters):
276     *
277     * <blockquote><pre>GEODCRS[“WGS 84”,
278     *  DATUM[“World Geodetic System 1984”,
279     *    ELLIPSOID[“WGS 84”, 6378137, 298.257223563,
280     *      LENGTHUNIT[“metre”,1.0]]],
281     *  CS[ellipsoidal,3],
282     *    AXIS[“(lat)”,north,ANGLEUNIT[“degree”,0.0174532925199433]],
283     *    AXIS[“(lon)”,east,ANGLEUNIT[“degree”,0.0174532925199433]],
284     *    AXIS[“ellipsoidal height (h)”,up,LENGTHUNIT[“metre”,1.0]]]</pre></blockquote>
285     *
286     * @throws FactoryException if an error occurred during the WKT parsing.
287     *
288     * @see <a href="http://docs.opengeospatial.org/is/12-063r5/12-063r5.html#56">OGC 12-063r5 §8.4 example 2</a>
289     */
290    @Test
291    public void testGeographic3D() throws FactoryException {
292        final GeodeticCRS crs = parse(GeodeticCRS.class,
293                "GEODCRS[“WGS 84”,\n" +
294                "  DATUM[“World Geodetic System 1984”,\n" +
295                "    ELLIPSOID[“WGS 84”, 6378137, 298.257223563,\n" +
296                "      LENGTHUNIT[“metre”,1.0]]],\n" +
297                "  CS[ellipsoidal,3],\n" +
298                "    AXIS[“(lat)”,north,ANGLEUNIT[“degree”,0.0174532925199433]],\n" +
299                "    AXIS[“(lon)”,east,ANGLEUNIT[“degree”,0.0174532925199433]],\n" +
300                "    AXIS[“ellipsoidal height (h)”,up,LENGTHUNIT[“metre”,1.0]]]");
301
302        if (isValidationEnabled) {
303            configurationTip = Configuration.Key.isValidationEnabled;
304            validators.validate(crs);
305            configurationTip = null;
306        }
307        verifyWGS84(crs, true, units.degree(), units.metre());
308        verifyAxisAbbreviations(crs.getCoordinateSystem(), null, null, "h");
309    }
310
311    /**
312     * Verifies the CRS name, datum and axes for {@code GEODCRS[“WGS 84”]}.
313     * This method does not verify axis abbreviations.
314     *
315     * @param  crs     the Coordinate Reference System which is expected to be WGS 84.
316     * @param  is3D    whether the CRS contains an ellipsoidal height axis.
317     * @param  degree  value of {@link org.opengis.test.Units#degree()} (for fetching it only once per test).
318     * @param  metre   value of {@link org.opengis.test.Units#metre()}  (for fetching it only once per test).
319     */
320    private void verifyWGS84(final GeodeticCRS crs, final boolean is3D,
321            final Unit<Angle> degree, final Unit<Length> metre)
322    {
323        final GeodeticDatum   datum;
324        final AxisDirection[] directions;
325
326        verifyIdentification (crs, "WGS 84", null);
327        verifyDatum          (datum = crs.getDatum(), "World Geodetic System 1984");
328        verifyFlattenedSphere(datum.getEllipsoid(), "WGS 84", 6378137, 298.257223563, metre);
329        verifyPrimeMeridian  (datum.getPrimeMeridian(), null, 0, degree);
330        directions = new AxisDirection[is3D ? 3 : 2];
331        directions[0] = NORTH;
332        directions[1] = EAST;
333        if (is3D) {
334            directions[2] = UP;
335        }
336        verifyCoordinateSystem(crs.getCoordinateSystem(), EllipsoidalCS.class, directions, degree, degree, metre);
337    }
338
339    /**
340     * Parses a geodetic CRS which contain a remark written using non-ASCII characters.
341     * The WKT parsed by this test is (except for quote characters):
342     *
343     * <blockquote><pre>GEODCRS[“S-95”,
344     *  DATUM[“Pulkovo 1995”,
345     *    ELLIPSOID[“Krassowsky 1940”, 6378245, 298.3,
346     *      LENGTHUNIT[“metre”,1.0]]],
347     *  CS[ellipsoidal,2],
348     *    AXIS[“latitude”,north,ORDER[1]],
349     *    AXIS[“longitude”,east,ORDER[2]],
350     *    ANGLEUNIT[“degree”,0.0174532925199433],
351     *  REMARK[“Система Геодеэических Координвт года 1995(СК-95)”]]</pre></blockquote>
352     *
353     * @throws FactoryException if an error occurred during the WKT parsing.
354     *
355     * @see <a href="http://docs.opengeospatial.org/is/12-063r5/12-063r5.html#34">OGC 12-063r5 §7.3.5 example 3</a>
356     */
357    @Test
358    public void testGeographicWithUnicode() throws FactoryException {
359        final GeodeticCRS crs = parse(GeodeticCRS.class,
360                "GEODCRS[“S-95”,\n" +
361                "  DATUM[“Pulkovo 1995”,\n" +
362                "    ELLIPSOID[“Krassowsky 1940”, 6378245, 298.3,\n" +
363                "      LENGTHUNIT[“metre”,1.0]]],\n" +
364                "  CS[ellipsoidal,2],\n" +
365                "    AXIS[“latitude”,north,ORDER[1]],\n" +
366                "    AXIS[“longitude”,east,ORDER[2]],\n" +
367                "    ANGLEUNIT[“degree”,0.0174532925199433],\n" +
368                "  REMARK[“Система Геодеэических Координвт года 1995(СК-95)”]]");
369
370        if (isValidationEnabled) {
371            configurationTip = Configuration.Key.isValidationEnabled;
372            validators.validate(crs);
373            configurationTip = null;
374        }
375        final Unit<Angle> degree = units.degree();
376        final Unit<Length> metre = units.metre();
377        final GeodeticDatum datum;
378
379        verifyIdentification  (crs, "S-95", null);
380        verifyDatum           (datum = crs.getDatum(), "Pulkovo 1995");
381        verifyFlattenedSphere (datum.getEllipsoid(), "Krassowsky 1940", 6378245, 298.3, metre);
382        verifyPrimeMeridian   (datum.getPrimeMeridian(), null, 0, degree);
383        verifyCoordinateSystem(crs.getCoordinateSystem(), EllipsoidalCS.class, new AxisDirection[] {NORTH,EAST}, degree);
384        assertNullOrEquals("remark", "Система Геодеэических Координвт года 1995(СК-95)", crs.getRemarks());
385    }
386
387    /**
388     * Parses a geodetic CRS which contains a remark and an identifier.
389     * The WKT parsed by this test is (except for quote characters):
390     *
391     * <blockquote><pre>GEODCRS[“NAD83”,
392     *  DATUM[“North American Datum 1983”,
393     *    ELLIPSOID[“GRS 1980”, 6378137, 298.257222101, LENGTHUNIT[“metre”,1.0]]],
394     *  CS[ellipsoidal,2],
395     *    AXIS[“latitude”,north],
396     *    AXIS[“longitude”,east],
397     *    ANGLEUNIT[“degree”,0.0174532925199433],
398     *  ID[“EPSG”,4269],
399     *  REMARK[“1986 realisation”]]</pre></blockquote>
400     *
401     * @throws FactoryException if an error occurred during the WKT parsing.
402     *
403     * @see <a href="http://docs.opengeospatial.org/is/12-063r5/12-063r5.html#56">OGC 12-063r5 §8.4 example 3</a>
404     */
405    @Test
406    public void testGeographicWithIdentifier() throws FactoryException {
407        final GeodeticCRS crs = parse(GeodeticCRS.class,
408                "GEODCRS[“NAD83”,\n" +
409                "  DATUM[“North American Datum 1983”,\n" +
410                "    ELLIPSOID[“GRS 1980”, 6378137, 298.257222101, LENGTHUNIT[“metre”,1.0]]],\n" +
411                "  CS[ellipsoidal,2],\n" +
412                "    AXIS[“latitude”,north],\n" +
413                "    AXIS[“longitude”,east],\n" +
414                "    ANGLEUNIT[“degree”,0.0174532925199433],\n" +
415                "  ID[“EPSG”,4269],\n" +
416                "  REMARK[“1986 realisation”]]");
417
418        if (isValidationEnabled) {
419            configurationTip = Configuration.Key.isValidationEnabled;
420            validators.validate(crs);
421            configurationTip = null;
422        }
423        verifyNAD23(crs, true, units.degree(), units.metre());
424        assertNullOrEquals("remark", "1986 realisation", crs.getRemarks());
425    }
426
427    /**
428     * Verifies the CRS name, datum and axes for {@code GEODCRS[“NAD83”]}.
429     * This method does not verify the remark, since it is not included in the components of {@code COMPOUNDCRS[…]}.
430     *
431     * @param  degree  value of {@link org.opengis.test.Units#degree()} (for fetching it only once per test).
432     * @param  metre   value of {@link org.opengis.test.Units#metre()}  (for fetching it only once per test).
433     */
434    private void verifyNAD23(final GeodeticCRS crs, final boolean hasIdentifier,
435            final Unit<Angle> degree, final Unit<Length> metre)
436    {
437        final GeodeticDatum datum;
438
439        verifyIdentification  (crs, "NAD83", hasIdentifier ? "4269" : null);
440        verifyDatum           (datum = crs.getDatum(), "North American Datum 1983");
441        verifyFlattenedSphere (datum.getEllipsoid(), "GRS 1980", 6378137, 298.257222101, metre);
442        verifyPrimeMeridian   (datum.getPrimeMeridian(), null, 0, degree);
443        verifyCoordinateSystem(crs.getCoordinateSystem(), EllipsoidalCS.class, new AxisDirection[] {NORTH,EAST}, degree);
444    }
445
446    /**
447     * Parses a geodetic CRS with a prime meridian other than Greenwich and all angular units in grads.
448     * The WKT parsed by this test is (except for quote characters):
449     *
450     * <blockquote><pre>GEODCRS[“NTF (Paris)”,
451     *  DATUM[“Nouvelle Triangulation Francaise”,
452     *    ELLIPSOID[“Clarke 1880 (IGN)”, 6378249.2, 293.4660213]],
453     *  PRIMEM[“Paris”,2.5969213],
454     *  CS[ellipsoidal,2],
455     *    AXIS[“latitude”,north,ORDER[1]],
456     *    AXIS[“longitude”,east,ORDER[2]],
457     *    ANGLEUNIT[“grad”,0.015707963267949],
458     *  REMARK[“Nouvelle Triangulation Française”]]</pre></blockquote>
459     *
460     * @throws FactoryException if an error occurred during the WKT parsing.
461     *
462     * @see <a href="http://docs.opengeospatial.org/is/12-063r5/12-063r5.html#56">OGC 12-063r5 §8.4 example 4</a>
463     */
464    @Test
465    public void testGeographicWithGradUnits() throws FactoryException {
466        final GeodeticCRS crs = parse(GeodeticCRS.class,
467                "GEODCRS[“NTF (Paris)”,\n" +
468                "  DATUM[“Nouvelle Triangulation Francaise”,\n" +
469                "    ELLIPSOID[“Clarke 1880 (IGN)”, 6378249.2, 293.4660213]],\n" +
470                "  PRIMEM[“Paris”,2.5969213],\n" +
471                "  CS[ellipsoidal,2],\n" +
472                "    AXIS[“latitude”,north,ORDER[1]],\n" +
473                "    AXIS[“longitude”,east,ORDER[2]],\n" +
474                "    ANGLEUNIT[“grad”,0.015707963267949],\n" +
475                "  REMARK[“Nouvelle Triangulation Française”]]");
476
477        if (isValidationEnabled) {
478            configurationTip = Configuration.Key.isValidationEnabled;
479            validators.validate(crs);
480            configurationTip = null;
481        }
482        final Unit<Angle>  grad  = units.grad();
483        final Unit<Length> metre = units.metre();
484        final GeodeticDatum datum;
485
486        verifyIdentification  (crs, "NTF (Paris)", null);
487        verifyDatum           (datum = crs.getDatum(), "Nouvelle Triangulation Francaise");
488        verifyFlattenedSphere (datum.getEllipsoid(), "Clarke 1880 (IGN)", 6378249.2, 293.4660213, metre);
489        verifyPrimeMeridian   (datum.getPrimeMeridian(), "Paris", 2.5969213, grad);
490        verifyCoordinateSystem(crs.getCoordinateSystem(), EllipsoidalCS.class, new AxisDirection[] {NORTH,EAST}, grad);
491        assertNullOrEquals("remark", "Nouvelle Triangulation Française", crs.getRemarks());
492    }
493
494    /**
495     * Parses a geodetic CRS with Cartesian coordinate system.
496     * The WKT parsed by this test is (except for quote characters):
497     *
498     * <blockquote><pre>GEODETICCRS[“JGD2000”,
499     *  DATUM[“Japanese Geodetic Datum 2000”,
500     *    ELLIPSOID[“GRS 1980”, 6378137, 298.257222101]],
501     *  CS[Cartesian,3],
502     *    AXIS[“(X)”,geocentricX],
503     *    AXIS[“(Y)”,geocentricY],
504     *    AXIS[“(Z)”,geocentricZ],
505     *    LENGTHUNIT[“metre”,1.0],
506     *  SCOPE[“Geodesy, topographic mapping and cadastre”],
507     *  AREA[“Japan”],
508     *  BBOX[17.09,122.38,46.05,157.64],
509     *  TIMEEXTENT[2002-04-01,2011-10-21],
510     *  ID[“EPSG”,4946,URI[“urn:ogc:def:crs:EPSG::4946”]],
511     *  REMARK[“注:JGD2000ジオセントリックは現在JGD2011に代わりました。”]]</pre></blockquote>
512     *
513     * @throws FactoryException if an error occurred during the WKT parsing.
514     *
515     * @see <a href="http://docs.opengeospatial.org/is/12-063r5/12-063r5.html#56">OGC 12-063r5 §8.4 example 1</a>
516     */
517    @Test
518    public void testGeocentric() throws FactoryException {
519        final GeodeticCRS crs = parse(GeodeticCRS.class,
520                "GEODETICCRS[“JGD2000”,\n" +
521                "  DATUM[“Japanese Geodetic Datum 2000”,\n" +
522                "    ELLIPSOID[“GRS 1980”, 6378137, 298.257222101]],\n" +
523                "  CS[Cartesian,3],\n" +
524                "    AXIS[“(X)”,geocentricX],\n" +
525                "    AXIS[“(Y)”,geocentricY],\n" +
526                "    AXIS[“(Z)”,geocentricZ],\n" +
527                "    LENGTHUNIT[“metre”,1.0],\n" +
528                "  SCOPE[“Geodesy, topographic mapping and cadastre”],\n" +
529                "  AREA[“Japan”],\n" +
530                "  BBOX[17.09,122.38,46.05,157.64],\n" +
531                "  TIMEEXTENT[2002-04-01,2011-10-21],\n" +
532                "  ID[“EPSG”,4946,URI[“urn:ogc:def:crs:EPSG::4946”]],\n" +
533                "  REMARK[“注:JGD2000ジオセントリックは現在JGD2011に代わりました。”]]");
534
535        if (isValidationEnabled) {
536            configurationTip = Configuration.Key.isValidationEnabled;
537            validators.validate(crs);
538            configurationTip = null;
539        }
540        final Unit<Angle> degree = units.degree();
541        final Unit<Length> metre = units.metre();
542        final GeodeticDatum datum;
543        final CoordinateSystem cs;
544        final Extent extent;
545
546        verifyIdentification   (crs, "JGD2000", "4946");
547        verifyDatum            (datum = crs.getDatum(), "Japanese Geodetic Datum 2000");
548        verifyFlattenedSphere  (datum.getEllipsoid(), "GRS 1980", 6378137, 298.257222101, metre);
549        verifyPrimeMeridian    (datum.getPrimeMeridian(), null, 0, degree);
550        verifyAxisAbbreviations(cs = crs.getCoordinateSystem(), "X", "Y", "Z");
551        verifyCoordinateSystem (cs, CartesianCS.class, new AxisDirection[] {GEOCENTRIC_X, GEOCENTRIC_Y, GEOCENTRIC_Z}, metre);
552        verifyGeographicExtent (extent = crs.getDomainOfValidity(), "Japan", 17.09, 122.38, 46.05, 157.64);
553        verifyTimeExtent       (extent, new Date(1017619200000L), new Date(1319155200000L), 1);
554        assertNullOrEquals("scope", "Geodesy, topographic mapping and cadastre", crs.getScope());
555        assertNullOrEquals("remark", "注:JGD2000ジオセントリックは現在JGD2011に代わりました。", crs.getRemarks());
556    }
557
558    /**
559     * Parses a projected CRS with linear units in metres and axes in (<var>Y</var>,<var>X</var>) order.
560     * The WKT parsed by this test is (except for quote characters):
561     *
562     * <blockquote><pre>PROJCRS[“ETRS89 Lambert Azimuthal Equal Area CRS”,
563     *  BASEGEODCRS[“ETRS89”,
564     *    DATUM[“ETRS89”,
565     *      ELLIPSOID[“GRS 80”, 6378137, 298.257222101, LENGTHUNIT[“metre”,1.0]]]],
566     *  CONVERSION[“LAEA”,
567     *    METHOD[“Lambert Azimuthal Equal Area”,ID[“EPSG”,9820]],
568     *    PARAMETER[“Latitude of natural origin”,  52.0, ANGLEUNIT[“degree”,0.0174532925199433]],
569     *    PARAMETER[“Longitude of natural origin”, 10.0, ANGLEUNIT[“degree”,0.0174532925199433]],
570     *    PARAMETER[“False easting”,  4321000.0, LENGTHUNIT[“metre”,1.0]],
571     *    PARAMETER[“False northing”, 3210000.0, LENGTHUNIT[“metre”,1.0]]],
572     *  CS[Cartesian,2],
573     *    AXIS[“(Y)”,north,ORDER[1]],
574     *    AXIS[“(X)”,east,ORDER[2]],
575     *    LENGTHUNIT[“metre”,1.0],
576     *  SCOPE[“Description of a purpose”],
577     *  AREA[“An area description”],
578     *  ID[“EuroGeographics”,“ETRS-LAEA”]]</pre></blockquote>
579     *
580     * @throws FactoryException if an error occurred during the WKT parsing.
581     *
582     * @see <a href="http://docs.opengeospatial.org/is/12-063r5/12-063r5.html#68">OGC 12-063r5 §9.5 example 1</a>
583     */
584    @Test
585    public void testProjectedYX() throws FactoryException {
586        final ProjectedCRS crs = parse(ProjectedCRS.class,
587                "PROJCRS[“ETRS89 Lambert Azimuthal Equal Area CRS”,\n" +
588                "  BASEGEODCRS[“ETRS89”,\n" +
589                "    DATUM[“ETRS89”,\n" +
590                "      ELLIPSOID[“GRS 80”, 6378137, 298.257222101, LENGTHUNIT[“metre”,1.0]]]],\n" +
591                "  CONVERSION[“LAEA”,\n" +
592                "    METHOD[“Lambert Azimuthal Equal Area”,ID[“EPSG”,9820]],\n" +
593                "    PARAMETER[“Latitude of natural origin”,  52.0, ANGLEUNIT[“degree”,0.0174532925199433]],\n" +
594                "    PARAMETER[“Longitude of natural origin”, 10.0, ANGLEUNIT[“degree”,0.0174532925199433]],\n" +
595                "    PARAMETER[“False easting”,  4321000.0, LENGTHUNIT[“metre”,1.0]],\n" +
596                "    PARAMETER[“False northing”, 3210000.0, LENGTHUNIT[“metre”,1.0]]],\n" +
597                "  CS[Cartesian,2],\n" +
598                "    AXIS[“(Y)”,north,ORDER[1]],\n" +
599                "    AXIS[“(X)”,east,ORDER[2]],\n" +
600                "    LENGTHUNIT[“metre”,1.0],\n" +
601                "  SCOPE[“Description of a purpose”],\n" +
602                "  AREA[“An area description”],\n" +
603                "  ID[“EuroGeographics”,“ETRS-LAEA”]]");
604
605        if (isValidationEnabled) {
606            configurationTip = Configuration.Key.isValidationEnabled;
607            validators.validate(crs);
608            configurationTip = null;
609        }
610        final Unit<Angle> degree = units.degree();
611        final Unit<Length> metre = units.metre();
612        final GeodeticDatum datum;
613        final CoordinateSystem cs;
614
615        verifyIdentification   (crs, "ETRS89 Lambert Azimuthal Equal Area CRS", "ETRS-LAEA");
616        verifyIdentification   (crs.getBaseCRS(), "ETRS89", null);
617        verifyIdentification   (crs.getConversionFromBase(), "LAEA", null);
618        verifyDatum            (datum = crs.getDatum(), "ETRS89");
619        verifyFlattenedSphere  (datum.getEllipsoid(), "GRS 80", 6378137, 298.257222101, metre);
620        verifyPrimeMeridian    (datum.getPrimeMeridian(), null, 0, degree);
621        verifyAxisAbbreviations(cs = crs.getCoordinateSystem(), "Y", "X");
622        verifyCoordinateSystem (cs, CartesianCS.class, new AxisDirection[] {NORTH,EAST}, metre);
623
624        final ParameterValueGroup group = crs.getConversionFromBase().getParameterValues();
625        verifyParameter(group, "Latitude of natural origin",  52.0, degree);
626        verifyParameter(group, "Longitude of natural origin", 10.0, degree);
627        verifyParameter(group, "False easting",          4321000.0, metre);
628        verifyParameter(group, "False northing",         3210000.0, metre);
629
630        verifyGeographicExtent(crs.getDomainOfValidity(), "An area description", NaN, NaN, NaN, NaN);
631        assertNullOrEquals("scope", "Description of a purpose", crs.getScope());
632    }
633
634    /**
635     * Parses a projected CRS with linear units in feet.
636     * The WKT parsed by this test is (except for quote characters):
637     *
638     * <blockquote><pre>PROJCRS[“NAD27 / Texas South Central”,
639     *  BASEGEODCRS[“NAD27”,
640     *    DATUM[“North American Datum 1927”,
641     *      ELLIPSOID[“Clarke 1866”, 20925832.164, 294.97869821,
642     *        LENGTHUNIT[“US survey foot”,0.304800609601219]]]],
643     *  CONVERSION[“Texas South Central SPCS27”,
644     *    METHOD[“Lambert Conic Conformal (2SP)”,ID[“EPSG”,9802]],
645     *    PARAMETER[“Latitude of false origin”,27.83333333333333,
646     *      ANGLEUNIT[“degree”,0.0174532925199433],ID[“EPSG”,8821]],
647     *    PARAMETER[“Longitude of false origin”,-99.0,
648     *      ANGLEUNIT[“degree”,0.0174532925199433],ID[“EPSG”,8822]],
649     *    PARAMETER[“Latitude of 1st standard parallel”,28.383333333333,
650     *      ANGLEUNIT[“degree”,0.0174532925199433],ID[“EPSG”,8823]],
651     *    PARAMETER[“Latitude of 2nd standard parallel”,30.283333333333,
652     *      ANGLEUNIT[“degree”,0.0174532925199433],ID[“EPSG”,8824]],
653     *    PARAMETER[“Easting at false origin”,2000000.0,
654     *      LENGTHUNIT[“US survey foot”,0.304800609601219],ID[“EPSG”,8826]],
655     *    PARAMETER[“Northing at false origin”,0.0,
656     *      LENGTHUNIT[“US survey foot”,0.304800609601219],ID[“EPSG”,8827]]],
657     *  CS[Cartesian,2],
658     *    AXIS[“(x)”,east],
659     *    AXIS[“(y)”,north],
660     *    LENGTHUNIT[“US survey foot”,0.304800609601219],
661     *  REMARK[“Fundamental point: Meade’s Ranch KS, latitude 39°13'26.686"N, longitude 98°32'30.506"W.”]]</pre></blockquote>
662     *
663     * @throws FactoryException if an error occurred during the WKT parsing.
664     *
665     * @see <a href="http://docs.opengeospatial.org/is/12-063r5/12-063r5.html#68">OGC 12-063r5 §9.5 example 2</a>
666     */
667    @Test
668    public void testProjectedWithFootUnits() throws FactoryException {
669        final ProjectedCRS crs = parse(ProjectedCRS.class,
670                "PROJCRS[“NAD27 / Texas South Central”,\n" +
671                "  BASEGEODCRS[“NAD27”,\n" +
672                "    DATUM[“North American Datum 1927”,\n" +
673                "      ELLIPSOID[“Clarke 1866”, 20925832.164, 294.97869821,\n" +
674                "        LENGTHUNIT[“US survey foot”,0.304800609601219]]]],\n" +
675                "  CONVERSION[“Texas South Central SPCS27”,\n" +
676                "    METHOD[“Lambert Conic Conformal (2SP)”,ID[“EPSG”,9802]],\n" +
677                "    PARAMETER[“Latitude of false origin”,27.83333333333333,\n" +
678                "      ANGLEUNIT[“degree”,0.0174532925199433],ID[“EPSG”,8821]],\n" +
679                "    PARAMETER[“Longitude of false origin”,-99.0,\n" +
680                "      ANGLEUNIT[“degree”,0.0174532925199433],ID[“EPSG”,8822]],\n" +
681                "    PARAMETER[“Latitude of 1st standard parallel”,28.383333333333,\n" +
682                "      ANGLEUNIT[“degree”,0.0174532925199433],ID[“EPSG”,8823]],\n" +
683                "    PARAMETER[“Latitude of 2nd standard parallel”,30.283333333333,\n" +
684                "      ANGLEUNIT[“degree”,0.0174532925199433],ID[“EPSG”,8824]],\n" +
685                "    PARAMETER[“Easting at false origin”,2000000.0,\n" +
686                "      LENGTHUNIT[“US survey foot”,0.304800609601219],ID[“EPSG”,8826]],\n" +
687                "    PARAMETER[“Northing at false origin”,0.0,\n" +
688                "      LENGTHUNIT[“US survey foot”,0.304800609601219],ID[“EPSG”,8827]]],\n" +
689                "  CS[Cartesian,2],\n" +
690                "    AXIS[“(X)”,east],\n" +
691                "    AXIS[“(Y)”,north],\n" +
692                "    LENGTHUNIT[“US survey foot”,0.304800609601219],\n" +
693                "  REMARK[“Fundamental point: Meade’s Ranch KS, latitude 39°13'26.686\"N, longitude 98°32'30.506\"W.”]]");
694
695        if (isValidationEnabled) {
696            configurationTip = Configuration.Key.isValidationEnabled;
697            validators.validate(crs);
698            configurationTip = null;
699        }
700        final Unit<Angle>  degree       = units.degree();
701        final Unit<Length> footSurveyUS = units.footSurveyUS();
702        final CoordinateSystem cs;
703
704        verifyAxisAbbreviations(cs = crs.getCoordinateSystem(), "X", "Y");
705        verifyCoordinateSystem (cs, CartesianCS.class, new AxisDirection[] {EAST,NORTH}, footSurveyUS);
706        verifyTexasSouthCentral(crs, degree, footSurveyUS);
707        assertNullOrEquals("remark", "Fundamental point: Meade’s Ranch KS, latitude 39°13'26.686\"N, longitude 98°32'30.506\"W.", crs.getRemarks());
708    }
709
710    /**
711     * Verifies the CRS name, datum and conversion parameters for {@code PROJCRS[“NAD27 / Texas South Central”]}.
712     * This method does not verify the axes and remark, since they are not specified in {@code BASEPROJCRS[…]}.
713     *
714     * @param  degree        value of {@link org.opengis.test.Units#degree()} (for fetching it only once per test).
715     * @param  footSurveyUS  value of {@link org.opengis.test.Units#footSurveyUS()}.
716     */
717    private void verifyTexasSouthCentral(final ProjectedCRS crs,
718            final Unit<Angle> degree, final Unit<Length> footSurveyUS)
719    {
720        final GeodeticDatum datum;
721
722        verifyIdentification   (crs, "NAD27 / Texas South Central", null);
723        verifyIdentification   (crs.getBaseCRS(), "NAD27", null);
724        verifyIdentification   (crs.getConversionFromBase(), "Texas South Central SPCS27", null);
725        verifyDatum            (datum = crs.getDatum(), "North American Datum 1927");
726        verifyFlattenedSphere  (datum.getEllipsoid(), "Clarke 1866", 20925832.164, 294.97869821, footSurveyUS);
727        verifyPrimeMeridian    (datum.getPrimeMeridian(), null, 0, degree);
728
729        final ParameterValueGroup group = crs.getConversionFromBase().getParameterValues();
730        verifyParameter(group, "Latitude of false origin",          27.83333333333333, degree);
731        verifyParameter(group, "Longitude of false origin",        -99.0,              degree);
732        verifyParameter(group, "Latitude of 1st standard parallel", 28.383333333333,   degree);
733        verifyParameter(group, "Latitude of 2nd standard parallel", 30.283333333333,   degree);
734        verifyParameter(group, "Easting at false origin",           2000000.0,         footSurveyUS);
735        verifyParameter(group, "Northing at false origin",          0.0,               footSurveyUS);
736    }
737
738    /**
739     * Parses a projected CRS with implicit parameter units.
740     * The WKT parsed by this test is (except for quote characters and the line feed in {@code REMARK}):
741     *
742     * <blockquote><pre>PROJCRS[“NAD83 UTM 10”,
743     *  BASEGEODCRS[“NAD83(86)”,
744     *    DATUM[“North American Datum 1983”,
745     *      ELLIPSOID[“GRS 1980”,6378137,298.257222101]],
746     *    ANGLEUNIT[“degree”,0.0174532925199433],
747     *    PRIMEM[“Greenwich”,0]],
748     *  CONVERSION[“UTM zone 10N”,ID[“EPSG”,16010],
749     *    METHOD[“Transverse Mercator”],
750     *    PARAMETER[“Latitude of natural origin”,0.0],
751     *    PARAMETER[“Longitude of natural origin”,-123.0],
752     *    PARAMETER[“Scale factor”,0.9996],
753     *    PARAMETER[“False easting”,500000.0],
754     *    PARAMETER[“False northing”,0.0]],
755     *  CS[Cartesian,2],
756     *    AXIS[“(E)”,east,ORDER[1]],
757     *    AXIS[“(N)”,north,ORDER[2]],
758     *    LENGTHUNIT[“metre”,1.0],
759     *  REMARK[“In this example units are implied. This is allowed for backward compatibility.
760     *          It is recommended that units are explicitly given in the string,
761     *          as in the previous two examples.”]]</pre></blockquote>
762     *
763     * @throws FactoryException if an error occurred during the WKT parsing.
764     *
765     * @see <a href="http://docs.opengeospatial.org/is/12-063r5/12-063r5.html#68">OGC 12-063r5 §9.5 example 3</a>
766     */
767    @Test
768    public void testProjectedWithImplicitParameterUnits() throws FactoryException {
769        final ProjectedCRS crs = parse(ProjectedCRS.class,
770                "PROJCRS[“NAD83 UTM 10”,\n" +
771                "  BASEGEODCRS[“NAD83(86)”,\n" +
772                "    DATUM[“North American Datum 1983”,\n" +
773                "      ELLIPSOID[“GRS 1980”, 6378137, 298.257222101]],\n" +
774                "    ANGLEUNIT[“degree”,0.0174532925199433],\n" +
775                "    PRIMEM[“Greenwich”,0]],\n" +
776                "  CONVERSION[“UTM zone 10N”,ID[“EPSG”,16010],\n" +
777                "    METHOD[“Transverse Mercator”],\n" +
778                "    PARAMETER[“Latitude of natural origin”,0.0],\n" +
779                "    PARAMETER[“Longitude of natural origin”,-123.0],\n" +
780                "    PARAMETER[“Scale factor”,0.9996],\n" +
781                "    PARAMETER[“False easting”,500000.0],\n" +
782                "    PARAMETER[“False northing”,0.0]],\n" +
783                "  CS[Cartesian,2],\n" +
784                "    AXIS[“(E)”,east,ORDER[1]],\n" +
785                "    AXIS[“(N)”,north,ORDER[2]],\n" +
786                "    LENGTHUNIT[“metre”,1.0],\n" +
787                "  REMARK[“In this example units are implied. This is allowed for backward compatibility." +
788                         " It is recommended that units are explicitly given in the string," +
789                         " as in the previous two examples.”]]");
790
791        if (isValidationEnabled) {
792            configurationTip = Configuration.Key.isValidationEnabled;
793            validators.validate(crs);
794            configurationTip = null;
795        }
796        final Unit<Angle> degree = units.degree();
797        final Unit<Length> metre = units.metre();
798        final GeodeticDatum datum;
799        final CoordinateSystem cs;
800
801        verifyIdentification   (crs, "NAD83 UTM 10", null);
802        verifyIdentification   (crs.getBaseCRS(), "NAD83(86)", null);
803        verifyIdentification   (crs.getConversionFromBase(), "UTM zone 10N", "16010");
804        verifyDatum            (datum = crs.getDatum(), "North American Datum 1983");
805        verifyFlattenedSphere  (datum.getEllipsoid(), "GRS 1980", 6378137, 298.257222101, metre);
806        verifyPrimeMeridian    (datum.getPrimeMeridian(), null, 0, degree);
807        verifyAxisAbbreviations(cs = crs.getCoordinateSystem(), "E", "N");
808        verifyCoordinateSystem (cs, CartesianCS.class, new AxisDirection[] {EAST,NORTH}, metre);
809
810        final ParameterValueGroup group = crs.getConversionFromBase().getParameterValues();
811        verifyParameter(group, "Latitude of natural origin",     0.0, degree);
812        verifyParameter(group, "Longitude of natural origin", -123.0, degree);
813        verifyParameter(group, "Scale factor",                0.9996, units.one());
814        verifyParameter(group, "False easting",             500000.0, metre);
815        verifyParameter(group, "False northing",                 0.0, metre);
816    }
817
818    /**
819     * Parses a vertical CRS.
820     * The WKT parsed by this test is (except for quote characters):
821     *
822     * <blockquote><pre>VERTCRS[“NAVD88”,
823     *  VDATUM[“North American Vertical Datum 1988”],
824     *  CS[vertical,1],
825     *    AXIS[“gravity-related height (H)”,up],LENGTHUNIT[“metre”,1.0]]</pre></blockquote>
826     *
827     * @throws FactoryException if an error occurred during the WKT parsing.
828     *
829     * @see <a href="http://docs.opengeospatial.org/is/12-063r5/12-063r5.html#73">OGC 12-063r5 §10.4</a>
830     */
831    @Test
832    public void testVertical() throws FactoryException {
833        final VerticalCRS crs = parse(VerticalCRS.class,
834                "VERTCRS[“NAVD88”,\n" +
835                "  VDATUM[“North American Vertical Datum 1988”],\n" +
836                "  CS[vertical,1],\n" +
837                "    AXIS[“gravity-related height (H)”,up],LENGTHUNIT[“metre”,1.0]]");
838
839        if (isValidationEnabled) {
840            configurationTip = Configuration.Key.isValidationEnabled;
841            validators.validate(crs);
842            configurationTip = null;
843        }
844        verifyNAD28(crs, units.metre());
845    }
846
847    /**
848     * Verifies the CRS name, datum and axis for {@code VERTCRS[“NAD88”]}.
849     *
850     * @param  metre  value of {@link org.opengis.test.Units#metre()}  (for fetching it only once per test).
851     */
852    private void verifyNAD28(final VerticalCRS crs, final Unit<Length> metre) {
853        verifyIdentification(crs, "NAVD88", null);
854        verifyDatum(crs.getDatum(), "North American Vertical Datum 1988");
855        verifyCoordinateSystem (crs.getCoordinateSystem(), VerticalCS.class, new AxisDirection[] {UP}, metre);
856        verifyAxisAbbreviations(crs.getCoordinateSystem(), "H");
857    }
858
859    /**
860     * Parses a temporal CRS.
861     * The WKT parsed by this test is (except for quote characters):
862     *
863     * <blockquote><pre>TIMECRS[“GPS Time”,
864     *   TDATUM[“Time origin”,TIMEORIGIN[1980-01-01T00:00:00.0Z]],
865     *   CS[temporal,1],AXIS[“time”,future],TIMEUNIT[“day”,86400.0]]</pre></blockquote>
866     *
867     * @throws FactoryException if an error occurred during the WKT parsing.
868     *
869     * @see <a href="http://docs.opengeospatial.org/is/12-063r5/12-063r5.html#92">OGC 12-063r5 §14.4</a>
870     */
871    @Test
872    public void testTemporal() throws FactoryException {
873        final TemporalCRS crs = parse(TemporalCRS.class,
874                "TIMECRS[“GPS Time”,\n" +
875                "  TDATUM[“Time origin”,TIMEORIGIN[1980-01-01T00:00:00.0Z]],\n" +
876                "  CS[temporal,1],AXIS[“time”,future],TIMEUNIT[“day”,86400.0]]");
877
878        if (isValidationEnabled) {
879            configurationTip = Configuration.Key.isValidationEnabled;
880            validators.validate(crs);
881            configurationTip = null;
882        }
883        verifyGPSTime(crs);
884    }
885
886    /**
887     * Verifies the CRS name, datum and axis for {@code TIMECRS[“GPS Time”]}.
888     */
889    private void verifyGPSTime(final TemporalCRS crs) {
890        verifyIdentification   (crs, "GPS Time", null);
891        verifyDatum            (crs.getDatum(), "Time origin");
892        verifyCoordinateSystem (crs.getCoordinateSystem(), TimeCS.class, new AxisDirection[] {FUTURE}, units.day());
893        assertEquals("TimeOrigin", new Date(315532800000L), crs.getDatum().getOrigin());
894    }
895
896    /**
897     * Parses a parametric CRS.
898     * The WKT parsed by this test is (except for quote characters):
899     *
900     * <blockquote><pre>PARAMETRICCRS[“WMO standard atmosphere layer 0”,
901     *   PDATUM[“Mean Sea Level”,ANCHOR[“1013.25 hPa at 15°C”]],
902     *   CS[parametric,1],
903     *   AXIS[“pressure (hPa)”,up],
904     *   PARAMETRICUNIT[“hPa”,100.0]]</pre></blockquote>
905     *
906     * @throws FactoryException if an error occurred during the WKT parsing.
907     *
908     * @see <a href="http://docs.opengeospatial.org/is/12-063r5/12-063r5.html#87">OGC 12-063r5 §13.4 example 1</a>
909     */
910    @Test
911    public void testParametric() throws FactoryException {
912        final ParametricCRS crs = parse(ParametricCRS.class,
913                "PARAMETRICCRS[“WMO standard atmosphere layer 0”,\n" +
914                "PDATUM[“Mean Sea Level”,ANCHOR[“1013.25 hPa at 15°C”]],\n" +
915                "CS[parametric,1],\n" +
916                "AXIS[“pressure (hPa)”,up],PARAMETRICUNIT[“hPa”,100.0]]");
917
918        if (isValidationEnabled) {
919            configurationTip = Configuration.Key.isValidationEnabled;
920            validators.validate(crs);
921            configurationTip = null;
922        }
923        verifyIdentification   (crs, "WMO standard atmosphere layer 0", null);
924        verifyDatum            (crs.getDatum(), "Mean Sea Level");
925        verifyCoordinateSystem (crs.getCoordinateSystem(), ParametricCS.class, new AxisDirection[] {UP}, units.hectopascal());
926    }
927
928    /**
929     * Parses an engineering CRS with North and West axis directions.
930     * The WKT parsed by this test is (except for quote characters):
931     *
932     * <blockquote><pre>ENGINEERINGCRS[“Astra Minas Grid”,
933     *  ENGINEERINGDATUM[“Astra Minas”],
934     *  CS[Cartesian,2],
935     *    AXIS[“northing (X)”,north,ORDER[1]],
936     *    AXIS[“westing (Y)”,west,ORDER[2]],
937     *    LENGTHUNIT[“metre”,1.0],
938     *  ID[“EPSG”,5800]]</pre></blockquote>
939     *
940     * @throws FactoryException if an error occurred during the WKT parsing.
941     *
942     * @see <a href="http://docs.opengeospatial.org/is/12-063r5/12-063r5.html#78">OGC 12-063r5 §11.4 example 2</a>
943     */
944    @Test
945    public void testEngineering() throws FactoryException {
946        final EngineeringCRS crs = parse(EngineeringCRS.class,
947                "ENGINEERINGCRS[“Astra Minas Grid”,\n" +
948                "  ENGINEERINGDATUM[“Astra Minas”],\n" +
949                "  CS[Cartesian,2],\n" +
950                "    AXIS[“northing (X)”,north,ORDER[1]],\n" +
951                "    AXIS[“westing (Y)”,west,ORDER[2]],\n" +
952                "    LENGTHUNIT[“metre”,1.0],\n" +
953                "  ID[“EPSG”,5800]]");
954
955        if (isValidationEnabled) {
956            configurationTip = Configuration.Key.isValidationEnabled;
957            validators.validate(crs);
958            configurationTip = null;
959        }
960        final Unit<Length> metre = units.metre();
961        final CoordinateSystem cs;
962
963        verifyIdentification   (crs, "Astra Minas Grid", "5800");
964        verifyDatum            (crs.getDatum(), "Astra Minas");
965        verifyAxisAbbreviations(cs = crs.getCoordinateSystem(), "X", "Y");
966        verifyCoordinateSystem (cs, CartesianCS.class, new AxisDirection[] {NORTH,WEST}, metre);
967    }
968
969    /**
970     * Parses an engineering CRS with South-West and South-East axis directions.
971     * The WKT parsed by this test is (except for quote characters):
972     *
973     * <blockquote><pre>ENGCRS[“A construction site CRS”,
974     *  EDATUM[“P1”,ANCHOR[“Peg in south corner”]],
975     *  CS[Cartesian,2],
976     *    AXIS[“site east”,southWest,ORDER[1]],
977     *    AXIS[“site north”,southEast,ORDER[2]],
978     *    LENGTHUNIT[“metre”,1.0],
979     *  TIMEEXTENT[“date/time t1”,“date/time t2”]]</pre></blockquote>
980     *
981     * @throws FactoryException if an error occurred during the WKT parsing.
982     *
983     * @see <a href="http://docs.opengeospatial.org/is/12-063r5/12-063r5.html#78">OGC 12-063r5 §11.4 example 1</a>
984     */
985    @Test
986    public void testEngineeringRotated() throws FactoryException {
987        final EngineeringCRS crs = parse(EngineeringCRS.class,
988                "ENGCRS[“A construction site CRS”,\n" +
989                "  EDATUM[“P1”,ANCHOR[“Peg in south corner”]],\n" +
990                "  CS[Cartesian,2],\n" +
991                "    AXIS[“site east”,southWest,ORDER[1]],\n" +
992                "    AXIS[“site north”,southEast,ORDER[2]],\n" +
993                "    LENGTHUNIT[“metre”,1.0],\n" +
994                "  TIMEEXTENT[“date/time t1”,“date/time t2”]]");
995
996        if (isValidationEnabled) {
997            configurationTip = Configuration.Key.isValidationEnabled;
998            validators.validate(crs);
999            configurationTip = null;
1000        }
1001        final Unit<Length> metre = units.metre();
1002
1003        verifyIdentification   (crs, "A construction site CRS", null);
1004        verifyDatum            (crs.getDatum(), "P1");
1005        assertNullOrEquals     ("datum.anchor", "Peg in south corner", crs.getDatum().getAnchorPoint());
1006        verifyCoordinateSystem (crs.getCoordinateSystem(), CartesianCS.class, new AxisDirection[] {SOUTH_WEST, SOUTH_EAST}, metre);
1007    }
1008
1009    /**
1010     * Parses an engineering CRS anchored to a ship.
1011     * The WKT parsed by this test is (except for quote characters):
1012     *
1013     * <blockquote><pre>ENGCRS[“A ship-centred CRS”,
1014     *  EDATUM[“Ship reference point”,ANCHOR[“Centre of buoyancy”]],
1015     *  CS[Cartesian,3],
1016     *    AXIS[“(x)”,forward],
1017     *    AXIS[“(y)”,starboard],
1018     *    AXIS[“(z)”,down],
1019     *    LENGTHUNIT[“metre”,1.0]]</pre></blockquote>
1020     *
1021     * @throws FactoryException if an error occurred during the WKT parsing.
1022     *
1023     * @see <a href="http://docs.opengeospatial.org/is/12-063r5/12-063r5.html#78">OGC 12-063r5 §11.4 example 2</a>
1024     */
1025    @Test
1026    public void testEngineeringForShip() throws FactoryException {
1027        final EngineeringCRS crs = parse(EngineeringCRS.class,
1028                "ENGCRS[“A ship-centred CRS”,\n" +
1029                "  EDATUM[“Ship reference point”,ANCHOR[“Centre of buoyancy”]],\n" +
1030                "  CS[Cartesian,3],\n" +
1031                "    AXIS[“(x)”,forward],\n" +
1032                "    AXIS[“(y)”,starboard],\n" +
1033                "    AXIS[“(z)”,down],\n" +
1034                "    LENGTHUNIT[“metre”,1.0]]");
1035
1036        if (isValidationEnabled) {
1037            configurationTip = Configuration.Key.isValidationEnabled;
1038            validators.validate(crs);
1039            configurationTip = null;
1040        }
1041        final Unit<Length> metre = units.metre();
1042        final CoordinateSystem cs;
1043
1044        verifyIdentification   (crs, "A ship-centred CRS", null);
1045        verifyDatum            (crs.getDatum(), "Ship reference point");
1046        assertNullOrEquals     ("datum.anchor", "Centre of buoyancy", crs.getDatum().getAnchorPoint());
1047        verifyAxisAbbreviations(cs = crs.getCoordinateSystem(), "x", "y", "z");
1048        verifyCoordinateSystem (cs, CartesianCS.class, new AxisDirection[] {valueOf("forward"), valueOf("starboard"), DOWN}, metre);
1049    }
1050
1051    /**
1052     * Parses a derived geodetic CRS.
1053     * The WKT parsed by this test is (except for quote characters):
1054     *
1055     * <blockquote><pre>GEODCRS[“ETRS89 Lambert Azimuthal Equal Area CRS”,
1056     *  BASEGEODCRS[“WGS 84”,
1057     *    DATUM[“WGS 84”,
1058     *      ELLIPSOID[“WGS 84”,6378137,298.2572236,LENGTHUNIT[“metre”,1.0]]]],
1059     *  DERIVINGCONVERSION[“Atlantic pole”,
1060     *    METHOD[“Pole rotation”,ID[“Authority”,1234]],
1061     *    PARAMETER[“Latitude of rotated pole”,52.0,
1062     *      ANGLEUNIT[“degree”,0.0174532925199433]],
1063     *    PARAMETER[“Longitude of rotated pole”,-30.0,
1064     *      ANGLEUNIT[“degree”,0.0174532925199433]],
1065     *    PARAMETER[“Axis rotation”,-25.0,
1066     *      ANGLEUNIT[“degree”,0.0174532925199433]]],
1067     *  CS[ellipsoidal,2],
1068     *    AXIS[“latitude”,north,ORDER[1]],
1069     *    AXIS[“longitude”,east,ORDER[2]],
1070     *    ANGLEUNIT[“degree”,0.0174532925199433]]</pre></blockquote>
1071     *
1072     * @throws FactoryException if an error occurred during the WKT parsing.
1073     *
1074     * @see <a href="http://docs.opengeospatial.org/is/12-063r5/12-063r5.html#103">OGC 12-063r5 §15.3.5 example 3</a>
1075     */
1076    @Test
1077    public void testDerivedGeodetic() throws FactoryException {
1078        final DerivedCRS crs = parse(DerivedCRS.class,
1079                "GEODCRS[“ETRS89 Lambert Azimuthal Equal Area CRS”,\n" +
1080                "  BASEGEODCRS[“WGS 84”,\n" +
1081                "    DATUM[“WGS 84”,\n" +
1082                "      ELLIPSOID[“WGS 84”, 6378137, 298.2572236, LENGTHUNIT[“metre”,1.0]]]],\n" +
1083                "  DERIVINGCONVERSION[“Atlantic pole”,\n" +
1084                "    METHOD[“Pole rotation”,ID[“Authority”,1234]],\n" +
1085                "    PARAMETER[“Latitude of rotated pole”,52.0,\n" +
1086                "      ANGLEUNIT[“degree”,0.0174532925199433]],\n" +
1087                "    PARAMETER[“Longitude of rotated pole”,-30.0,\n" +
1088                "      ANGLEUNIT[“degree”,0.0174532925199433]],\n" +
1089                "    PARAMETER[“Axis rotation”,-25.0,\n" +
1090                "      ANGLEUNIT[“degree”,0.0174532925199433]]],\n" +
1091                "  CS[ellipsoidal,2],\n" +
1092                "    AXIS[“latitude”,north,ORDER[1]],\n" +
1093                "    AXIS[“longitude”,east,ORDER[2]],\n" +
1094                "    ANGLEUNIT[“degree”,0.0174532925199433]]");
1095
1096        if (isValidationEnabled) {
1097            configurationTip = Configuration.Key.isValidationEnabled;
1098            validators.validate(crs);
1099            configurationTip = null;
1100        }
1101        final Unit<Angle> degree = units.degree();
1102        final Unit<Length> metre = units.metre();
1103        final GeodeticDatum datum;
1104
1105        verifyIdentification  (crs, "ETRS89 Lambert Azimuthal Equal Area CRS", null);
1106        verifyCoordinateSystem(crs.getCoordinateSystem(), EllipsoidalCS.class, new AxisDirection[] {NORTH,EAST}, degree);
1107
1108        assertInstanceOf("baseCRS", GeodeticCRS.class, crs.getBaseCRS());
1109        verifyDatum           (datum = ((GeodeticCRS) crs.getBaseCRS()).getDatum(), "WGS 84");
1110        verifyFlattenedSphere (datum.getEllipsoid(), "WGS 84", 6378137, 298.2572236, metre);
1111        verifyPrimeMeridian   (datum.getPrimeMeridian(), null, 0, degree);
1112
1113        final ParameterValueGroup group = crs.getConversionFromBase().getParameterValues();
1114        verifyParameter(group, "Latitude of rotated pole",   52, degree);
1115        verifyParameter(group, "Longitude of rotated pole", -30, degree);
1116        verifyParameter(group, "Axis rotation",             -25, degree);
1117    }
1118
1119    /**
1120     * Parses a derived engineering CRS having a base geodetic CRS.
1121     * The WKT parsed by this test is (except for quote characters):
1122     *
1123     * <blockquote><pre>ENGCRS[“Topocentric example A”,
1124     *  BASEGEODCRS[“WGS 84”,
1125     *    DATUM[“WGS 84”,
1126     *      ELLIPSOID[“WGS 84”, 6378137, 298.2572236, LENGTHUNIT[“metre”,1.0]]]],
1127     *  DERIVINGCONVERSION[“Topocentric example A”,
1128     *    METHOD[“Geographic/topocentric conversions”,ID[“EPSG”,9837]],
1129     *    PARAMETER[“Latitude of topocentric origin”,55.0,
1130     *      ANGLEUNIT[“degree”,0.0174532925199433]],
1131     *    PARAMETER[“Longitude of topocentric origin”,5.0,
1132     *      ANGLEUNIT[“degree”,0.0174532925199433]],
1133     *    PARAMETER[“Ellipsoidal height of topocentric origin”,0.0,
1134     *      LENGTHUNIT[“metre”,1.0]]],
1135     *  CS[Cartesian,3],
1136     *    AXIS[“Topocentric East (U)”,east,ORDER[1]],
1137     *    AXIS[“Topocentric North (V)”,north,ORDER[2]],
1138     *    AXIS[“Topocentric height (W)”,up,ORDER[3]],
1139     *    LENGTHUNIT[“metre”,1.0]]</pre></blockquote>
1140     *
1141     * @throws FactoryException if an error occurred during the WKT parsing.
1142     *
1143     * @see <a href="http://docs.opengeospatial.org/is/12-063r5/12-063r5.html#107">OGC 12-063r5 §15.5.2 example 2</a>
1144     */
1145    @Test
1146    public void testDerivedEngineeringFromGeodetic() throws FactoryException {
1147        final DerivedCRS crs = parse(DerivedCRS.class,
1148                "ENGCRS[“Topocentric example A”,\n" +
1149                "  BASEGEODCRS[“WGS 84”,\n" +
1150                "    DATUM[“WGS 84”,\n" +
1151                "      ELLIPSOID[“WGS 84”, 6378137, 298.2572236, LENGTHUNIT[“metre”,1.0]]]],\n" +
1152                "  DERIVINGCONVERSION[“Topocentric example A”,\n" +
1153                "    METHOD[“Geographic/topocentric conversions”,ID[“EPSG”,9837]],\n" +
1154                "    PARAMETER[“Latitude of topocentric origin”,55.0,\n" +
1155                "      ANGLEUNIT[“degree”,0.0174532925199433]],\n" +
1156                "    PARAMETER[“Longitude of topocentric origin”,5.0,\n" +
1157                "      ANGLEUNIT[“degree”,0.0174532925199433]],\n" +
1158                "    PARAMETER[“Ellipsoidal height of topocentric origin”,0.0,\n" +
1159                "     LENGTHUNIT[“metre”,1.0]]],\n" +
1160                "  CS[Cartesian,3],\n" +
1161                "    AXIS[“Topocentric East (U)”,east,ORDER[1]],\n" +
1162                "    AXIS[“Topocentric North (V)”,north,ORDER[2]],\n" +
1163                "    AXIS[“Topocentric height (W)”,up,ORDER[3]],\n" +
1164                "    LENGTHUNIT[“metre”,1.0]]");
1165
1166        if (isValidationEnabled) {
1167            configurationTip = Configuration.Key.isValidationEnabled;
1168            validators.validate(crs);
1169            configurationTip = null;
1170        }
1171        final Unit<Angle> degree = units.degree();
1172        final Unit<Length> metre = units.metre();
1173        final GeodeticDatum datum;
1174
1175        verifyIdentification   (crs, "Topocentric example A", null);
1176        verifyCoordinateSystem (crs.getCoordinateSystem(), EllipsoidalCS.class, new AxisDirection[] {EAST,NORTH,UP}, metre);
1177        verifyAxisAbbreviations(crs.getCoordinateSystem(), "U", "V", "W");
1178
1179        assertInstanceOf("baseCRS", GeodeticCRS.class, crs.getBaseCRS());
1180        verifyDatum           (datum = ((GeodeticCRS) crs.getBaseCRS()).getDatum(), "WGS 84");
1181        verifyFlattenedSphere (datum.getEllipsoid(), "WGS 84", 6378137, 298.2572236, metre);
1182        verifyPrimeMeridian   (datum.getPrimeMeridian(), null, 0, degree);
1183
1184        final ParameterValueGroup group = crs.getConversionFromBase().getParameterValues();
1185        verifyParameter(group, "Latitude of topocentric origin",          55, degree);
1186        verifyParameter(group, "Longitude of topocentric origin",          5, degree);
1187        verifyParameter(group, "Ellipsoidal height of topocentric origin", 0, metre);
1188    }
1189
1190    /**
1191     * Parses a derived engineering CRS having a base projected CRS.
1192     * The WKT parsed by this test is (except for quote characters):
1193     *
1194     * <blockquote><pre>ENGCRS[“Gulf of Mexico speculative seismic survey bin grid”,
1195     *  BASEPROJCRS[“NAD27 / Texas South Central”,
1196     *    BASEGEODCRS[“NAD27”,
1197     *      DATUM[“North American Datum 1927”,
1198     *        ELLIPSOID[“Clarke 1866”,20925832.164,294.97869821,
1199     *          LENGTHUNIT[“US survey foot”,0.304800609601219]]]],
1200     *    CONVERSION[“Texas South CentralSPCS27”,
1201     *      METHOD[“Lambert Conic Conformal (2SP)”,ID[“EPSG”,9802]],
1202     *      PARAMETER[“Latitude of false origin”,27.83333333333333,
1203     *        ANGLEUNIT[“degree”,0.0174532925199433],ID[“EPSG”,8821]],
1204     *      PARAMETER[“Longitude of false origin”,-99.0,
1205     *        ANGLEUNIT[“degree”,0.0174532925199433],ID[“EPSG”,8822]],
1206     *      PARAMETER[“Latitude of 1st standard parallel”,28.383333333333,
1207     *        ANGLEUNIT[“degree”,0.0174532925199433],ID[“EPSG”,8823]],
1208     *      PARAMETER[“Latitude of 2nd standard parallel”,30.283333333333,
1209     *        ANGLEUNIT[“degree”,0.0174532925199433],ID[“EPSG”,8824]],
1210     *      PARAMETER[“Easting at false origin”,2000000.0,
1211     *        LENGTHUNIT[“US survey foot”,0.304800609601219],ID[“EPSG”,8826]],
1212     *      PARAMETER[“Northing at false origin”,0.0,
1213     *        LENGTHUNIT[“US survey foot”,0.304800609601219],ID[“EPSG”,8827]]]],
1214     *  DERIVINGCONVERSION[“Gulf of Mexico speculative survey bin grid”,
1215     *    METHOD[“P6 (I = J-90°) seismic bin grid transformation”,ID[“EPSG”,1049]],
1216     *    PARAMETER[“Bin grid origin I”,5000,SCALEUNIT[“Bin”,1.0],ID[“EPSG”,8733]],
1217     *    PARAMETER[“Bin grid origin J”,0,SCALEUNIT[“Bin”,1.0],ID[“EPSG”,8734]],
1218     *    PARAMETER[“Bin grid origin Easting”,871200,
1219     *      LENGTHUNIT[“US survey foot”,0.304800609601219],ID[“EPSG”,8735]],
1220     *    PARAMETER[“Bin grid origin Northing”, 10280160,
1221     *      LENGTHUNIT[“US survey foot”,0.304800609601219],ID[“EPSG”,8736]],
1222     *    PARAMETER[“Scale factor of bin grid”,1.0,
1223     *      SCALEUNIT[“Unity”,1.0],ID[“EPSG”,8737]],
1224     *    PARAMETER[“Bin width on I-axis”,82.5,
1225     *      LENGTHUNIT[“US survey foot”,0.304800609601219],ID[“EPSG”,8738]],
1226     *    PARAMETER[“Bin width on J-axis”,41.25,
1227     *      LENGTHUNIT[“US survey foot”,0.304800609601219],ID[“EPSG”,8739]],
1228     *    PARAMETER[“Map grid bearing of bin grid J-axis”,340,
1229     *      ANGLEUNIT[“degree”,0.0174532925199433],ID[“EPSG”,8740]],
1230     *    PARAMETER[“Bin node increment on I-axis”,1.0,
1231     *      SCALEUNIT[“Bin”,1.0],ID[“EPSG”,8741]],
1232     *    PARAMETER[“Bin node increment on J-axis”,1.0,
1233     *      SCALEUNIT[“Bin”,1.0],ID[“EPSG”,8742]]],
1234     *  CS[Cartesian,2],
1235     *    AXIS[“(I)”,northNorthWest],
1236     *    AXIS[“(J)”,westSouthWest],
1237     *    SCALEUNIT[“Bin”,1.0]]</pre></blockquote>
1238     *
1239     * @throws FactoryException if an error occurred during the WKT parsing.
1240     *
1241     * @see <a href="http://docs.opengeospatial.org/is/12-063r5/12-063r5.html#107">OGC 12-063r5 §15.5.2 example 1</a>
1242     */
1243    @Test
1244    public void testDerivedEngineeringFromProjected() throws FactoryException {
1245        final DerivedCRS crs = parse(DerivedCRS.class,
1246                "ENGCRS[“Gulf of Mexico speculative seismic survey bin grid”,\n" +
1247                "  BASEPROJCRS[“NAD27 / Texas South Central”,\n" +
1248                "    BASEGEODCRS[“NAD27”,\n" +
1249                "      DATUM[“North American Datum 1927”,\n" +
1250                "        ELLIPSOID[“Clarke 1866”,20925832.164,294.97869821,\n" +
1251                "          LENGTHUNIT[“US survey foot”,0.304800609601219]]]],\n" +
1252                "    CONVERSION[“Texas South CentralSPCS27”,\n" +
1253                "      METHOD[“Lambert Conic Conformal (2SP)”,ID[“EPSG”,9802]],\n" +
1254                "      PARAMETER[“Latitude of false origin”,27.83333333333333,\n" +
1255                "        ANGLEUNIT[“degree”,0.0174532925199433],ID[“EPSG”,8821]],\n" +
1256                "      PARAMETER[“Longitude of false origin”,-99.0,\n" +
1257                "        ANGLEUNIT[“degree”,0.0174532925199433],ID[“EPSG”,8822]],\n" +
1258                "      PARAMETER[“Latitude of 1st standard parallel”,28.383333333333,\n" +
1259                "        ANGLEUNIT[“degree”,0.0174532925199433],ID[“EPSG”,8823]],\n" +
1260                "      PARAMETER[“Latitude of 2nd standard parallel”,30.283333333333,\n" +
1261                "        ANGLEUNIT[“degree”,0.0174532925199433],ID[“EPSG”,8824]],\n" +
1262                "      PARAMETER[“Easting at false origin”,2000000.0,\n" +
1263                "        LENGTHUNIT[“US survey foot”,0.304800609601219],ID[“EPSG”,8826]],\n" +
1264                "      PARAMETER[“Northing at false origin”,0.0,\n" +
1265                "        LENGTHUNIT[“US survey foot”,0.304800609601219],ID[“EPSG”,8827]]]],\n" +
1266                "  DERIVINGCONVERSION[“Gulf of Mexico speculative survey bin grid”,\n" +
1267                "    METHOD[“P6 (I = J-90°) seismic bin grid transformation”,ID[“EPSG”,1049]],\n" +
1268                "    PARAMETER[“Bin grid origin I”,5000,SCALEUNIT[“Bin”,1.0],ID[“EPSG”,8733]],\n" +
1269                "    PARAMETER[“Bin grid origin J”,0,SCALEUNIT[“Bin”,1.0],ID[“EPSG”,8734]],\n" +
1270                "    PARAMETER[“Bin grid origin Easting”,871200,\n" +
1271                "      LENGTHUNIT[“US survey foot”,0.304800609601219],ID[“EPSG”,8735]],\n" +
1272                "    PARAMETER[“Bin grid origin Northing”, 10280160,\n" +
1273                "      LENGTHUNIT[“US survey foot”,0.304800609601219],ID[“EPSG”,8736]],\n" +
1274                "    PARAMETER[“Scale factor of bin grid”,1.0,\n" +
1275                "      SCALEUNIT[“Unity”,1.0],ID[“EPSG”,8737]],\n" +
1276                "    PARAMETER[“Bin width on I-axis”,82.5,\n" +
1277                "      LENGTHUNIT[“US survey foot”,0.304800609601219],ID[“EPSG”,8738]],\n" +
1278                "    PARAMETER[“Bin width on J-axis”,41.25,\n" +
1279                "      LENGTHUNIT[“US survey foot”,0.304800609601219],ID[“EPSG”,8739]],\n" +
1280                "    PARAMETER[“Map grid bearing of bin grid J-axis”,340,\n" +
1281                "      ANGLEUNIT[“degree”,0.0174532925199433],ID[“EPSG”,8740]],\n" +
1282                "    PARAMETER[“Bin node increment on I-axis”,1.0,\n" +
1283                "      SCALEUNIT[“Bin”,1.0],ID[“EPSG”,8741]],\n" +
1284                "    PARAMETER[“Bin node increment on J-axis”,1.0,\n" +
1285                "      SCALEUNIT[“Bin”,1.0],ID[“EPSG”,8742]]],\n" +
1286                "  CS[Cartesian,2],\n" +
1287                "    AXIS[“(I)”,northNorthWest],\n" +
1288                "    AXIS[“(J)”,westSouthWest],\n" +
1289                "    SCALEUNIT[“Bin”,1.0]]");
1290
1291        if (isValidationEnabled) {
1292            configurationTip = Configuration.Key.isValidationEnabled;
1293            validators.validate(crs);
1294            configurationTip = null;
1295        }
1296        final Unit<Angle>  degree       = units.degree();
1297        final Unit<Length> footSurveyUS = units.footSurveyUS();
1298        final Unit<Dimensionless> one   = units.one();
1299        final CoordinateSystem cs;
1300
1301        verifyIdentification   (crs, "Gulf of Mexico speculative seismic survey bin grid", null);
1302        verifyAxisAbbreviations(cs = crs.getCoordinateSystem(), "I", "J");
1303        verifyCoordinateSystem (cs, CartesianCS.class, new AxisDirection[] {NORTH_NORTH_WEST, WEST_SOUTH_WEST}, one);
1304        assertInstanceOf("baseCRS", ProjectedCRS.class, crs.getBaseCRS());
1305        verifyTexasSouthCentral((ProjectedCRS) crs.getBaseCRS(), degree, footSurveyUS);
1306
1307        final ParameterValueGroup group = crs.getConversionFromBase().getParameterValues();
1308        verifyParameter(group, "Bin grid origin I",                  5000, one);
1309        verifyParameter(group, "Bin grid origin J",                     0, one);
1310        verifyParameter(group, "Bin grid origin Easting",          871200, footSurveyUS);
1311        verifyParameter(group, "Bin grid origin Northing",       10280160, footSurveyUS);
1312        verifyParameter(group, "Scale factor of bin grid",              1, one);
1313        verifyParameter(group, "Bin width on I-axis",               82.50, footSurveyUS);
1314        verifyParameter(group, "Bin width on J-axis",               41.25, footSurveyUS);
1315        verifyParameter(group, "Map grid bearing of bin grid J-axis", 340, degree);
1316        verifyParameter(group, "Bin node increment on I-axis",          1, one);
1317        verifyParameter(group, "Bin node increment on J-axis",          1, one);
1318    }
1319
1320    /**
1321     * Parses a compound CRS with a vertical component.
1322     * The WKT parsed by this test is (except for quote characters):
1323     *
1324     * <blockquote><pre>COMPOUNDCRS[“NAD83 + NAVD88”,
1325     *  GEODCRS[“NAD83”,
1326     *    DATUM[“North American Datum 1983”,
1327     *      ELLIPSOID[“GRS 1980”,6378137,298.257222101,
1328     *        LENGTHUNIT[“metre”,1.0]]],
1329     *      PRIMEMERIDIAN[“Greenwich”,0],
1330     *    CS[ellipsoidal,2],
1331     *      AXIS[“latitude”,north,ORDER[1]],
1332     *      AXIS[“longitude”,east,ORDER[2]],
1333     *      ANGLEUNIT[“degree”,0.0174532925199433]],
1334     *    VERTCRS[“NAVD88”,
1335     *      VDATUM[“North American Vertical Datum 1988”],
1336     *      CS[vertical,1],
1337     *        AXIS[“gravity-related height (H)”,up],
1338     *        LENGTHUNIT[“metre”,1]]]</pre></blockquote>
1339     *
1340     * @throws FactoryException if an error occurred during the WKT parsing.
1341     *
1342     * @see <a href="http://docs.opengeospatial.org/is/12-063r5/12-063r5.html#112">OGC 12-063r5 §16.2 example 1</a>
1343     */
1344    @Test
1345    public void testCompoundWithVertical() throws FactoryException {
1346        final CompoundCRS crs = parse(CompoundCRS.class,
1347                "COMPOUNDCRS[“NAD83 + NAVD88”,\n" +
1348                "  GEODCRS[“NAD83”,\n" +
1349                "    DATUM[“North American Datum 1983”,\n" +
1350                "      ELLIPSOID[“GRS 1980”,6378137,298.257222101,\n" +
1351                "        LENGTHUNIT[“metre”,1.0]]],\n" +
1352                "      PRIMEMERIDIAN[“Greenwich”,0],\n" +
1353                "    CS[ellipsoidal,2],\n" +
1354                "      AXIS[“latitude”,north,ORDER[1]],\n" +
1355                "      AXIS[“longitude”,east,ORDER[2]],\n" +
1356                "      ANGLEUNIT[“degree”,0.0174532925199433]],\n" +
1357                "  VERTCRS[“NAVD88”,\n" +
1358                "    VDATUM[“North American Vertical Datum 1988”],\n" +
1359                "    CS[vertical,1],\n" +
1360                "      AXIS[“gravity-related height (H)”,up],\n" +
1361                "      LENGTHUNIT[“metre”,1]]]");
1362
1363        if (isValidationEnabled) {
1364            configurationTip = Configuration.Key.isValidationEnabled;
1365            validators.validate(crs);
1366            configurationTip = null;
1367        }
1368        final Unit<Angle> degree = units.degree();
1369        final Unit<Length> metre = units.metre();
1370
1371        verifyIdentification(crs, "NAD83 + NAVD88", null);
1372        final List<CoordinateReferenceSystem> components = crs.getComponents();
1373        assertEquals("components.size()", 2, components.size());
1374        assertInstanceOf("components[0]", GeodeticCRS.class, components.get(0));
1375        assertInstanceOf("components[1]", VerticalCRS.class, components.get(1));
1376        verifyNAD23((GeodeticCRS) components.get(0), false, degree, metre);
1377        verifyNAD28((VerticalCRS) components.get(1), metre);
1378    }
1379
1380    /**
1381     * Parses a compound CRS with a temporal component.
1382     * The WKT parsed by this test is (except for quote characters):
1383     *
1384     * <blockquote><pre>COMPOUNDCRS[“GPS position and time”,
1385     *   GEODCRS[“WGS 84”,
1386     *     DATUM[“World Geodetic System 1984”,
1387     *       ELLIPSOID[“WGS 84”,6378137,298.257223563]],
1388     *     CS[ellipsoidal,2],
1389     *       AXIS[“(lat)”,north,ORDER[1]],
1390     *       AXIS[“(lon)”,east,ORDER[2]],
1391     *       ANGLEUNIT[“degree”,0.0174532925199433]],
1392     *   TIMECRS[“GPS Time”,
1393     *     TIMEDATUM[“Time origin”,TIMEORIGIN[1980-01-01]],
1394     *     CS[temporal,1],
1395     *       AXIS[“time (T)”,future],
1396     *       TIMEUNIT[“day”,86400]]]</pre></blockquote>
1397     *
1398     * @throws FactoryException if an error occurred during the WKT parsing.
1399     *
1400     * @see <a href="http://docs.opengeospatial.org/is/12-063r5/12-063r5.html#112">OGC 12-063r5 §16.2 example 3</a>
1401     */
1402    @Test
1403    public void testCompoundWithTime() throws FactoryException {
1404        final CompoundCRS crs = parse(CompoundCRS.class,
1405                "COMPOUNDCRS[“GPS position and time”,\n" +
1406                "  GEODCRS[“WGS 84”,\n" +
1407                "    DATUM[“World Geodetic System 1984”,\n" +
1408                "      ELLIPSOID[“WGS 84”,6378137,298.257223563]],\n" +
1409                "    CS[ellipsoidal,2],\n" +
1410                "      AXIS[“(lat)”,north,ORDER[1]],\n" +
1411                "      AXIS[“(lon)”,east,ORDER[2]],\n" +
1412                "      ANGLEUNIT[“degree”,0.0174532925199433]],\n" +
1413                "  TIMECRS[“GPS Time”,\n" +
1414                "    TIMEDATUM[“Time origin”,TIMEORIGIN[1980-01-01]],\n" +
1415                "    CS[temporal,1],\n" +
1416                "      AXIS[“time (T)”,future],\n" +
1417                "      TIMEUNIT[“day”,86400]]]");
1418
1419        if (isValidationEnabled) {
1420            configurationTip = Configuration.Key.isValidationEnabled;
1421            validators.validate(crs);
1422            configurationTip = null;
1423        }
1424        final Unit<Angle> degree = units.degree();
1425        final Unit<Length> metre = units.metre();
1426
1427        verifyIdentification(crs, "GPS position and time", null);
1428        final List<CoordinateReferenceSystem> components = crs.getComponents();
1429        assertEquals("components.size()", 2, components.size());
1430        assertInstanceOf("components[0]", GeodeticCRS.class, components.get(0));
1431        assertInstanceOf("components[1]", TemporalCRS.class, components.get(1));
1432        verifyWGS84  ((GeodeticCRS) components.get(0), false, degree, metre);
1433        verifyGPSTime((TemporalCRS) components.get(1));
1434    }
1435
1436    /**
1437     * Parses a compound CRS with a parametric component.
1438     * The WKT parsed by this test is (except for quote characters):
1439     *
1440     * <blockquote><pre>COMPOUNDCRS[“ICAO layer 0”,
1441     *   GEODETICCRS[“WGS 84”,
1442     *     DATUM[“World Geodetic System 1984”,
1443     *       ELLIPSOID[“WGS 84”,6378137,298.257223563,
1444     *         LENGTHUNIT[“metre”,1.0]]],
1445     *     CS[ellipsoidal,2],
1446     *       AXIS[“latitude”,north,ORDER[1]],
1447     *       AXIS[“longitude”,east,ORDER[2]],
1448     *       ANGLEUNIT[“degree”,0.0174532925199433]],
1449     *   PARAMETRICCRS[“WMO standard atmosphere”,
1450     *     PARAMETRICDATUM[“Mean Sea Level”,
1451     *       ANCHOR[“Mean Sea Level = 1013.25 hPa”]],
1452     *         CS[parametric,1],
1453     *           AXIS[“pressure (P)”,unspecified],
1454     *           PARAMETRICUNIT[“hPa”,100]]]</pre></blockquote>
1455     *
1456     * @throws FactoryException if an error occurred during the WKT parsing.
1457     *
1458     * @see <a href="http://docs.opengeospatial.org/is/12-063r5/12-063r5.html#112">OGC 12-063r5 §16.2 example 2</a>
1459     */
1460    @Test
1461    public void testCompoundWithParametric() throws FactoryException {
1462        final CompoundCRS crs = parse(CompoundCRS.class,
1463                "COMPOUNDCRS[“ICAO layer 0”,\n" +
1464                "  GEODETICCRS[“WGS 84”,\n" +
1465                "    DATUM[“World Geodetic System 1984”,\n" +
1466                "      ELLIPSOID[“WGS 84”,6378137,298.257223563,\n" +
1467                "        LENGTHUNIT[“metre”,1.0]]],\n" +
1468                "    CS[ellipsoidal,2],\n" +
1469                "      AXIS[“latitude”,north,ORDER[1]],\n" +
1470                "      AXIS[“longitude”,east,ORDER[2]],\n" +
1471                "      ANGLEUNIT[“degree”,0.0174532925199433]],\n" +
1472                "  PARAMETRICCRS[“WMO standard atmosphere”,\n" +
1473                "    PARAMETRICDATUM[“Mean Sea Level”,\n" +
1474                "      ANCHOR[“Mean Sea Level = 1013.25 hPa”]],\n" +
1475                "        CS[parametric,1],\n" +
1476                "          AXIS[“pressure (P)”,unspecified],\n" +
1477                "          PARAMETRICUNIT[“hPa”,100]]]");
1478
1479        if (isValidationEnabled) {
1480            configurationTip = Configuration.Key.isValidationEnabled;
1481            validators.validate(crs);
1482            configurationTip = null;
1483        }
1484        final Unit<Angle> degree = units.degree();
1485        final Unit<Length> metre = units.metre();
1486
1487        verifyIdentification(crs, "ICAO layer 0", null);
1488        final List<CoordinateReferenceSystem> components = crs.getComponents();
1489        assertEquals("components.size()", 2, components.size());
1490        assertInstanceOf("components[0]", GeodeticCRS.class, components.get(0));
1491        assertInstanceOf("components[1]", ParametricCRS.class, components.get(1));
1492        verifyWGS84((GeodeticCRS) components.get(0), false, degree, metre);
1493
1494        final ParametricCRS ps = (ParametricCRS) components.get(1);
1495        verifyIdentification(ps, "WMO standard atmosphere", null);
1496        verifyDatum(ps.getDatum(), "Mean Sea Level");
1497        assertInstanceOf("coordinateSystem", ParametricCS.class, ps.getCoordinateSystem());
1498    }
1499}