001/*
002 *    GeoAPI - Java interfaces for OGC/ISO standards
003 *    Copyright © 2011-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.Set;
021import java.awt.geom.Rectangle2D;
022
023import org.opengis.util.FactoryException;
024import org.opengis.referencing.cs.*;
025import org.opengis.referencing.crs.*;
026import org.opengis.referencing.datum.*;
027import org.opengis.referencing.ObjectDomain;
028import org.opengis.referencing.IdentifiedObject;
029import org.opengis.referencing.AuthorityFactory;
030import org.opengis.referencing.NoSuchAuthorityCodeException;
031import org.opengis.referencing.operation.TransformException;
032import org.opengis.referencing.operation.MathTransform;
033import org.opengis.referencing.operation.Conversion;
034import org.opengis.metadata.extent.Extent;
035import org.opengis.metadata.extent.GeographicExtent;
036import org.opengis.metadata.extent.GeographicBoundingBox;
037import org.opengis.test.CalculationType;
038import org.opengis.test.ToleranceModifier;
039import org.opengis.test.Configuration;
040
041import org.junit.jupiter.api.Test;
042
043import static org.junit.jupiter.api.Assertions.*;
044import static org.junit.jupiter.api.Assumptions.abort;
045import static org.junit.jupiter.api.Assumptions.assumeTrue;
046import static org.opengis.test.Validator.DEFAULT_TOLERANCE;
047import static org.opengis.test.Assertions.assertAxisDirectionsEqual;
048
049
050/**
051 * Tests the creation of referencing objects from the {@linkplain AuthorityFactory authority factories}
052 * given at construction time.
053 *
054 * <p>Many {@link ProjectedCRS} instances tested in this class use the same projections as the
055 * {@link MathTransform} instances tested in {@link ParameterizedTransformTest}. However, the latter
056 * test class expects (λ,φ) input coordinates in degrees and (<var>x</var>,<var>y</var>)
057 * output coordinates in metres, while this {@code AuthorityFactoryTest} class expects
058 * input and output coordinates in CRS-dependent units and axis order.</p>
059 *
060 * <h2>Usage example:</h2>
061 * in order to specify their factories and run the tests in a JUnit framework, implementers can
062 * define a subclass in their own test suite as in the example below:
063 *
064 * {@snippet lang="java" :
065 * import org.opengis.test.referencing.AuthorityFactoryTest;
066 *
067 * public class MyTest extends AuthorityFactoryTest {
068 *     public MyTest() {
069 *         super(new MyCRSAuthorityFactory(), new MyCSAuthorityFactory(), new MyDatumAuthorityFactory());
070 *     }
071 * }}
072 *
073 * @see ObjectFactoryTest
074 * @see ParameterizedTransformTest
075 *
076 * @author  Cédric Briançon (Geomatys)
077 * @author  Martin Desruisseaux (Geomatys)
078 * @version 3.1
079 * @since   2.3
080 */
081@SuppressWarnings("strictfp")   // Because we still target Java 11.
082public strictfp class AuthorityFactoryTest extends ReferencingTestCase {
083    /**
084     * The message when a test is disabled because no authority factory has been found.
085     */
086    private static final String NO_CRS_FACTORY = "No CRS authority factory found.";
087
088    /**
089     * Factory to use for building {@link CoordinateReferenceSystem} instances, or {@code null} if none.
090     */
091    protected final CRSAuthorityFactory crsAuthorityFactory;
092
093    /**
094     * Factory to use for building {@link CoordinateSystem} instances, or {@code null} if none.
095     */
096    protected final CSAuthorityFactory csAuthorityFactory;
097
098    /**
099     * Factory to use for building {@link Datum} instances, or {@code null} if none.
100     */
101    protected final DatumAuthorityFactory datumAuthorityFactory;
102
103    /**
104     * The identified object (typically a {@link CoordinateReferenceSystem}) being tested.
105     * Every test methods in this class will set this field to a non-null value.
106     * Implementers can use this value for their own assertions after any test has been run.
107     *
108     * @since 3.1
109     */
110    protected IdentifiedObject object;
111
112    /**
113     * {@code true} if the longitude and latitude axes shall be swapped. This flag applies
114     * only to geographic coordinates.
115     *
116     * <p><b>Default value:</b> {@code true}, since the majority of {@link GeographicCRS}
117     * defined in the EPSG database uses the (φλ) axis order.</p>
118     *
119     * @since 3.1
120     */
121    protected boolean swapλφ = true;
122
123    /**
124     * {@code true} if the easting and northing axes shall be swapped. This flag applies only
125     * to projected coordinates.
126     *
127     * <p><b>Default value:</b> {@code false}, since the majority of {@link ProjectedCRS} defined
128     * in the EPSG database uses the (<var>x</var>,<var>y</var>) axis order.</p>
129     *
130     * @since 3.1
131     */
132    protected boolean swapxy;
133
134    /**
135     * {@code true} if the sign of coordinate values shall be reversed in both projected axes.
136     * This flag applies only to projected coordinates. This flag is set to {@code true} for
137     * <i>South Oriented</i> {@link ProjectedCRS}.
138     *
139     * <p><b>Default value:</b> {@code false}.</p>
140     *
141     * @since 3.1
142     */
143    private boolean flipxy;
144
145    /**
146     * {@code true} if the {@linkplain CoordinateReferenceSystem Coordinate Reference System} being
147     * tested is a polar CRS. Such CRS have axis orientation like <q>South along 90°E</q>
148     * instead of {@linkplain AxisDirection#EAST East} or {@linkplain AxisDirection#NORTH North}.
149     *
150     * <p><b>Default value:</b> {@code false}.</p>
151     *
152     * @since 3.1
153     */
154    private boolean isPolar;
155
156    /**
157     * The expected prime meridian of the CRS being tested, in decimal degrees from Greenwich.
158     *
159     * <p><b>Default value:</b> {@code 0.0}.</p>
160     *
161     * @since 3.1
162     */
163    protected double primeMeridian;
164
165    /**
166     * Conversion factor from degrees to the CRS-specific angular units. This value is different
167     * than one when the latitude or longitude angles need to be converted from degrees before to
168     * run a test.
169     *
170     * <p><b>Default value:</b> {@code 1.0}.</p>
171     *
172     * @since 3.1
173     */
174    protected double toAngularUnit = 1.0;
175
176    /**
177     * Conversion factor from metres to the CRS-specific linear units. This value is different
178     * than one when the easting or northing values need to be converted from metres before to
179     * run a test.
180     *
181     * <p><b>Default value:</b> {@code 1.0}.</p>
182     *
183     * @since 3.1
184     */
185    protected double toLinearUnit = 1.0;
186
187    /**
188     * {@code true} if {@link #crsAuthorityFactory} and {@link #csAuthorityFactory} supports the
189     * creation of coordinate system with (<var>y</var>,<var>x</var>) axis order. If this field is
190     * set to {@code false}, then the tests that would normally expect (<var>y</var>,<var>x</var>)
191     * axis order or <i>South Oriented</i> CRS will rather use the (<var>x</var>,<var>y</var>)
192     * axis order and <i>North Oriented</i> CRS in their test.
193     *
194     * @since 3.1
195     */
196    protected boolean isAxisSwappingSupported;
197
198    /**
199     * A helper class to use for running the tests. The {@link #runProjectionTest(int)} method
200     * will use the following {@code ParameterizedTransformTest} package-private methods:
201     *
202     * <ul>
203     *   <li>{@link ParameterizedTransformTest#verifyKnownSamplePoints}</li>
204     *   <li>{@link ParameterizedTransformTest#verifyInDomainOfValidity}</li>
205     * </ul>
206     */
207    private final ParameterizedTransformTest test;
208
209    /**
210     * Creates a new test using the given factories. If a given factory is {@code null},
211     * then the tests which depend on it will be skipped.
212     *
213     * @param crsFactory    factory for creating {@link CoordinateReferenceSystem} instances.
214     * @param csFactory     factory for creating {@link CoordinateSystem} instances.
215     * @param datumFactory  factory for creating {@link Datum} instances.
216     */
217    @SuppressWarnings("this-escape")
218    public AuthorityFactoryTest(final CRSAuthorityFactory crsFactory,
219            final CSAuthorityFactory csFactory, final DatumAuthorityFactory datumFactory)
220    {
221        crsAuthorityFactory   = crsFactory;
222        csAuthorityFactory    = csFactory;
223        datumAuthorityFactory = datumFactory;
224        final Configuration.Key<Boolean>[] keys = ParameterizedTransformTest.getEnabledKeys(1);
225        final int offset = keys.length - 1;                     // First free slot for our keys.
226        keys[offset] = Configuration.Key.isAxisSwappingSupported;
227        final boolean[] isEnabled = getEnabledFlags(keys);
228        test = new ParameterizedTransformTest(isEnabled);
229        isAxisSwappingSupported = isEnabled[offset];
230    }
231
232    /**
233     * Returns information about the configuration of the test which has been run.
234     * This method returns a map containing:
235     *
236     * <ul>
237     *   <li>All the entries defined in the {@link ParameterizedTransformTest#configuration()
238     *       ParameterizedTransformTest} class except {@code mtFactory}.</li>
239     *   <li>All the following values associated to the {@link org.opengis.test.Configuration.Key} of the same name:
240     *     <ul>
241     *       <li>{@link #isAxisSwappingSupported}</li>
242     *       <li>{@link #crsAuthorityFactory}</li>
243     *       <li>{@link #csAuthorityFactory}</li>
244     *       <li>{@link #datumAuthorityFactory}</li>
245     *     </ul>
246     *   </li>
247     * </ul>
248     *
249     * @return the configuration of the test being run, or an empty map if none.
250     *
251     * @since 3.1
252     */
253    @Override
254    public Configuration configuration() {
255        final Configuration op = test.configuration();
256        assertNull(op.remove(Configuration.Key.mtFactory));
257        assertNull(op.put(Configuration.Key.isAxisSwappingSupported, isAxisSwappingSupported));
258        assertNull(op.put(Configuration.Key.crsAuthorityFactory,     crsAuthorityFactory));
259        assertNull(op.put(Configuration.Key.csAuthorityFactory,      csAuthorityFactory));
260        assertNull(op.put(Configuration.Key.datumAuthorityFactory,   datumAuthorityFactory));
261        return op;
262    }
263
264    /**
265     * Returns the longitude value relative to the Greenwich Meridian, expressed in decimal degrees.
266     *
267     * @param  pm  the prime meridian from which to get the Greenwich longitude, or {@code null}.
268     * @return the prime meridian in the given units, or 0 if the given prime meridian was null.
269     */
270    private double getGreenwichLongitude(final PrimeMeridian pm) {
271        return (pm != null) ? pm.getAngularUnit().getConverterTo(units.degree()).convert(pm.getGreenwichLongitude()) : 0;
272    }
273
274    /**
275     * Tests the creation of the EPSG:4326 {@link GeographicCRS}.
276     *
277     * @throws NoSuchAuthorityCodeException if the specified code is not found among the ones present in the database.
278     * @throws FactoryException if the creation of the {@link CoordinateReferenceSystem} failed for another reason.
279     */
280    @Test
281    public void testWGS84() throws NoSuchAuthorityCodeException, FactoryException {
282        assumeTrue(crsAuthorityFactory != null, NO_CRS_FACTORY);
283        final GeographicCRS crs = crsAuthorityFactory.createGeographicCRS("EPSG:4326");
284        assertNotNull(crs, "CRSAuthorityFactory.createGeographicCRS()");
285        object = crs;
286        validators.validate(crs);
287        verifyIdentification(crs, "WGS 84", null);
288        /*
289         * Coordinate system validation. In theory, the coordinate system is mandatory.
290         * This is verified by the above call to validate(crs). However, the user could
291         * have set the Validator.requireMandatoryAttributes to `false`, in which case
292         * we need to be lenient as the user wish.
293         */
294        final EllipsoidalCS cs = crs.getCoordinateSystem();
295        if (cs != null) {
296            verifyCoordinateSystem(cs, EllipsoidalCS.class,
297                    new AxisDirection[] {
298                        AxisDirection.NORTH,
299                        AxisDirection.EAST
300                    }, units.degree());
301            verifyIdentification(cs.getAxis(0), "Geodetic latitude", null);
302            verifyIdentification(cs.getAxis(1), "Geodetic longitude", null);
303        }
304        /*
305         * Datum validation. Same rational about `null` value as for the coordinate system.
306         */
307        final GeodeticDatum datum = crs.getDatum();
308        if (datum != null) {
309            verifyIdentification(datum, "World Geodetic System 1984", null);
310            verifyPrimeMeridian(datum.getPrimeMeridian(), "Greenwich", 0.0, units.degree());
311        }
312    }
313
314    /**
315     * Verifies the horizontal axis direction of the given coordinate system. The standard
316     * directions are (East,North), but the Boolean argument allows to swap and flip those
317     * directions.
318     *
319     * @param message  the message to report in case of error.
320     * @param cs       the coordinate system to check, or {@code null}.
321     * @param swap     {@code true} if the easting and northing axes should be interchanged.
322     * @param flip     {@code true} if the sign of both axes should be reversed.
323     */
324    private static void verifyAxisDirection(final String message, final CoordinateSystem cs,
325            final boolean swap, final boolean flip)
326    {
327        if (cs != null) {
328            AxisDirection X,Y;
329            if (flip) {
330                X = AxisDirection.WEST;
331                Y = AxisDirection.SOUTH;
332            } else {
333                X = AxisDirection.EAST;
334                Y = AxisDirection.NORTH;
335            }
336            if (swap) {
337                final AxisDirection t = X;
338                X=Y; Y=t;
339            }
340            assertAxisDirectionsEqual(cs, new AxisDirection[] {X, Y}, message);
341        }
342    }
343
344    /**
345     * Creates a {@link ProjectedCRS} identified by the given EPSG code, and tests its
346     * math transform. The set of allowed codes is documented in second column of the
347     * {@link PseudoEpsgFactory#createParameters(int)} method.
348     *
349     * @param  code  the EPSG code of a target Coordinate Reference System.
350     * @throws FactoryException if the math transform cannot be created.
351     * @throws TransformException if a point cannot be transformed.
352     */
353    private void runProjectionTest(final int code) throws FactoryException, TransformException {
354        if (!isAxisSwappingSupported) {
355            swapλφ = swapxy = flipxy = false;
356        }
357        assumeTrue(crsAuthorityFactory != null, NO_CRS_FACTORY);
358        final ProjectedCRS crs;
359        try {
360            crs = crsAuthorityFactory.createProjectedCRS("EPSG:" + code);
361        } catch (NoSuchAuthorityCodeException e) {
362            /*
363             * If a code was not found, ensure that the factory does not declare that it was
364             * a supported code. If the code was unsupported, then the test will be ignored.
365             */
366            final Set<String> authorityCodes = crsAuthorityFactory.getAuthorityCodes(ProjectedCRS.class);
367            if (authorityCodes == null || !authorityCodes.contains(String.valueOf(code))) {
368                abort("Unsupported CRS: " + e);     // Will mark the test as "ignored".
369            }
370            throw e;                                // Will mark the test as "failed".
371        }
372        assertNotNull(crs, "CRSAuthorityFactory.createProjectedCRS()");
373        object = crs;
374        validators.validate(crs);
375        /*
376         * Coordinate system validation. In theory, the coordinate system is mandatory.
377         * This is verified by the above call to validate(crs). However, the user could
378         * have set the Validator.requireMandatoryAttributes to `false`, in which case
379         * we need to be lenient as the user wishes.
380         */
381        final GeodeticCRS baseCRS = crs.getBaseCRS();
382        if (baseCRS != null) {
383            verifyAxisDirection("BaseCRS", baseCRS.getCoordinateSystem(), swapλφ, false);
384            final GeodeticDatum datum = baseCRS.getDatum();
385            if (datum != null) {
386                assertEquals(primeMeridian, getGreenwichLongitude(datum.getPrimeMeridian()), DEFAULT_TOLERANCE,
387                             "PrimeMeridian.greenwichLongitude");
388            }
389        }
390        /*
391         * Verifies axis direction only if the CRS is not polar. Polar CRS has unusual
392         * axis directions like "South along 90°E". Since there is no GeoAPI code list
393         * for such direction, we consider them as implementation-dependent.
394         */
395        if (!isPolar) {
396            verifyAxisDirection("ProjectedCRS", crs.getCoordinateSystem(), swapxy, flipxy);
397        }
398        /*
399         * Test the projection of sample point values.
400         */
401        final Conversion conversion = crs.getConversionFromBase();
402        if (conversion != null) {
403            final MathTransform projection = conversion.getMathTransform();
404            if (projection != null) {
405                test.description = Utilities.getName(crs);
406                test.transform = projection;
407                validators.validate(projection);
408                /*
409                 * Get the sample points and swap coordinate values if needed.
410                 * Finally apply a unit conversion if the CRS doesn't use the usual units.
411                 */
412                final SamplePoints sample = SamplePoints.forCRS(code);
413                if (primeMeridian != 0) sample.rotateLongitude(primeMeridian);
414                if (swapλφ) SamplePoints.swap(sample.sourcePoints);
415                if (swapxy) SamplePoints.swap(sample.targetPoints);
416                if (flipxy) SamplePoints.flip(sample.targetPoints);
417                test.setTolerance(swapλφ ? ToleranceModifier.PROJECTION_FROM_φλ : ToleranceModifier.PROJECTION);
418                if (toLinearUnit  != 1) test.applyUnitConversion(CalculationType.DIRECT_TRANSFORM,  sample.targetPoints, toLinearUnit);
419                if (toAngularUnit != 1) test.applyUnitConversion(CalculationType.INVERSE_TRANSFORM, sample.sourcePoints, toAngularUnit);
420                test.verifyTransform(sample.sourcePoints, sample.targetPoints);
421                /*
422                 * Tests random points in every domains of validity declared in the CRS.
423                 * If the CRS does not declare any domain of validity, then we will use
424                 * the one which is hard-coded in the SamplePoints class.
425                 */
426                boolean tested = false;
427                final Rectangle2D areaOfValidity = sample.areaOfValidity;
428                double λmin = areaOfValidity.getMinX();
429                double λmax = areaOfValidity.getMaxX();
430                double φmin = areaOfValidity.getMinY();
431                double φmax = areaOfValidity.getMaxY();
432                for (final ObjectDomain domain : crs.getDomains()) {
433                    final Extent extent = domain.getDomainOfValidity();
434                    if (extent != null) {
435                        validators.validate(extent);
436                        for (final GeographicExtent element : extent.getGeographicElements()) {
437                            if (element instanceof GeographicBoundingBox && Boolean.TRUE.equals(element.getInclusion())) {
438                                final GeographicBoundingBox bbox = (GeographicBoundingBox) element;
439                                λmin = bbox.getWestBoundLongitude();
440                                λmax = bbox.getEastBoundLongitude();
441                                φmin = bbox.getSouthBoundLatitude();
442                                φmax = bbox.getNorthBoundLatitude();
443                                setRect(areaOfValidity, λmin, φmin, λmax, φmax, swapλφ, toAngularUnit);
444                                assertFalse(areaOfValidity.isEmpty(), "Empty geographic bounding box.");
445                                test.verifyInDomainOfValidity(areaOfValidity);
446                                tested = true;
447                            }
448                        }
449                    }
450                }
451                if (!tested) {
452                    setRect(areaOfValidity, λmin, φmin, λmax, φmax, swapλφ, toAngularUnit);
453                    test.verifyInDomainOfValidity(areaOfValidity);
454                }
455            }
456        }
457    }
458
459    /**
460     * Sets the area of validity, swapping axis and converting units if necessary.
461     *
462     * @param areaOfValidity  the rectangle to set.
463     * @param λmin            the new longitude minimum.
464     * @param φmin            the new latitude minimum.
465     * @param λmax            the new longitude maximum.
466     * @param φmax            the new latitude maximum.
467     * @param swapλφ          whether to swap axis order.
468     * @param toAngularUnit   conversion factor to axis units.
469     */
470    private static void setRect(final Rectangle2D areaOfValidity,
471            double λmin, double φmin, double λmax, double φmax,
472            final boolean swapλφ, final double toAngularUnit)
473    {
474        if (λmax < λmin) {                      // Domain crosses anti-meridian.
475            if (180 + λmax < 180 - λmin) {      // Which bound is closest to anti-meridian?
476                λmax += 360;                    // Example: -175° → +185°
477            } else {
478                λmin -= 360;                    // Example: +175° → -185°
479            }
480        }
481        λmin *= toAngularUnit;
482        λmax *= toAngularUnit;
483        φmin *= toAngularUnit;
484        φmax *= toAngularUnit;
485        if (swapλφ) {
486            double t;
487            t=λmin; λmin=φmin; φmin=t;
488            t=λmax; λmax=φmax; φmax=t;
489        }
490        areaOfValidity.setRect(λmin, φmin, λmax-λmin, φmax-φmin);
491    }
492
493    /**
494     * Tests the EPSG:3002 (<cite>Makassar / NEIEZ</cite>) projected CRS.
495     *
496     * <table class="ogc">
497     * <caption>CRS characteristics</caption>
498     * <tr><td>Projection method:</td>   <td>Mercator (variant A)</td></tr>
499     * <tr><td>Prime meridian:</td>      <td>Greenwich</td></tr>
500     * <tr><td>Source coordinates:</td>  <td>(φ,λ) in degrees</td></tr>
501     * <tr><td>Output coordinates:</td>  <td>(<var>x</var>,<var>y</var>) in metres</td></tr>
502     * </table>
503     *
504     * @throws FactoryException if the math transform cannot be created.
505     * @throws TransformException if the example point cannot be transformed.
506     *
507     * @see ParameterizedTransformTest#testMercator1SP()
508     */
509    @Test
510    public void testEPSG_3002() throws FactoryException, TransformException {
511        runProjectionTest(3002);
512    }
513
514    /**
515     * Tests the EPSG:3388 (<cite>Pulkovo 1942 / Caspian Sea Mercator</cite>) projected CRS.
516     *
517     * <table class="ogc">
518     * <caption>CRS characteristics</caption>
519     * <tr><td>Projection method:</td>   <td>Mercator (variant B)</td></tr>
520     * <tr><td>Prime meridian:</td>      <td>Greenwich</td></tr>
521     * <tr><td>Source coordinates:</td>  <td>(φ,λ) in degrees</td></tr>
522     * <tr><td>Output coordinates:</td>  <td>(<var>y</var>,<var>x</var>) in metres - <strong>note the axis order!</strong></td></tr>
523     * </table>
524     *
525     * @throws FactoryException if the math transform cannot be created.
526     * @throws TransformException if the example point cannot be transformed.
527     *
528     * @see ParameterizedTransformTest#testMercator2SP()
529     */
530    @Test
531    public void testEPSG_3388() throws FactoryException, TransformException {
532        swapxy = true;
533        runProjectionTest(3388);
534    }
535
536    /**
537     * Tests the EPSG:3857 (<cite>WGS 84 / Pseudo-Mercator</cite>) projected CRS.
538     *
539     * <table class="ogc">
540     * <caption>CRS characteristics</caption>
541     * <tr><td>Projection method:</td>   <td>Mercator Popular Visualisation Pseudo Mercator</td></tr>
542     * <tr><td>Prime meridian:</td>      <td>Greenwich</td></tr>
543     * <tr><td>Source coordinates:</td>  <td>(φ,λ) in degrees</td></tr>
544     * <tr><td>Output coordinates:</td>  <td>(<var>x</var>,<var>y</var>) in metres</td></tr>
545     * </table>
546     *
547     * @throws FactoryException if the math transform cannot be created.
548     * @throws TransformException if the example point cannot be transformed.
549     *
550     * @see ParameterizedTransformTest#testPseudoMercator()
551     */
552    @Test
553    public void testEPSG_3857() throws FactoryException, TransformException {
554        runProjectionTest(3857);
555    }
556
557    /**
558     * Tests the EPSG:29873 (<cite>Timbalai 1948 / RSO Borneo (m)</cite>) projected CRS.
559     *
560     * <table class="ogc">
561     * <caption>CRS characteristics</caption>
562     * <tr><td>Projection method:</td>   <td>Hotine Oblique Mercator (variant B)</td></tr>
563     * <tr><td>Prime meridian:</td>      <td>Greenwich</td></tr>
564     * <tr><td>Source coordinates:</td>  <td>(φ,λ) in degrees</td></tr>
565     * <tr><td>Output coordinates:</td>  <td>(<var>x</var>,<var>y</var>) in metres</td></tr>
566     * </table>
567     *
568     * @throws FactoryException if the math transform cannot be created.
569     * @throws TransformException if the example point cannot be transformed.
570     *
571     * @see ParameterizedTransformTest#testHotineObliqueMercator()
572     */
573    @Test
574    public void testEPSG_29873() throws FactoryException, TransformException {
575        runProjectionTest(29873);
576    }
577
578    /**
579     * Tests the EPSG:27700 (<cite>OSGB 1936 / British National Grid</cite>) projected CRS.
580     *
581     * <table class="ogc">
582     * <caption>CRS characteristics</caption>
583     * <tr><td>Projection method:</td>   <td>Transverse Mercator</td></tr>
584     * <tr><td>Prime meridian:</td>      <td>Greenwich</td></tr>
585     * <tr><td>Source coordinates:</td>  <td>(φ,λ) in degrees</td></tr>
586     * <tr><td>Output coordinates:</td>  <td>(<var>x</var>,<var>y</var>) in metres</td></tr>
587     * </table>
588     *
589     * @throws FactoryException if the math transform cannot be created.
590     * @throws TransformException if the example point cannot be transformed.
591     *
592     * @see ParameterizedTransformTest#testTransverseMercator()
593     */
594    @Test
595    public void testEPSG_27700() throws FactoryException, TransformException {
596        runProjectionTest(27700);
597    }
598
599    /**
600     * Tests the EPSG:2314 (<cite>Trinidad 1903 / Trinidad Grid</cite>) projected CRS.
601     *
602     * <table class="ogc">
603     * <caption>CRS characteristics</caption>
604     * <tr><td>Projection method:</td>   <td>Cassini-Soldner</td></tr>
605     * <tr><td>Prime meridian:</td>      <td>Greenwich</td></tr>
606     * <tr><td>Source coordinates:</td>  <td>(φ,λ) in degrees</td></tr>
607     * <tr><td>Output coordinates:</td>  <td>(<var>x</var>,<var>y</var>) in Clarke's foot - <strong>note the units!</strong></td></tr>
608     * </table>
609     *
610     * @throws FactoryException if the math transform cannot be created.
611     * @throws TransformException if the example point cannot be transformed.
612     *
613     * @see ParameterizedTransformTest#testCassiniSoldner()
614     */
615    @Test
616    public void testEPSG_2314() throws FactoryException, TransformException {
617        toLinearUnit = 1/PseudoEpsgFactory.CLARKE_FEET;
618        /*
619         * Relax the tolerance threshold because the sample point in IOGP Publication 373-7-2 §3.2.2 — September 2019
620         * has been computed for a CRS defined with slightly different numbers than in EPSG:9.8.11:2314 definition.
621         * The differences are in units of measurement used for defining axis lengths and false easting/northing.
622         */
623        if (test.tolerance < 0.8) {
624            test.tolerance = 0.8;
625        }
626        runProjectionTest(2314);
627    }
628
629    /**
630     * Tests the EPSG:3139 (<cite>Vanua Levu 1915 / Vanua Levu Grid</cite>) projected CRS.
631     *
632     * <table class="ogc">
633     * <caption>CRS characteristics</caption>
634     * <tr><td>Projection method:</td>   <td>Hyperbolic Cassini-Soldner</td></tr>
635     * <tr><td>Prime meridian:</td>      <td>Greenwich</td></tr>
636     * <tr><td>Source coordinates:</td>  <td>(φ,λ) in degrees</td></tr>
637     * <tr><td>Output coordinates:</td>  <td>(<var>x</var>,<var>y</var>) in links - <strong>note the units!</strong></td></tr>
638     * </table>
639     *
640     * @throws FactoryException if the math transform cannot be created.
641     * @throws TransformException if the example point cannot be transformed.
642     *
643     * @see ParameterizedTransformTest#testHyperbolicCassiniSoldner()
644     */
645    @Test
646    public void testEPSG_3139() throws FactoryException, TransformException {
647        toLinearUnit = 1/PseudoEpsgFactory.LINKS;
648        swapxy = true;
649        runProjectionTest(3139);
650    }
651
652    /**
653     * Tests the EPSG:24200 (<cite>JAD69 / Jamaica National Grid</cite>) projected CRS.
654     *
655     * <table class="ogc">
656     * <caption>CRS characteristics</caption>
657     * <tr><td>Projection method:</td>   <td>Lambert Conic Conformal (1SP)</td></tr>
658     * <tr><td>Prime meridian:</td>      <td>Greenwich</td></tr>
659     * <tr><td>Source coordinates:</td>  <td>(φ,λ) in degrees</td></tr>
660     * <tr><td>Output coordinates:</td>  <td>(<var>x</var>,<var>y</var>) in metres</td></tr>
661     * </table>
662     *
663     * @throws FactoryException if the math transform cannot be created.
664     * @throws TransformException if the example point cannot be transformed.
665     *
666     * @see ParameterizedTransformTest#testLambertConicConformal1SP()
667     */
668    @Test
669    public void testEPSG_24200() throws FactoryException, TransformException {
670        runProjectionTest(24200);
671    }
672
673    /**
674     * Tests the EPSG:32040 (<cite>NAD27 / Texas South Central</cite>) projected CRS.
675     *
676     * <table class="ogc">
677     * <caption>CRS characteristics</caption>
678     * <tr><td>Projection method:</td>   <td>Lambert Conic Conformal (2SP)</td></tr>
679     * <tr><td>Prime meridian:</td>      <td>Greenwich</td></tr>
680     * <tr><td>Source coordinates:</td>  <td>(φ,λ) in degrees</td></tr>
681     * <tr><td>Output coordinates:</td>  <td>(<var>x</var>,<var>y</var>) in US feet - <strong>note the units!</strong></td></tr>
682     * </table>
683     *
684     * @throws FactoryException if the math transform cannot be created.
685     * @throws TransformException if the example point cannot be transformed.
686     *
687     * @see ParameterizedTransformTest#testLambertConicConformal2SP()
688     */
689    @Test
690    public void testEPSG_32040() throws FactoryException, TransformException {
691        toLinearUnit = PseudoEpsgFactory.R_US_FEET;
692        runProjectionTest(32040);
693    }
694
695    /**
696     * Tests the EPSG:31300 (<cite>Belge 1972 / Belge Lambert 72</cite>) projected CRS.
697     *
698     * <table class="ogc">
699     * <caption>CRS characteristics</caption>
700     * <tr><td>Projection method:</td>   <td>Lambert Conic Conformal (2SP Belgium)</td></tr>
701     * <tr><td>Prime meridian:</td>      <td>Greenwich</td></tr>
702     * <tr><td>Source coordinates:</td>  <td>(φ,λ) in degrees</td></tr>
703     * <tr><td>Output coordinates:</td>  <td>(<var>x</var>,<var>y</var>) in metres</td></tr>
704     * </table>
705     *
706     * @throws FactoryException if the math transform cannot be created.
707     * @throws TransformException if the example point cannot be transformed.
708     *
709     * @see ParameterizedTransformTest#testLambertConicConformalBelgium()
710     */
711    @Test
712    public void testEPSG_31300() throws FactoryException, TransformException {
713        runProjectionTest(31300);
714    }
715
716    /**
717     * Tests the EPSG:3035 (<cite>ETRS89 / LAEA Europe</cite>) projected CRS.
718     *
719     * <table class="ogc">
720     * <caption>CRS characteristics</caption>
721     * <tr><td>Projection method:</td>   <td>Lambert Azimuthal Equal Area</td></tr>
722     * <tr><td>Prime meridian:</td>      <td>Greenwich</td></tr>
723     * <tr><td>Source coordinates:</td>  <td>(φ,λ) in degrees</td></tr>
724     * <tr><td>Output coordinates:</td>  <td>(<var>y</var>,<var>x</var>) in metres - <strong>note the axis order!</strong></td></tr>
725     * </table>
726     *
727     * @throws FactoryException if the math transform cannot be created.
728     * @throws TransformException if the example point cannot be transformed.
729     *
730     * @see ParameterizedTransformTest#testLambertAzimuthalEqualArea()
731     */
732    @Test
733    public void testEPSG_3035() throws FactoryException, TransformException {
734        swapxy = true;
735        runProjectionTest(3035);
736    }
737
738    /**
739     * Tests the EPSG:5041 (<cite>WGS 84 / UPS North (E,N)</cite>) projected CRS.
740     *
741     * <table class="ogc">
742     * <caption>CRS characteristics</caption>
743     * <tr><td>Projection method:</td>   <td>Polar Stereographic (variant A)</td></tr>
744     * <tr><td>Prime meridian:</td>      <td>Greenwich</td></tr>
745     * <tr><td>Source coordinates:</td>  <td>(φ,λ) in degrees</td></tr>
746     * <tr><td>Output coordinates:</td>  <td>(<var>x</var>,<var>y</var>) in metres</td></tr>
747     * </table>
748     *
749     * @throws FactoryException if the math transform cannot be created.
750     * @throws TransformException if the example point cannot be transformed.
751     *
752     * @see ParameterizedTransformTest#testPolarStereographicA()
753     */
754    @Test
755    public void testEPSG_5041() throws FactoryException, TransformException {
756        isPolar = true;
757        runProjectionTest(5041);
758    }
759
760    /**
761     * Tests the EPSG:32661 (<cite>WGS 84 / UPS North (N,E)</cite>) projected CRS.
762     * This CRS is identical to EPSG:5041 except for axis order.
763     *
764     * <table class="ogc">
765     * <caption>CRS characteristics</caption>
766     * <tr><td>Projection method:</td>   <td>Polar Stereographic (variant A)</td></tr>
767     * <tr><td>Prime meridian:</td>      <td>Greenwich</td></tr>
768     * <tr><td>Source coordinates:</td>  <td>(φ,λ) in degrees</td></tr>
769     * <tr><td>Output coordinates:</td>  <td>(<var>y</var>,<var>x</var>) in metres - <strong>note the axis order!</strong></td></tr>
770     * </table>
771     *
772     * @throws FactoryException if the math transform cannot be created.
773     * @throws TransformException if the example point cannot be transformed.
774     *
775     * @see ParameterizedTransformTest#testPolarStereographicA()
776     */
777    @Test
778    public void testEPSG_32661() throws FactoryException, TransformException {
779        isPolar = true;
780        swapxy  = true;
781        runProjectionTest(32661);
782    }
783
784    /**
785     * Tests the EPSG:3032 (<cite>WGS 84 / Australian Antarctic Polar Stereographic</cite>) projected CRS.
786     *
787     * <table class="ogc">
788     * <caption>CRS characteristics</caption>
789     * <tr><td>Projection method:</td>   <td>Polar Stereographic (variant B)</td></tr>
790     * <tr><td>Prime meridian:</td>      <td>Greenwich</td></tr>
791     * <tr><td>Source coordinates:</td>  <td>(φ,λ) in degrees</td></tr>
792     * <tr><td>Output coordinates:</td>  <td>(<var>x</var>,<var>y</var>) in metres</td></tr>
793     * </table>
794     *
795     * @throws FactoryException if the math transform cannot be created.
796     * @throws TransformException if the example point cannot be transformed.
797     *
798     * @see ParameterizedTransformTest#testPolarStereographicB()
799     */
800    @Test
801    public void testEPSG_3032() throws FactoryException, TransformException {
802        isPolar = true;
803        runProjectionTest(3032);
804    }
805
806    /**
807     * Tests the EPSG:28992 (<cite>Amersfoort / RD New</cite>) projected CRS.
808     *
809     * <table class="ogc">
810     * <caption>CRS characteristics</caption>
811     * <tr><td>Projection method:</td>   <td>Oblique Stereographic</td></tr>
812     * <tr><td>Prime meridian:</td>      <td>Greenwich</td></tr>
813     * <tr><td>Source coordinates:</td>  <td>(φ,λ) in degrees</td></tr>
814     * <tr><td>Output coordinates:</td>  <td>(<var>x</var>,<var>y</var>) in metres</td></tr>
815     * </table>
816     *
817     * @throws FactoryException if the math transform cannot be created.
818     * @throws TransformException if the example point cannot be transformed.
819     *
820     * @see ParameterizedTransformTest#testObliqueStereographic()
821     */
822    @Test
823    public void testEPSG_28992() throws FactoryException, TransformException {
824        runProjectionTest(28992);
825    }
826
827    /**
828     * Tests the EPSG:2065 (<cite>CRS S-JTSK (Ferro) / Krovak</cite>) projected CRS.
829     *
830     * <table class="ogc">
831     * <caption>CRS characteristics</caption>
832     * <tr><td>Projection method:</td>   <td>Krovak</td></tr>
833     * <tr><td>Prime meridian:</td>      <td>Ferro <strong>(17°40'W from Greenwich)</strong></td></tr>
834     * <tr><td>Source coordinates:</td>  <td>(φ,λ) in degrees</td></tr>
835     * <tr><td>Output coordinates:</td>  <td>(<var>y</var>,<var>x</var>) in metres, <strong>south oriented (S,W)</strong></td></tr>
836     * </table>
837     *
838     * @throws FactoryException if the math transform cannot be created.
839     * @throws TransformException if the example point cannot be transformed.
840     *
841     * @see ParameterizedTransformTest#testKrovak()
842     */
843    @Test
844    public void testEPSG_2065() throws FactoryException, TransformException {
845        swapxy = true;
846        flipxy = true;
847        primeMeridian = -(17 + 40.0/60);
848        runProjectionTest(2065);
849    }
850}