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.Arrays;
021import java.util.EnumSet;
022import java.util.Collections;
023import java.util.Random;
024import java.awt.geom.Rectangle2D;
025
026import org.opengis.util.FactoryException;
027import org.opengis.util.NoSuchIdentifierException;
028import org.opengis.geometry.DirectPosition;
029import org.opengis.parameter.ParameterValueGroup;
030import org.opengis.parameter.ParameterDescriptorGroup;
031import org.opengis.referencing.crs.ProjectedCRS;
032import org.opengis.referencing.operation.Matrix;
033import org.opengis.referencing.operation.MathTransform;
034import org.opengis.referencing.operation.MathTransform2D;
035import org.opengis.referencing.operation.TransformException;
036import org.opengis.referencing.operation.MathTransformFactory;
037import org.opengis.referencing.operation.Conversion;
038import org.opengis.referencing.operation.Transformation;
039import org.opengis.referencing.operation.SingleOperation;
040import org.opengis.test.ToleranceModifiers;
041import org.opengis.test.ToleranceModifier;
042import org.opengis.test.CalculationType;
043import org.opengis.test.Configuration;
044
045import org.junit.jupiter.api.Test;
046
047import static java.lang.StrictMath.*;
048import static org.junit.jupiter.api.Assertions.*;
049import static org.junit.jupiter.api.Assumptions.*;
050import static org.opengis.test.ToleranceModifiers.NAUTICAL_MILE;
051import static org.opengis.test.referencing.AffineTransformTest.NO_FACTORY;
052
053
054/**
055 * Tests {@linkplain MathTransformFactory#createParameterizedTransform(ParameterValueGroup)
056 * parameterized math transforms} from the {@code org.opengis.referencing.operation} package.
057 * Math transform instances are created using the factory given at construction time.
058 *
059 * <h2>Skipping tests for unsupported operations</h2>
060 * If the tested factory throws a {@link NoSuchIdentifierException} during the invocation
061 * of one of the following methods:
062 *
063 * <ul>
064 *   <li>{@link MathTransformFactory#getDefaultParameters(String)}</li>
065 *   <li>{@link MathTransformFactory#createParameterizedTransform(ParameterValueGroup)}</li>
066 * </ul>
067 *
068 * then the tests is skipped. If any other kind of exception is thrown, or if {@code NoSuchIdentifierException}
069 * is thrown under other circumstances than the invocation of above methods, then the test fails.
070 *
071 * <h2>Tests and accuracy</h2>
072 * By default, every tests expect an accuracy of 1 centimetre. This accuracy matches the precision
073 * of most example points given in the EPSG guidance notice. Implementers can modify the kind of
074 * tests being executed and the tolerance threshold in different ways:
075 *
076 * <ul>
077 *   <li>Set some <code>is&lt;<var>Operation</var>&gt;Supported</code> fields to {@code false}.</li>
078 *   <li>Override some of the {@code testFoo()} method and set the {@link #tolerance tolerance} field
079 *       before to invoke {@code super.testFoo()}.</li>
080 *   <li>Override {@link #normalize(DirectPosition, DirectPosition, CalculationType)
081 *       normalize(DirectPosition, DirectPosition, CalculationType)}.</li>
082 *   <li>Override {@link #assertMatrixEquals(Matrix, Matrix, Matrix, String)}.</li>
083 * </ul>
084 *
085 * <h2>Usage example</h2>
086 * in order to specify their factories and run the tests in a JUnit framework, implementers can define
087 * a subclass in their own test suite as in the example below. That example shows also how implementers
088 * can alter some tests (here the tolerance value for the <cite>Lambert Azimuthal Equal Area</cite> projection)
089 * and add more checks to be executed after every tests (here ensuring that the {@linkplain #transform transform}
090 * implements the {@link MathTransform2D} interface):
091 *
092 * {@snippet lang="java" :
093 * import org.junit.jupiter.api.Test;
094 * import org.opengis.test.referencing.ParameterizedTransformTest;
095 * import static org.junit.jupiter.api.Assertions.*;
096 *
097 * public class MyTest extends ParameterizedTransformTest {
098 *     public MyTest() {
099 *         super(new MyMathTransformFactory());
100 *     }
101 *
102 *     @Test
103 *     @Override
104 *     public void testLambertAzimuthalEqualArea() throws FactoryException, TransformException {
105 *         tolerance = 0.1;                              // Increase the tolerance value to 10 cm.
106 *         super.testLambertAzimuthalEqualArea();
107 *         // If more tests specific to this projection are wanted, do them here.
108 *         // In this example, we replace the ellipsoid by a sphere and test again.
109 *         // Note that spherical formulas can have an error up to 30 km compared
110 *         // to ellipsoidal formulas, so we have to relax again the tolerance threshold.
111 *         parameters.parameter("semi_minor").setValue(parameters.parameter("semi_major").doubleValue());
112 *         tolerance = 30000;                            // Increase the tolerance value to 30 km.
113 *         super.testLambertAzimuthalEqualArea();
114 *     }
115 *
116 *     @After
117 *     public void ensureAllTransformAreMath2D() {
118 *         assertTrue(transform instanceof MathTransform2D);
119 *     }
120 * }}
121 *
122 * @see AffineTransformTest
123 * @see AuthorityFactoryTest
124 *
125 * @author  Martin Desruisseaux (Geomatys)
126 * @version 3.1
127 * @since   3.1
128 */
129@SuppressWarnings("strictfp")   // Because we still target Java 11.
130public strictfp class ParameterizedTransformTest extends TransformTestCase {
131    /**
132     * The default tolerance threshold for comparing the results of direct transforms.
133     * This is set to the precision of coordinate point givens in the EPSG and IGNF
134     * documentation.
135     */
136    private static final double TRANSFORM_TOLERANCE = 0.01;
137
138    /**
139     * The tolerance threshold for comparing the derivative coefficients. In each column of the
140     * derivative matrix of a map projection, there is typically one value greater than 100000
141     * (100 km - same order of magnitude than the transformed coordinate values) and all other
142     * values are close to zero. However, we cannot use the {@link #TRANSFORM_TOLERANCE} value
143     * in every cases because the expected derivative coefficients are computed using a numerical
144     * approximation. Some empirical tests have show that the difference between <i>forward
145     * difference</i> and <i>backward difference</i> can be close to 0.25, so we must
146     * be prepared to increase this tolerance threshold.
147     */
148    private static final double DERIVATIVE_TOLERANCE = 0.01;
149
150    /**
151     * The delta value to use for computing an approximation of the derivative by finite
152     * difference, in metres. The conversion from metres to degrees is performed using
153     * the standard length of a nautical mile.
154     *
155     * <p>The 100 metres value has been determined empirically as a good compromise for map
156     * projections.  Experience suggests that smaller values often <em>decrease</em> the
157     * precision, because of floating point errors when subtracting big numbers that are
158     * close in magnitude.</p>
159     */
160    private static final double DERIVATIVE_DELTA = 100;
161
162    /**
163     * The factory for creating {@link MathTransform} objects, or {@code null} if none.
164     */
165    protected final MathTransformFactory mtFactory;
166
167    /**
168     * The parameters of the math transform being tested. This field is set, together with the
169     * {@link #transform transform} field, during the execution of every {@code testFoo()} method
170     * in this class.
171     *
172     * <p>If this field is non-null before a test is run, then those parameters will be used directly.
173     * This allow implementers to alter the parameters before to run the test one more time.</p>
174     */
175    protected ParameterValueGroup parameters;
176
177    /**
178     * A description of the test being run. This field is provided only for information purpose
179     * (typically for producing logging or error messages); it is not actually used by the tests.
180     * The value can be:
181     *
182     * <ul>
183     *   <li>The name of the target {@link ProjectedCRS} when the {@linkplain #transform transform}
184     *       being tested is a map projection</li>
185     *   <li>The transformation name when the {@linkplain #transform transform} being tested is a
186     *       datum shift operation.</li>
187     * </ul>
188     */
189    protected String description;
190
191    /**
192     * Creates a new test without factory and with the given {@code isFooSupported} flags.
193     * The given array must be the result of a call to {@link #getEnabledKeys(int)}.
194     *
195     * @param  isEnabled  the enabled status of all options.
196     */
197    ParameterizedTransformTest(final boolean[] isEnabled) {
198        super(isEnabled);
199        mtFactory = null;
200    }
201
202    /**
203     * Creates a new test using the given factory. If the given factory is {@code null},
204     * then the tests will be skipped.
205     *
206     * @param factory  factory for creating {@link MathTransform} instances.
207     */
208    public ParameterizedTransformTest(final MathTransformFactory factory) {
209        this.mtFactory = factory;
210    }
211
212    /**
213     * Returns information about the configuration of the test which has been run.
214     * This method returns a map containing:
215     *
216     * <ul>
217     *   <li>All the entries defined in the {@linkplain TransformTestCase#configuration() parent class}.</li>
218     *   <li>All the following values associated to the {@link org.opengis.test.Configuration.Key} of the same name:
219     *     <ul>
220     *       <li>{@link #mtFactory}</li>
221     *     </ul>
222     *   </li>
223     * </ul>
224     *
225     * @return {@inheritDoc}
226     */
227    @Override
228    public Configuration configuration() {
229        final Configuration op = super.configuration();
230        assertNull(op.put(Configuration.Key.mtFactory, mtFactory));
231        return op;
232    }
233
234    /**
235     * Invoked for preparing the header of a test failure message.
236     *
237     * @param buffer   the buffer in which to append the header.
238     * @param message  user supplied message to append, or {@code null}.
239     */
240    @Override
241    final void appendErrorHeader(final StringBuilder buffer, final String message) {
242        if (description != null) {
243            buffer.append("Error in “").append(description).append("”: ");
244        }
245        super.appendErrorHeader(buffer, message);
246    }
247
248    /**
249     * Returns the error message for an unsupported operation method.
250     *
251     * @param  name  the operation method for which to return an error message.
252     * @return error message for an unsupported operation method.
253     */
254    private static String unsupportedMethod(final String name) {
255        return "The “" + name + "” operation method is not supported by the tested implementation.";
256    }
257
258    /**
259     * Initialized the {@link #parameters} field to the default values for the given operation method.
260     * If the tested implementation does not support the specified operation method, then the test will
261     * be skipped.
262     *
263     * @param  method  the operation method for which to set parameter values.
264     */
265    private void createParameters(final String method) {
266        assumeTrue(mtFactory != null, NO_FACTORY);
267        try {
268            parameters = mtFactory.getDefaultParameters(method);
269        } catch (NoSuchIdentifierException e) {
270            abort(unsupportedMethod(method));           // Will mark the test as "ignored".
271        }
272    }
273
274    /**
275     * Creates a math transform for the coordinate operation identified by {@link SamplePoints#operation}
276     * and stores the result in the {@link #transform} field.
277     * The set of allowed codes is documented in second column of the
278     * {@link PseudoEpsgFactory#createParameters(int)} method.
279     *
280     * @param  type    either {@code Conversion.class} or {@code Transformation.class}.
281     * @param  sample  the points which will be transformed.
282     * @throws FactoryException if the math transform cannot be created.
283     */
284    private void createMathTransform(final Class<? extends SingleOperation> type, final SamplePoints sample)
285            throws FactoryException
286    {
287        try {
288            if (parameters == null) {
289                assumeTrue(mtFactory != null, NO_FACTORY);
290                parameters = PseudoEpsgFactory.createParameters(mtFactory, sample.operation);
291                validators.validate(parameters);
292            }
293            if (transform == null) {
294                assumeTrue(mtFactory != null, NO_FACTORY);
295                transform = mtFactory.createParameterizedTransform(parameters);
296                assertNotNull(transform, description);
297                validators.validate(transform);
298            }
299        } catch (NoSuchIdentifierException e) {
300            /*
301             * If a code was not found, ensure that the factory does not declare that it was
302             * a supported code. If the code was unsupported, then the test will be ignored.
303             */
304            final String message;
305            if (parameters != null) {
306                final ParameterDescriptorGroup descriptor = parameters.getDescriptor();
307                if (!Collections.disjoint(Utilities.getNameAndAliases(descriptor),
308                        Utilities.getNameAndAliases(mtFactory.getAvailableMethods(type))))
309                {
310                    throw e;                // Will mark the test as "failed".
311                }
312                message = unsupportedMethod(Utilities.getName(descriptor));
313            } else {
314                message = "The “EPSG:" + sample.operation + "” coordinate operation uses the “" + e.getIdentifierCode()
315                        + "” method, which is not supported by the tested implementation.";
316            }
317            abort(message);                 // Will mark the test as "ignored".
318        }
319    }
320
321    /**
322     * Initializes the tolerance thresholds to their default values if the user did not specified custom thresholds.
323     * This method sets the {@linkplain TransformTestCase#tolerance tolerance} threshold in units of the target CRS
324     * (typically <var>metres</var> for map projections), and the {@linkplain #derivativeDeltas derivative deltas}
325     * in units of the source CRS (typically <var>degrees</var> for map projections).
326     * The current implementation sets the following values:
327     *
328     * <ul>
329     *   <li>{@link #tolerance} is sets to {@link #TRANSFORM_TOLERANCE}, unless a greater
330     *       tolerance threshold is already set in which case the existing value is left
331     *       unchanged.</li>
332     *   <li>{@link #derivativeDeltas} is set to a value in degrees corresponding to
333     *       approximately 1 metre on Earth (calculated using the standard nautical mile length).
334     *       A finer value can lead to more accurate derivative approximation by the
335     *       {@link #verifyDerivative(double[]) verifyDerivative(double...)} method,
336     *       at the expense of more sensitivity to the accuracy of the
337     *       {@link MathTransform#transform MathTransform.transform(…)} method being tested.</li>
338     * </ul>
339     *
340     * This method should be invoked <strong>after</strong> {@link #createMathTransform(Class, SamplePoints)}
341     * because because it needs to know the number of dimensions.
342     *
343     * @param  modifier  the tolerance modifier to apply.
344     */
345    final void setTolerance(final ToleranceModifier modifier) {
346        if (toleranceModifier == null) {
347            toleranceModifier = modifier;
348        }
349        if (!(tolerance >= TRANSFORM_TOLERANCE)) {      // !(a >= b) instead of (a < b) in order to catch NaN.
350            tolerance = TRANSFORM_TOLERANCE;
351        }
352        if (derivativeDeltas == null) {
353            derivativeDeltas = new double[transform.getSourceDimensions()];
354            Arrays.fill(derivativeDeltas, DERIVATIVE_DELTA / (60 * NAUTICAL_MILE));
355        }
356    }
357
358    /**
359     * Applies a unit conversion on the given coordinate values. This method is invoked by
360     * {@link AuthorityFactoryTest} before to test a {@link ProjectedCRS} using different
361     * units than the standard one. In addition to scale the units, this method scales also
362     * the tolerance factor by the same factor.
363     *
364     * @param mode  {@link CalculationType#DIRECT_TRANSFORM} for scaling the output units (from
365     *        metres to another linear unit), or {@link CalculationType#INVERSE_TRANSFORM} for
366     *        scaling the input units (from degrees to another angular unit).
367     * @param coordinates  the source or expected target points to scale.
368     * @param scale  the scale factor, from standard units to the CRS units.
369     */
370    final void applyUnitConversion(final CalculationType mode, final double[] coordinates, final double scale) {
371        for (int i=coordinates.length; --i>=0;) {
372            coordinates[i] *= scale;
373        }
374        toleranceModifier = ToleranceModifiers.concatenate(toleranceModifier,
375                ToleranceModifiers.scale(EnumSet.of(mode), scale, scale));
376    }
377
378    /**
379     * Tests the transform consistency using many random points inside the area of validity.
380     *
381     * @param  areaOfValidity  the domain in which to create test points.
382     * @throws TransformException if a point cannot be transformed.
383     */
384    final void verifyInDomainOfValidity(final Rectangle2D areaOfValidity) throws TransformException {
385        verifyInDomain(new double[] {
386            areaOfValidity.getMinX(),
387            areaOfValidity.getMinY()
388        }, new double[] {
389            areaOfValidity.getMaxX(),
390            areaOfValidity.getMaxY()
391        }, new int[] {
392            20, 20
393        }, new Random());
394    }
395
396    /**
397     * Tests the <q>Mercator (variant A)</q> (EPSG:9804) projection method.
398     * First, this method transforms the point given in the example section of the
399     * EPSG guidance note and compares the {@link MathTransform} result with the expected result.
400     * Next, this method transforms a random set of points in the projection area of validity
401     * and ensures that the {@linkplain MathTransform#inverse() inverse transform} and the
402     * {@linkplain MathTransform#derivative derivatives} are coherent.
403     *
404     * <p>The math transform parameters and the sample coordinates are:</p>
405     *
406     * <div class="horizontal-flow">
407     *   <table class="ogc">
408     *     <caption>CRS characteristics</caption>
409     *     <tr><th>Parameter</th>                      <th>Value</th></tr>
410     *     <tr><td>semi-major axis</td>                <td>6377397.155 m</td></tr>
411     *     <tr><td>semi-minor axis</td>                <td>6356078.962818189 m</td></tr>
412     *     <tr><td>Latitude of natural origin</td>     <td>0.0°</td></tr>
413     *     <tr><td>Longitude of natural origin</td>    <td>110.0°</td></tr>
414     *     <tr><td>Scale factor at natural origin</td> <td>0.997</td></tr>
415     *     <tr><td>False easting</td>                  <td>3900000.0 m</td></tr>
416     *     <tr><td>False northing</td>                 <td>900000.0 m</td></tr>
417     *   </table>
418     *   <table class="ogc">
419     *     <caption>Test points</caption>
420     *     <tr>
421     *       <th>Source coordinates</th>
422     *       <th>Expected results</th>
423     *     </tr><tr class="coordinates">
424     *       <td>120°E<br>3°S</td>
425     *       <td>5009726.58 m<br>569150.82 m</td>
426     *     </tr><tr class="coordinates">
427     *       <td>110°E<br>0°N</td>
428     *       <td>3900000.00 m<br>900000.00 m</td>
429     *     </tr>
430     *   </table>
431     * </div>
432     *
433     * @throws FactoryException if the math transform cannot be created.
434     * @throws TransformException if the example point cannot be transformed.
435     *
436     * @see AuthorityFactoryTest#testEPSG_3002()
437     */
438    @Test
439    public void testMercator1SP() throws FactoryException, TransformException {
440        description = "Makassar / NEIEZ";
441        final SamplePoints sample = SamplePoints.forCRS(3002);
442        createMathTransform(Conversion.class, sample);
443        setTolerance(ToleranceModifier.PROJECTION);
444        verifyTransform(sample.sourcePoints, sample.targetPoints);
445        verifyInDomainOfValidity(sample.areaOfValidity);
446    }
447
448    /**
449     * Tests the <q>Mercator (variant B)</q> (EPSG:9805) projection method.
450     * First, this method transforms the point given in the example section of the
451     * EPSG guidance note and compares the {@link MathTransform} result with the expected result.
452     * Next, this method transforms a random set of points in the projection area of validity
453     * and ensures that the {@linkplain MathTransform#inverse() inverse transform} and the
454     * {@linkplain MathTransform#derivative derivatives} are coherent.
455     *
456     * <p>The math transform parameters and the sample coordinates are:</p>
457     *
458     * <div class="horizontal-flow">
459     *   <table class="ogc">
460     *     <caption>CRS characteristics</caption>
461     *     <tr><th>Parameter</th>                         <th>Value</th></tr>
462     *     <tr><td>semi-major axis</td>                   <td>6378245.0 m</td></tr>
463     *     <tr><td>semi-minor axis</td>                   <td>6356863.018773047 m</td></tr>
464     *     <tr><td>Latitude of 1st standard parallel</td> <td>42.0°</td></tr>
465     *     <tr><td>Longitude of natural origin</td>       <td>51.0°</td></tr>
466     *     <tr><td>False easting</td>                     <td>0.0 m</td></tr>
467     *     <tr><td>False northing</td>                    <td>0.0 m</td></tr>
468     *   </table>
469     *   <table class="ogc">
470     *     <caption>Test points</caption>
471     *     <tr>
472     *       <th>Source coordinates</th>
473     *       <th>Expected results</th>
474     *     </tr><tr class="coordinates">
475     *       <td>53°E<br>53°N</td>
476     *       <td>165704.29 m<br>5171848.07 m</td>
477     *     </tr><tr class="coordinates">
478     *       <td>51°E<br>0°N</td>
479     *       <td>0.00 m<br>0.00 m</td>
480     *     </tr>
481     *   </table>
482     * </div>
483     *
484     * @throws FactoryException if the math transform cannot be created.
485     * @throws TransformException if the example point cannot be transformed.
486     *
487     * @see AuthorityFactoryTest#testEPSG_3388()
488     */
489    @Test
490    public void testMercator2SP() throws FactoryException, TransformException {
491        description = "Pulkovo 1942 / Caspian Sea Mercator";
492        final SamplePoints sample = SamplePoints.forCRS(3388);
493        createMathTransform(Conversion.class, sample);
494        setTolerance(ToleranceModifier.PROJECTION);
495        verifyTransform(sample.sourcePoints, sample.targetPoints);
496        verifyInDomainOfValidity(sample.areaOfValidity);
497    }
498
499    /**
500     * Tests the <q>Mercator (variant C)</q> (EPSG:1044) projection method.
501     * First, this method transforms the point given in the example section of the
502     * EPSG guidance note and compares the {@link MathTransform} result with the expected result.
503     * Next, this method transforms a random set of points in the projection area of validity
504     * and ensures that the {@linkplain MathTransform#inverse() inverse transform} and the
505     * {@linkplain MathTransform#derivative derivatives} are coherent.
506     *
507     * <p>The math transform parameters and the sample coordinates are below.
508     * Note that this is similar to {@link #testMercator2SP()}, except that the
509     * <q>latitude of false origin</q> parameter is set to 42°N.</p>
510     *
511     * <div class="horizontal-flow">
512     *   <table class="ogc">
513     *     <caption>CRS characteristics</caption>
514     *     <tr><th>Parameter</th>                         <th>Value</th></tr>
515     *     <tr><td>semi-major axis</td>                   <td>6378245.0 m</td></tr>
516     *     <tr><td>semi-minor axis</td>                   <td>6356863.018773047 m</td></tr>
517     *     <tr><td>Latitude of 1st standard parallel</td> <td>42.0°</td></tr>
518     *     <tr><td>Longitude of natural origin</td>       <td>51.0°</td></tr>
519     *     <tr><td>Latitude of false origin</td>          <td>42.0°</td></tr>
520     *     <tr><td>Easting at false origin</td>           <td>0.0 m</td></tr>
521     *     <tr><td>Northing at false origin</td>          <td>0.0 m</td></tr>
522     *   </table>
523     *   <table class="ogc">
524     *     <caption>Test points</caption>
525     *     <tr>
526     *       <th>Source coordinates</th>
527     *       <th>Expected results</th>
528     *     </tr><tr class="coordinates">
529     *       <td>53°E<br>53°N</td>
530     *       <td>165704.29 m<br>1351950.22 m</td>
531     *     </tr><tr class="coordinates">
532     *       <td>51°E<br>42°N</td>
533     *       <td>0.00 m<br>0.00 m</td>
534     *     </tr>
535     *   </table>
536     * </div>
537     *
538     * @throws FactoryException if the math transform cannot be created.
539     * @throws TransformException if the example point cannot be transformed.
540     */
541    @Test
542    public void testMercatorVariantC() throws FactoryException, TransformException {
543        description = "Pulkovo 1942 / Caspian Sea Mercator";
544        final SamplePoints sample = SamplePoints.forCRS(3388);
545        sample.targetPoints[1] = 1351950.22;    // New Northing value for 53°N.
546        sample.sourcePoints[3] = 42;            // New latitude where we expect a northing of 0 m.
547        /*
548         * Following is basically a copy-and-paste of PseudoEpsgFactory.createParameters(mtFactory, 3388)
549         * with a different projection ("variant C" instead of "variant B") and one more parameter value
550         * (the "Latitude of false origin").
551         */
552        createParameters("Mercator (variant C)");
553        parameters.parameter("semi_major").setValue(6378245.0);                         // Krassowski 1940
554        parameters.parameter("semi_minor").setValue(6378245.0 * (1 - 1/298.3));
555        parameters.parameter("Latitude of 1st standard parallel").setValue(42.0);
556        parameters.parameter("Longitude of natural origin")      .setValue(51.0);
557        parameters.parameter("Latitude of false origin")         .setValue(42.0);
558        validators.validate(parameters);
559        /*
560         * Following is common to all tests in this class.
561         */
562        createMathTransform(Conversion.class, sample);
563        setTolerance(ToleranceModifier.PROJECTION);
564        verifyTransform(sample.sourcePoints, sample.targetPoints);
565        verifyInDomainOfValidity(sample.areaOfValidity);
566    }
567
568    /**
569     * Tests the <q>Mercator (Spherical)</q> (EPSG:1026) projection method.
570     * First, this method transforms the point given in the example section of the
571     * EPSG guidance note and compares the {@link MathTransform} result with the expected result.
572     * Next, this method transforms a random set of points in the projection area of validity
573     * and ensures that the {@linkplain MathTransform#inverse() inverse transform} and the
574     * {@linkplain MathTransform#derivative derivatives} are coherent.
575     *
576     * <p>The math transform parameters and the sample coordinates are below.
577     * Note that the sample point is the same as for {@link #testPseudoMercator()},
578     * but with a different result in projected coordinates.</p>
579     *
580     * <div class="horizontal-flow">
581     *   <table class="ogc">
582     *     <caption>CRS characteristics</caption>
583     *     <tr><th>Parameter</th>                   <th>Value</th></tr>
584     *     <tr><td>semi-major axis</td>             <td>6371007.0 m</td></tr>
585     *     <tr><td>semi-minor axis</td>             <td>6371007.0 m</td></tr>
586     *     <tr><td>Longitude of natural origin</td> <td>0.0°</td></tr>
587     *     <tr><td>False easting</td>               <td>0.0 m</td></tr>
588     *     <tr><td>False northing</td>              <td>0.0 m</td></tr>
589     *   </table>
590     *   <table class="ogc">
591     *     <caption>Test points</caption>
592     *     <tr>
593     *       <th>Source coordinates</th>
594     *       <th>Expected results</th>
595     *     </tr><tr class="coordinates">
596     *       <td>100°20'00.000"W<br>24°22'54.433"N</td>
597     *       <td>-11156569.90 m<br>2796869.94 m</td>
598     *     </tr><tr class="coordinates">
599     *       <td>0°E<br>0°N</td>
600     *       <td>0.00 m<br>0.00 m</td>
601     *     </tr>
602     *   </table>
603     * </div>
604     *
605     * @throws FactoryException if the math transform cannot be created.
606     * @throws TransformException if the example point cannot be transformed.
607     */
608    @Test
609    public void testMercatorSpherical() throws FactoryException, TransformException {
610        description = "World Spherical Mercator";
611        final SamplePoints sample = SamplePoints.forCRS(3857);
612        sample.targetPoints[0] = -11156569.90;    // New Easting value.
613        sample.targetPoints[1] =   2796869.94;    // New Northing value.
614        createParameters("Mercator (Spherical)");
615        parameters.parameter("semi_major").setValue(6371007.0);
616        parameters.parameter("semi_minor").setValue(6371007.0);
617        validators.validate(parameters);
618        createMathTransform(Conversion.class, sample);
619        setTolerance(ToleranceModifier.PROJECTION);
620        verifyTransform(sample.sourcePoints, sample.targetPoints);
621        verifyInDomainOfValidity(sample.areaOfValidity);
622    }
623
624    /**
625     * Tests the <q>Mercator Popular Visualisation Pseudo Mercator</q> (EPSG:1024) projection
626     * method. First, this method transforms the point given in the example section of the
627     * EPSG guidance note and compares the {@link MathTransform} result with the expected result.
628     * Next, this method transforms a random set of points in the projection area of validity
629     * and ensures that the {@linkplain MathTransform#inverse() inverse transform} and the
630     * {@linkplain MathTransform#derivative derivatives} are coherent.
631     *
632     * <p>The math transform parameters and the sample coordinates are:</p>
633     *
634     * <div class="horizontal-flow">
635     *   <table class="ogc">
636     *     <caption>CRS characteristics</caption>
637     *     <tr><th>Parameter</th>                   <th>Value</th></tr>
638     *     <tr><td>semi-major axis</td>             <td>6378137.0 m</td></tr>
639     *     <tr><td>semi-minor axis</td>             <td>6356752.314247833 m</td></tr>
640     *     <tr><td>Latitude of natural origin</td>  <td>0.0°</td></tr>
641     *     <tr><td>Longitude of natural origin</td> <td>0.0°</td></tr>
642     *     <tr><td>False easting</td>               <td>0.0 m</td></tr>
643     *     <tr><td>False northing</td>              <td>0.0 m</td></tr>
644     *   </table>
645     *   <table class="ogc">
646     *     <caption>Test points</caption>
647     *     <tr>
648     *       <th>Source coordinates</th>
649     *       <th>Expected results</th>
650     *     </tr><tr class="coordinates">
651     *       <td>100°20'00.000"W<br>24°22'54.433"N</td>
652     *       <td>-11169055.58 m<br>2800000.00 m</td>
653     *     </tr><tr class="coordinates">
654     *       <td>0°E<br>0°N</td>
655     *       <td>0.00 m<br>0.00 m</td>
656     *     </tr>
657     *   </table>
658     * </div>
659     *
660     * @throws FactoryException if the math transform cannot be created.
661     * @throws TransformException if the example point cannot be transformed.
662     *
663     * @see AuthorityFactoryTest#testEPSG_3857()
664     */
665    @Test
666    public void testPseudoMercator() throws FactoryException, TransformException {
667        description = "WGS 84 / Pseudo-Mercator";
668        final SamplePoints sample = SamplePoints.forCRS(3857);
669        createMathTransform(Conversion.class, sample);
670        setTolerance(ToleranceModifier.PROJECTION);
671        verifyTransform(sample.sourcePoints, sample.targetPoints);
672        verifyInDomainOfValidity(sample.areaOfValidity);
673    }
674
675    /**
676     * Tests the <q>IGNF:MILLER</q> projection.
677     * First, this method transforms the point given below
678     * and compares the {@link MathTransform} result with the expected result. Next, this method
679     * transforms a random set of points in the projection area of validity and ensures that the
680     * {@linkplain MathTransform#inverse() inverse transform} and the
681     * {@linkplain MathTransform#derivative derivatives} are coherent.
682     *
683     * <p>The math transform parameters and the sample coordinates are:</p>
684     *
685     * <div class="horizontal-flow">
686     *   <table class="ogc">
687     *     <caption>CRS characteristics</caption>
688     *     <tr><th>Parameter</th>        <th>Value</th></tr>
689     *     <tr><td>semi_major</td>       <td>6378137.0 m</td></tr>
690     *     <tr><td>semi_minor</td>       <td>6378137.0 m</td></tr>
691     *     <tr><td>central_meridian</td> <td>0.0°</td></tr>
692     *     <tr><td>false_easting</td>    <td>0.0 m</td></tr>
693     *     <tr><td>false_northing</td>   <td>0.0 m</td></tr>
694     *   </table>
695     *   <table class="ogc">
696     *     <caption>Test points</caption>
697     *     <tr>
698     *       <th>Source coordinates</th>
699     *       <th>Expected results</th>
700     *     </tr><tr class="coordinates">
701     *       <td>2.478917°E<br>48.805639°N</td>
702     *       <td>275951.78 m<br>5910061.78 m</td>
703     *     </tr><tr class="coordinates">
704     *       <td>0°E<br>0°N</td>
705     *       <td>0.00 m<br>0.00 m</td>
706     *     </tr>
707     *   </table>
708     * </div>
709     *
710     * @throws FactoryException if the math transform cannot be created.
711     * @throws TransformException if the example point cannot be transformed.
712     */
713    @Test
714    public void testMiller() throws FactoryException, TransformException {
715        description = "IGNF:MILLER";
716        final SamplePoints sample = SamplePoints.forCRS(310642901);
717        createMathTransform(Conversion.class, sample);
718        setTolerance(ToleranceModifier.PROJECTION);
719        verifyTransform(sample.sourcePoints, sample.targetPoints);
720        verifyInDomainOfValidity(sample.areaOfValidity);
721    }
722
723    /**
724     * Tests the <q>Hotine Oblique Mercator (variant B)</q> (EPSG:9815) projection method.
725     * First, this method transforms the point given in the example section of the
726     * EPSG guidance note and compares the {@link MathTransform} result with the expected result.
727     * Next, this method transforms a random set of points in the projection area of validity
728     * and ensures that the {@linkplain MathTransform#inverse() inverse transform} and the
729     * {@linkplain MathTransform#derivative derivatives} are coherent.
730     *
731     * <p>The math transform parameters and the sample coordinates are:</p>
732     *
733     * <div class="horizontal-flow">
734     *   <table class="ogc">
735     *     <caption>CRS characteristics</caption>
736     *     <tr><th>Parameter</th>                         <th>Value</th></tr>
737     *     <tr><td>semi-major axis</td>                   <td>6377298.556 m</td></tr>
738     *     <tr><td>semi-minor axis</td>                   <td>6356097.550300896 m</td></tr>
739     *     <tr><td>Latitude of projection centre</td>     <td>4.0°</td></tr>
740     *     <tr><td>Longitude of projection centre</td>    <td>109.6855202029758°</td></tr>
741     *     <tr><td>Azimuth of initial line</td>           <td>53.31582047222222°</td></tr>
742     *     <tr><td>Angle from Rectified to Skew Grid</td> <td>53.13010236111111°</td></tr>
743     *     <tr><td>Scale factor on initial line</td>      <td>0.99984</td></tr>
744     *     <tr><td>Easting at projection centre</td>      <td>590476.87 m</td></tr>
745     *     <tr><td>Northing at projection centre</td>     <td>442857.65 m</td></tr>
746     *   </table>
747     *   <table class="ogc">
748     *     <caption>Test points</caption>
749     *     <tr>
750     *       <th>Source coordinates</th>
751     *       <th>Expected results</th>
752     *     </tr><tr class="coordinates">
753     *       <td>115°48'19.8196"E<br>5°23'14.1129"N</td>
754     *       <td>679245.73 m<br>596562.78 m</td>
755     *     </tr><tr class="coordinates">
756     *       <td>115°E<br>4°N</td>
757     *       <td>590476.87 m<br>442857.65 m</td>
758     *     </tr>
759     *   </table>
760     * </div>
761     *
762     * @throws FactoryException if the math transform cannot be created.
763     * @throws TransformException if the example point cannot be transformed.
764     *
765     * @see AuthorityFactoryTest#testEPSG_29873()
766     */
767    @Test
768    public void testHotineObliqueMercator() throws FactoryException, TransformException {
769        description = "Timbalai 1948 / RSO Borneo (m)";
770        final SamplePoints sample = SamplePoints.forCRS(29873);
771        createMathTransform(Conversion.class, sample);
772        setTolerance(ToleranceModifier.PROJECTION);
773        verifyTransform(sample.sourcePoints, sample.targetPoints);
774        verifyInDomainOfValidity(sample.areaOfValidity);
775    }
776
777    /**
778     * Tests the <q>Transverse Mercator</q> (EPSG:9807) projection method.
779     * First, this method transforms the point given in the example section of the
780     * EPSG guidance note and compares the {@link MathTransform} result with the expected result.
781     * Next, this method transforms a random set of points in the projection area of validity
782     * and ensures that the {@linkplain MathTransform#inverse() inverse transform} and the
783     * {@linkplain MathTransform#derivative derivatives} are coherent.
784     *
785     * <p>The math transform parameters and the sample coordinates are:</p>
786     *
787     * <div class="horizontal-flow">
788     *   <table class="ogc">
789     *     <caption>CRS characteristics</caption>
790     *     <tr><th>Parameter</th>                      <th>Value</th></tr>
791     *     <tr><td>semi-major axis</td>                <td>6377563.396 m</td></tr>
792     *     <tr><td>semi-minor axis</td>                <td>6356256.908909849 m</td></tr>
793     *     <tr><td>Latitude of natural origin</td>     <td>49.0°</td></tr>
794     *     <tr><td>Longitude of natural origin</td>    <td>-2.0°</td></tr>
795     *     <tr><td>Scale factor at natural origin</td> <td>0.9996012717</td></tr>
796     *     <tr><td>False easting</td>                  <td>400000.0 m</td></tr>
797     *     <tr><td>False northing</td>                 <td>-100000.0 m</td></tr>
798     *   </table>
799     *   <table class="ogc">
800     *     <caption>Test points</caption>
801     *     <tr>
802     *       <th>Source coordinates</th>
803     *       <th>Expected results</th>
804     *     </tr><tr class="coordinates">
805     *       <td>00°30'E<br>50°30'N</td>
806     *       <td>577274.98 m<br>69740.49 m</td>
807     *     </tr><tr class="coordinates">
808     *       <td>2°W<br>49°N</td>
809     *       <td>400000.00 m<br>-100000.00 m</td>
810     *     </tr>
811     *   </table>
812     * </div>
813     *
814     * @throws FactoryException if the math transform cannot be created.
815     * @throws TransformException if the example point cannot be transformed.
816     *
817     * @see AuthorityFactoryTest#testEPSG_27700()
818     */
819    @Test
820    public void testTransverseMercator() throws FactoryException, TransformException {
821        description = "OSGB 1936 / British National Grid";
822        final SamplePoints sample = SamplePoints.forCRS(27700);
823        createMathTransform(Conversion.class, sample);
824        setTolerance(ToleranceModifier.PROJECTION);
825        verifyTransform(sample.sourcePoints, sample.targetPoints);
826        verifyInDomainOfValidity(sample.areaOfValidity);
827    }
828
829    /**
830     * Tests the <cite>Transverse Mercator (South Orientated)</cite> (EPSG:9808) projection method.
831     * First, this method transforms the point given in the example section of the
832     * EPSG guidance note and compares the {@link MathTransform} result with the expected result.
833     * Next, this method transforms a random set of points in the projection area of validity
834     * and ensures that the {@linkplain MathTransform#inverse() inverse transform} and the
835     * {@linkplain MathTransform#derivative derivatives} are coherent.
836     *
837     * <p>The math transform parameters and the sample coordinates are:</p>
838     *
839     * <div class="horizontal-flow">
840     *   <table class="ogc">
841     *     <caption>CRS characteristics</caption>
842     *     <tr><th>Parameter</th>                      <th>Value</th></tr>
843     *     <tr><td>semi-major axis</td>                <td>6378137.0 m</td></tr>
844     *     <tr><td>semi-minor axis</td>                <td>6356752.314247833 m</td></tr>
845     *     <tr><td>Latitude of natural origin</td>     <td>0°</td></tr>
846     *     <tr><td>Longitude of natural origin</td>    <td>29°</td></tr>
847     *     <tr><td>Scale factor at natural origin</td> <td>1</td></tr>
848     *     <tr><td>False easting</td>                  <td>0 m</td></tr>
849     *     <tr><td>False northing</td>                 <td>0 m</td></tr>
850     *   </table>
851     *   <table class="ogc">
852     *     <caption>Test points</caption>
853     *     <tr>
854     *       <th>Source coordinates</th>
855     *       <th>Expected results</th>
856     *     </tr><tr class="coordinates">
857     *       <td>28°16'57.479"E<br>25°43'55.302"S</td>
858     *       <td>71984.48 m<br>2847342.74 m</td>
859     *     </tr><tr class="coordinates">
860     *       <td>20°E<br>0°S</td>
861     *       <td>0 m<br>0 m</td>
862     *     </tr>
863     *   </table>
864     * </div>
865     *
866     * @throws FactoryException if the math transform cannot be created.
867     * @throws TransformException if the example point cannot be transformed.
868     */
869    @Test
870    public void testTransverseMercatorSouthOrientated() throws FactoryException, TransformException {
871        description = "Hartebeesthoek94 / Lo29";
872        final SamplePoints sample = SamplePoints.forCRS(2053);
873        createMathTransform(Conversion.class, sample);
874        setTolerance(ToleranceModifier.PROJECTION);
875        /*
876         * In this particular case we have a conflict between the change of axis direction performed by the
877         * "Transverse Mercator (South Orientated)" operation method  and the (east, north) axis directions
878         * documented in the MathTransformFactory.createParameterizedTransform(…) method. We do not mandate
879         * any particular behavior at this time, so we have to determine what the implementer choose to do,
880         * by projecting a point in the south hemisphere and checking the sign of the result.
881         */
882        double[] expected = sample.targetPoints;
883        final double[] check = new double[] {-0.5, -0.5};
884        transform.transform(check, 0, check, 0, 1);
885        if (check[1] < 0) {
886            /*
887             * Point in the South hemisphere have negative y values. In other words, the implementer chooses to
888             * keep (east,north) directions instead of (west,south). Reverse the sign of all expected coordinates.
889             */
890            expected = expected.clone();
891            for (int i=0; i<expected.length; i++) {
892                expected[i] = -expected[i];
893            }
894        }
895        verifyTransform(sample.sourcePoints, expected);
896        verifyInDomainOfValidity(sample.areaOfValidity);
897    }
898
899    /**
900     * Tests the <q>Cassini-Soldner</q> (EPSG:9806) projection method.
901     * First, this method transforms the point given in the example section of the
902     * EPSG guidance note and compares the {@link MathTransform} result with the expected result.
903     * Next, this method transforms a random set of points in the projection area of validity
904     * and ensures that the {@linkplain MathTransform#inverse() inverse transform} and the
905     * {@linkplain MathTransform#derivative derivatives} are coherent.
906     *
907     * <p>The math transform parameters and the sample coordinates are:</p>
908     *
909     * <div class="horizontal-flow">
910     *   <table class="ogc">
911     *     <caption>CRS characteristics</caption>
912     *     <tr><th>Parameter</th>                   <th>Value</th></tr>
913     *     <tr><td>semi-major axis</td>             <td>6378350.8704 m</td></tr>
914     *     <tr><td>semi-minor axis</td>             <td>6356675.0184 m</td></tr>
915     *     <tr><td>Latitude of natural origin</td>  <td>10.441666666666666°</td></tr>
916     *     <tr><td>Longitude of natural origin</td> <td>-61.33333333333333°</td></tr>
917     *     <tr><td>False easting</td>               <td>86501.46392052001 m</td></tr>
918     *     <tr><td>False northing</td>              <td>65379.0134283 m</td></tr>
919     *   </table>
920     *   <table class="ogc">
921     *     <caption>Test points</caption>
922     *     <tr>
923     *       <th>Source coordinates</th>
924     *       <th>Expected results</th>
925     *     </tr><tr class="coordinates">
926     *       <td>60°00'00"W<br>10°00'00"N</td>
927     *       <td>66644.94 links<br>82536.22 links</td>
928     *     </tr><tr class="coordinates">
929     *       <td>61°20'00"W<br>10°26'30"N</td>
930     *       <td>430000.00 links<br>325000.00 links</td>
931     *     </tr>
932     *   </table>
933     *   <p class="right-note">1 link = 0.66 feet<br>1 feet = 0.3048 metre</p>
934     * </div>
935     *
936     * @throws FactoryException if the math transform cannot be created.
937     * @throws TransformException if the example point cannot be transformed.
938     *
939     * @see AuthorityFactoryTest#testEPSG_2314()
940     */
941    @Test
942    public void testCassiniSoldner() throws FactoryException, TransformException {
943        description = "Trinidad 1903 / Trinidad Grid";
944        final SamplePoints sample = SamplePoints.forCRS(2314);
945        createMathTransform(Conversion.class, sample);
946        setTolerance(ToleranceModifier.PROJECTION);
947        verifyTransform(sample.sourcePoints, sample.targetPoints);
948        verifyInDomainOfValidity(sample.areaOfValidity);
949    }
950
951    /**
952     * Tests the <q>Hyperbolic Cassini-Soldner</q> (EPSG:9833) projection method.
953     * First, this method transforms the point given in the example section of the
954     * EPSG guidance note and compares the {@link MathTransform} result with the expected result.
955     * Next, this method transforms a random set of points in the projection area of validity
956     * and ensures that the {@linkplain MathTransform#inverse() inverse transform} and the
957     * {@linkplain MathTransform#derivative derivatives} are coherent.
958     *
959     * <p>The math transform parameters and the sample coordinates are:</p>
960     *
961     * <div class="horizontal-flow">
962     *   <table class="ogc">
963     *     <caption>CRS characteristics</caption>
964     *     <tr><th>Parameter</th>                   <th>Value</th></tr>
965     *     <tr><td>semi-major axis</td>             <td>6378306.3696 m</td></tr>
966     *     <tr><td>semi-minor axis</td>             <td>6356571.9960 m</td></tr>
967     *     <tr><td>Latitude of natural origin</td>  <td>-16.25°</td></tr>
968     *     <tr><td>Longitude of natural origin</td> <td>179.33333333333333°</td></tr>
969     *     <tr><td>False easting</td>               <td>251727.9155424 m</td></tr>
970     *     <tr><td>False northing</td>              <td>334519.9537680 m</td></tr>
971     *   </table>
972     *   <table class="ogc">
973     *     <caption>Test points</caption>
974     *     <tr>
975     *       <th>Source coordinates</th>
976     *       <th>Expected results</th>
977     *     </tr><tr class="coordinates">
978     *       <td>179°59′39.6115″E<br>16°50′29.2435″S</td>
979     *       <td>1601528.90 links<br>1336966.01 links</td>
980     *     </tr><tr class="coordinates">
981     *       <td>16°15'00"S<br>179°20'00"E</td>
982     *       <td>41251331.8 links<br>1662888.5 links</td>
983     *     </tr>
984     *   </table>
985     *   <p class="right-note">1 link = 0.66 feet<br>1 feet = 0.3048 metre</p>
986     * </div>
987     *
988     * @throws FactoryException if the math transform cannot be created.
989     * @throws TransformException if the example point cannot be transformed.
990     *
991     * @see AuthorityFactoryTest#testEPSG_3139()
992     */
993    @Test
994    public void testHyperbolicCassiniSoldner() throws FactoryException, TransformException {
995        description = "Vanua Levu 1915 / Vanua Levu Grid";
996        final SamplePoints sample = SamplePoints.forCRS(3139);
997        createMathTransform(Conversion.class, sample);
998        setTolerance(ToleranceModifier.PROJECTION);
999        verifyTransform(sample.sourcePoints, sample.targetPoints);
1000        verifyInDomainOfValidity(sample.areaOfValidity);
1001    }
1002
1003    /**
1004     * Tests the <q>Lambert Conic Conformal (1SP)</q> (EPSG:9801) projection method.
1005     * First, this method transforms the point given in the example section of the
1006     * EPSG guidance note and compares the {@link MathTransform} result with the expected result.
1007     * Next, this method transforms a random set of points in the projection area of validity
1008     * and ensures that the {@linkplain MathTransform#inverse() inverse transform} and the
1009     * {@linkplain MathTransform#derivative derivatives} are coherent.
1010     *
1011     * <p>The math transform parameters and the sample coordinates are:</p>
1012     *
1013     * <div class="horizontal-flow">
1014     *   <table class="ogc">
1015     *     <caption>CRS characteristics</caption>
1016     *     <tr><th>Parameter</th>                      <th>Value</th></tr>
1017     *     <tr><td>semi-major axis</td>                <td>6378206.4 m</td></tr>
1018     *     <tr><td>semi-minor axis</td>                <td>6356583.8 m</td></tr>
1019     *     <tr><td>Latitude of natural origin</td>     <td>18.0°</td></tr>
1020     *     <tr><td>Longitude of natural origin</td>    <td>-77.0°</td></tr>
1021     *     <tr><td>Scale factor at natural origin</td> <td>1.0</td></tr>
1022     *     <tr><td>False easting</td>                  <td>250000.0 m</td></tr>
1023     *     <tr><td>False northing</td>                 <td>150000.0 m</td></tr>
1024     *   </table>
1025     *   <table class="ogc">
1026     *     <caption>Test points</caption>
1027     *     <tr>
1028     *       <th>Source coordinates</th>
1029     *       <th>Expected results</th>
1030     *     </tr><tr class="coordinates">
1031     *       <td>76°56'37.26"W<br>17°55'55.80"N</td>
1032     *       <td>255966.58 m<br>142493.51 m</td>
1033     *     </tr><tr class="coordinates">
1034     *       <td>77°W<br>18°N</td>
1035     *       <td>250000.00 m<br>150000.00 m</td>
1036     *     </tr>
1037     *   </table>
1038     * </div>
1039     *
1040     * @throws FactoryException if the math transform cannot be created.
1041     * @throws TransformException if the example point cannot be transformed.
1042     *
1043     * @see AuthorityFactoryTest#testEPSG_24200()
1044     */
1045    @Test
1046    public void testLambertConicConformal1SP() throws FactoryException, TransformException {
1047        description = "JAD69 / Jamaica National Grid";
1048        final SamplePoints sample = SamplePoints.forCRS(24200);
1049        createMathTransform(Conversion.class, sample);
1050        setTolerance(ToleranceModifier.PROJECTION);
1051        verifyTransform(sample.sourcePoints, sample.targetPoints);
1052        verifyInDomainOfValidity(sample.areaOfValidity);
1053    }
1054
1055    /**
1056     * Tests the <q>Lambert Conic Conformal (2SP)</q> (EPSG:9802) projection method.
1057     * First, this method transforms the point given in the example section of the
1058     * EPSG guidance note and compares the {@link MathTransform} result with the expected result.
1059     * Next, this method transforms a random set of points in the projection area of validity
1060     * and ensures that the {@linkplain MathTransform#inverse() inverse transform} and the
1061     * {@linkplain MathTransform#derivative derivatives} are coherent.
1062     *
1063     * <p>The math transform parameters and the sample coordinates are:</p>
1064     *
1065     * <div class="horizontal-flow">
1066     *   <table class="ogc">
1067     *     <caption>CRS characteristics</caption>
1068     *     <tr><th>Parameter</th>                         <th>Value</th></tr>
1069     *     <tr><td>semi-major axis</td>                   <td>6378206.4 m</td></tr>
1070     *     <tr><td>semi-minor axis</td>                   <td>6356583.8 m</td></tr>
1071     *     <tr><td>Latitude of false origin</td>          <td>27.833333333333333°</td></tr>
1072     *     <tr><td>Longitude of false origin</td>         <td>-99.0°</td></tr>
1073     *     <tr><td>Latitude of 1st standard parallel</td> <td>28.383333333333333°</td></tr>
1074     *     <tr><td>Latitude of 2nd standard parallel</td> <td>30.283333333333333°</td></tr>
1075     *     <tr><td>Easting at false origin</td>           <td>609601.2192024385 m</td></tr>
1076     *     <tr><td>Northing at false origin</td>          <td>0.0 m</td></tr>
1077     *   </table>
1078     *   <table class="ogc">
1079     *     <caption>Test points</caption>
1080     *     <tr>
1081     *       <th>Source coordinates</th>
1082     *       <th>Expected results</th>
1083     *     </tr><tr class="coordinates">
1084     *       <td>96°00'W<br>28°30'N</td>
1085     *       <td>2963503.91 US feet<br>254759.80 US feet</td>
1086     *     </tr><tr class="coordinates">
1087     *       <td>99°00'W<br>27°30'N</td>
1088     *        <td>2000000.00 US feet<br>0 US feet</td>
1089     *     </tr>
1090     *   </table>
1091     *   <p class="right-note">1 metre = 3.2808333… US feet</p>
1092     * </div>
1093     *
1094     * @throws FactoryException if the math transform cannot be created.
1095     * @throws TransformException if the example point cannot be transformed.
1096     *
1097     * @see AuthorityFactoryTest#testEPSG_32040()
1098     */
1099    @Test
1100    public void testLambertConicConformal2SP() throws FactoryException, TransformException {
1101        description = "NAD27 / Texas South Central";
1102        final SamplePoints sample = SamplePoints.forCRS(32040);
1103        createMathTransform(Conversion.class, sample);
1104        setTolerance(ToleranceModifier.PROJECTION);
1105        verifyTransform(sample.sourcePoints, sample.targetPoints);
1106        verifyInDomainOfValidity(sample.areaOfValidity);
1107    }
1108
1109    /**
1110     * Tests the <q>Lambert Conic Conformal (2SP Belgium)</q> (EPSG:9803) projection method.
1111     * First, this method transforms the point given in the example section of the
1112     * EPSG guidance note and compares the {@link MathTransform} result with the expected result.
1113     * Next, this method transforms a random set of points in the projection area of validity
1114     * and ensures that the {@linkplain MathTransform#inverse() inverse transform} and the
1115     * {@linkplain MathTransform#derivative derivatives} are coherent.
1116     *
1117     * <p>The math transform parameters and the sample coordinates are:</p>
1118     *
1119     * <div class="horizontal-flow">
1120     *   <table class="ogc">
1121     *     <caption>CRS characteristics</caption>
1122     *     <tr><th>Parameter</th>                         <th>Value</th></tr>
1123     *     <tr><td>semi-major axis</td>                   <td>6378388.0 m</td></tr>
1124     *     <tr><td>semi-minor axis</td>                   <td>6356911.9461279465 m</td></tr>
1125     *     <tr><td>Latitude of false origin</td>          <td>90.0°</td></tr>
1126     *     <tr><td>Longitude of false origin</td>         <td>4.356939722222222°</td></tr>
1127     *     <tr><td>Latitude of 1st standard parallel</td> <td>49.83333333333333°</td></tr>
1128     *     <tr><td>Latitude of 2nd standard parallel</td> <td>51.16666666666667°</td></tr>
1129     *     <tr><td>Easting at false origin</td>           <td>150000.01256 m</td></tr>
1130     *     <tr><td>Northing at false origin</td>          <td>5400088.4378 m</td></tr>
1131     *   </table>
1132     *   <table class="ogc">
1133     *     <caption>Test points</caption>
1134     *     <tr>
1135     *       <th>Source coordinates</th>
1136     *       <th>Expected results</th>
1137     *     </tr><tr class="coordinates">
1138     *       <td>5°48'26.533"E<br>50°40'46.461"N</td>
1139     *       <td>251763.20 m<br>153034.13 m</td>
1140     *     </tr><tr class="coordinates">
1141     *       <td>4°21'24.983"E<br>90°00'00.000"N</td>
1142     *       <td>150000.01 m<br>5400088.44 m</td>
1143     *     </tr>
1144     *   </table>
1145     * </div>
1146     *
1147     * @throws FactoryException if the math transform cannot be created.
1148     * @throws TransformException if the example point cannot be transformed.
1149     *
1150     * @see AuthorityFactoryTest#testEPSG_31300()
1151     */
1152    @Test
1153    public void testLambertConicConformalBelgium() throws FactoryException, TransformException {
1154        description = "Belge 1972 / Belge Lambert 72";
1155        final SamplePoints sample = SamplePoints.forCRS(31300);
1156        createMathTransform(Conversion.class, sample);
1157        setTolerance(ToleranceModifier.PROJECTION);
1158        verifyTransform(sample.sourcePoints, sample.targetPoints);
1159        verifyInDomainOfValidity(sample.areaOfValidity);
1160    }
1161
1162    /**
1163     * Tests the <q>Lambert Conic Conformal (2SP Michigan)</q> (EPSG:1051) projection method.
1164     * First, this method transforms the point given in the example section of the
1165     * EPSG guidance note and compares the {@link MathTransform} result with the expected result.
1166     * Next, this method transforms a random set of points in the projection area of validity
1167     * and ensures that the {@linkplain MathTransform#inverse() inverse transform} and the
1168     * {@linkplain MathTransform#derivative derivatives} are coherent.
1169     *
1170     * <p>The math transform parameters and the sample coordinates are:</p>
1171     *
1172     * <div class="horizontal-flow">
1173     *   <table class="ogc">
1174     *     <caption>CRS characteristics</caption>
1175     *     <tr><th>Parameter</th>                         <th>Value</th></tr>
1176     *     <tr><td>semi-major axis</td>                   <td>6378206.4 m</td></tr>
1177     *     <tr><td>semi-minor axis</td>                   <td>6356583.8 m</td></tr>
1178     *     <tr><td>Latitude of false origin</td>          <td>43.316666666666667°</td></tr>
1179     *     <tr><td>Longitude of false origin</td>         <td>-84.333333333333333°</td></tr>
1180     *     <tr><td>Latitude of 1st standard parallel</td> <td>44.183333333333333°</td></tr>
1181     *     <tr><td>Latitude of 2nd standard parallel</td> <td>45.7°</td></tr>
1182     *     <tr><td>Easting at false origin</td>           <td>609601.2192024385 m</td></tr>
1183     *     <tr><td>Northing at false origin</td>          <td>0.0 m</td></tr>
1184     *   </table>
1185     *   <table class="ogc">
1186     *     <caption>Test points</caption>
1187     *     <tr>
1188     *       <th>Source coordinates</th>
1189     *       <th>Expected results</th>
1190     *     </tr><tr class="coordinates">
1191     *       <td>83°10"W<br>43°45'N</td>
1192     *       <td>2308335.75 US feet<br>160210.48 US feet</td>
1193     *     </tr><tr class="coordinates">
1194     *       <td>84°20'W<br>43°19'N</td>
1195     *       <td>2000000.00 US feet<br>0 US feet</td>
1196     *     </tr>
1197     *   </table>
1198     *   <p class="right-note">1 metre = 3.2808333… US feet</p>
1199     * </div>
1200     *
1201     * @throws FactoryException if the math transform cannot be created.
1202     * @throws TransformException if the example point cannot be transformed.
1203     */
1204    @Test
1205    public void testLambertConicConformalMichigan() throws FactoryException, TransformException {
1206        description = "NAD27 / Michigan Central";
1207        final SamplePoints sample = SamplePoints.forCRS(6201);
1208        createMathTransform(Conversion.class, sample);
1209        setTolerance(ToleranceModifier.PROJECTION);
1210        verifyTransform(sample.sourcePoints, sample.targetPoints);
1211        verifyInDomainOfValidity(sample.areaOfValidity);
1212    }
1213
1214    /**
1215     * Tests the <q>Lambert Azimuthal Equal Area</q> (EPSG:9820) projection method.
1216     * First, this method transforms the point given in the example section of the
1217     * EPSG guidance note and compares the {@link MathTransform} result with the expected result.
1218     * Next, this method transforms a random set of points in the projection area of validity
1219     * and ensures that the {@linkplain MathTransform#inverse() inverse transform} and the
1220     * {@linkplain MathTransform#derivative derivatives} are coherent.
1221     *
1222     * <p>The math transform parameters and the sample coordinates are:</p>
1223     *
1224     * <div class="horizontal-flow">
1225     *   <table class="ogc">
1226     *     <caption>CRS characteristics</caption>
1227     *     <tr><th>Parameter</th>                   <th>Value</th></tr>
1228     *     <tr><td>semi-major axis</td>             <td>6378137.0 m</td></tr>
1229     *     <tr><td>semi-minor axis</td>             <td>6356752.314140284 m</td></tr>
1230     *     <tr><td>Latitude of natural origin</td>  <td>52.0°</td></tr>
1231     *     <tr><td>Longitude of natural origin</td> <td>10.0°</td></tr>
1232     *     <tr><td>False easting</td>               <td>4321000.0 m</td></tr>
1233     *     <tr><td>False northing</td>              <td>3210000.0 m</td></tr>
1234     *   </table>
1235     *   <table class="ogc">
1236     *     <caption>Test points</caption>
1237     *     <tr>
1238     *       <th>Source coordinates</th>
1239     *       <th>Expected results</th>
1240     *     </tr><tr class="coordinates">
1241     *       <td>5°E<br>50°N</td>
1242     *       <td>3962799.45 m<br>2999718.85 m</td>
1243     *     </tr><tr class="coordinates">
1244     *       <td>10°E<br>52°N</td>
1245     *       <td>4321000.00 m<br>3210000.00 m</td>
1246     *     </tr>
1247     *   </table>
1248     * </div>
1249     *
1250     * @throws FactoryException if the math transform cannot be created.
1251     * @throws TransformException if the example point cannot be transformed.
1252     *
1253     * @see AuthorityFactoryTest#testEPSG_3035()
1254     */
1255    @Test
1256    public void testLambertAzimuthalEqualArea() throws FactoryException, TransformException {
1257        description = "ETRS89 / LAEA Europe";
1258        final SamplePoints sample = SamplePoints.forCRS(3035);
1259        createMathTransform(Conversion.class, sample);
1260        setTolerance(ToleranceModifier.PROJECTION);
1261        verifyTransform(sample.sourcePoints, sample.targetPoints);
1262        verifyInDomainOfValidity(sample.areaOfValidity);
1263    }
1264
1265    /**
1266     * Tests the <q>Polar Stereographic (variant A)</q> (EPSG:9810) projection method.
1267     * First, this method transforms the point given in the example section of the
1268     * EPSG guidance note and compares the {@link MathTransform} result with the expected result.
1269     * Next, this method transforms a random set of points in the projection area of validity
1270     * and ensures that the {@linkplain MathTransform#inverse() inverse transform} and the
1271     * {@linkplain MathTransform#derivative derivatives} are coherent.
1272     *
1273     * <p>The math transform parameters and the sample coordinates are:</p>
1274     *
1275     * <div class="horizontal-flow">
1276     *   <table class="ogc">
1277     *     <caption>CRS characteristics</caption>
1278     *     <tr><th>Parameter</th>                      <th>Value</th></tr>
1279     *     <tr><td>semi-major axis</td>                <td>6378137.0 m</td></tr>
1280     *     <tr><td>semi-minor axis</td>                <td>6356752.314247833 m</td></tr>
1281     *     <tr><td>Latitude of natural origin</td>     <td>90.0°</td></tr>
1282     *     <tr><td>Longitude of natural origin</td>    <td>0.0°</td></tr>
1283     *     <tr><td>Scale factor at natural origin</td> <td>0.994</td></tr>
1284     *     <tr><td>False easting</td>                  <td>2000000.0 m</td></tr>
1285     *     <tr><td>False northing</td>                 <td>2000000.0 m</td></tr>
1286     *   </table>
1287     *   <table class="ogc">
1288     *     <caption>Test points</caption>
1289     *     <tr>
1290     *       <th>Source coordinates</th>
1291     *       <th>Expected results</th>
1292     *     </tr><tr class="coordinates">
1293     *       <td>44°E<br>73°N</td>
1294     *       <td>3320416.75 m<br>632668.43 m</td>
1295     *     </tr><tr class="coordinates">
1296     *       <td>0°E<br>90°N</td>
1297     *       <td>2000000.00 m<br>2000000.00 m</td>
1298     *     </tr>
1299     *   </table>
1300     * </div>
1301     *
1302     * @throws FactoryException if the math transform cannot be created.
1303     * @throws TransformException if the example point cannot be transformed.
1304     *
1305     * @see AuthorityFactoryTest#testEPSG_5041()
1306     * @see AuthorityFactoryTest#testEPSG_32661()
1307     */
1308    @Test
1309    public void testPolarStereographicA() throws FactoryException, TransformException {
1310        description = "WGS 84 / UPS North (E,N)";
1311        final SamplePoints sample = SamplePoints.forCRS(5041);
1312        createMathTransform(Conversion.class, sample);
1313        setTolerance(ToleranceModifier.PROJECTION);
1314        verifyTransform(sample.sourcePoints, sample.targetPoints);
1315        verifyInDomainOfValidity(sample.areaOfValidity);
1316    }
1317
1318    /**
1319     * Tests the <q>Polar Stereographic (variant B)</q> (EPSG:9829) projection method.
1320     * First, this method transforms the point given in the example section of the
1321     * EPSG guidance note and compares the {@link MathTransform} result with the expected result.
1322     * Next, this method transforms a random set of points in the projection area of validity
1323     * and ensures that the {@linkplain MathTransform#inverse() inverse transform} and the
1324     * {@linkplain MathTransform#derivative derivatives} are coherent.
1325     *
1326     * <p>The math transform parameters and the sample coordinates are:</p>
1327     *
1328     * <div class="horizontal-flow">
1329     *   <table class="ogc">
1330     *     <caption>CRS characteristics</caption>
1331     *     <tr><th>Parameter</th>                     <th>Value</th></tr>
1332     *     <tr><th>Source coordinates</th>              <th>Expected results</th></tr>
1333     *     <tr><td>semi-major axis</td>               <td>6378137.0 m</td></tr>
1334     *     <tr><td>semi-minor axis</td>               <td>6356752.314247833 m</td></tr>
1335     *     <tr><td>Latitude of standard parallel</td> <td>-71.0°</td></tr>
1336     *     <tr><td>Longitude of origin</td>           <td>70.0°</td></tr>
1337     *     <tr><td>False easting</td>                 <td>6000000.0 m</td></tr>
1338     *     <tr><td>False northing</td>                <td>6000000.0 m</td></tr>
1339     *   </table>
1340     *   <table class="ogc">
1341     *     <caption>Test points</caption>
1342     *     <tr>
1343     *       <th>Source coordinates</th>
1344     *       <th>Expected results</th>
1345     *     </tr><tr class="coordinates">
1346     *       <td>120°E<br>75°S</td>
1347     *       <td>7255380.79 m<br>7053389.56 m</td>
1348     *     </tr><tr class="coordinates">
1349     *       <td>70°E<br>90°S</td>
1350     *       <td>6000000.00 m<br>6000000.00 m</td>
1351     *     </tr>
1352     *   </table>
1353     * </div>
1354     *
1355     * @throws FactoryException if the math transform cannot be created.
1356     * @throws TransformException if the example point cannot be transformed.
1357     *
1358     * @see AuthorityFactoryTest#testEPSG_3032()
1359     */
1360    @Test
1361    public void testPolarStereographicB() throws FactoryException, TransformException {
1362        description = "Australian Antarctic Polar Stereographic";
1363        final SamplePoints sample = SamplePoints.forCRS(3032);
1364        createMathTransform(Conversion.class, sample);
1365        setTolerance(ToleranceModifier.PROJECTION);
1366        verifyTransform(sample.sourcePoints, sample.targetPoints);
1367        verifyInDomainOfValidity(sample.areaOfValidity);
1368    }
1369
1370    /**
1371     * Tests the <q>Polar Stereographic (variant C)</q> (EPSG:9830) projection method.
1372     * First, this method transforms the point given in the example section of the
1373     * EPSG guidance note and compares the {@link MathTransform} result with the expected result.
1374     * Next, this method transforms a random set of points in the projection area of validity
1375     * and ensures that the {@linkplain MathTransform#inverse() inverse transform} and the
1376     * {@linkplain MathTransform#derivative derivatives} are coherent.
1377     *
1378     * <p>The math transform parameters and the sample coordinates are:</p>
1379     *
1380     * <div class="horizontal-flow">
1381     *   <table class="ogc">
1382     *     <caption>CRS characteristics</caption>
1383     *     <tr><th>Parameter</th>                     <th>Value</th></tr>
1384     *     <tr><th>Source coordinates</th>              <th>Expected results</th></tr>
1385     *     <tr><td>semi-major axis</td>               <td>6378388.0 m</td></tr>
1386     *     <tr><td>semi-minor axis</td>               <td>6356911.9461279465 m</td></tr>
1387     *     <tr><td>Latitude of standard parallel</td> <td>-67°</td></tr>
1388     *     <tr><td>Longitude of origin</td>           <td>140°</td></tr>
1389     *     <tr><td>False easting</td>                 <td>300000 m</td></tr>
1390     *     <tr><td>False northing</td>                <td>200000 m</td></tr>
1391     *   </table>
1392     *   <table class="ogc">
1393     *     <caption>Test points</caption>
1394     *     <tr>
1395     *       <th>Source coordinates</th>
1396     *       <th>Expected results</th>
1397     *     </tr><tr class="coordinates">
1398     *       <td>140°04'17.040"E<br>66°36'18.820"S</td>
1399     *       <td>303169.52 m<br>244055.72 m</td>
1400     *     </tr><tr class="coordinates">
1401     *       <td>67°E<br>90°S</td>
1402     *       <td>300000.00 m<br>200000.00 m</td>
1403     *     </tr>
1404     *   </table>
1405     * </div>
1406     *
1407     * @throws FactoryException if the math transform cannot be created.
1408     * @throws TransformException if the example point cannot be transformed.
1409     *
1410     * @see AuthorityFactoryTest#testEPSG_3032()
1411     */
1412    @Test
1413    public void testPolarStereographicC() throws FactoryException, TransformException {
1414        description = "Petrels 1972 / Terre Adelie Polar Stereographic";
1415        final SamplePoints sample = SamplePoints.forCRS(2985);
1416        createMathTransform(Conversion.class, sample);
1417        setTolerance(ToleranceModifier.PROJECTION);
1418        verifyTransform(sample.sourcePoints, sample.targetPoints);
1419        verifyInDomainOfValidity(sample.areaOfValidity);
1420    }
1421
1422    /**
1423     * Tests the <q>Oblique Stereographic</q> (EPSG:9809) projection method.
1424     * First, this method transforms the point given in the example section of the
1425     * EPSG guidance note and compares the {@link MathTransform} result with the expected result.
1426     * Next, this method transforms a random set of points in the projection area of validity
1427     * and ensures that the {@linkplain MathTransform#inverse() inverse transform} and the
1428     * {@linkplain MathTransform#derivative derivatives} are coherent.
1429     *
1430     * <p>The math transform parameters and the sample coordinates are:</p>
1431     *
1432     * <div class="horizontal-flow">
1433     *   <table class="ogc">
1434     *     <caption>CRS characteristics</caption>
1435     *     <tr><th>Parameter</th>                      <th>Value</th></tr>
1436     *     <tr><td>semi-major axis</td>                <td>6377397.155 m</td></tr>
1437     *     <tr><td>semi-minor axis</td>                <td>6356078.9626186555 m</td></tr>
1438     *     <tr><td>Latitude of natural origin</td>     <td>52.15616055555556°</td></tr>
1439     *     <tr><td>Longitude of natural origin</td>    <td>5.38763888888889°</td></tr>
1440     *     <tr><td>Scale factor at natural origin</td> <td>0.9999079</td></tr>
1441     *     <tr><td>False easting</td>                  <td>155000.0 m</td></tr>
1442     *     <tr><td>False northing</td>                 <td>463000.0 m</td></tr>
1443     *   </table>
1444     *   <table class="ogc">
1445     *     <caption>Test points</caption>
1446     *     <tr>
1447     *       <th>Source coordinates</th>
1448     *       <th>Expected results</th>
1449     *     </tr><tr class="coordinates">
1450     *       <td>6°E<br>53°N</td>
1451     *       <td>196105.283 m<br>557057.739 m</td>
1452     *     </tr><tr class="coordinates">
1453     *       <td>5°23'15.500"E<br>52°09'22.178"N</td>
1454     *       <td>155000.000 m<br>463000.000 m</td>
1455     *     </tr>
1456     *   </table>
1457     * </div>
1458     *
1459     * @throws FactoryException if the math transform cannot be created.
1460     * @throws TransformException if the example point cannot be transformed.
1461     *
1462     * @see AuthorityFactoryTest#testEPSG_28992()
1463     */
1464    @Test
1465    public void testObliqueStereographic() throws FactoryException, TransformException {
1466        description = "Amersfoort / RD New";
1467        final SamplePoints sample = SamplePoints.forCRS(28992);
1468        createMathTransform(Conversion.class, sample);
1469        setTolerance(ToleranceModifier.PROJECTION);
1470        verifyTransform(sample.sourcePoints, sample.targetPoints);
1471        verifyInDomainOfValidity(sample.areaOfValidity);
1472    }
1473
1474    /**
1475     * Tests the <q>American Polyconic</q> (EPSG:9818) projection.
1476     * First, this method transforms the some of the points given in Table 19, p 132 of
1477     * <a href="http://pubs.er.usgs.gov/usgspubs/pp/pp1395">Map Projections, a working manual</a>
1478     * by John P.Snyder. Next, this method transforms a random set of points in the projection
1479     * area of validity and ensures that the {@linkplain MathTransform#inverse() inverse transform}
1480     * and the {@linkplain MathTransform#derivative derivatives} are coherent.
1481     *
1482     * <p>The math transform parameters and the sample coordinates are:</p>
1483     *
1484     * <div class="horizontal-flow">
1485     *   <table class="ogc">
1486     *     <caption>CRS characteristics</caption>
1487     *     <tr><th>Parameter</th>                   <th>Value</th></tr>
1488     *     <tr><td>semi-major axis</td>             <td>6378206.4 m</td></tr>
1489     *     <tr><td>semi-minor axis</td>             <td>6356583.8 m</td></tr>
1490     *     <tr><td>Latitude of natural origin</td>  <td>0.0°</td></tr>
1491     *     <tr><td>Longitude of natural origin</td> <td>0.0°</td></tr>
1492     *     <tr><td>False easting</td>               <td>0.0 m</td></tr>
1493     *     <tr><td>False northing</td>              <td>0.0 m</td></tr>
1494     *   </table>
1495     *   <table class="ogc">
1496     *     <caption>Test points</caption>
1497     *     <tr>
1498     *       <th>Source coordinates</th>
1499     *       <th>Expected results</th>
1500     *     </tr><tr class="coordinates">
1501     *       <td>See source</td>
1502     *       <td>See source</td>
1503     *     </tr>
1504     *   </table>
1505     * </div>
1506     *
1507     * @throws FactoryException if the math transform cannot be created.
1508     * @throws TransformException if the example point cannot be transformed.
1509     */
1510    @Test
1511    public void testPolyconic() throws FactoryException, TransformException {
1512        tolerance = max(tolerance, 0.5);                        // The sample points are only accurate to 1 metre.
1513        description = "American Polyconic";
1514        final SamplePoints sample = SamplePoints.forCRS(9818);
1515        createMathTransform(Conversion.class, sample);
1516        setTolerance(ToleranceModifier.PROJECTION);
1517        verifyTransform(sample.sourcePoints, sample.targetPoints);
1518        verifyInDomainOfValidity(sample.areaOfValidity);
1519    }
1520
1521    /**
1522     * Tests the <q>Krovak</q> (EPSG:9819) projection.
1523     * First, this method transforms the point given in the example section of the
1524     * EPSG guidance note and compares the {@link MathTransform} result with the expected result.
1525     * Next, this method transforms a random set of points in the projection area of validity
1526     * and ensures that the {@linkplain MathTransform#inverse() inverse transform} and the
1527     * {@linkplain MathTransform#derivative derivatives} are coherent.
1528     *
1529     * <p>The math transform parameters and the sample coordinates are:</p>
1530     *
1531     * <div class="horizontal-flow">
1532     *   <table class="ogc">
1533     *     <caption>CRS characteristics</caption>
1534     *     <tr><th>Parameter</th>                                <th>Value</th></tr>
1535     *     <tr><td>semi-major axis</td>                          <td>6377397.155 m</td></tr>
1536     *     <tr><td>semi-minor axis</td>                          <td>6356078.9626186555 m</td></tr>
1537     *     <tr><td>Latitude of projection centre</td>            <td>49.5°</td></tr>
1538     *     <tr><td>Longitude of origin</td>                      <td>24.5°</td></tr>
1539     *     <tr><td>Co-latitude of cone axis</td>                 <td>30.288139722222222°</td></tr>
1540     *     <tr><td>Latitude of pseudo standard parallel</td>     <td>78.5°</td></tr>
1541     *     <tr><td>Scale factor on pseudo standard parallel</td> <td>0.9999</td></tr>
1542     *     <tr><td>False easting</td>                            <td>0.0 m</td></tr>
1543     *     <tr><td>False northing</td>                           <td>0.0 m</td></tr>
1544     *   </table>
1545     *   <table class="ogc">
1546     *     <caption>Test points</caption>
1547     *     <tr>
1548     *       <th>Source coordinates</th>
1549     *       <th>Expected results</th>
1550     *     </tr><tr class="coordinates">
1551     *       <td>16°50'59.179"E<br>50°12'32.442"N</td>
1552     *       <td>-568990.997 m<br>-1050538.643 m</td>
1553     *     </tr>
1554     *   </table>
1555     * </div>
1556     *
1557     * @throws FactoryException if the math transform cannot be created.
1558     * @throws TransformException if the example point cannot be transformed.
1559     *
1560     * @see AuthorityFactoryTest#testEPSG_2065()
1561     */
1562    @Test
1563    public void testKrovak() throws FactoryException, TransformException {
1564        description = "CRS S-JTSK (Ferro) / Krovak";
1565        final SamplePoints sample = SamplePoints.forCRS(2065);
1566        createMathTransform(Conversion.class, sample);
1567        setTolerance(ToleranceModifier.PROJECTION);
1568        verifyTransform(sample.sourcePoints, sample.targetPoints);
1569        verifyInDomainOfValidity(sample.areaOfValidity);
1570    }
1571
1572    /**
1573     * Tests the <q>Orthographic</q> (EPSG:9840) projection.
1574     * First, this method transforms the point given in the example section of the
1575     * EPSG guidance note and compares the {@link MathTransform} result with the expected result.
1576     * Next, this method transforms a random set of points in the projection area of validity
1577     * and ensures that the {@linkplain MathTransform#inverse() inverse transform} and the
1578     * {@linkplain MathTransform#derivative derivatives} are coherent.
1579     *
1580     * <p>The math transform parameters and the sample coordinates are:</p>
1581     *
1582     * <div class="horizontal-flow">
1583     *   <table class="ogc">
1584     *     <caption>CRS characteristics</caption>
1585     *     <tr><th>Parameter</th>                                <th>Value</th></tr>
1586     *     <tr><td>semi-major axis</td>                          <td>6378137.0 m</td></tr>
1587     *     <tr><td>semi-minor axis</td>                          <td>6356752.314247833 m</td></tr>
1588     *     <tr><td>Latitude of natural origin</td>               <td>55.0°</td></tr>
1589     *     <tr><td>Longitude of natural origin</td>              <td>5.0°</td></tr>
1590     *     <tr><td>False easting</td>                            <td>0.0 m</td></tr>
1591     *     <tr><td>False northing</td>                           <td>0.0 m</td></tr>
1592     *   </table>
1593     *   <table class="ogc">
1594     *     <caption>Test points</caption>
1595     *     <tr>
1596     *       <th>Source coordinates</th>
1597     *       <th>Expected results</th>
1598     *     </tr><tr class="coordinates">
1599     *       <td>2°07'46.38"E<br>53°48'33.82"N</td>
1600     *       <td>–189011.711 m<br>–128 640.567 m</td>
1601     *     </tr>
1602     *   </table>
1603     * </div>
1604     *
1605     * @throws FactoryException if the math transform cannot be created.
1606     * @throws TransformException if the example point cannot be transformed.
1607     */
1608    @Test
1609    public void testOrthographic() throws FactoryException, TransformException {
1610        description = "WGS 84 / Orthographic";
1611        final SamplePoints sample = SamplePoints.forCRS(9840);
1612        createMathTransform(Conversion.class, sample);
1613        setTolerance(ToleranceModifier.PROJECTION);
1614        verifyTransform(sample.sourcePoints, sample.targetPoints);
1615        verifyInDomainOfValidity(sample.areaOfValidity);
1616    }
1617
1618    /**
1619     * Tests the <q>Equidistant Cylindrical</q> (EPSG:1028) projection.
1620     * First, this method transforms the point given in the example section of the
1621     * EPSG guidance note and compares the {@link MathTransform} result with the expected result.
1622     * Next, this method transforms a random set of points in the projection area of validity
1623     * and ensures that the {@linkplain MathTransform#inverse() inverse transform} and the
1624     * {@linkplain MathTransform#derivative derivatives} are coherent.
1625     *
1626     * <p>The math transform parameters and the sample coordinates are:</p>
1627     *
1628     * <div class="horizontal-flow">
1629     *   <table class="ogc">
1630     *     <caption>CRS characteristics</caption>
1631     *     <tr><th>Parameter</th>                                <th>Value</th></tr>
1632     *     <tr><td>semi-major axis</td>                          <td>6378137.0 m</td></tr>
1633     *     <tr><td>semi-minor axis</td>                          <td>6356752.314247833 m</td></tr>
1634     *     <tr><td>Latitude of natural origin</td>               <td>0°</td></tr>
1635     *     <tr><td>Longitude of natural origin</td>              <td>0°</td></tr>
1636     *     <tr><td>False easting</td>                            <td>0 m</td></tr>
1637     *     <tr><td>False northing</td>                           <td>0 m</td></tr>
1638     *   </table>
1639     *   <table class="ogc">
1640     *     <caption>Test points</caption>
1641     *     <tr>
1642     *       <th>Source coordinates</th>
1643     *       <th>Expected results</th>
1644     *     </tr><tr class="coordinates">
1645     *       <td>10°00'00.000"E<br>55°00'00.000"N</td>
1646     *       <td>1113194.91 m<br>6097230.31 m</td>
1647     *     </tr>
1648     *   </table>
1649     * </div>
1650     *
1651     * @throws FactoryException if the math transform cannot be created.
1652     * @throws TransformException if the example point cannot be transformed.
1653     */
1654    @Test
1655    public void testEquidistantCylindrical() throws FactoryException, TransformException {
1656        description = "WGS84 / World Equidistant Cylindrical";
1657        final SamplePoints sample = SamplePoints.forCRS(4087);
1658        createMathTransform(Conversion.class, sample);
1659        setTolerance(ToleranceModifier.PROJECTION);
1660        verifyTransform(sample.sourcePoints, sample.targetPoints);
1661        verifyInDomainOfValidity(sample.areaOfValidity);
1662    }
1663
1664    /**
1665     * Tests the <q>Modified Azimuthal Equidistant</q> (EPSG:9832) projection.
1666     * First, this method transforms the point given in the example section of the
1667     * EPSG guidance note and compares the {@link MathTransform} result with the expected result.
1668     * Next, this method transforms a random set of points in the projection area of validity
1669     * and ensures that the {@linkplain MathTransform#inverse() inverse transform} and the
1670     * {@linkplain MathTransform#derivative derivatives} are coherent.
1671     *
1672     * <p>The math transform parameters and the sample coordinates are:</p>
1673     *
1674     * <div class="horizontal-flow">
1675     *   <table class="ogc">
1676     *     <caption>CRS characteristics</caption>
1677     *     <tr><th>Parameter</th>                                <th>Value</th></tr>
1678     *     <tr><td>semi-major axis</td>                          <td>6378206.4 m</td></tr>
1679     *     <tr><td>semi-minor axis</td>                          <td>6356583.8 m</td></tr>
1680     *     <tr><td>Latitude of natural origin</td>               <td>9.546708333333333°</td></tr>
1681     *     <tr><td>Longitude of natural origin</td>              <td>138.168744444444444°</td></tr>
1682     *     <tr><td>False easting</td>                            <td>40000.0 m</td></tr>
1683     *     <tr><td>False northing</td>                           <td>60000.0 m</td></tr>
1684     *   </table>
1685     *   <table class="ogc">
1686     *     <caption>Test points</caption>
1687     *     <tr>
1688     *       <th>Source coordinates</th>
1689     *       <th>Expected results</th>
1690     *     </tr><tr class="coordinates">
1691     *       <td>138°11'34.908"E<br>9°35'47.493"N</td>
1692     *       <td>42665.90 m<br>65509.82 m</td>
1693     *     </tr>
1694     *   </table>
1695     * </div>
1696     *
1697     * @throws FactoryException if the math transform cannot be created.
1698     * @throws TransformException if the example point cannot be transformed.
1699     */
1700    @Test
1701    public void testModifiedAzimuthalEquidistant() throws FactoryException, TransformException {
1702        description = "Guam 1963 / Yap Islands";
1703        final SamplePoints sample = SamplePoints.forCRS(3295);
1704        createMathTransform(Conversion.class, sample);
1705        setTolerance(ToleranceModifier.PROJECTION);
1706        verifyTransform(sample.sourcePoints, sample.targetPoints);
1707        verifyInDomainOfValidity(sample.areaOfValidity);
1708    }
1709
1710    /**
1711     * Tests the <q>Abridged Molodensky</q> (EPSG:9605) datum shift operation.
1712     * First, this method transforms the point given in the example section of the
1713     * EPSG guidance note and compares the {@link MathTransform} result with the expected result.
1714     * Next, this method transforms a random set of geographic coordinates
1715     * and ensures that the {@linkplain MathTransform#inverse() inverse transform} and the
1716     * {@linkplain MathTransform#derivative derivatives} are coherent.
1717     *
1718     * <p>The math transform parameters and the sample coordinates are:</p>
1719     *
1720     * <div class="horizontal-flow">
1721     *   <table class="ogc">
1722     *     <caption>Conversion characteristics</caption>
1723     *     <tr><th>Parameter</th>                         <th>Value</th></tr>
1724     *     <tr><td>dim</td>                               <td>3</td></tr>
1725     *     <tr><td>src_semi_major</td>                    <td>6378137.0 m</td></tr>
1726     *     <tr><td>src_semi_minor</td>                    <td>6356752.314247833 m</td></tr>
1727     *     <tr><td>X-axis translation</td>                <td>84.87 m</td></tr>
1728     *     <tr><td>Y-axis translation</td>                <td>96.49 m</td></tr>
1729     *     <tr><td>Z-axis translation</td>                <td>116.95 m</td></tr>
1730     *     <tr><td>Semi-major axis length difference</td> <td>251 m</td></tr>
1731     *     <tr><td>Flattening difference</td>             <td>1.41927E-05</td></tr>
1732     *   </table>
1733     *   <table class="ogc">
1734     *     <caption>Test points</caption>
1735     *     <tr>
1736     *       <th>Source coordinates</th>
1737     *       <th>Expected results</th>
1738     *     </tr><tr class="coordinates">
1739     *       <td>2°7'46.380"E<br>53°48'33.820"N<br>73.000 m</td>
1740     *       <td>2°7'51.477"E<br>53°48'36.563"N<br>28.091 m</td>
1741     *     </tr>
1742     *   </table>
1743     * </div>
1744     *
1745     * @throws FactoryException if the math transform cannot be created.
1746     * @throws TransformException if the example point cannot be transformed.
1747     */
1748    @Test
1749    public void testAbridgedMolodensky() throws FactoryException, TransformException {
1750        tolerance = max(tolerance, 0.001 * (NAUTICAL_MILE/60));    // 0.001 angular second (about 3 cm at equator).
1751        description = "WGS84 to ED50";
1752        final SamplePoints sample = SamplePoints.forCRS(4230);
1753        createMathTransform(Transformation.class, sample);
1754        setTolerance(ToleranceModifier.GEOGRAPHIC);
1755        verifyTransform(sample.sourcePoints, sample.targetPoints);
1756        verifyInDomain3D(sample.areaOfValidity, -1000, +1000);
1757    }
1758
1759    /**
1760     * Tests the <q>Geographic/topocentric conversions</q> (EPSG:9837).
1761     * This method transforms the point given in the example section of the
1762     * EPSG guidance note and compares the {@link MathTransform} result with the expected result.
1763     *
1764     * <p>A CRS using this method is "EPSG topocentric example A" (EPSG:5819)".
1765     * The math transform parameters and the sample coordinates are:</p>
1766     *
1767     * <div class="horizontal-flow">
1768     *   <table class="ogc">
1769     *     <caption>Conversion characteristics</caption>
1770     *     <tr><th>Parameter</th>                                <th>Value</th></tr>
1771     *     <tr><td>semi_major</td>                               <td>6378137.0 m</td></tr>
1772     *     <tr><td>semi_minor</td>                               <td>6356752.314245179 m</td></tr>
1773     *     <tr><td>Latitude of topocentric origin</td>           <td>55°00'00.000"N</td></tr>
1774     *     <tr><td>Longitude of topocentric origin</td>          <td>5°00'00.000"E</td></tr>
1775     *     <tr><td>Ellipsoidal height of topocentric origin</td> <td>200 m</td></tr>
1776     *   </table>
1777     *   <table class="ogc">
1778     *     <caption>Test points</caption>
1779     *     <tr>
1780     *       <th>Source coordinates</th>
1781     *       <th>Expected results</th>
1782     *     </tr><tr class="coordinates">
1783     *       <td>2°07'46.38"E<br>53°48'33.82"N<br>73.0 m</td>
1784     *       <td>–189013.869 m<br>–128642.040 m<br>–4220.171 m</td>
1785     *     </tr><tr class="coordinates">
1786     *       <td>5°00'00.000"E<br>55°00'00.000"N<br>200 m</td>
1787     *       <td>0 m<br>0 m<br>0 m</td>
1788     *     </tr>
1789     *   </table>
1790     * </div>
1791     *
1792     * @throws FactoryException if the math transform cannot be created.
1793     * @throws TransformException if the example point cannot be transformed.
1794     */
1795    @Test
1796    public void testGeographicTopocentric() throws FactoryException, TransformException {
1797        description = "EPSG topocentric example A";
1798        final SamplePoints sample = SamplePoints.forCRS(5819);
1799        createMathTransform(Transformation.class, sample);
1800        setTolerance(null);
1801        verifyTransform(sample.sourcePoints, sample.targetPoints);
1802        verifyInDomain3D(sample.areaOfValidity, -100, +100);
1803    }
1804
1805    /**
1806     * Tests the <q>Geocentric/topocentric conversions</q> (EPSG:9836).
1807     * This method transforms the point given in the example section of the
1808     * EPSG guidance note and compares the {@link MathTransform} result with the expected result.
1809     *
1810     * <p>A CRS using this method is "EPSG topocentric example B" (EPSG:5820)".
1811     * The math transform parameters and the sample coordinates are:</p>
1812     *
1813     * <div class="horizontal-flow">
1814     *   <table class="ogc">
1815     *     <caption>Conversion characteristics</caption>
1816     *     <tr><th>Parameter</th>                          <th>Value</th></tr>
1817     *     <tr><td>semi_major</td>                         <td>6378137.0 m</td></tr>
1818     *     <tr><td>semi_minor</td>                         <td>6356752.314245179 m</td></tr>
1819     *     <tr><td>Geocentric X of topocentric origin</td> <td>3652755.3058 m</td></tr>
1820     *     <tr><td>Geocentric Y of topocentric origin</td> <td> 319574.6799 m</td></tr>
1821     *     <tr><td>Geocentric Z of topocentric origin</td> <td>5201547.3536 m</td></tr>
1822     *   </table>
1823     *   <table class="ogc">
1824     *     <caption>Test points</caption>
1825     *     <tr>
1826     *       <th>Source coordinates</th>
1827     *       <th>Expected results</th>
1828     *     </tr><tr class="coordinates">
1829     *       <td>3771793.968 m<br> 140253.342 m<br>5124304.349 m</td>
1830     *       <td>–189013.869 m<br>–128642.040 m<br>  –4220.171 m</td>
1831     *     </tr><tr class="coordinates">
1832     *       <td>3652755.3058 m<br>319574.6799 m<br>5201547.3536 m</td>
1833     *       <td>0 m<br>0 m<br>0 m</td>
1834     *     </tr>
1835     *   </table>
1836     * </div>
1837     *
1838     * @throws FactoryException if the math transform cannot be created.
1839     * @throws TransformException if the example point cannot be transformed.
1840     */
1841    @Test
1842    public void testGeocentricTopocentric() throws FactoryException, TransformException {
1843        description = "EPSG topocentric example B";
1844        final SamplePoints sample = SamplePoints.forCRS(5820);
1845        createMathTransform(Transformation.class, sample);
1846        setTolerance(null);
1847        verifyTransform(sample.sourcePoints, sample.targetPoints);
1848        verifyInDomain3D(sample.areaOfValidity, 5000000, 5200000);
1849    }
1850
1851    /**
1852     * Executes {@link #verifyInDomain(double[], double[], int[], Random)} using a three-dimensional domain.
1853     *
1854     * @param  areaOfValidity  the horizontal domain of test points.
1855     * @param  zmin            the vertical minimum value of test points.
1856     * @param  zmax            the vertical maximum value of test points.
1857     * @throws TransformException if a point cannot be transformed.
1858     */
1859    private void verifyInDomain3D(final Rectangle2D areaOfValidity, final double zmin, final double zmax)
1860            throws TransformException
1861    {
1862        verifyInDomain(new double[] {areaOfValidity.getMinX(), areaOfValidity.getMinY(), zmin},
1863                       new double[] {areaOfValidity.getMaxX(), areaOfValidity.getMaxY(), zmax},
1864                       new int[] {10, 10, 10}, new Random());
1865    }
1866
1867    /**
1868     * Asserts that a matrix of derivatives is equal to the expected ones within a positive delta.
1869     *
1870     * @hidden
1871     */
1872    @Override
1873    protected void assertMatrixEquals(final Matrix expected, final Matrix actual, final Matrix tolmat, final String message)
1874            throws DerivativeFailure
1875    {
1876        if (tolmat != null) {
1877            final int numRow = tolmat.getNumRow();
1878            final int numCol = tolmat.getNumCol();
1879            for (int j=0; j<numRow; j++) {
1880                for (int i=0; i<numCol; i++) {
1881                    tolmat.setElement(j, i, DERIVATIVE_TOLERANCE);
1882                }
1883            }
1884        }
1885        super.assertMatrixEquals(expected, actual, tolmat, message);
1886    }
1887}