001/*
002 *    GeoAPI - Java interfaces for OGC/ISO standards
003 *    http://www.geoapi.org
004 *
005 *    Copyright (C) 2011-2019 Open Geospatial Consortium, Inc.
006 *    All Rights Reserved. http://www.opengeospatial.org/ogc/legal
007 *
008 *    Permission to use, copy, and modify this software and its documentation, with
009 *    or without modification, for any purpose and without fee or royalty is hereby
010 *    granted, provided that you include the following on ALL copies of the software
011 *    and documentation or portions thereof, including modifications, that you make:
012 *
013 *    1. The full text of this NOTICE in a location viewable to users of the
014 *       redistributed or derivative work.
015 *    2. Notice of any changes or modifications to the OGC files, including the
016 *       date changes were made.
017 *
018 *    THIS SOFTWARE AND DOCUMENTATION IS PROVIDED "AS IS," AND COPYRIGHT HOLDERS MAKE
019 *    NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
020 *    TO, WARRANTIES OF MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR PURPOSE OR THAT
021 *    THE USE OF THE SOFTWARE OR DOCUMENTATION WILL NOT INFRINGE ANY THIRD PARTY
022 *    PATENTS, COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS.
023 *
024 *    COPYRIGHT HOLDERS WILL NOT BE LIABLE FOR ANY DIRECT, INDIRECT, SPECIAL OR
025 *    CONSEQUENTIAL DAMAGES ARISING OUT OF ANY USE OF THE SOFTWARE OR DOCUMENTATION.
026 *
027 *    The name and trademarks of copyright holders may NOT be used in advertising or
028 *    publicity pertaining to the software without specific, written prior permission.
029 *    Title to copyright in this software and any associated documentation will at all
030 *    times remain with copyright holders.
031 */
032package org.opengis.test.referencing;
033
034import java.util.List;
035import java.util.Arrays;
036import java.util.EnumSet;
037import java.util.Collections;
038import java.util.Random;
039import java.awt.geom.Rectangle2D;
040
041import org.opengis.util.Factory;
042import org.opengis.util.FactoryException;
043import org.opengis.util.NoSuchIdentifierException;
044import org.opengis.geometry.DirectPosition;
045import org.opengis.parameter.ParameterValueGroup;
046import org.opengis.parameter.ParameterDescriptorGroup;
047import org.opengis.referencing.crs.ProjectedCRS;
048import org.opengis.referencing.operation.Matrix;
049import org.opengis.referencing.operation.MathTransform;
050import org.opengis.referencing.operation.MathTransform2D;
051import org.opengis.referencing.operation.TransformException;
052import org.opengis.referencing.operation.MathTransformFactory;
053import org.opengis.referencing.operation.Projection;
054import org.opengis.referencing.operation.Transformation;
055import org.opengis.referencing.operation.SingleOperation;
056import org.opengis.test.ToleranceModifiers;
057import org.opengis.test.ToleranceModifier;
058import org.opengis.test.CalculationType;
059import org.opengis.test.Configuration;
060
061import org.junit.Test;
062import org.junit.runner.RunWith;
063import org.junit.runners.Parameterized;
064
065import static java.lang.StrictMath.*;
066import static org.junit.Assert.*;
067import static org.junit.Assume.*;
068import static org.opengis.test.ToleranceModifiers.NAUTICAL_MILE;
069
070
071/**
072 * Tests {@linkplain MathTransformFactory#createParameterizedTransform(ParameterValueGroup)
073 * parameterized math transforms} from the {@code org.opengis.referencing.operation} package.
074 * Math transform instances are created using the factory given at construction time.
075 *
076 * <p><b>Skipping tests for unsupported operations:</b><br>
077 * If the tested factory throws a {@link NoSuchIdentifierException} during the invocation
078 * of one of the following methods:
079 *
080 * <ul>
081 *   <li>{@link MathTransformFactory#getDefaultParameters(String)}</li>
082 *   <li>{@link MathTransformFactory#createParameterizedTransform(ParameterValueGroup)}</li>
083 * </ul>
084 *
085 * then the tests is skipped. If any other kind of exception is thrown, or if {@code NoSuchIdentifierException}
086 * is thrown under other circumstances than the invocation of above methods, then the test fails.
087 *
088 * <p><b>Tests and accuracy:</b><br>
089 * By default, every tests expect an accuracy of 1 centimetre. This accuracy matches the precision
090 * of most example points given in the EPSG guidance notice. Implementors can modify the kind of
091 * tests being executed and the tolerance threshold in different ways:</p>
092 *
093 * <ul>
094 *   <li>Set some <code>is&lt;<var>Operation</var>&gt;Supported</code> fields to {@code false}.</li>
095 *   <li>Override some of the {@code testFoo()} method and set the {@link #tolerance tolerance} field
096 *       before to invoke {@code super.testFoo()}.</li>
097 *   <li>Override {@link #normalize(DirectPosition, DirectPosition, CalculationType)
098 *       normalize(DirectPosition, DirectPosition, CalculationType)}.</li>
099 *   <li>Override {@link #assertMatrixEquals(String, Matrix, Matrix, Matrix)}.</li>
100 * </ul>
101 *
102 * <div class="note"><b>Usage example:</b>
103 * in order to specify their factories and run the tests in a JUnit framework, implementors can define
104 * a subclass in their own test suite as in the example below. That example shows also how implementors
105 * can alter some tests (here the tolerance value for the <cite>Lambert Azimuthal Equal Area</cite> projection)
106 * and add more checks to be executed after every tests (here ensuring that the {@linkplain #transform transform}
107 * implements the {@link MathTransform2D} interface):
108 *
109 * <blockquote><pre>import org.junit.*;
110 *import org.junit.runner.RunWith;
111 *import org.junit.runners.JUnit4;
112 *import org.opengis.test.referencing.ParameterizedTransformTest;
113 *import static org.junit.Assert.*;
114 *
115 *&#64;RunWith(JUnit4.class)
116 *public class MyTest extends ParameterizedTransformTest {
117 *    public MyTest() {
118 *        super(new MyMathTransformFactory());
119 *    }
120 *
121 *    &#64;Test
122 *    &#64;Override
123 *    public void testLambertAzimuthalEqualArea() throws FactoryException, TransformException {
124 *        tolerance = 0.1;                              // Increase the tolerance value to 10 cm.
125 *        super.testLambertAzimuthalEqualArea();
126 *        // If more tests specific to this projection are wanted, do them here.
127 *        // In this example, we replace the ellipsoid by a sphere and test again.
128 *        // Note that spherical formulas can have an error up to 30 km compared
129 *        // to ellipsoidal formulas, so we have to relax again the tolerance threshold.
130 *        parameters.parameter("semi_minor").setValue(parameters.parameter("semi_major").doubleValue());
131 *        tolerance = 30000;                            // Increase the tolerance value to 30 km.
132 *        super.testLambertAzimuthalEqualArea();
133 *    }
134 *
135 *    &#64;After
136 *    public void ensureAllTransformAreMath2D() {
137 *        assertTrue(transform instanceof MathTransform2D);
138 *    }
139 *}</pre></blockquote>
140 * </div>
141 *
142 * @see AffineTransformTest
143 * @see AuthorityFactoryTest
144 * @see org.opengis.test.TestSuite
145 *
146 * @author  Martin Desruisseaux (Geomatys)
147 * @version 3.1
148 * @since   3.1
149 */
150@RunWith(Parameterized.class)
151public strictfp class ParameterizedTransformTest extends TransformTestCase {
152    /**
153     * The default tolerance threshold for comparing the results of direct transforms.
154     * This is set to the precision of coordinate point givens in the EPSG and IGNF
155     * documentation.
156     */
157    private static final double TRANSFORM_TOLERANCE = 0.01;
158
159    /**
160     * The tolerance threshold for comparing the derivative coefficients. In each column of the
161     * derivative matrix of a map projection, there is typically one value greater than 100000
162     * (100 km - same order of magnitude than the transformed coordinate values) and all other
163     * values are close to zero. However we can not use the {@link #TRANSFORM_TOLERANCE} value
164     * in every cases because the expected derivative coefficients are computed using a numerical
165     * approximation. Some empirical tests have show that the difference between <cite>forward
166     * difference</cite> and <cite>backward difference</cite> can be close to 0.25, so we must
167     * be prepared to increase this tolerance threshold.
168     */
169    private static final double DERIVATIVE_TOLERANCE = 0.01;
170
171    /**
172     * The delta value to use for computing an approximation of the derivative by finite
173     * difference, in metres. The conversion from metres to degrees is performed using
174     * the standard length of a nautical mile.
175     *
176     * <p>The 100 metres value has been determined empirically as a good compromise for map
177     * projections.  Experience suggests that smaller values often <em>decrease</em> the
178     * precision, because of floating point errors when subtracting big numbers that are
179     * close in magnitude.</p>
180     */
181    private static final double DERIVATIVE_DELTA = 100;
182
183    /**
184     * The factory for creating {@link MathTransform} objects, or {@code null} if none.
185     */
186    protected final MathTransformFactory mtFactory;
187
188    /**
189     * The parameters of the math transform being tested. This field is set, together with the
190     * {@link #transform transform} field, after the execution of every {@code testFoo()} method
191     * in this class.
192     *
193     * <p>If this field is non-null before a test is run, then those parameters will be used
194     * directly. This allow implementors to alter the parameters before to run the test one
195     * more time.</p>
196     */
197    protected ParameterValueGroup parameters;
198
199    /**
200     * A description of the test being run. This field is provided only for information purpose
201     * (typically for producing logging or error messages); it is not actually used by the tests.
202     * The value can be:
203     *
204     * <ul>
205     *   <li>The name of the target {@link ProjectedCRS} when the {@linkplain #transform transform}
206     *       being tested is a map projection</li>
207     *   <li>The transformation name when the {@linkplain #transform transform} being tested is a
208     *       datum shift operation.</li>
209     * </ul>
210     */
211    protected String description;
212
213    /**
214     * Returns a default set of factories to use for running the tests. Those factories are given
215     * in arguments to the constructor when this test class is instantiated directly by JUnit (for
216     * example as a {@linkplain org.junit.runners.Suite.SuiteClasses suite} element), instead than
217     * subclassed by the implementor. The factories are fetched as documented in the
218     * {@link #factories(Class[])} javadoc.
219     *
220     * @return the default set of arguments to be given to the {@code ParameterizedTransformTest} constructor.
221     */
222    @Parameterized.Parameters
223    @SuppressWarnings("unchecked")
224    public static List<Factory[]> factories() {
225        return factories(MathTransformFactory.class);
226    }
227
228    /**
229     * Creates a new test without factory and with the given {@code isFooSupported} flags.
230     * The given array must be the result of a call to {@link #getEnabledKeys(int)}.
231     */
232    ParameterizedTransformTest(final boolean[] isEnabled) {
233        super(isEnabled);
234        mtFactory = null;
235    }
236
237    /**
238     * Creates a new test using the given factory. If the given factory is {@code null},
239     * then the tests will be skipped.
240     *
241     * @param factory  factory for creating {@link MathTransform} instances.
242     */
243    public ParameterizedTransformTest(final MathTransformFactory factory) {
244        super(factory);
245        this.mtFactory = factory;
246    }
247
248    /**
249     * Returns information about the configuration of the test which has been run.
250     * This method returns a map containing:
251     *
252     * <ul>
253     *   <li>All the entries defined in the {@linkplain TransformTestCase#configuration() parent class}.</li>
254     *   <li>All the following values associated to the {@link org.opengis.test.Configuration.Key} of the same name:
255     *     <ul>
256     *       <li>{@link #mtFactory}</li>
257     *     </ul>
258     *   </li>
259     * </ul>
260     *
261     * @return {@inheritDoc}
262     */
263    @Override
264    public Configuration configuration() {
265        final Configuration op = super.configuration();
266        assertNull(op.put(Configuration.Key.mtFactory, mtFactory));
267        return op;
268    }
269
270    /**
271     * Invoked for preparing the header of a test failure message.
272     *
273     * @param buffer   the buffer in which to append the header.
274     * @param message  user-supplied message to append, or {@code null}.
275     */
276    @Override
277    final void appendErrorHeader(final StringBuilder buffer, final String message) {
278        if (description != null) {
279            buffer.append("Error in “").append(description).append("”: ");
280        }
281        super.appendErrorHeader(buffer, message);
282    }
283
284    /**
285     * Returns the error message for an unsupported operation method.
286     */
287    private static String unsupportedMethod(final String name) {
288        return "The “" + name + "” operation method is not supported by the tested implementation.";
289    }
290
291    /**
292     * Initialized the {@link #parameters} field to the default values for the given operation method.
293     * If the tested implementation does not support the specified operation method, then the test will
294     * be skipped.
295     */
296    private void createParameters(final String method) {
297        assumeNotNull(mtFactory);
298        try {
299            parameters = mtFactory.getDefaultParameters(method);
300        } catch (NoSuchIdentifierException e) {
301            assumeNoException(unsupportedMethod(method), e);        // Will mark the test as "ignored".
302        }
303    }
304
305    /**
306     * Creates a math transform for the {@linkplain SingleOperation coordinate operation} identified by
307     * {@link SamplePoints#operation} and stores the result in the {@link #transform} field.
308     * The set of allowed codes is documented in second column of the
309     * {@link PseudoEpsgFactory#createParameters(int)} method.
310     *
311     * <p>This method shall also set the {@linkplain TransformTestCase#tolerance tolerance} threshold
312     * in units of the target CRS (typically <var>metres</var> for map projections), and the
313     * {@linkplain #derivativeDeltas derivative deltas} in units of the source CRS (typically
314     * <var>degrees</var> for map projections). The current implementation sets the following values:</p>
315     *
316     * <ul>
317     *   <li><p>{@link #tolerance} is sets to {@link #TRANSFORM_TOLERANCE}, unless a greater
318     *       tolerance threshold is already set in which case the existing value is left
319     *       unchanged.</p></li>
320     *   <li><p>{@link #derivativeDeltas} is set to a value in degrees corresponding to
321     *       approximately 1 metre on Earth (calculated using the standard nautical mile length).
322     *       A finer value can lead to more accurate derivative approximation by the
323     *       {@link #verifyDerivative(double[]) verifyDerivative(double...)} method,
324     *       at the expense of more sensitivity to the accuracy of the
325     *       {@link MathTransform#transform MathTransform.transform(…)} method being tested.</p></li>
326     * </ul>
327     *
328     * @param  type  either {@code Projection.class} or {@code Transformation.class}.
329     * @throws FactoryException if the math transform can not be created.
330     */
331    private void createMathTransform(final Class<? extends SingleOperation> type, final SamplePoints sample)
332            throws FactoryException
333    {
334        try {
335            if (parameters == null) {
336                assumeNotNull(mtFactory);
337                parameters = PseudoEpsgFactory.createParameters(mtFactory, sample.operation);
338                validators.validate(parameters);
339            }
340            if (transform == null) {
341                assumeNotNull(mtFactory);
342                transform = mtFactory.createParameterizedTransform(parameters);
343                assertNotNull(description, transform);
344                validators.validate(transform);
345            }
346        } catch (NoSuchIdentifierException e) {
347            /*
348             * If a code was not found, ensure that the factory does not declare that it was
349             * a supported code. If the code was unsupported, then the test will be ignored.
350             */
351            final String message;
352            if (parameters != null) {
353                final ParameterDescriptorGroup descriptor = parameters.getDescriptor();
354                if (!Collections.disjoint(Utilities.getNameAndAliases(descriptor),
355                        Utilities.getNameAndAliases(mtFactory.getAvailableMethods(type))))
356                {
357                    throw e;                            // Will mark the test as "failed".
358                }
359                message = unsupportedMethod(Utilities.getName(descriptor));
360            } else {
361                message = "The “EPSG:" + sample.operation + "” coordinate operation uses the “" + e.getIdentifierCode()
362                        + "” method, which is not supported by the tested implementation.";
363            }
364            assumeNoException(message, e);              // Will mark the test as "ignored".
365        }
366        /*
367         * Set the tolerance after we have set the transform,
368         * because we need to know the number of dimensions.
369         */
370        if (type == Projection.class) {
371            setTolerance(ToleranceModifier.PROJECTION);
372        } else if (type == Transformation.class) {
373            setTolerance(ToleranceModifier.GEOGRAPHIC);
374        } else {
375            throw new IllegalArgumentException("Illegal type: " + type);
376        }
377    }
378
379    /**
380     * Initializes the tolerance thresholds to their default values if the users did not
381     * specified his own thresholds.
382     */
383    final void setTolerance(final ToleranceModifier modifier) {
384        if (toleranceModifier == null) {
385            toleranceModifier = modifier;
386        }
387        if (!(tolerance >= TRANSFORM_TOLERANCE)) {      // !(a >= b) instead than (a < b) in order to catch NaN.
388            tolerance = TRANSFORM_TOLERANCE;
389        }
390        if (derivativeDeltas == null) {
391            derivativeDeltas = new double[transform.getSourceDimensions()];
392            Arrays.fill(derivativeDeltas, DERIVATIVE_DELTA / (60 * NAUTICAL_MILE));
393        }
394    }
395
396    /**
397     * Applies a unit conversion on the given coordinate values. This method is invoked by
398     * {@link AuthorityFactoryTest} before to test a {@link ProjectedCRS} using different
399     * units than the standard one. In addition to scale the units, this method scales also
400     * the tolerance factor by the same factor.
401     *
402     * @param mode  {@link CalculationType#DIRECT_TRANSFORM} for scaling the output units (from
403     *        metres to an other linear unit), or {@link CalculationType#INVERSE_TRANSFORM} for
404     *        scaling the input units (from degrees to an other angular unit).
405     * @param coordinates  the source or expected target points to scale.
406     * @param scale  the scale factor, from standard units to the CRS units.
407     */
408    final void applyUnitConversion(final CalculationType mode, final double[] coordinates, final double scale) {
409        for (int i=coordinates.length; --i>=0;) {
410            coordinates[i] *= scale;
411        }
412        toleranceModifier = ToleranceModifiers.concatenate(toleranceModifier,
413                ToleranceModifiers.scale(EnumSet.of(mode), scale, scale));
414    }
415
416    /**
417     * Tests the transform consistency using many random points inside the area of validity.
418     *
419     * @throws TransformException if a point can not be transformed.
420     */
421    final void verifyInDomainOfValidity(final Rectangle2D areaOfValidity) throws TransformException {
422        verifyInDomain(new double[] {
423            areaOfValidity.getMinX(),
424            areaOfValidity.getMinY()
425        }, new double[] {
426            areaOfValidity.getMaxX(),
427            areaOfValidity.getMaxY()
428        }, new int[] {
429            20, 20
430        }, new Random());
431    }
432
433    /**
434     * Tests the <cite>"Mercator (variant A)"</cite> (EPSG:9804) projection method.
435     * First, this method transforms the point given in the <cite>Example</cite> section of the
436     * EPSG guidance note and compares the {@link MathTransform} result with the expected result.
437     * Next, this method transforms a random set of points in the projection area of validity
438     * and ensures that the {@linkplain MathTransform#inverse() inverse transform} and the
439     * {@linkplain MathTransform#derivative derivatives} are coherent.
440     *
441     * <p>The math transform parameters and the sample coordinates are:</p>
442     *
443     * <table cellspacing="15" summary="Test data">
444     * <tr valign="top"><td><table class="ogc">
445     * <caption>CRS characteristics</caption>
446     * <tr><th>Parameter</th>                      <th>Value</th></tr>
447     * <tr><td>semi-major axis</td>                <td>6377397.155 m</td></tr>
448     * <tr><td>semi-minor axis</td>                <td>6356078.962818189 m</td></tr>
449     * <tr><td>Latitude of natural origin</td>     <td>0.0°</td></tr>
450     * <tr><td>Longitude of natural origin</td>    <td>110.0°</td></tr>
451     * <tr><td>Scale factor at natural origin</td> <td>0.997</td></tr>
452     * <tr><td>False easting</td>                  <td>3900000.0 m</td></tr>
453     * <tr><td>False northing</td>                 <td>900000.0 m</td></tr>
454     * </table></td><td>
455     * <table class="ogc">
456     * <caption>Test points</caption>
457     * <tr><th>Source ordinates</th>           <th>Expected results</th></tr>
458     * <tr align="right"><td>110°E<br>0°N</td> <td>3900000.00 m<br>900000.00 m</td></tr>
459     * <tr align="right"><td>120°E<br>3°S</td> <td>5009726.58 m<br>569150.82 m</td></tr>
460     * </table></td></tr></table>
461     *
462     * @throws FactoryException if the math transform can not be created.
463     * @throws TransformException if the example point can not be transformed.
464     *
465     * @see AuthorityFactoryTest#testEPSG_3002()
466     */
467    @Test
468    public void testMercator1SP() throws FactoryException, TransformException {
469        description = "Makassar / NEIEZ";
470        final SamplePoints sample = SamplePoints.forCRS(3002);
471        createMathTransform(Projection.class, sample);
472        verifyTransform(sample.sourcePoints, sample.targetPoints);
473        verifyInDomainOfValidity(sample.areaOfValidity);
474    }
475
476    /**
477     * Tests the <cite>"Mercator (variant B)"</cite> (EPSG:9805) projection method.
478     * First, this method transforms the point given in the <cite>Example</cite> section of the
479     * EPSG guidance note and compares the {@link MathTransform} result with the expected result.
480     * Next, this method transforms a random set of points in the projection area of validity
481     * and ensures that the {@linkplain MathTransform#inverse() inverse transform} and the
482     * {@linkplain MathTransform#derivative derivatives} are coherent.
483     *
484     * <p>The math transform parameters and the sample coordinates are:</p>
485     *
486     * <table cellspacing="15" summary="Test data">
487     * <tr valign="top"><td><table class="ogc">
488     * <caption>CRS characteristics</caption>
489     * <tr><th>Parameter</th>                         <th>Value</th></tr>
490     * <tr><td>semi-major axis</td>                   <td>6378245.0 m</td></tr>
491     * <tr><td>semi-minor axis</td>                   <td>6356863.018773047 m</td></tr>
492     * <tr><td>Latitude of 1st standard parallel</td> <td>42.0°</td></tr>
493     * <tr><td>Longitude of natural origin</td>       <td>51.0°</td></tr>
494     * <tr><td>False easting</td>                     <td>0.0 m</td></tr>
495     * <tr><td>False northing</td>                    <td>0.0 m</td></tr>
496     * </table></td><td>
497     * <table class="ogc">
498     * <caption>Test points</caption>
499     * <tr><th>Source ordinates</th>           <th>Expected results</th></tr>
500     * <tr align="right"><td>51°E<br>0°N</td>  <td>0.00 m<br>0.00 m</td></tr>
501     * <tr align="right"><td>53°E<br>53°N</td> <td>165704.29 m<br>5171848.07 m</td></tr>
502     * </table></td></tr></table>
503     *
504     * @throws FactoryException if the math transform can not be created.
505     * @throws TransformException if the example point can not be transformed.
506     *
507     * @see AuthorityFactoryTest#testEPSG_3388()
508     */
509    @Test
510    public void testMercator2SP() throws FactoryException, TransformException {
511        description = "Pulkovo 1942 / Caspian Sea Mercator";
512        final SamplePoints sample = SamplePoints.forCRS(3388);
513        createMathTransform(Projection.class, sample);
514        verifyTransform(sample.sourcePoints, sample.targetPoints);
515        verifyInDomainOfValidity(sample.areaOfValidity);
516    }
517
518    /**
519     * Tests the <cite>"Mercator (variant C)"</cite> (EPSG:1044) projection method.
520     * First, this method transforms the point given in the <cite>Example</cite> section of the
521     * EPSG guidance note and compares the {@link MathTransform} result with the expected result.
522     * Next, this method transforms a random set of points in the projection area of validity
523     * and ensures that the {@linkplain MathTransform#inverse() inverse transform} and the
524     * {@linkplain MathTransform#derivative derivatives} are coherent.
525     *
526     * <p>The math transform parameters and the sample coordinates are below.
527     * Note that this is similar to {@link #testMercator2SP()}, except that the
528     * <cite>"latitude of false origin"</cite> parameter is set to 42°N.</p>
529     *
530     * <table cellspacing="15" summary="Test data">
531     * <tr valign="top"><td><table class="ogc">
532     * <caption>CRS characteristics</caption>
533     * <tr><th>Parameter</th>                         <th>Value</th></tr>
534     * <tr><td>semi-major axis</td>                   <td>6378245.0 m</td></tr>
535     * <tr><td>semi-minor axis</td>                   <td>6356863.018773047 m</td></tr>
536     * <tr><td>Latitude of 1st standard parallel</td> <td>42.0°</td></tr>
537     * <tr><td>Longitude of natural origin</td>       <td>51.0°</td></tr>
538     * <tr><td>Latitude of false origin</td>          <td>42.0°</td></tr>
539     * <tr><td>Easting at false origin</td>           <td>0.0 m</td></tr>
540     * <tr><td>Northing at false origin</td>          <td>0.0 m</td></tr>
541     * </table></td><td>
542     * <table class="ogc">
543     * <caption>Test points</caption>
544     * <tr><th>Source ordinates</th>           <th>Expected results</th></tr>
545     * <tr align="right"><td>51°E<br>42°N</td> <td>0.00 m<br>0.00 m</td></tr>
546     * <tr align="right"><td>53°E<br>53°N</td> <td>165704.29 m<br>1351950.22 m</td></tr>
547     * </table></td></tr></table>
548     *
549     * @throws FactoryException if the math transform can not be created.
550     * @throws TransformException if the example point can not be transformed.
551     */
552    @Test
553    public void testMercatorVariantC() throws FactoryException, TransformException {
554        description = "Pulkovo 1942 / Caspian Sea Mercator";
555        final SamplePoints sample = SamplePoints.forCRS(3388);
556        sample.sourcePoints[1] = 42;            // New latitude where we expect a northing of 0 m.
557        sample.targetPoints[3] = 1351950.22;    // New Northing value for 53°N.
558        /*
559         * Following is basically a copy-and-paste of PseudoEpsgFactory.createParameters(mtFactory, 3388)
560         * with a different projection ("variant C" instead of "variant B") and one more parameter value
561         * (the "Latitude of false origin").
562         */
563        createParameters("Mercator (variant C)");
564        parameters.parameter("semi_major").setValue(6378245.0);                         // Krassowski 1940
565        parameters.parameter("semi_minor").setValue(6378245.0 * (1 - 1/298.3));
566        parameters.parameter("Latitude of 1st standard parallel").setValue(42.0);
567        parameters.parameter("Longitude of natural origin")      .setValue(51.0);
568        parameters.parameter("Latitude of false origin")         .setValue(42.0);
569        validators.validate(parameters);
570        /*
571         * Following is common to all tests in this class.
572         */
573        createMathTransform(Projection.class, sample);
574        verifyTransform(sample.sourcePoints, sample.targetPoints);
575        verifyInDomainOfValidity(sample.areaOfValidity);
576    }
577
578    /**
579     * Tests the <cite>"Mercator (Spherical)"</cite> (EPSG:1026) projection method.
580     * First, this method transforms the point given in the <cite>Example</cite> section of the
581     * EPSG guidance note and compares the {@link MathTransform} result with the expected result.
582     * Next, this method transforms a random set of points in the projection area of validity
583     * and ensures that the {@linkplain MathTransform#inverse() inverse transform} and the
584     * {@linkplain MathTransform#derivative derivatives} are coherent.
585     *
586     * <p>The math transform parameters and the sample coordinates are below.
587     * Note that the sample point is the same than for {@link #testPseudoMercator()},
588     * but with a different result in projected coordinates.</p>
589     *
590     * <table cellspacing="15" summary="Test data">
591     * <tr valign="top"><td><table class="ogc">
592     * <caption>CRS characteristics</caption>
593     * <tr><th>Parameter</th>                         <th>Value</th></tr>
594     * <tr><td>semi-major axis</td>                   <td>6371007.0 m</td></tr>
595     * <tr><td>semi-minor axis</td>                   <td>6371007.0 m</td></tr>
596     * <tr><td>Longitude of natural origin</td>       <td>0.0°</td></tr>
597     * <tr><td>False easting</td>                     <td>0.0 m</td></tr>
598     * <tr><td>False northing</td>                    <td>0.0 m</td></tr>
599     * </table></td><td>
600     * <table class="ogc">
601     * <caption>Test points</caption>
602     * <tr><th>Source ordinates</th>         <th>Expected results</th></tr>
603     * <tr align="right"><td>0°E<br>0°N</td> <td>0.00 m<br>0.00 m</td></tr>
604     * <tr align="right"><td>100°20'00.000"W<br>24°22'54.433"N</td>
605     * <td>-11156569.90 m<br>2796869.94 m</td></tr>
606     * </table></td></tr></table>
607     *
608     * @throws FactoryException if the math transform can not be created.
609     * @throws TransformException if the example point can not be transformed.
610     */
611    @Test
612    public void testMercatorSpherical() throws FactoryException, TransformException {
613        description = "World Spherical Mercator";
614        final SamplePoints sample = SamplePoints.forCRS(3857);
615        sample.targetPoints[2] = -11156569.90;    // New Easting value.
616        sample.targetPoints[3] =   2796869.94;    // New Northing value.
617        createParameters("Mercator (Spherical)");
618        parameters.parameter("semi_major").setValue(6371007.0);
619        parameters.parameter("semi_minor").setValue(6371007.0);
620        validators.validate(parameters);
621        createMathTransform(Projection.class, sample);
622        verifyTransform(sample.sourcePoints, sample.targetPoints);
623        verifyInDomainOfValidity(sample.areaOfValidity);
624    }
625
626    /**
627     * Tests the <cite>"Mercator Popular Visualisation Pseudo Mercator"</cite> (EPSG:1024) projection
628     * method. First, this method transforms the point given in the <cite>Example</cite> section of the
629     * EPSG guidance note and compares the {@link MathTransform} result with the expected result.
630     * Next, this method transforms a random set of points in the projection area of validity
631     * and ensures that the {@linkplain MathTransform#inverse() inverse transform} and the
632     * {@linkplain MathTransform#derivative derivatives} are coherent.
633     *
634     * <p>The math transform parameters and the sample coordinates are:</p>
635     *
636     * <table cellspacing="15" summary="Test data">
637     * <tr valign="top"><td><table class="ogc">
638     * <caption>CRS characteristics</caption>
639     * <tr><th>Parameter</th>                   <th>Value</th></tr>
640     * <tr><td>semi-major axis</td>             <td>6378137.0 m</td></tr>
641     * <tr><td>semi-minor axis</td>             <td>6356752.314247833 m</td></tr>
642     * <tr><td>Latitude of natural origin</td>  <td>0.0°</td></tr>
643     * <tr><td>Longitude of natural origin</td> <td>0.0°</td></tr>
644     * <tr><td>False easting</td>               <td>0.0 m</td></tr>
645     * <tr><td>False northing</td>              <td>0.0 m</td></tr>
646     * </table></td><td>
647     * <table class="ogc">
648     * <caption>Test points</caption>
649     * <tr><th>Source ordinates</th>         <th>Expected results</th></tr>
650     * <tr align="right"><td>0°E<br>0°N</td> <td>0.00 m<br>0.00 m</td></tr>
651     * <tr align="right"><td>100°20'00.000"W<br>24°22'54.433"N</td>
652     * <td>-11169055.58 m<br>2800000.00 m</td></tr>
653     * </table></td></tr></table>
654     *
655     * @throws FactoryException if the math transform can not be created.
656     * @throws TransformException if the example point can not be transformed.
657     *
658     * @see AuthorityFactoryTest#testEPSG_3857()
659     */
660    @Test
661    public void testPseudoMercator() throws FactoryException, TransformException {
662        description = "WGS 84 / Pseudo-Mercator";
663        final SamplePoints sample = SamplePoints.forCRS(3857);
664        createMathTransform(Projection.class, sample);
665        verifyTransform(sample.sourcePoints, sample.targetPoints);
666        verifyInDomainOfValidity(sample.areaOfValidity);
667    }
668
669    /**
670     * Tests the <cite>"IGNF:MILLER"</cite> projection.
671     * First, this method transforms the point given below
672     * and compares the {@link MathTransform} result with the expected result. Next, this method
673     * transforms a random set of points in the projection area of validity and ensures that the
674     * {@linkplain MathTransform#inverse() inverse transform} and the
675     * {@linkplain MathTransform#derivative derivatives} are coherent.
676     *
677     * <p>The math transform parameters and the sample coordinates are:</p>
678     *
679     * <table cellspacing="15" summary="Test data">
680     * <tr valign="top"><td><table class="ogc">
681     * <caption>CRS characteristics</caption>
682     * <tr><th>Parameter</th>           <th>Value</th></tr>
683     * <tr><td>semi_major</td>          <td>6378137.0 m</td></tr>
684     * <tr><td>semi_minor</td>          <td>6378137.0 m</td></tr>
685     * <tr><td>central_meridian</td>    <td>0.0°</td></tr>
686     * <tr><td>false_easting</td>       <td>0.0 m</td></tr>
687     * <tr><td>false_northing</td>      <td>0.0 m</td></tr>
688     * </table></td><td>
689     * <table class="ogc">
690     * <caption>Test points</caption>
691     * <tr><th>Source ordinates</th>                        <th>Expected results</th></tr>
692     * <tr align="right"><td>0°E<br>0°N</td>                <td>0.00 m<br>0.00 m</td></tr>
693     * <tr align="right"><td>2.478917°E<br>48.805639°N</td> <td>275951.78 m<br>5910061.78 m</td></tr>
694     * </table></td></tr></table>
695     *
696     * @throws FactoryException if the math transform can not be created.
697     * @throws TransformException if the example point can not be transformed.
698     *
699     * @see AuthorityFactoryTest#testIGNF_MILLER()
700     */
701    @Test
702    public void testMiller() throws FactoryException, TransformException {
703        description = "IGNF:MILLER";
704        final SamplePoints sample = SamplePoints.forCRS(310642901);
705        createMathTransform(Projection.class, sample);
706        verifyTransform(sample.sourcePoints, sample.targetPoints);
707        verifyInDomainOfValidity(sample.areaOfValidity);
708    }
709
710    /**
711     * Tests the <cite>"Hotine Oblique Mercator (variant B)"</cite> (EPSG:9815) projection method.
712     * First, this method transforms the point given in the <cite>Example</cite> section of the
713     * EPSG guidance note and compares the {@link MathTransform} result with the expected result.
714     * Next, this method transforms a random set of points in the projection area of validity
715     * and ensures that the {@linkplain MathTransform#inverse() inverse transform} and the
716     * {@linkplain MathTransform#derivative derivatives} are coherent.
717     *
718     * <p>The math transform parameters and the sample coordinates are:</p>
719     *
720     * <table cellspacing="15" summary="Test data">
721     * <tr valign="top"><td><table class="ogc">
722     * <caption>CRS characteristics</caption>
723     * <tr><th>Parameter</th>                         <th>Value</th></tr>
724     * <tr><td>semi-major axis</td>                   <td>6377298.556 m</td></tr>
725     * <tr><td>semi-minor axis</td>                   <td>6356097.550300896 m</td></tr>
726     * <tr><td>Latitude of projection centre</td>     <td>4.0°</td></tr>
727     * <tr><td>Longitude of projection centre</td>    <td>109.6855202029758°</td></tr>
728     * <tr><td>Azimuth of initial line</td>           <td>53.31582047222222°</td></tr>
729     * <tr><td>Angle from Rectified to Skew Grid</td> <td>53.13010236111111°</td></tr>
730     * <tr><td>Scale factor on initial line</td>      <td>0.99984</td></tr>
731     * <tr><td>Easting at projection centre</td>      <td>590476.87 m</td></tr>
732     * <tr><td>Northing at projection centre</td>     <td>442857.65 m</td></tr>
733     * </table></td><td>
734     * <table class="ogc">
735     * <caption>Test points</caption>
736     * <tr><th>Source ordinates</th>         <th>Expected results</th></tr>
737     * <tr align="right"><td>115°E<br>4°N</td> <td>590476.87 m<br>442857.65 m</td></tr>
738     * <tr align="right"><td>115°48'19.8196"E<br>5°23'14.1129"N</td>
739     * <td>679245.73 m<br>596562.78 m</td></tr>
740     * </table></td></tr></table>
741     *
742     * @throws FactoryException if the math transform can not be created.
743     * @throws TransformException if the example point can not be transformed.
744     *
745     * @see AuthorityFactoryTest#testEPSG_29873()
746     */
747    @Test
748    public void testHotineObliqueMercator() throws FactoryException, TransformException {
749        description = "Timbalai 1948 / RSO Borneo (m)";
750        final SamplePoints sample = SamplePoints.forCRS(29873);
751        createMathTransform(Projection.class, sample);
752        verifyTransform(sample.sourcePoints, sample.targetPoints);
753        verifyInDomainOfValidity(sample.areaOfValidity);
754    }
755
756    /**
757     * Tests the <cite>"Transverse Mercator"</cite> (EPSG:9807) projection method.
758     * First, this method transforms the point given in the <cite>Example</cite> section of the
759     * EPSG guidance note and compares the {@link MathTransform} result with the expected result.
760     * Next, this method transforms a random set of points in the projection area of validity
761     * and ensures that the {@linkplain MathTransform#inverse() inverse transform} and the
762     * {@linkplain MathTransform#derivative derivatives} are coherent.
763     *
764     * <p>The math transform parameters and the sample coordinates are:</p>
765     *
766     * <table cellspacing="15" summary="Test data">
767     * <tr valign="top"><td><table class="ogc">
768     * <caption>CRS characteristics</caption>
769     * <tr><th>Parameter</th>                      <th>Value</th></tr>
770     * <tr><td>semi-major axis</td>                <td>6377563.396 m</td></tr>
771     * <tr><td>semi-minor axis</td>                <td>6356256.908909849 m</td></tr>
772     * <tr><td>Latitude of natural origin</td>     <td>49.0°</td></tr>
773     * <tr><td>Longitude of natural origin</td>    <td>-2.0°</td></tr>
774     * <tr><td>Scale factor at natural origin</td> <td>0.9996012717</td></tr>
775     * <tr><td>False easting</td>                  <td>400000.0 m</td></tr>
776     * <tr><td>False northing</td>                 <td>-100000.0 m</td></tr>
777     * </table></td><td>
778     * <table class="ogc">
779     * <caption>Test points</caption>
780     * <tr><th>Source ordinates</th>                 <th>Expected results</th></tr>
781     * <tr align="right"><td>2°W<br>49°N</td>        <td>400000.00 m<br>-100000.00 m</td></tr>
782     * <tr align="right"><td>00°30'E<br>50°30'N</td> <td>577274.98 m<br>69740.49 m</td></tr>
783     * </table></td></tr></table>
784     *
785     * @throws FactoryException if the math transform can not be created.
786     * @throws TransformException if the example point can not be transformed.
787     *
788     * @see AuthorityFactoryTest#testEPSG_27700()
789     */
790    @Test
791    public void testTransverseMercator() throws FactoryException, TransformException {
792        description = "OSGB 1936 / British National Grid";
793        final SamplePoints sample = SamplePoints.forCRS(27700);
794        createMathTransform(Projection.class, sample);
795        verifyTransform(sample.sourcePoints, sample.targetPoints);
796        verifyInDomainOfValidity(sample.areaOfValidity);
797    }
798
799    /**
800     * Tests the <cite>Transverse Mercator (South Orientated)</cite> (EPSG:9808) projection method.
801     * First, this method transforms the point given in the <cite>Example</cite> section of the
802     * EPSG guidance note and compares the {@link MathTransform} result with the expected result.
803     * Next, this method transforms a random set of points in the projection area of validity
804     * and ensures that the {@linkplain MathTransform#inverse() inverse transform} and the
805     * {@linkplain MathTransform#derivative derivatives} are coherent.
806     *
807     * <p>The math transform parameters and the sample coordinates are:</p>
808     *
809     * <table cellspacing="15" summary="Test data">
810     * <tr valign="top"><td><table class="ogc">
811     * <caption>CRS characteristics</caption>
812     * <tr><th>Parameter</th>                      <th>Value</th></tr>
813     * <tr><td>semi-major axis</td>                <td>6378137.0 m</td></tr>
814     * <tr><td>semi-minor axis</td>                <td>6356752.314247833 m</td></tr>
815     * <tr><td>Latitude of natural origin</td>     <td>0°</td></tr>
816     * <tr><td>Longitude of natural origin</td>    <td>29°</td></tr>
817     * <tr><td>Scale factor at natural origin</td> <td>1</td></tr>
818     * <tr><td>False easting</td>                  <td>0 m</td></tr>
819     * <tr><td>False northing</td>                 <td>0 m</td></tr>
820     * </table></td><td>
821     * <table class="ogc">
822     * <caption>Test points</caption>
823     * <tr><th>Source ordinates</th>                 <th>Expected results</th></tr>
824     * <tr align="right"><td>20°E<br>0°S</td>        <td>0 m<br>0 m</td></tr>
825     * <tr align="right"><td>28°16'57.479"E<br>25°43'55.302"S</td> <td>71984.48 m<br>2847342.74 m</td></tr>
826     * </table></td></tr></table>
827     *
828     * @throws FactoryException if the math transform can not be created.
829     * @throws TransformException if the example point can not be transformed.
830     */
831    @Test
832    public void testTransverseMercatorSouthOrientated() throws FactoryException, TransformException {
833        description = "Hartebeesthoek94 / Lo29";
834        final SamplePoints sample = SamplePoints.forCRS(2053);
835        createMathTransform(Projection.class, sample);
836        /*
837         * In this particular case we have a conflict between the change of axis direction performed by the
838         * "Transverse Mercator (South Orientated)" operation method  and the (east, north) axis directions
839         * documented in the MathTransformFactory.createParameterizedTransform(…) method. We do not mandate
840         * any particular behavior at this time, so we have to determine what the implementor choose to do,
841         * by projecting a point in the south hemisphere and checking the sign of the result.
842         */
843        double[] expected = sample.targetPoints;
844        final double[] check = new double[] {-0.5, -0.5};
845        transform.transform(check, 0, check, 0, 1);
846        if (check[1] < 0) {
847            /*
848             * Point in the South hemisphere have negative y values. In other words, the implementor chooses to
849             * keep (east,north) directions instead of (west,south). Reverse the sign of all expected coordinates.
850             */
851            expected = expected.clone();
852            for (int i=0; i<expected.length; i++) {
853                expected[i] = -expected[i];
854            }
855        }
856        verifyTransform(sample.sourcePoints, expected);
857        verifyInDomainOfValidity(sample.areaOfValidity);
858    }
859
860    /**
861     * Tests the <cite>"Cassini-Soldner"</cite> (EPSG:9806) projection method.
862     * First, this method transforms the point given in the <cite>Example</cite> section of the
863     * EPSG guidance note and compares the {@link MathTransform} result with the expected result.
864     * Next, this method transforms a random set of points in the projection area of validity
865     * and ensures that the {@linkplain MathTransform#inverse() inverse transform} and the
866     * {@linkplain MathTransform#derivative derivatives} are coherent.
867     *
868     * <p>The math transform parameters and the sample coordinates are:</p>
869     *
870     * <table cellspacing="15" summary="Test data">
871     * <tr valign="top"><td><table class="ogc">
872     * <caption>CRS characteristics</caption>
873     * <tr><th>Parameter</th>                   <th>Value</th></tr>
874     * <tr><td>semi-major axis</td>             <td>6378350.8704 m</td></tr>
875     * <tr><td>semi-minor axis</td>             <td>6356675.0184 m</td></tr>
876     * <tr><td>Latitude of natural origin</td>  <td>10.441666666666666°</td></tr>
877     * <tr><td>Longitude of natural origin</td> <td>-61.33333333333333°</td></tr>
878     * <tr><td>False easting</td>               <td>86501.46392052001 m</td></tr>
879     * <tr><td>False northing</td>              <td>65379.0134283 m</td></tr>
880     * </table></td><td>
881     * <table class="ogc">
882     * <caption>Test points</caption>
883     * <tr><th>Source ordinates</th>                       <th>Expected results</th></tr>
884     * <tr align="right"><td>61°20'00"W<br>10°26'30"N</td> <td>430000.00 links<br>325000.00 links</td></tr>
885     * <tr align="right"><td>60°00'00"W<br>10°00'00"N</td> <td>66644.94 links<br>82536.22 links</td></tr>
886     * </table>
887     * <p align="right">1 link = 0.66 feet<br>1 feet = 0.3048 metre</p>
888     * </td></tr></table>
889     *
890     * @throws FactoryException if the math transform can not be created.
891     * @throws TransformException if the example point can not be transformed.
892     *
893     * @see AuthorityFactoryTest#testEPSG_2314()
894     */
895    @Test
896    public void testCassiniSoldner() throws FactoryException, TransformException {
897        description = "Trinidad 1903 / Trinidad Grid";
898        final SamplePoints sample = SamplePoints.forCRS(2314);
899        createMathTransform(Projection.class, sample);
900        verifyTransform(sample.sourcePoints, sample.targetPoints);
901        verifyInDomainOfValidity(sample.areaOfValidity);
902    }
903
904    /**
905     * Tests the <cite>"Lambert Conic Conformal (1SP)"</cite> (EPSG:9801) projection method.
906     * First, this method transforms the point given in the <cite>Example</cite> section of the
907     * EPSG guidance note and compares the {@link MathTransform} result with the expected result.
908     * Next, this method transforms a random set of points in the projection area of validity
909     * and ensures that the {@linkplain MathTransform#inverse() inverse transform} and the
910     * {@linkplain MathTransform#derivative derivatives} are coherent.
911     *
912     * <p>The math transform parameters and the sample coordinates are:</p>
913     *
914     * <table cellspacing="15" summary="Test data">
915     * <tr valign="top"><td><table class="ogc">
916     * <caption>CRS characteristics</caption>
917     * <tr><th>Parameter</th>                       <th>Value</th></tr>
918     * <tr><td>semi-major axis</td>                 <td>6378206.4 m</td></tr>
919     * <tr><td>semi-minor axis</td>                 <td>6356583.8 m</td></tr>
920     * <tr><td>Latitude of natural origin</td>      <td>18.0°</td></tr>
921     * <tr><td>Longitude of natural origin</td>     <td>-77.0°</td></tr>
922     * <tr><td>Scale factor at natural origin</td>  <td>1.0</td></tr>
923     * <tr><td>False easting</td>                   <td>250000.0 m</td></tr>
924     * <tr><td>False northing</td>                  <td>150000.0 m</td></tr>
925     * </table></td><td>
926     * <table class="ogc">
927     * <caption>Test points</caption>
928     * <tr><th>Source ordinates</th>           <th>Expected results</th></tr>
929     * <tr align="right"><td>77°W<br>18°N</td> <td>250000.00 m<br>150000.00 m</td></tr>
930     * <tr align="right"><td>76°56'37.26"W<br>17°55'55.80"N</td>
931     * <td>255966.58 m<br>142493.51 m</td></tr>
932     * </table></td></tr></table>
933     *
934     * @throws FactoryException if the math transform can not be created.
935     * @throws TransformException if the example point can not be transformed.
936     *
937     * @see AuthorityFactoryTest#testEPSG_24200()
938     */
939    @Test
940    public void testLambertConicConformal1SP() throws FactoryException, TransformException {
941        description = "JAD69 / Jamaica National Grid";
942        final SamplePoints sample = SamplePoints.forCRS(24200);
943        createMathTransform(Projection.class, sample);
944        verifyTransform(sample.sourcePoints, sample.targetPoints);
945        verifyInDomainOfValidity(sample.areaOfValidity);
946    }
947
948    /**
949     * Tests the <cite>"Lambert Conic Conformal (2SP)"</cite> (EPSG:9802) projection method.
950     * First, this method transforms the point given in the <cite>Example</cite> section of the
951     * EPSG guidance note and compares the {@link MathTransform} result with the expected result.
952     * Next, this method transforms a random set of points in the projection area of validity
953     * and ensures that the {@linkplain MathTransform#inverse() inverse transform} and the
954     * {@linkplain MathTransform#derivative derivatives} are coherent.
955     *
956     * <p>The math transform parameters and the sample coordinates are:</p>
957     *
958     * <table cellspacing="15" summary="Test data">
959     * <tr valign="top"><td><table class="ogc">
960     * <caption>CRS characteristics</caption>
961     * <tr><th>Parameter</th>                         <th>Value</th></tr>
962     * <tr><td>semi-major axis</td>                   <td>6378206.4 m</td></tr>
963     * <tr><td>semi-minor axis</td>                   <td>6356583.8 m</td></tr>
964     * <tr><td>Latitude of false origin</td>          <td>27.833333333333333°</td></tr>
965     * <tr><td>Longitude of false origin</td>         <td>-99.0°</td></tr>
966     * <tr><td>Latitude of 1st standard parallel</td> <td>28.383333333333333°</td></tr>
967     * <tr><td>Latitude of 2nd standard parallel</td> <td>30.283333333333333°</td></tr>
968     * <tr><td>Easting at false origin</td>           <td>609601.2192024385 m</td></tr>
969     * <tr><td>Northing at false origin</td>          <td>0.0 m</td></tr>
970     * </table></td><td>
971     * <table class="ogc">
972     * <caption>Test points</caption>
973     * <tr><th>Source ordinates</th>                 <th>Expected results</th></tr>
974     * <tr align="right"><td>99°00'W<br>27°30'N</td> <td>2000000.00 US feet<br>0 US feet</td></tr>
975     * <tr align="right"><td>96°00'W<br>28°30'N</td> <td>2963503.91 US feet<br>254759.80 US feet</td></tr>
976     * </table>
977     * <p align="right">1 metre = 3.2808333… US feet</p>
978     * </td></tr></table>
979     *
980     * @throws FactoryException if the math transform can not be created.
981     * @throws TransformException if the example point can not be transformed.
982     *
983     * @see AuthorityFactoryTest#testEPSG_32040()
984     */
985    @Test
986    public void testLambertConicConformal2SP() throws FactoryException, TransformException {
987        description = "NAD27 / Texas South Central";
988        final SamplePoints sample = SamplePoints.forCRS(32040);
989        createMathTransform(Projection.class, sample);
990        verifyTransform(sample.sourcePoints, sample.targetPoints);
991        verifyInDomainOfValidity(sample.areaOfValidity);
992    }
993
994    /**
995     * Tests the <cite>"Lambert Conic Conformal (2SP Belgium)"</cite> (EPSG:9803) projection method.
996     * First, this method transforms the point given in the <cite>Example</cite> section of the
997     * EPSG guidance note and compares the {@link MathTransform} result with the expected result.
998     * Next, this method transforms a random set of points in the projection area of validity
999     * and ensures that the {@linkplain MathTransform#inverse() inverse transform} and the
1000     * {@linkplain MathTransform#derivative derivatives} are coherent.
1001     *
1002     * <p>The math transform parameters and the sample coordinates are:</p>
1003     *
1004     * <table cellspacing="15" summary="Test data">
1005     * <tr valign="top"><td><table class="ogc">
1006     * <caption>CRS characteristics</caption>
1007     * <tr><th>Parameter</th>                         <th>Value</th></tr>
1008     * <tr><td>semi-major axis</td>                   <td>6378388.0 m</td></tr>
1009     * <tr><td>semi-minor axis</td>                   <td>6356911.9461279465 m</td></tr>
1010     * <tr><td>Latitude of false origin</td>          <td>90.0°</td></tr>
1011     * <tr><td>Longitude of false origin</td>         <td>4.356939722222222°</td></tr>
1012     * <tr><td>Latitude of 1st standard parallel</td> <td>49.83333333333333°</td></tr>
1013     * <tr><td>Latitude of 2nd standard parallel</td> <td>51.16666666666667°</td></tr>
1014     * <tr><td>Easting at false origin</td>           <td>150000.01256 m</td></tr>
1015     * <tr><td>Northing at false origin</td>          <td>5400088.4378 m</td></tr>
1016     * </table></td><td>
1017     * <table class="ogc">
1018     * <caption>Test points</caption>
1019     * <tr><th>Source ordinates</th>                              <th>Expected results</th></tr>
1020     * <tr align="right"><td>4°21'24.983"E<br>90°00'00.000"N</td> <td>150000.01 m<br>5400088.44 m</td></tr>
1021     * <tr align="right"><td>5°48'26.533"E<br>50°40'46.461"N</td> <td>251763.20 m<br>153034.13 m</td></tr>
1022     * </table></td></tr></table>
1023     *
1024     * @throws FactoryException if the math transform can not be created.
1025     * @throws TransformException if the example point can not be transformed.
1026     *
1027     * @see AuthorityFactoryTest#testEPSG_31300()
1028     */
1029    @Test
1030    public void testLambertConicConformalBelgium() throws FactoryException, TransformException {
1031        description = "Belge 1972 / Belge Lambert 72";
1032        final SamplePoints sample = SamplePoints.forCRS(31300);
1033        createMathTransform(Projection.class, sample);
1034        verifyTransform(sample.sourcePoints, sample.targetPoints);
1035        verifyInDomainOfValidity(sample.areaOfValidity);
1036    }
1037
1038    /**
1039     * Tests the <cite>"Lambert Conic Conformal (2SP Michigan)"</cite> (EPSG:1051) projection method.
1040     * First, this method transforms the point given in the <cite>Example</cite> section of the
1041     * EPSG guidance note and compares the {@link MathTransform} result with the expected result.
1042     * Next, this method transforms a random set of points in the projection area of validity
1043     * and ensures that the {@linkplain MathTransform#inverse() inverse transform} and the
1044     * {@linkplain MathTransform#derivative derivatives} are coherent.
1045     *
1046     * <p>The math transform parameters and the sample coordinates are:</p>
1047     *
1048     * <table cellspacing="15" summary="Test data">
1049     * <tr valign="top"><td><table class="ogc">
1050     * <caption>CRS characteristics</caption>
1051     * <tr><th>Parameter</th>                         <th>Value</th></tr>
1052     * <tr><td>semi-major axis</td>                   <td>6378206.4 m</td></tr>
1053     * <tr><td>semi-minor axis</td>                   <td>6356583.8 m</td></tr>
1054     * <tr><td>Latitude of false origin</td>          <td>43.316666666666667°</td></tr>
1055     * <tr><td>Longitude of false origin</td>         <td>-84.333333333333333°</td></tr>
1056     * <tr><td>Latitude of 1st standard parallel</td> <td>44.183333333333333°</td></tr>
1057     * <tr><td>Latitude of 2nd standard parallel</td> <td>45.7°</td></tr>
1058     * <tr><td>Easting at false origin</td>           <td>609601.2192024385 m</td></tr>
1059     * <tr><td>Northing at false origin</td>          <td>0.0 m</td></tr>
1060     * </table></td><td>
1061     * <table class="ogc">
1062     * <caption>Test points</caption>
1063     * <tr><th>Source ordinates</th>                 <th>Expected results</th></tr>
1064     * <tr align="right"><td>84°20'W<br>43°19'N</td> <td>2000000.00 US feet<br>0 US feet</td></tr>
1065     * <tr align="right"><td>83°10"W<br>43°45'N</td> <td>2308335.75 US feet<br>160210.48 US feet</td></tr>
1066     * </table>
1067     * <p align="right">1 metre = 3.2808333… US feet</p>
1068     * </td></tr></table>
1069     *
1070     * @throws FactoryException if the math transform can not be created.
1071     * @throws TransformException if the example point can not be transformed.
1072     */
1073    @Test
1074    public void testLambertConicConformalMichigan() throws FactoryException, TransformException {
1075        description = "NAD27 / Michigan Central";
1076        final SamplePoints sample = SamplePoints.forCRS(6201);
1077        createMathTransform(Projection.class, sample);
1078        verifyTransform(sample.sourcePoints, sample.targetPoints);
1079        verifyInDomainOfValidity(sample.areaOfValidity);
1080    }
1081
1082    /**
1083     * Tests the <cite>"Lambert Azimuthal Equal Area"</cite> (EPSG:9820) projection method.
1084     * First, this method transforms the point given in the <cite>Example</cite> section of the
1085     * EPSG guidance note and compares the {@link MathTransform} result with the expected result.
1086     * Next, this method transforms a random set of points in the projection area of validity
1087     * and ensures that the {@linkplain MathTransform#inverse() inverse transform} and the
1088     * {@linkplain MathTransform#derivative derivatives} are coherent.
1089     *
1090     * <p>The math transform parameters and the sample coordinates are:</p>
1091     *
1092     * <table cellspacing="15" summary="Test data">
1093     * <tr valign="top"><td><table class="ogc">
1094     * <caption>CRS characteristics</caption>
1095     * <tr><th>Parameter</th>                   <th>Value</th></tr>
1096     * <tr><td>semi-major axis</td>             <td>6378137.0 m</td></tr>
1097     * <tr><td>semi-minor axis</td>             <td>6356752.314140284 m</td></tr>
1098     * <tr><td>Latitude of natural origin</td>  <td>52.0°</td></tr>
1099     * <tr><td>Longitude of natural origin</td> <td>10.0°</td></tr>
1100     * <tr><td>False easting</td>               <td>4321000.0 m</td></tr>
1101     * <tr><td>False northing</td>              <td>3210000.0 m</td></tr>
1102     * </table></td><td>
1103     * <table class="ogc">
1104     * <caption>Test points</caption>
1105     * <tr><th>Source ordinates</th>           <th>Expected results</th></tr>
1106     * <tr align="right"><td>10°E<br>52°N</td> <td>4321000.00 m<br>3210000.00 m</td></tr>
1107     * <tr align="right"><td>5°E<br>50°N</td>  <td>3962799.45 m<br>2999718.85 m</td></tr>
1108     * </table></td></tr></table>
1109     *
1110     * @throws FactoryException if the math transform can not be created.
1111     * @throws TransformException if the example point can not be transformed.
1112     *
1113     * @see AuthorityFactoryTest#testEPSG_3035()
1114     */
1115    @Test
1116    public void testLambertAzimuthalEqualArea() throws FactoryException, TransformException {
1117        description = "ETRS89 / LAEA Europe";
1118        final SamplePoints sample = SamplePoints.forCRS(3035);
1119        createMathTransform(Projection.class, sample);
1120        verifyTransform(sample.sourcePoints, sample.targetPoints);
1121        verifyInDomainOfValidity(sample.areaOfValidity);
1122    }
1123
1124    /**
1125     * Tests the <cite>"Polar Stereographic (variant A)"</cite> (EPSG:9810) projection method.
1126     * First, this method transforms the point given in the <cite>Example</cite> section of the
1127     * EPSG guidance note and compares the {@link MathTransform} result with the expected result.
1128     * Next, this method transforms a random set of points in the projection area of validity
1129     * and ensures that the {@linkplain MathTransform#inverse() inverse transform} and the
1130     * {@linkplain MathTransform#derivative derivatives} are coherent.
1131     *
1132     * <p>The math transform parameters and the sample coordinates are:</p>
1133     *
1134     * <table cellspacing="15" summary="Test data">
1135     * <tr valign="top"><td><table class="ogc">
1136     * <caption>CRS characteristics</caption>
1137     * <tr><th>Parameter</th>                      <th>Value</th></tr>
1138     * <tr><td>semi-major axis</td>                <td>6378137.0 m</td></tr>
1139     * <tr><td>semi-minor axis</td>                <td>6356752.314247833 m</td></tr>
1140     * <tr><td>Latitude of natural origin</td>     <td>90.0°</td></tr>
1141     * <tr><td>Longitude of natural origin</td>    <td>0.0°</td></tr>
1142     * <tr><td>Scale factor at natural origin</td> <td>0.994</td></tr>
1143     * <tr><td>False easting</td>                  <td>2000000.0 m</td></tr>
1144     * <tr><td>False northing</td>                 <td>2000000.0 m</td></tr>
1145     * </table></td><td>
1146     * <table class="ogc">
1147     * <caption>Test points</caption>
1148     * <tr><th>Source ordinates</th>           <th>Expected results</th></tr>
1149     * <tr align="right"><td>0°E<br>90°N</td>  <td>2000000.00 m<br>2000000.00 m</td></tr>
1150     * <tr align="right"><td>44°E<br>73°N</td> <td>3320416.75 m<br>632668.43 m</td></tr>
1151     * </table></td></tr></table>
1152     *
1153     * @throws FactoryException if the math transform can not be created.
1154     * @throws TransformException if the example point can not be transformed.
1155     *
1156     * @see AuthorityFactoryTest#testEPSG_5041()
1157     * @see AuthorityFactoryTest#testEPSG_32661()
1158     */
1159    @Test
1160    public void testPolarStereographicA() throws FactoryException, TransformException {
1161        description = "WGS 84 / UPS North (E,N)";
1162        final SamplePoints sample = SamplePoints.forCRS(5041);
1163        createMathTransform(Projection.class, sample);
1164        verifyTransform(sample.sourcePoints, sample.targetPoints);
1165        verifyInDomainOfValidity(sample.areaOfValidity);
1166    }
1167
1168    /**
1169     * Tests the <cite>"Polar Stereographic (variant B)"</cite> (EPSG:9829) projection method.
1170     * First, this method transforms the point given in the <cite>Example</cite> section of the
1171     * EPSG guidance note and compares the {@link MathTransform} result with the expected result.
1172     * Next, this method transforms a random set of points in the projection area of validity
1173     * and ensures that the {@linkplain MathTransform#inverse() inverse transform} and the
1174     * {@linkplain MathTransform#derivative derivatives} are coherent.
1175     *
1176     * <p>The math transform parameters and the sample coordinates are:</p>
1177     *
1178     * <table cellspacing="15" summary="Test data">
1179     * <tr valign="top"><td><table class="ogc">
1180     * <caption>CRS characteristics</caption>
1181     * <tr><th>Parameter</th>                      <th>Value</th></tr>
1182     * <tr><th>Source ordinates</th>               <th>Expected results</th></tr>
1183     * <tr><td>semi-major axis</td>                <td>6378137.0 m</td></tr>
1184     * <tr><td>semi-minor axis</td>                <td>6356752.314247833 m</td></tr>
1185     * <tr><td>Latitude of standard parallel</td>  <td>-71.0°</td></tr>
1186     * <tr><td>Longitude of origin</td>            <td>70.0°</td></tr>
1187     * <tr><td>False easting</td>                  <td>6000000.0 m</td></tr>
1188     * <tr><td>False northing</td>                 <td>6000000.0 m</td></tr>
1189     * </table></td><td>
1190     * <table class="ogc">
1191     * <caption>Test points</caption>
1192     * <tr><th>Source ordinates</th>            <th>Expected results</th></tr>
1193     * <tr align="right"><td>70°E<br>90°S</td>  <td>6000000.00 m<br>6000000.00 m</td></tr>
1194     * <tr align="right"><td>120°E<br>75°S</td> <td>7255380.79 m<br>7053389.56 m</td></tr>
1195     * </table></td></tr></table>
1196     *
1197     * @throws FactoryException if the math transform can not be created.
1198     * @throws TransformException if the example point can not be transformed.
1199     *
1200     * @see AuthorityFactoryTest#testEPSG_3032()
1201     */
1202    @Test
1203    public void testPolarStereographicB() throws FactoryException, TransformException {
1204        description = "Australian Antarctic Polar Stereographic";
1205        final SamplePoints sample = SamplePoints.forCRS(3032);
1206        createMathTransform(Projection.class, sample);
1207        verifyTransform(sample.sourcePoints, sample.targetPoints);
1208        verifyInDomainOfValidity(sample.areaOfValidity);
1209    }
1210
1211    /**
1212     * Tests the <cite>"Polar Stereographic (variant C)"</cite> (EPSG:9830) projection method.
1213     * First, this method transforms the point given in the <cite>Example</cite> section of the
1214     * EPSG guidance note and compares the {@link MathTransform} result with the expected result.
1215     * Next, this method transforms a random set of points in the projection area of validity
1216     * and ensures that the {@linkplain MathTransform#inverse() inverse transform} and the
1217     * {@linkplain MathTransform#derivative derivatives} are coherent.
1218     *
1219     * <p>The math transform parameters and the sample coordinates are:</p>
1220     *
1221     * <table cellspacing="15" summary="Test data">
1222     * <tr valign="top"><td><table class="ogc">
1223     * <caption>CRS characteristics</caption>
1224     * <tr><th>Parameter</th>                      <th>Value</th></tr>
1225     * <tr><th>Source ordinates</th>               <th>Expected results</th></tr>
1226     * <tr><td>semi-major axis</td>                <td>6378388.0 m</td></tr>
1227     * <tr><td>semi-minor axis</td>                <td>6356911.9461279465 m</td></tr>
1228     * <tr><td>Latitude of standard parallel</td>  <td>-67°</td></tr>
1229     * <tr><td>Longitude of origin</td>            <td>140°</td></tr>
1230     * <tr><td>False easting</td>                  <td>300000 m</td></tr>
1231     * <tr><td>False northing</td>                 <td>200000 m</td></tr>
1232     * </table></td><td>
1233     * <table class="ogc">
1234     * <caption>Test points</caption>
1235     * <tr><th>Source ordinates</th>            <th>Expected results</th></tr>
1236     * <tr align="right"><td>67°E<br>90°S</td>  <td>300000.00 m<br>200000.00 m</td></tr>
1237     * <tr align="right"><td>140°04'17.040"E<br>66°36'18.820"S</td> <td>303169.52 m<br>244055.72 m</td></tr>
1238     * </table></td></tr></table>
1239     *
1240     * @throws FactoryException if the math transform can not be created.
1241     * @throws TransformException if the example point can not be transformed.
1242     *
1243     * @see AuthorityFactoryTest#testEPSG_3032()
1244     */
1245    @Test
1246    public void testPolarStereographicC() throws FactoryException, TransformException {
1247        description = "Petrels 1972 / Terre Adelie Polar Stereographic";
1248        final SamplePoints sample = SamplePoints.forCRS(2985);
1249        createMathTransform(Projection.class, sample);
1250        verifyTransform(sample.sourcePoints, sample.targetPoints);
1251        verifyInDomainOfValidity(sample.areaOfValidity);
1252    }
1253
1254    /**
1255     * Tests the <cite>"Oblique Stereographic"</cite> (EPSG:9809) projection method.
1256     * First, this method transforms the point given in the <cite>Example</cite> section of the
1257     * EPSG guidance note and compares the {@link MathTransform} result with the expected result.
1258     * Next, this method transforms a random set of points in the projection area of validity
1259     * and ensures that the {@linkplain MathTransform#inverse() inverse transform} and the
1260     * {@linkplain MathTransform#derivative derivatives} are coherent.
1261     *
1262     * <p>The math transform parameters and the sample coordinates are:</p>
1263     *
1264     * <table cellspacing="15" summary="Test data">
1265     * <tr valign="top"><td><table class="ogc">
1266     * <caption>CRS characteristics</caption>
1267     * <tr><th>Parameter</th>                      <th>Value</th></tr>
1268     * <tr><td>semi-major axis</td>                <td>6377397.155 m</td></tr>
1269     * <tr><td>semi-minor axis</td>                <td>6356078.9626186555 m</td></tr>
1270     * <tr><td>Latitude of natural origin</td>     <td>52.15616055555556°</td></tr>
1271     * <tr><td>Longitude of natural origin</td>    <td>5.38763888888889°</td></tr>
1272     * <tr><td>Scale factor at natural origin</td> <td>0.9999079</td></tr>
1273     * <tr><td>False easting</td>                  <td>155000.0 m</td></tr>
1274     * <tr><td>False northing</td>                 <td>463000.0 m</td></tr>
1275     * </table></td><td>
1276     * <table class="ogc">
1277     * <caption>Test points</caption>
1278     * <tr><th>Source ordinates</th>                              <th>Expected results</th></tr>
1279     * <tr align="right"><td>5°23'15.500"E<br>52°09'22.178"N</td> <td>155000.000 m<br>463000.000 m</td></tr>
1280     * <tr align="right"><td>6°E<br>53°N</td>                     <td>196105.283 m<br>557057.739 m</td></tr>
1281     * </table></td></tr></table>
1282     *
1283     * @throws FactoryException if the math transform can not be created.
1284     * @throws TransformException if the example point can not be transformed.
1285     *
1286     * @see AuthorityFactoryTest#testEPSG_28992()
1287     */
1288    @Test
1289    public void testObliqueStereographic() throws FactoryException, TransformException {
1290        description = "Amersfoort / RD New";
1291        final SamplePoints sample = SamplePoints.forCRS(28992);
1292        createMathTransform(Projection.class, sample);
1293        verifyTransform(sample.sourcePoints, sample.targetPoints);
1294        verifyInDomainOfValidity(sample.areaOfValidity);
1295    }
1296
1297    /**
1298     * Tests the <cite>"American Polyconic"</cite> (EPSG:9818) projection.
1299     * First, this method transforms the some of the points given in Table 19, p 132 of
1300     * <a href="http://pubs.er.usgs.gov/usgspubs/pp/pp1395">Map Projections, a working manual</a>
1301     * by John P.Snyder. Next, this method transforms a random set of points in the projection
1302     * area of validity and ensures that the {@linkplain MathTransform#inverse() inverse transform}
1303     * and the {@linkplain MathTransform#derivative derivatives} are coherent.
1304     *
1305     * <p>The math transform parameters and the sample coordinates are:</p>
1306     *
1307     * <table cellspacing="15" summary="Test data">
1308     * <tr valign="top"><td><table class="ogc">
1309     * <caption>CRS characteristics</caption>
1310     * <tr><th>Parameter</th>                                <th>Value</th></tr>
1311     * <tr><td>semi-major axis</td>                          <td>6378206.4 m</td></tr>
1312     * <tr><td>semi-minor axis</td>                          <td>6356583.8 m</td></tr>
1313     * <tr><td>Latitude of natural origin</td>               <td>0.0°</td></tr>
1314     * <tr><td>Longitude of natural origin</td>              <td>0.0°</td></tr>
1315     * <tr><td>False easting</td>                            <td>0.0 m</td></tr>
1316     * <tr><td>False northing</td>                           <td>0.0 m</td></tr>
1317     * </table></td><td>
1318     * <table class="ogc">
1319     * <caption>Test points</caption>
1320     * <tr><th>Source ordinates</th>         <th>Expected results</th></tr>
1321     * <tr align="right"><td>See source</td> <td>See source</td></tr>
1322     * </table></td></tr></table>
1323     *
1324     * @throws FactoryException if the math transform can not be created.
1325     * @throws TransformException if the example point can not be transformed.
1326     */
1327    @Test
1328    public void testPolyconic() throws FactoryException, TransformException {
1329        tolerance = max(tolerance, 0.5);                        // The sample points are only accurate to 1 metre.
1330        description = "American Polyconic";
1331        final SamplePoints sample = SamplePoints.forCRS(9818);
1332        createMathTransform(Projection.class, sample);
1333        verifyTransform(sample.sourcePoints, sample.targetPoints);
1334        verifyInDomainOfValidity(sample.areaOfValidity);
1335    }
1336
1337    /**
1338     * Tests the <cite>"Krovak"</cite> (EPSG:9819) projection.
1339     * First, this method transforms the point given in the <cite>Example</cite> section of the
1340     * EPSG guidance note and compares the {@link MathTransform} result with the expected result.
1341     * Next, this method transforms a random set of points in the projection area of validity
1342     * and ensures that the {@linkplain MathTransform#inverse() inverse transform} and the
1343     * {@linkplain MathTransform#derivative derivatives} are coherent.
1344     *
1345     * <p>The math transform parameters and the sample coordinates are:</p>
1346     *
1347     * <table cellspacing="15" summary="Test data">
1348     * <tr valign="top"><td><table class="ogc">
1349     * <caption>CRS characteristics</caption>
1350     * <tr><th>Parameter</th>                                <th>Value</th></tr>
1351     * <tr><td>semi-major axis</td>                          <td>6377397.155 m</td></tr>
1352     * <tr><td>semi-minor axis</td>                          <td>6356078.9626186555 m</td></tr>
1353     * <tr><td>Latitude of projection centre</td>            <td>49.5°</td></tr>
1354     * <tr><td>Longitude of origin</td>                      <td>24.5°</td></tr>
1355     * <tr><td>Co-latitude of cone axis</td>                 <td>30.288139722222222°</td></tr>
1356     * <tr><td>Latitude of pseudo standard parallel</td>     <td>78.5°</td></tr>
1357     * <tr><td>Scale factor on pseudo standard parallel</td> <td>0.9999</td></tr>
1358     * <tr><td>False easting</td>                            <td>0.0 m</td></tr>
1359     * <tr><td>False northing</td>                           <td>0.0 m</td></tr>
1360     * </table></td><td>
1361     * <table class="ogc">
1362     * <caption>Test points</caption>
1363     * <tr><th>Source ordinates</th>                               <th>Expected results</th></tr>
1364     * <tr align="right"><td>16°50'59.179"E<br>50°12'32.442"N</td> <td>-568990.997 m<br>-1050538.643 m</td></tr>
1365     * </table></td></tr></table>
1366     *
1367     * @throws FactoryException if the math transform can not be created.
1368     * @throws TransformException if the example point can not be transformed.
1369     *
1370     * @see AuthorityFactoryTest#testEPSG_2065()
1371     */
1372    @Test
1373    public void testKrovak() throws FactoryException, TransformException {
1374        description = "CRS S-JTSK (Ferro) / Krovak";
1375        final SamplePoints sample = SamplePoints.forCRS(2065);
1376        createMathTransform(Projection.class, sample);
1377        verifyTransform(sample.sourcePoints, sample.targetPoints);
1378        verifyInDomainOfValidity(sample.areaOfValidity);
1379    }
1380
1381    /**
1382     * Tests the <cite>"Abridged Molodensky"</cite> (EPSG:9605) datum shift operation.
1383     * First, this method transforms the point given in the <cite>Example</cite> section of the
1384     * EPSG guidance note and compares the {@link MathTransform} result with the expected result.
1385     * Next, this method transforms a random set of geographic coordinates
1386     * and ensures that the {@linkplain MathTransform#inverse() inverse transform} and the
1387     * {@linkplain MathTransform#derivative derivatives} are coherent.
1388     *
1389     * <p>The math transform parameters and the sample coordinates are:</p>
1390     *
1391     * <table cellspacing="15" summary="Test data">
1392     * <tr valign="top"><td><table class="ogc">
1393     * <caption>CRS characteristics</caption>
1394     * <tr><th>Parameter</th>                         <th>Value</th></tr>
1395     * <tr><td>dim</td>                               <td>3</td></tr>
1396     * <tr><td>src_semi_major</td>                    <td>6378137.0 m</td></tr>
1397     * <tr><td>src_semi_minor</td>                    <td>6356752.314247833 m</td></tr>
1398     * <tr><td>X-axis translation</td>                <td>84.87 m</td></tr>
1399     * <tr><td>Y-axis translation</td>                <td>96.49 m</td></tr>
1400     * <tr><td>Z-axis translation</td>                <td>116.95 m</td></tr>
1401     * <tr><td>Semi-major axis length difference</td> <td>251 m</td></tr>
1402     * <tr><td>Flattening difference</td>             <td>1.41927E-05</td></tr>
1403     * </table></td><td>
1404     * <table class="ogc">
1405     * <caption>Test points</caption>
1406     * <tr><th>Source ordinates</th><th>Expected results</th></tr>
1407     * <tr align="right">
1408     *   <td>2°7'46.380"E<br>53°48'33.820"N<br>73.000 m</td>
1409     *   <td>2°7'51.477"E<br>53°48'36.563"N<br>28.091 m</td>
1410     * </tr></table></td></tr></table>
1411     *
1412     * @throws FactoryException if the math transform can not be created.
1413     * @throws TransformException if the example point can not be transformed.
1414     */
1415    @Test
1416    public void testAbridgedMolodensky() throws FactoryException, TransformException {
1417        tolerance = max(tolerance, 0.001 * (NAUTICAL_MILE/60));    // 0.001 angular second (about 3 cm at equator).
1418        description = "WGS84 to ED50";
1419        final SamplePoints sample = SamplePoints.forCRS(4230);
1420        createMathTransform(Transformation.class, sample);
1421        verifyTransform(sample.sourcePoints, sample.targetPoints);
1422        final Rectangle2D areaOfValidity = sample.areaOfValidity;
1423        verifyInDomain(new double[] {
1424            areaOfValidity.getMinX(),
1425            areaOfValidity.getMinY(),
1426            -1000
1427        }, new double[] {
1428            areaOfValidity.getMaxX(),
1429            areaOfValidity.getMaxY(),
1430            +1000
1431        }, new int[] {
1432            10, 10, 10
1433        }, new Random());
1434    }
1435
1436    /**
1437     * Asserts that a matrix of derivatives is equals to the expected ones within a positive delta.
1438     */
1439    @Override
1440    protected void assertMatrixEquals(final String message, final Matrix expected, final Matrix actual, final Matrix tolmat)
1441            throws DerivativeFailure
1442    {
1443        if (tolmat != null) {
1444            final int numRow = tolmat.getNumRow();
1445            final int numCol = tolmat.getNumCol();
1446            for (int j=0; j<numRow; j++) {
1447                for (int i=0; i<numCol; i++) {
1448                    tolmat.setElement(j, i, DERIVATIVE_TOLERANCE);
1449                }
1450            }
1451        }
1452        super.assertMatrixEquals(message, expected, actual, tolmat);
1453    }
1454}