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