001/*
002 *    GeoAPI - Java interfaces for OGC/ISO standards
003 *    http://www.geoapi.org
004 *
005 *    Copyright (C) 2008-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.Random;
035import java.util.Arrays;
036import java.awt.geom.Point2D;
037
038import org.opengis.util.Factory;
039import org.opengis.geometry.DirectPosition;
040import org.opengis.referencing.operation.Matrix;
041import org.opengis.referencing.operation.MathTransform;
042import org.opengis.referencing.operation.MathTransform1D;
043import org.opengis.referencing.operation.MathTransform2D;
044import org.opengis.referencing.operation.TransformException;
045import org.opengis.test.ToleranceModifiers;
046import org.opengis.test.ToleranceModifier;
047import org.opengis.test.CalculationType;
048import org.opengis.test.Configuration;
049
050import org.opengis.test.TestCase;
051
052import static java.lang.StrictMath.*;
053import static org.opengis.test.Assert.*;
054
055
056/**
057 * Base class for {@link MathTransform} implementation tests. Subclasses shall assign a value
058 * to the {@link #transform} field before to invoke any method in this class. The specified
059 * math transform shall support the following mandatory operations:
060 *
061 * <ul>
062 *   <li>{@link MathTransform#getSourceDimensions()}</li>
063 *   <li>{@link MathTransform#getTargetDimensions()}</li>
064 *   <li>{@link MathTransform#transform(DirectPosition, DirectPosition)}</li>
065 * </ul>
066 *
067 * All other operations are optional. However subclasses shall declare which methods, if any,
068 * are unsupported. By default every operations are assumed supported. Tests can be disabled
069 * on a case-by-case basis by setting the appropriate
070 * <code>is&lt;<var>Operation</var>&gt;Supported</code> fields to {@code false}.
071 *
072 * <p>After {@code TransformTestCase} has been setup, subclasses can invoke any of the {@code verify}
073 * methods in their JUnit test methods. Callers must supply the input coordinate points to be used
074 * for testing purpose, since the range of valid values is usually transform-dependent.</p>
075 *
076 * <p>Some general rules:</p>
077 * <ul>
078 *   <li>Coordinate values, or information used for computing coordinate values, are always
079 *       specified as arguments to the {@code verify} methods. Everything else are fields in
080 *       the {@code TransformTestCase} object.</li>
081 *   <li>The methods in this class do not {@linkplain org.opengis.test.Validators#validate(MathTransform)
082 *       validate} the transform. It is caller responsibility to validate the transform if wanted.</li>
083 *   <li>Unless otherwise indicated, every {@code verify} methods are independent. For example invoking
084 *       {@link #verifyConsistency(float[])} does not imply a call to {@link #verifyInverse(float[])}
085 *       or {@link #verifyDerivative(double[])}. The later methods must be invoked explicitly if wanted.</li>
086 * </ul>
087 *
088 * @author  Martin Desruisseaux (Geomatys)
089 * @version 3.1
090 * @since   2.2
091 */
092public strictfp abstract class TransformTestCase extends TestCase {
093    /**
094     * The maximal offset (in number of coordinate points), exclusive, to apply when testing
095     * {@code MathTransform.transform(…)} with overlapping arrays.  Higher values make the
096     * tests more extensive but slower. Small values like 8 are usually enough.
097     */
098    private static final int POINTS_OFFSET = 8;
099
100    /**
101     * The transform being tested. Subclasses should assign a value to this field,
102     * together with the {@link #tolerance} field, before any test is run.
103     *
104     * <p>All {@link ParameterizedTransformTest} test methods will set this field to a non-null value.
105     * Implementors can use this value for their own assertions after any test method has been run.</p>
106     *
107     * @see #tolerance
108     */
109    protected MathTransform transform;
110
111    /**
112     * {@code true} if {@link MathTransform#transform(double[],int,double[],int,int)}
113     * is supported. The default value is {@code true}. Vendor can set this value to
114     * {@code false} in order to test a transform which is not fully implemented.
115     *
116     * @see #isFloatToFloatSupported
117     * @see #isDoubleToFloatSupported
118     * @see #isFloatToDoubleSupported
119     * @see #verifyConsistency(float[])
120     */
121    protected boolean isDoubleToDoubleSupported;
122
123    /**
124     * {@code true} if {@link MathTransform#transform(float[],int,float[],int,int)}
125     * is supported. The default value is {@code true}. Vendor can set this value to
126     * {@code false} in order to test a transform which is not fully implemented.
127     *
128     * @see #isDoubleToDoubleSupported
129     * @see #isDoubleToFloatSupported
130     * @see #isFloatToDoubleSupported
131     * @see #verifyConsistency(float[])
132     */
133    protected boolean isFloatToFloatSupported;
134
135    /**
136     * {@code true} if {@link MathTransform#transform(double[],int,float[],int,int)}
137     * is supported. The default value is {@code true}. Vendor can set this value to
138     * {@code false} in order to test a transform which is not fully implemented.
139     *
140     * @see #isDoubleToDoubleSupported
141     * @see #isFloatToFloatSupported
142     * @see #isFloatToDoubleSupported
143     * @see #verifyConsistency(float[])
144     */
145    protected boolean isDoubleToFloatSupported;
146
147    /**
148     * {@code true} if {@link MathTransform#transform(float[],int,double[],int,int)}
149     * is supported. The default value is {@code true}. Vendor can set this value to
150     * {@code false} in order to test a transform which is not fully implemented.
151     *
152     * @see #isDoubleToDoubleSupported
153     * @see #isFloatToFloatSupported
154     * @see #isDoubleToFloatSupported
155     * @see #verifyConsistency(float[])
156     */
157    protected boolean isFloatToDoubleSupported;
158
159    /**
160     * {@code true} if the destination array can be the same than the source array,
161     * and the source and target region of the array can overlap. The default value
162     * is {@code true}. Vendor can set this value to {@code false} in order to test
163     * a transform which is not fully implemented.
164     *
165     * @see #verifyConsistency(float[])
166     */
167    protected boolean isOverlappingArraySupported;
168
169    /**
170     * {@code true} if {@link MathTransform#inverse()} is supported. The default value
171     * is {@code true}. Vendor can set this value to {@code false} in order to test a
172     * transform which is not fully implemented.
173     *
174     * @see #verifyTransform(double[], double[])
175     */
176    protected boolean isInverseTransformSupported;
177
178    /**
179     * {@code true} if {@link MathTransform#derivative(DirectPosition)} is supported. The
180     * default value is {@code true}. Vendor can set this value to {@code false} in order
181     * to test a transform which is not fully implemented.
182     *
183     * @see #derivativeDeltas
184     * @see #verifyDerivative(double[])
185     *
186     * @since 3.1
187     */
188    protected boolean isDerivativeSupported;
189
190    /**
191     * The deltas to use for approximating {@linkplain MathTransform#derivative(DirectPosition) math
192     * transform derivatives} by the <a href="http://en.wikipedia.org/wiki/Finite_difference">finite
193     * differences</a> method. Each value in this array is the delta to use for the corresponding
194     * dimension, in units of the source ordinates of the {@linkplain #transform} being tested.
195     * The array length is theoretically the {@linkplain MathTransform#getSourceDimensions() number
196     * of source dimensions}, but different lengths are accepted for developers convenience. If the
197     * array is smaller than the number of dimensions, then the last delta value will be reused for
198     * all remaining dimensions.
199     *
200     * <p>Testers shall provide a non-null value if the {@link #isDerivativeSupported} flag is set to
201     * {@code true}. Smaller delta would theoretically increase the finite difference precision.
202     * However in practice too small deltas <em>decrease</em> the precision, because of floating
203     * point errors when subtracting big numbers that are close in magnitude. In the particular
204     * case of map projections, experience suggests that a distance of 100 metres converted to
205     * decimal degrees is a good compromise. The conversion from metres to degrees can be done using
206     * the standard nautical mile length ({@value org.opengis.test.ToleranceModifiers#NAUTICAL_MILE}
207     * metres by minute of angle) as below:</p>
208     *
209     * <blockquote><pre>derivativeDeltas = new double[] {100.0 / (60 * 1852)};      // Approximately 100 metres.</pre></blockquote>
210     *
211     * @see #isDerivativeSupported
212     * @see #verifyDerivative(double[])
213     *
214     * @since 3.1
215     */
216    protected double[] derivativeDeltas;
217
218    /**
219     * Maximum difference to be accepted when comparing a transformed ordinate value with
220     * the expected value. By default this threshold is constant for all dimensions of all
221     * coordinates to be compared. If a subclass needs to adjust the tolerance threshold
222     * according the dimension or the coordinates values, then it should assign a value to
223     * the {@link #toleranceModifier} field in addition to this one.
224     *
225     * <p><b>Example:</b> the comparisons of geographic coordinates require increasing tolerance
226     * in longitude values as the latitude get closer to a pole. For such comparisons, this
227     * {@code tolerance} field shall be set to a threshold value in <em>metres</em> and the
228     * {@link #toleranceModifier} field shall be assigned the {@link ToleranceModifier#GEOGRAPHIC}
229     * value. See the {@code GEOGRAPHIC} modifier javadoc for more information.</p>
230     *
231     * <p>The default value is 0, which means that strict equality will be required. Subclasses
232     * should set a more suitable tolerance threshold when {@linkplain #transform} is assigned
233     * a value.</p>
234     *
235     * @see #transform
236     * @see #toleranceModifier
237     */
238    protected double tolerance;
239
240    /**
241     * Optional modification to the {@linkplain #tolerance} threshold before to compare a
242     * coordinate points. {@link ToleranceModifier} instance assigned to this field (if any)
243     * are {@linkplain #transform}-dependent. The modifications applied by a particular
244     * {@code ToleranceModifier} instance to the tolerance thresholds is position-dependent.
245     *
246     * <p>Common values assigned to this field are {@link ToleranceModifier#PROJECTION} and
247     * {@link ToleranceModifier#GEOGRAPHIC}.</p>
248     *
249     * @since 3.1
250     */
251    protected ToleranceModifier toleranceModifier;
252
253    /**
254     * Cached information for {@link #getToleranceModifier()} purpose. The {@code cachedModifier}
255     * field contains the value returned by {@code getToleranceModifier()}, cached because needed
256     * every time an {@code assertCoordinateEquals(…)} method is invoked (which typically occur
257     * often).  The {@link #modifierUsedByCache} and {@link #transformUsedByCache} fields contain
258     * the values of {@link #toleranceModifier} and {@link #transform} respectively at the time
259     * the {@code cachedModifier} value has been computed. They are used in order to detect when
260     * {@code cachedModifier} needs to be recalculated.
261     *
262     * <p>Note that the above checks will not detect the case where the user invoke
263     * {@link org.opengis.test.TestSuite#clear()}. We presume that typical users
264     * will not invoke that method in the middle of a test (it is okay if that
265     * method is invoked between two tests however).</p>
266     *
267     * @see #getToleranceModifier()
268     */
269    private transient ToleranceModifier cachedModifier, modifierUsedByCache;
270
271    /**
272     * Cached information for {@link #getToleranceModifier()} purpose.
273     * See the {@link #cachedModifier} javadoc for more information.
274     *
275     * @see #getToleranceModifier()
276     */
277    private transient MathTransform transformUsedByCache;
278
279    /**
280     * {@code true} if the {@link #getToleranceModifier()} method found at least one
281     * {@link ToleranceModifier} registered on the classpath. We presume that the
282     * implementor specified such tolerance modifier in order to relax the tolerance
283     * threshold.
284     *
285     * @since 3.1
286     */
287    private boolean isToleranceRelaxed;
288
289    /**
290     * Creates a new test without factory. This constructor is provided for subclasses
291     * that instantiate their {@link MathTransform} directly, without using any factory.
292     */
293    protected TransformTestCase() {
294        setEnabledFlags(getEnabledFlags(getEnabledKeys(0)));
295    }
296
297    /**
298     * Creates a new test without factory and with the given {@code isFooSupported} flags.
299     * The given array must be the result of a call to {@link #getEnabledKeys(int)}.
300     */
301    TransformTestCase(final boolean[] isEnabled) {
302        setEnabledFlags(isEnabled);
303    }
304
305    /**
306     * Creates a test case initialized to default values. The {@linkplain #transform}
307     * is initially null, the {@linkplain #tolerance} threshold is initially zero and
308     * all <code>is&lt;</code><var>Operation</var><code>&gt;Supported</code> are set
309     * to {@code true} unless at least one {@link org.opengis.test.ImplementationDetails}
310     * object disabled some tests.
311     *
312     * @param factories  the factories to be used by the test. Those factories will be given to
313     *        {@link org.opengis.test.ImplementationDetails#configuration(Factory[])} in order
314     *        to decide which tests should be enabled.
315     */
316    @SuppressWarnings("unchecked")
317    protected TransformTestCase(final Factory... factories) {
318        super(factories);
319        setEnabledFlags(getEnabledFlags(getEnabledKeys(0)));
320    }
321
322    /**
323     * Sets all {@code isFooSupported} fields to the values in the given array.
324     * The given array must be the result of a call to {@link #getEnabledKeys(int)}.
325     *
326     * <p>This work is usually performed right into the constructor. However in the particular case
327     * of {@code TransformTestCase}, we allow the configuration to be supplied externally because
328     * {@link AuthorityFactoryTest} will use this class internally with a set of flags determined
329     * from a different set of factories than the factories given to the constructor of this class.</p>
330     */
331    private void setEnabledFlags(final boolean[] isEnabled) {
332        isDoubleToDoubleSupported   = isEnabled[0];
333        isFloatToFloatSupported     = isEnabled[1];
334        isDoubleToFloatSupported    = isEnabled[2];
335        isFloatToDoubleSupported    = isEnabled[3];
336        isOverlappingArraySupported = isEnabled[4];
337        isInverseTransformSupported = isEnabled[5];
338        isDerivativeSupported       = isEnabled[6];
339    }
340
341    /**
342     * Returns the keys to gives to the {@link #setEnabledFlags(boolean[])} method. The
343     * elements in the returned array <strong>must</strong> be in the order expected by
344     * the {@link #setEnabledFlags(boolean[])} method for setting the fields.
345     */
346    static Configuration.Key<Boolean>[] getEnabledKeys(final int extraSpace) {
347        @SuppressWarnings({"unchecked","rawtypes"})
348        final Configuration.Key<Boolean>[] keys = new Configuration.Key[7 + extraSpace];
349        keys[0] = Configuration.Key.isDoubleToDoubleSupported;
350        keys[1] = Configuration.Key.isFloatToFloatSupported;
351        keys[2] = Configuration.Key.isDoubleToFloatSupported;
352        keys[3] = Configuration.Key.isFloatToDoubleSupported;
353        keys[4] = Configuration.Key.isOverlappingArraySupported;
354        keys[5] = Configuration.Key.isInverseTransformSupported;
355        keys[6] = Configuration.Key.isDerivativeSupported;
356        return keys;
357    }
358
359    /**
360     * Returns information about the configuration of the test which has been run.
361     * This method returns a map containing:
362     *
363     * <ul>
364     *   <li>All the following keys defined in the {@link org.opengis.test.Configuration.Key} enumeration,
365     *       associated to the value {@link Boolean#TRUE} or {@link Boolean#FALSE}:
366     *     <ul>
367     *       <li>{@link #isDoubleToDoubleSupported}</li>
368     *       <li>{@link #isFloatToFloatSupported}</li>
369     *       <li>{@link #isDoubleToFloatSupported}</li>
370     *       <li>{@link #isFloatToDoubleSupported}</li>
371     *       <li>{@link #isOverlappingArraySupported}</li>
372     *       <li>{@link #isInverseTransformSupported}</li>
373     *       <li>{@link #isDerivativeSupported}</li>
374     *     </ul>
375     *   </li>
376     *   <li>The {@code "isToleranceRelaxed"} key associated to the value {@link Boolean#TRUE}
377     *       if the {@link ToleranceModifiers#getImplementationSpecific(MathTransform)} method
378     *       found at least one {@link ToleranceModifier} on the classpath, or
379     *       {@link Boolean#FALSE} otherwise.</li>
380     * </ul>
381     *
382     * @return {@inheritDoc}
383     *
384     * @since 3.1
385     */
386    @Override
387    public Configuration configuration() {
388        final Configuration op = super.configuration();
389        assertNull(op.put(Configuration.Key.isDoubleToDoubleSupported,   isDoubleToDoubleSupported));
390        assertNull(op.put(Configuration.Key.isFloatToFloatSupported,     isFloatToFloatSupported));
391        assertNull(op.put(Configuration.Key.isDoubleToFloatSupported,    isDoubleToFloatSupported));
392        assertNull(op.put(Configuration.Key.isFloatToDoubleSupported,    isFloatToDoubleSupported));
393        assertNull(op.put(Configuration.Key.isOverlappingArraySupported, isOverlappingArraySupported));
394        assertNull(op.put(Configuration.Key.isInverseTransformSupported, isInverseTransformSupported));
395        assertNull(op.put(Configuration.Key.isDerivativeSupported,       isDerivativeSupported));
396        assertNull(op.put(Configuration.Key.isToleranceRelaxed,          isToleranceRelaxed));
397        return op;
398    }
399
400    /**
401     * Returns the tolerance threshold for comparing the given ordinate value. The default
402     * implementation returns the {@link #tolerance} value directly, thus implementing an
403     * absolute tolerance threshold. If a subclass needs a relative tolerance threshold
404     * instead, it can override this method as below:
405     *
406     * <blockquote><code>
407     * return tolerance * Math.abs(ordinate);
408     * </code></blockquote>
409     *
410     * @param  ordinate  the ordinate value being compared.
411     * @return the absolute tolerance threshold to use for comparing the given ordinate.
412     *
413     * @deprecated Replaced by {@link #toleranceModifier}.
414     */
415    @Deprecated
416    protected double tolerance(final double ordinate) {
417        return tolerance;
418    }
419
420    /**
421     * Ensures that all <code>is&lt;</code><var>Operation</var><code>&gt;Supported</code> fields
422     * are set to {@code true}. This method can be invoked before testing a math transform which
423     * is expected to be fully implemented.
424     *
425     * @deprecated No replacement.
426     */
427    @Deprecated
428    protected void assertAllTestsEnabled() {
429        assertTrue("isDoubleToDoubleSupported",   isDoubleToDoubleSupported  );
430        assertTrue("isFloatToFloatSupported",     isFloatToFloatSupported    );
431        assertTrue("isDoubleToFloatSupported",    isDoubleToFloatSupported   );
432        assertTrue("isFloatToDoubleSupported",    isFloatToDoubleSupported   );
433        assertTrue("isOverlappingArraySupported", isOverlappingArraySupported);
434        assertTrue("isInverseTransformSupported", isInverseTransformSupported);
435        assertTrue("isDerivativeSupported",       isDerivativeSupported      );
436    }
437
438    /**
439     * Transforms the given coordinates and verifies that the result is equals (within a positive
440     * delta) to the expected ones. If the difference between an expected and actual ordinate value
441     * is greater than the {@linkplain #tolerance tolerance} threshold (after optional
442     * {@linkplain #toleranceModifier tolerance modification}), then the assertion fails.
443     *
444     * <p>If {@link #isInverseTransformSupported} is {@code true}, then this method will also
445     * transform the expected coordinate points using the {@linkplain MathTransform#inverse()
446     * inverse transform} and compare with the source coordinates.</p>
447     *
448     * @param  coordinates  the coordinate points to transform.
449     * @param  expected     the expect result of the transformation, or
450     *         {@code null} if {@code coordinates} is expected to be null.
451     * @throws TransformException if the transformation failed.
452     *
453     * @see #isInverseTransformSupported
454     */
455    protected void verifyTransform(final double[] coordinates, final double[] expected)
456            throws TransformException
457    {
458        /*
459         * Checks the state of this TransformTestCase object, including a shallow inspection of
460         * the MathTransform. We check only the parts that are significant to this test method.
461         * Full MathTransform validation is not the job of this method.
462         */
463        final MathTransform transform = this.transform;             // Protect from changes.
464        assertNotNull("TransformTestCase.transform shall be assigned a value.", transform);
465        final int sourceDimension = transform.getSourceDimensions();
466        final int targetDimension = transform.getTargetDimensions();
467        assertStrictlyPositive("Source dimension shall be positive.", sourceDimension);
468        assertStrictlyPositive("Target dimension shall be positive.", targetDimension);
469        final MathTransform inverse;
470        if (isInverseTransformSupported) {
471            final Configuration.Key<Boolean> oldTip = configurationTip;
472            configurationTip = Configuration.Key.isInverseTransformSupported;
473            inverse = transform.inverse();
474            assertNotNull("MathTransform.inverse() shall not return null.", inverse);
475            assertEquals("Inconsistent source dimension of the inverse transform.",
476                    targetDimension, inverse.getSourceDimensions());
477            assertEquals("Inconsistent target dimension of the inverse transform.",
478                    sourceDimension, inverse.getTargetDimensions());
479            configurationTip = oldTip;
480        } else {
481            inverse = null;
482        }
483        /*
484         * Checks the method arguments.
485         */
486        if (expected == null) {
487            assertNull(coordinates);
488            return;
489        }
490        assertNotNull(coordinates);
491        assertEquals("Source dimension is not a divisor of the coordinates array length.",
492                0, coordinates.length % sourceDimension);
493        assertEquals("Target dimension is not a divisor of the expected array length.",
494                0, expected.length % targetDimension);
495        final int numPts = expected.length / targetDimension;
496        assertEquals("Mismatched number of points.", numPts, coordinates.length / sourceDimension);
497        /*
498         * Now performs the test.
499         */
500        final SimpleDirectPosition source = new SimpleDirectPosition(sourceDimension);
501        final SimpleDirectPosition target = new SimpleDirectPosition(targetDimension);
502        final SimpleDirectPosition back   = new SimpleDirectPosition(sourceDimension);
503        for (int i=0; i<numPts; i++) {
504            final int sourceOffset = i * sourceDimension;
505            final int targetOffset = i * targetDimension;
506            System.arraycopy(coordinates, sourceOffset, source.ordinates, 0, sourceDimension);
507            assertSame("MathTransform.transform(DirectPosition, …) shall use the given target.",
508                    target, transform.transform(source, target));
509            assertCoordinatesEqual("Unexpected transform result.", targetDimension,
510                    expected, targetOffset, target.ordinates, 0, 1, CalculationType.DIRECT_TRANSFORM, i);
511            assertCoordinatesEqual("Source coordinate has been modified.", sourceDimension,
512                    coordinates, sourceOffset, source.ordinates, 0, 1, CalculationType.IDENTITY, i);
513            /*
514             * Tests the inverse transform, if supported. We could use the 'target' point directly,
515             * which contain the result of the transform performed by the application under testing.
516             * Nevertheless we overwrite the 'target' point with the 'expected' coordinate provided
517             * in argument to this method. It is not necessarily more accurate since the expected
518             * coordinates are often provided with limited precision. However this allow more
519             * consistent behavior.
520             */
521            if (inverse != null) {
522                System.arraycopy(expected, targetOffset, target.ordinates, 0, targetDimension);
523                assertSame("MathTransform.transform(DirectPosition, …) shall use the given target.",
524                        back, inverse.transform(target, back));
525                assertCoordinateEquals("Unexpected result of inverse transform.",
526                        source.ordinates, back.ordinates, i, CalculationType.INVERSE_TRANSFORM);
527                assertCoordinatesEqual("Source coordinate has been modified.", targetDimension,
528                        expected, targetOffset, target.ordinates, 0, 1, CalculationType.IDENTITY, i);
529            }
530        }
531    }
532
533    /**
534     * Transforms the given coordinates, applies the inverse transform and compares with the
535     * original values. If a difference between the expected and actual ordinate values is
536     * greater than the {@linkplain #tolerance tolerance} threshold (after optional
537     * {@linkplain #toleranceModifier tolerance modification}), then the assertion fails.
538     *
539     * <p>At the difference of {@link #verifyTransform(double[],double[])}, this method does
540     * not require an array of expected values. The expected values are calculated from
541     * the transform itself.</p>
542     *
543     * @param  coordinates  the source coordinates to transform.
544     * @throws TransformException if at least one coordinate can't be transformed.
545     */
546    protected void verifyInverse(final double... coordinates) throws TransformException {
547        assertTrue("isInverseTransformSupported == false.", isInverseTransformSupported);
548        /*
549         * Checks the state of this TransformTestCase object, including a shallow inspection of
550         * the MathTransform. We check only the parts that are significant to this test method.
551         * Full MathTransform validation is not the job of this method.
552         */
553        final MathTransform transform = this.transform;                 // Protect from changes.
554        assertNotNull("TransformTestCase.transform shall be assigned a value.", transform);
555        final int sourceDimension = transform.getSourceDimensions();
556        final int targetDimension = transform.getTargetDimensions();
557        assertStrictlyPositive("Source dimension shall be positive.", sourceDimension);
558        assertStrictlyPositive("Target dimension shall be positive.", targetDimension);
559        final MathTransform inverse = transform.inverse();
560        assertNotNull("MathTransform.inverse() shall not return null.", inverse);
561        assertEquals("Inconsistent source dimension of the inverse transform.",
562                targetDimension, inverse.getSourceDimensions());
563        assertEquals("Inconsistent target dimension of the inverse transform.",
564                sourceDimension, inverse.getTargetDimensions());
565        /*
566         * Checks the method arguments.
567         */
568        assertNotNull("Coordinates array expected in argument.", coordinates);
569        assertEquals("Source dimension is not a divisor of the coordinates array length.",
570                0, coordinates.length % sourceDimension);
571        final int numPts = coordinates.length / sourceDimension;
572        /*
573         * Now performs the test.
574         */
575        final SimpleDirectPosition source = new SimpleDirectPosition(sourceDimension);
576        final SimpleDirectPosition back   = new SimpleDirectPosition(sourceDimension);
577        DirectPosition target = null;
578        for (int i=0; i<numPts; i++) {
579            final int offset = i*sourceDimension;
580            System.arraycopy(coordinates, offset, source.ordinates, 0, sourceDimension);
581            target = transform.transform(source, target);
582            assertNotNull("MathTransform.transform(DirectPosition, …) shall not return null.", target);
583            assertEquals("Transformed point has wrong dimension.",
584                    targetDimension, target.getDimension());
585            assertSame("MathTransform.transform(DirectPosition, …) shall use the given target.",
586                    back, inverse.transform(target, back));
587            assertCoordinateEquals("Unexpected result of inverse transform.",
588                    source.ordinates, back.ordinates, i, CalculationType.INVERSE_TRANSFORM);
589            assertCoordinatesEqual("Source coordinate has been modified.", sourceDimension,
590                    coordinates, offset, source.ordinates, 0, 1, CalculationType.IDENTITY, i);
591        }
592    }
593
594    /**
595     * Transforms the given coordinates, applies the inverse transform and compares with the
596     * original values. If a difference between the expected and actual ordinate values is
597     * greater than the {@linkplain #tolerance tolerance} threshold (after optional
598     * {@linkplain #toleranceModifier tolerance modification}), then the assertion fails.
599     *
600     * <p>The default implementation delegates to {@link #verifyInverse(double[])}.</p>
601     *
602     * @param  coordinates  the source coordinates to transform.
603     * @throws TransformException if at least one coordinate can't be transformed.
604     */
605    protected void verifyInverse(final float... coordinates) throws TransformException {
606        assertTrue("isInverseTransformSupported == false.", isInverseTransformSupported);
607        final double[] sourceDoubles = new double[coordinates.length];
608        for (int i=0; i<coordinates.length; i++) {
609            sourceDoubles[i] = coordinates[i];
610        }
611        verifyInverse(sourceDoubles);
612        final int dimension = transform.getSourceDimensions();
613        assertCoordinatesEqual("Unexpected change in source coordinates.", dimension,
614                coordinates, 0, sourceDoubles, 0, coordinates.length / dimension, CalculationType.IDENTITY);
615    }
616
617    /**
618     * Transforms coordinates using various versions of {@code MathTransform.transform(…)}
619     * and verifies that they produce the same numerical values. The values calculated by
620     * {@link MathTransform#transform(DirectPosition,DirectPosition)} are used as the reference.
621     * Other transform methods (operating on arrays) will be compared against that reference,
622     * unless their checks were disabled (see class javadoc for details).
623     *
624     * <p>This method expects an array of {@code float} values instead than {@code double}
625     * for making sure that the {@code MathTransform.transform(float[], …)} and
626     * {@code MathTransform.transform(double[], …)} methods produces the same numerical values.
627     * The {@code double} values may show extra digits when formatted in base 10, but this is not
628     * significant if their IEEE 754 representation (which use base 2) are equivalent.</p>
629     *
630     * <p>This method does not verify the inverse transform or the derivatives. If desired,
631     * those later methods can be verified with the {@link #verifyInverse(float[])} and
632     * {@link #verifyDerivative(double[])} methods respectively.</p>
633     *
634     * @param  sourceFloats  the source coordinates to transform as an array of {@code float} values.
635     * @return the transformed coordinates, returned for convenience.
636     * @throws TransformException if at least one coordinate can't be transformed.
637     *
638     * @see #isDoubleToDoubleSupported
639     * @see #isFloatToFloatSupported
640     * @see #isDoubleToFloatSupported
641     * @see #isFloatToDoubleSupported
642     * @see #isOverlappingArraySupported
643     */
644    protected float[] verifyConsistency(final float... sourceFloats) throws TransformException {
645        final MathTransform transform = this.transform;                 // Protect from changes.
646        assertNotNull("TransformTestCase.transform shall be assigned a value.", transform);
647        final int sourceDimension = transform.getSourceDimensions();
648        final int targetDimension = transform.getTargetDimensions();
649        assertEquals("Source dimension is not a divisor of the coordinates array length.",
650                0, sourceFloats.length % sourceDimension);
651        final int numPts = sourceFloats.length / sourceDimension;
652        final float [] targetFloats    = new float [max(sourceDimension, targetDimension) * (numPts + POINTS_OFFSET)];
653        final float [] expectedFloats  = new float [targetDimension * numPts];
654        final double[] sourceDoubles   = new double[sourceFloats.length];
655        final double[] targetDoubles   = new double[targetFloats.length];
656        final double[] expectedDoubles = new double[expectedFloats.length];
657        /*
658         * Copies the source ordinates (to be used later) and performs the transformations using
659         * MathTransform.transform(DirectPosition) method. Result is stored in the "transformed"
660         * array and will not be modified anymore from that point.
661         */
662        for (int i=0; i<sourceDoubles.length; i++) {
663            sourceDoubles[i] = sourceFloats[i];
664        }
665        if (true) { // MathTransform.transform(DirectPosition) is not optional in this test.
666            final SimpleDirectPosition sourcePosition = new SimpleDirectPosition(sourceDimension);
667            DirectPosition targetPosition = null;
668            int targetOffset = 0;
669            for (int i=0; i < sourceDoubles.length; i += sourceDimension) {
670                System.arraycopy(sourceDoubles, i, sourcePosition.ordinates, 0, sourceDimension);
671                targetPosition = transform.transform(sourcePosition, targetPosition);
672                assertNotNull("MathTransform.transform(DirectPosition, …) shall not return null.", targetPosition);
673                assertNotSame("MathTransform.transform(DirectPosition, …) shall not overwrite " +
674                        "the source position.", sourcePosition, targetPosition);
675                assertEquals("MathTransform.transform(DirectPosition) must return a position having " +
676                        "the same dimension than MathTransform.getTargetDimension().",
677                        targetDimension, targetPosition.getDimension());
678                for (int j=0; j<targetDimension; j++) {
679                    expectedFloats[targetOffset] = (float) (expectedDoubles[targetOffset] = targetPosition.getOrdinate(j));
680                    targetOffset++;
681                }
682            }
683            assertEquals(expectedFloats.length, targetOffset);
684        }
685        /*
686         * Tests transformation in distincts (non-overlapping) arrays.
687         */
688        final Configuration.Key<Boolean> oldTip = configurationTip;
689        if (isDoubleToDoubleSupported) {
690            configurationTip = Configuration.Key.isDoubleToDoubleSupported;
691            Arrays.fill(targetDoubles, Double.NaN);
692            transform.transform(sourceDoubles, 0, targetDoubles, 0, numPts);
693            assertCoordinatesEqual("MathTransform.transform(double[],0,double[],0,n) modified a source coordinate.",
694                    sourceDimension, sourceFloats, 0, sourceDoubles, 0, numPts, CalculationType.IDENTITY);
695            assertCoordinatesEqual("MathTransform.transform(double[],0,double[],0,n) error.",
696                    targetDimension, expectedDoubles, 0, targetDoubles, 0, numPts, CalculationType.DIRECT_TRANSFORM);
697        }
698        if (isFloatToFloatSupported) {
699            configurationTip = Configuration.Key.isFloatToFloatSupported;
700            Arrays.fill(targetFloats, Float.NaN);
701            transform.transform(sourceFloats, 0, targetFloats, 0, numPts);
702            assertCoordinatesEqual("MathTransform.transform(float[],0,float[],0,n) modified a source coordinate.",
703                    sourceDimension, sourceDoubles, 0, sourceFloats, 0, numPts, CalculationType.IDENTITY);
704            assertCoordinatesEqual("MathTransform.transform(float[],0,float[],0,n) error.",
705                    targetDimension, expectedFloats, 0, targetFloats, 0, numPts, CalculationType.DIRECT_TRANSFORM);
706        }
707        if (isDoubleToFloatSupported) {
708            configurationTip = Configuration.Key.isDoubleToFloatSupported;
709            Arrays.fill(targetFloats, Float.NaN);
710            transform.transform(sourceDoubles, 0, targetFloats, 0, numPts);
711            assertCoordinatesEqual("MathTransform.transform(double[],0,float[],0,n) modified a source coordinate.",
712                    sourceDimension, sourceFloats, 0, sourceDoubles, 0, numPts, CalculationType.IDENTITY);
713            assertCoordinatesEqual("MathTransform.transform(double[],0,float[],0,n) error.",
714                    targetDimension, expectedFloats, 0, targetFloats, 0, numPts, CalculationType.DIRECT_TRANSFORM);
715        }
716        if (isFloatToDoubleSupported) {
717            configurationTip = Configuration.Key.isFloatToDoubleSupported;
718            Arrays.fill(targetDoubles, Double.NaN);
719            transform.transform(sourceFloats, 0, targetDoubles, 0, numPts);
720            assertCoordinatesEqual("MathTransform.transform(float[],0,double[],0,n) modified a source coordinate.",
721                    sourceDimension, sourceDoubles, 0, sourceFloats, 0, numPts, CalculationType.IDENTITY);
722            assertCoordinatesEqual("MathTransform.transform(float[],0,double[],0,n) error.",
723                    targetDimension, expectedDoubles, 0, targetDoubles, 0, numPts, CalculationType.DIRECT_TRANSFORM);
724        }
725        /*
726         * Tests transformation in overlapping arrays.
727         */
728        if (isOverlappingArraySupported) {
729            configurationTip = Configuration.Key.isOverlappingArraySupported;
730            for (int sourceOffset=0; sourceOffset < POINTS_OFFSET*sourceDimension; sourceOffset += sourceDimension) {
731                for (int targetOffset=0; targetOffset < POINTS_OFFSET*targetDimension; targetOffset += targetDimension) {
732                    System.arraycopy(sourceFloats,  0, targetFloats,  sourceOffset, sourceFloats .length);
733                    System.arraycopy(sourceDoubles, 0, targetDoubles, sourceOffset, sourceDoubles.length);
734                    transform.transform(targetFloats,  sourceOffset, targetFloats,  targetOffset, numPts);
735                    transform.transform(targetDoubles, sourceOffset, targetDoubles, targetOffset, numPts);
736                    assertCoordinatesEqual("MathTransform.transform(float[],0,float[],0,n) error.",
737                            targetDimension, expectedFloats, 0, targetFloats, targetOffset, numPts, CalculationType.DIRECT_TRANSFORM);
738                    assertCoordinatesEqual("MathTransform.transform(double[],0,double[],0,n) error.",
739                            targetDimension, expectedFloats, 0, targetDoubles, targetOffset, numPts, CalculationType.DIRECT_TRANSFORM);
740                }
741            }
742        }
743        configurationTip = oldTip;
744        /*
745         * Tests MathTransform1D methods.
746         */
747        if (transform instanceof MathTransform1D) {
748            assertEquals("MathTransform1D.getSourceDimension()", 1, sourceDimension);
749            assertEquals("MathTransform1D.getTargetDimension()", 1, targetDimension);
750            final MathTransform1D transform1D = (MathTransform1D) transform;
751            for (int i=0; i<sourceFloats.length; i++) {
752                targetDoubles[i] = transform1D.transform(sourceFloats[i]);
753            }
754            assertCoordinatesEqual("MathTransform1D.transform(double) error.",
755                    1, expectedDoubles, 0, targetDoubles, 0, numPts, CalculationType.DIRECT_TRANSFORM);
756        }
757        /*
758         * Tests MathTransform2D methods.
759         */
760        if (transform instanceof MathTransform2D) {
761            assertEquals("MathTransform2D.getSourceDimension()", 2, sourceDimension);
762            assertEquals("MathTransform2D.getTargetDimension()", 2, targetDimension);
763            final MathTransform2D transform2D = (MathTransform2D) transform;
764            final Point2D.Float  source = new Point2D.Float();
765            final Point2D.Double target = new Point2D.Double();
766            for (int i=0; i<sourceFloats.length;) {
767                source.x = sourceFloats[i];
768                source.y = sourceFloats[i+1];
769                assertSame("MathTransform2D.transform(Point2D, …) shall use the given target.",
770                        target, transform2D.transform(source, target));
771                assertNotNull("MathTransform2D.transform(Point2D, …) shall not return null.", target);
772                targetDoubles[i++] = target.x;
773                targetDoubles[i++] = target.y;
774            }
775            assertCoordinatesEqual("MathTransform2D.transform(Point2D,Point2D) error.",
776                    2, expectedDoubles, 0, targetDoubles, 0, numPts, CalculationType.DIRECT_TRANSFORM);
777        }
778        return expectedFloats;
779    }
780
781    /**
782     * Computes the {@linkplain MathTransform#derivative(DirectPosition) derivative at the given point}
783     * and compares the result with the <a href="http://en.wikipedia.org/wiki/Finite_difference">finite
784     * differences</a> approximation.
785     *
786     * <p>All the three common forms of finite differences (<cite>forward difference</cite>,
787     * <cite>backward difference</cite> and <cite>central difference</cite>) are computed.
788     * If the finite difference method was a "perfect" approximation, all those three forms
789     * would produce identical results. In practice the results will differ, especially in
790     * areas where the derivative function varies fast. The difference between the results
791     * will be used as an estimation of the approximation accuracy.</p>
792     *
793     * <p>The distance between the two points used by the <cite>central difference</cite>
794     * approximation shall be specified in the {@link #derivativeDeltas} array, in units
795     * of the source CRS. If the length of the {@code derivativeDeltas} array is smaller
796     * than the number of source dimensions, then the last delta value is used for all
797     * additional dimensions. This allows specifying a single delta value (in an array
798     * of length 1) for all dimensions.</p>
799     *
800     * <p>This method created the following objects:</p>
801     * <ul>
802     *   <li>{@code expected} - the expected derivative result estimated by the central
803     *       difference method.</li>
804     *   <li>{@code tolmat} - a <cite>tolerance matrix</cite> containing, for each matrix element,
805     *       the largest difference found between the three approximation methods. The values in
806     *       this matrix will not be lower than the {@linkplain #toleranceModifier modified}
807     *       {@linkplain #tolerance} threshold.</li>
808     * </ul>
809     *
810     * Those information are then passed to the {@link #assertMatrixEquals(String, Matrix, Matrix,
811     * Matrix)} method. Implementors can override the later method, for example in order to overwrite
812     * the tolerance values.
813     *
814     * @param  coordinate  the point where to compute the derivative, in units of the source CRS.
815     * @throws TransformException if the derivative can not be computed, or a point can not be transformed.
816     *
817     * @see MathTransform#derivative(DirectPosition)
818     * @see #assertMatrixEquals(String, Matrix, Matrix, Matrix)
819     *
820     * @since 3.1
821     */
822    protected void verifyDerivative(final double... coordinate) throws TransformException {
823        assertTrue("isDerivativeSupported == false.", isDerivativeSupported);
824        final MathTransform   transform = this.transform;                               // Protect from changes.
825        final double[] derivativeDeltas = this.derivativeDeltas;                        // Protect from changes.
826        assertNotNull("TransformTestCase.transform shall be assigned a value.", transform);
827        assertNotNull("TransformTestCase.derivativeDeltas shall be assigned a value.", derivativeDeltas);
828        assertTrue   ("TransformTestCase.derivativeDeltas shall not be empty.", derivativeDeltas.length != 0);
829        assertEquals ("Coordinate dimension shall be equal to the transform source dimension.",
830                transform.getSourceDimensions(), coordinate.length);
831        /*
832         * Invoke the MathTransform.derivative(DirectPosition) method to test
833         * and validate the result.
834         */
835        final int sourceDim = transform.getSourceDimensions();
836        final int targetDim = transform.getTargetDimensions();
837        final SimpleDirectPosition S0 = new SimpleDirectPosition(sourceDim);
838        final SimpleDirectPosition T0 = new SimpleDirectPosition(targetDim);
839        S0.setCoordinate(coordinate);
840        S0.unmodifiable = true;
841        assertSame(T0, transform.transform(S0, T0));
842        T0.unmodifiable = true;
843        final Matrix matrix = transform.derivative(S0);
844        final String message = "MathTransform.derivative(" + S0 + ')';
845        assertNotNull(message, matrix);
846        assertEquals("Unexpected number of columns.", sourceDim, matrix.getNumCol());
847        assertEquals("Unexpected number of rows.",    targetDim, matrix.getNumRow());
848        /*
849         * Get the user-specified tolerances.
850         */
851        final double[] tolerances = new double[targetDim];
852        Arrays.fill(tolerances, tolerance);
853        final ToleranceModifier modifier = getToleranceModifier();
854        if (modifier != null) {
855            modifier.adjust(tolerances, T0, CalculationType.TRANSFORM_DERIVATIVE);
856        }
857        /*
858         * Compute an approximation of the expected derivative.
859         */
860        final Matrix approx = new SimpleMatrix(targetDim, sourceDim, new double[sourceDim * targetDim]);
861        final Matrix tolmat = new SimpleMatrix(targetDim, sourceDim, new double[sourceDim * targetDim]);
862        final SimpleDirectPosition S1 = new SimpleDirectPosition(sourceDim);
863        final SimpleDirectPosition S2 = new SimpleDirectPosition(sourceDim);
864        final SimpleDirectPosition T1 = new SimpleDirectPosition(targetDim);
865        final SimpleDirectPosition T2 = new SimpleDirectPosition(targetDim);
866        for (int i=0; i<sourceDim; i++) {
867            S1.setCoordinate(coordinate);
868            S2.setCoordinate(coordinate);
869            final double ordinate = coordinate[i];
870            final double delta = derivativeDeltas[min(i, derivativeDeltas.length-1)];
871            S1.setOrdinate(i, ordinate - delta/2);
872            S2.setOrdinate(i, ordinate + delta/2);
873            assertSame(T1, transform.transform(S1, T1));
874            assertSame(T2, transform.transform(S2, T2));
875            for (int j=0; j<targetDim; j++) {
876                final double dc = (T2.getOrdinate(j) - T1.getOrdinate(j)) /  delta;         // Central difference
877                final double df = (T2.getOrdinate(j) - T0.getOrdinate(j)) / (delta/2);      // Forward difference
878                final double db = (T0.getOrdinate(j) - T1.getOrdinate(j)) / (delta/2);      // Backward difference
879                approx.setElement(j, i, dc);
880                tolmat.setElement(j, i, max(tolerances[j], max(abs(df - db), max(abs(dc - db), abs(dc - df)))));
881            }
882        }
883        /*
884         * Now compare the matrix elements. If the transform implements also
885         * the MathTransform1D or MathTransform2D interface, check consistency.
886         */
887        assertMatrixEquals(message, approx, matrix, tolmat);
888        if (transform instanceof MathTransform1D) {
889            assertEquals("MathTransform1D.getSourceDimensions()", 1, sourceDim);
890            assertEquals("MathTransform1D.getTargetDimensions()", 1, targetDim);
891            assertMatrixEquals("MathTransform1D.derivative(double) error.", matrix,
892                    new SimpleMatrix(1, 1, ((MathTransform1D) transform).derivative(coordinate[0])), tolmat);
893        }
894        if (transform instanceof MathTransform2D) {
895            assertEquals("MathTransform2D.getSourceDimensions()", 2, sourceDim);
896            assertEquals("MathTransform2D.getTargetDimensions()", 2, targetDim);
897            assertMatrixEquals("MathTransform2D.derivative(Point2D) error.", matrix,
898                    ((MathTransform2D) transform).derivative(new Point2D.Double(coordinate[0], coordinate[1])), tolmat);
899        }
900    }
901
902    /**
903     * Verifies all supported transform operations in the given domain. First, this method creates
904     * a grid of regularly spaced points along all source dimensions in the given envelope.
905     * Next, if the given random number generator is non-null, then this method adds small
906     * random displacements to every points and shuffle the coordinates in random order.
907     * Finally this method delegates the resulting array of coordinates to the following
908     * methods:
909     *
910     * <ul>
911     *   <li>{@link #verifyConsistency(float[])}</li>
912     *   <li>{@link #verifyInverse(float[])}</li>
913     *   <li>{@link #verifyDerivative(double[])}</li>
914     * </ul>
915     *
916     * The generated coordinates array is returned in case callers want to perform more tests
917     * in addition to the above-cited verifications.
918     *
919     * @param  minOrdinates     the minimal ordinate values of the domain where to test the transform.
920     * @param  maxOrdinates     the maximal ordinate values of the domain where to test the transform.
921     * @param  numOrdinates     the number of points along each dimension.
922     * @param  randomGenerator  an optional random number generator, or {@code null} for testing using a regular grid.
923     * @return the generated random coordinates inside the given domain of validity.
924     * @throws TransformException if a transform or a derivative can not be computed.
925     *
926     * @since 3.1
927     */
928    protected float[] verifyInDomain(final double[] minOrdinates, final double[] maxOrdinates,
929            final int[] numOrdinates, final Random randomGenerator) throws TransformException
930    {
931        final MathTransform transform = this.transform;             // Protect from changes.
932        assertNotNull("TransformTestCase.transform shall be assigned a value.", transform);
933        final int dimension = transform.getSourceDimensions();
934        assertEquals("The minOrdinates array doesn't have the expected length.", dimension, minOrdinates.length);
935        assertEquals("The maxOrdinates array doesn't have the expected length.", dimension, maxOrdinates.length);
936        assertEquals("The numOrdinates array doesn't have the expected length.", dimension, numOrdinates.length);
937        int numPoints = 1;
938        for (int i=0; i<dimension; i++) {
939            numPoints *= numOrdinates[i];
940            assertTrue("Invalid numOrdinates value.", numPoints >= 0);
941        }
942        final float[] coordinates = new float[numPoints * dimension];
943        /*
944         * Initialize the coordinate values for each dimension, and shuffle
945         * the result if a random numbers generator has been specified.
946         */
947        int step = 1;
948        for (int dim=0; dim<dimension; dim++) {
949            final int    n     =  numOrdinates[dim];
950            final double delta = (maxOrdinates[dim] - minOrdinates[dim]) / n;
951            final double start =  minOrdinates[dim] + delta/2;
952            int ordinateIndex=0, count=0;
953            float ordinate = (float) start;
954            for (int i=dim; i<coordinates.length; i+=dimension) {
955                coordinates[i] = ordinate;
956                if (randomGenerator != null) {
957                    coordinates[i] += (randomGenerator.nextFloat() - 0.5f) * delta;
958                }
959                if (++count == step) {
960                    count = 0;
961                    if (++ordinateIndex == n) {
962                        ordinateIndex = 0;
963                    }
964                    ordinate = (float) (ordinateIndex*delta + start);
965                }
966            }
967            step *= numOrdinates[dim];
968        }
969        if (randomGenerator != null) {
970            final float[] buffer = new float[dimension];
971            for (int i=coordinates.length; (i -= dimension) >= 0;) {
972                final int t = randomGenerator.nextInt(numPoints) * dimension;
973                System.arraycopy(coordinates, t, buffer,      0, dimension);
974                System.arraycopy(coordinates, i, coordinates, t, dimension);
975                System.arraycopy(buffer,      0, coordinates, i, dimension);
976            }
977        }
978        /*
979         * Delegate to other methods defined in this class.
980         */
981        verifyConsistency(coordinates);
982        final Configuration.Key<Boolean> oldTip = configurationTip;
983        if (isInverseTransformSupported) {
984            configurationTip = Configuration.Key.isInverseTransformSupported;
985            verifyInverse(coordinates);
986        }
987        if (isDerivativeSupported) {
988            configurationTip = Configuration.Key.isDerivativeSupported;
989            final double[] point = new double[dimension];
990            for (int i=0; i<coordinates.length; i+=dimension) {
991                for (int j=0; j<dimension; j++) {
992                    point[j] = coordinates[i+j];
993                }
994                verifyDerivative(point);
995            }
996        }
997        configurationTip = oldTip;
998        return coordinates;
999    }
1000
1001    /**
1002     * Asserts that a single coordinate is equal to the expected one within a positive delta.
1003     * If the comparison fails, the given message is completed with the expected and actual
1004     * values, and the index of the ordinate where the failure was found.
1005     *
1006     * @param message   the message to print in case of failure.
1007     * @param expected  the array of expected ordinate values.
1008     * @param actual    the array of ordinate values to check against the expected ones.
1009     * @param index     the index of the coordinate point being compared, for message formatting.
1010     * @param mode      indicates if the coordinates being compared are the result of a direct or
1011     *                  inverse transform, or if strict equality is requested.
1012     * @throws TransformFailure if at least one ordinate value is not equal to the expected value.
1013     */
1014    protected final void assertCoordinateEquals(final String message, final float[] expected,
1015            final float[] actual, final int index, final CalculationType mode) throws TransformFailure
1016    {
1017        final int dimension = expected.length;
1018        assertEquals("Coordinate array lengths differ.", dimension, actual.length);
1019        assertCoordinatesEqual(message, dimension, expected, 0, actual, 0, 1, mode, index);
1020    }
1021
1022    /**
1023     * Asserts that a single coordinate is equal to the expected one within a positive delta.
1024     * If the comparison fails, the given message is completed with the expected and actual
1025     * values, and the index of the ordinate where the failure was found.
1026     *
1027     * @param message   the message to print in case of failure.
1028     * @param expected  the array of expected ordinate values.
1029     * @param actual    the array of ordinate values to check against the expected ones.
1030     * @param index     the index of the coordinate point being compared, for message formatting.
1031     * @param mode      indicates if the coordinates being compared are the result of a direct or
1032     *                  inverse transform, or if strict equality is requested.
1033     * @throws TransformFailure if at least one ordinate value is not equal to the expected value.
1034     */
1035    protected final void assertCoordinateEquals(final String message, final float[] expected,
1036            final double[] actual, final int index, final CalculationType mode) throws TransformFailure
1037    {
1038        final int dimension = expected.length;
1039        assertEquals("Coordinate array lengths differ.", dimension, actual.length);
1040        assertCoordinatesEqual(message, dimension, expected, 0, actual, 0, 1, mode, index);
1041    }
1042
1043    /**
1044     * Asserts that a single coordinate is equal to the expected one within a positive delta.
1045     * If the comparison fails, the given message is completed with the expected and actual
1046     * values, and the index of the ordinate where the failure was found.
1047     *
1048     * @param message   the message to print in case of failure.
1049     * @param expected  the array of expected ordinate values.
1050     * @param actual    the array of ordinate values to check against the expected ones.
1051     * @param index     the index of the coordinate point being compared, for message formatting.
1052     * @param mode      indicates if the coordinates being compared are the result of a direct or
1053     *                  inverse transform, or if strict equality is requested.
1054     * @throws TransformFailure if at least one ordinate value is not equal to the expected value.
1055     */
1056    protected final void assertCoordinateEquals(final String message, final double[] expected,
1057            final float[] actual, final int index, final CalculationType mode) throws TransformFailure
1058    {
1059        final int dimension = expected.length;
1060        assertEquals("Coordinate array lengths differ.", dimension, actual.length);
1061        assertCoordinatesEqual(message, dimension, expected, 0, actual, 0, 1, mode, index);
1062    }
1063
1064    /**
1065     * Asserts that a single coordinate is equal to the expected one within a positive delta.
1066     * If the comparison fails, the given message is completed with the expected and actual
1067     * values, and the index of the ordinate where the failure was found.
1068     *
1069     * @param message   the message to print in case of failure.
1070     * @param expected  the array of expected ordinate values.
1071     * @param actual    the array of ordinate values to check against the expected ones.
1072     * @param index     the index of the coordinate point being compared, for message formatting.
1073     * @param mode      indicates if the coordinates being compared are the result of a direct or
1074     *                  inverse transform, or if strict equality is requested.
1075     * @throws TransformFailure if at least one ordinate value is not equal to the expected value.
1076     */
1077    protected final void assertCoordinateEquals(final String message, final double[] expected,
1078            final double[] actual, final int index, final CalculationType mode) throws TransformFailure
1079    {
1080        final int dimension = expected.length;
1081        assertEquals("Coordinate array lengths differ.", dimension, actual.length);
1082        assertCoordinatesEqual(message, dimension, expected, 0, actual, 0, 1, mode, index);
1083    }
1084
1085    /**
1086     * Asserts that coordinate values are equal to the expected ones within a positive delta.
1087     * If the comparison fails, the given message is completed with the expected and actual
1088     * values, and the index of the coordinate where the failure was found.
1089     *
1090     * @param message         the message to print in case of failure.
1091     * @param dimension       the dimension of each coordinate points in the arrays.
1092     * @param expectedPts     the array of expected coordinate values.
1093     * @param expectedOffset  index of the first valid ordinate in the {@code expectedPts} array.
1094     * @param actualPts       the array of coordinate values to check against the expected ones.
1095     * @param actualOffset    index of the first valid ordinate in the {@code actualPts} array.
1096     * @param numPoints       number of coordinate points to compare.
1097     * @param mode            indicates if the coordinates being compared are the result of a direct
1098     *                        or inverse transform, or if strict equality is requested.
1099     * @throws TransformFailure if at least one ordinate value is not equal to the expected value.
1100     */
1101    protected final void assertCoordinatesEqual(
1102            final String  message,     final int dimension,
1103            final float[] expectedPts, final int expectedOffset,
1104            final float[] actualPts,   final int actualOffset,
1105            final int     numPoints,   final CalculationType mode) throws TransformFailure
1106    {
1107        assertCoordinatesEqual(message, dimension, expectedPts, expectedOffset,
1108                actualPts, actualOffset, numPoints, mode, 0);
1109    }
1110
1111    /**
1112     * Asserts that coordinate values are equal to the expected ones within a positive delta.
1113     * If the comparison fails, the given message is completed with the expected and actual
1114     * values, and the index of the coordinate where the failure was found.
1115     *
1116     * @param message         the message to print in case of failure.
1117     * @param dimension       the dimension of each coordinate points in the arrays.
1118     * @param expectedPts     the array of expected coordinate values.
1119     * @param expectedOffset  index of the first valid ordinate in the {@code expectedPts} array.
1120     * @param actualPts       the array of coordinate values to check against the expected ones.
1121     * @param actualOffset    index of the first valid ordinate in the {@code actualPts} array.
1122     * @param numPoints       number of coordinate points to compare.
1123     * @param mode            indicates if the coordinates being compared are the result of a direct
1124     *                        or inverse transform, or if strict equality is requested.
1125     * @throws TransformFailure if at least one ordinate value is not equal to the expected value.
1126     */
1127    protected final void assertCoordinatesEqual(
1128            final String   message,     final int dimension,
1129            final float[]  expectedPts, final int expectedOffset,
1130            final double[] actualPts,   final int actualOffset,
1131            final int      numPoints,   final CalculationType mode) throws TransformFailure
1132    {
1133        assertCoordinatesEqual(message, dimension, expectedPts, expectedOffset,
1134                actualPts, actualOffset, numPoints, mode, 0);
1135    }
1136
1137    /**
1138     * Asserts that coordinate values are equal to the expected ones within a positive delta.
1139     * If the comparison fails, the given message is completed with the expected and actual
1140     * values, and the index of the coordinate where the failure was found.
1141     *
1142     * @param message         the message to print in case of failure.
1143     * @param dimension       the dimension of each coordinate points in the arrays.
1144     * @param expectedPts     the array of expected coordinate values.
1145     * @param expectedOffset  index of the first valid ordinate in the {@code expectedPts} array.
1146     * @param actualPts       the array of coordinate values to check against the expected ones.
1147     * @param actualOffset    index of the first valid ordinate in the {@code actualPts} array.
1148     * @param numPoints       number of coordinate points to compare.
1149     * @param mode            indicates if the coordinates being compared are the result of a direct
1150     *                        or inverse transform, or if strict equality is requested.
1151     * @throws TransformFailure if at least one ordinate value is not equal to the expected value.
1152     */
1153    protected final void assertCoordinatesEqual(
1154            final String   message,     final int dimension,
1155            final double[] expectedPts, final int expectedOffset,
1156            final float [] actualPts,   final int actualOffset,
1157            final int      numPoints,   final CalculationType mode) throws TransformFailure
1158    {
1159        assertCoordinatesEqual(message, dimension, expectedPts, expectedOffset,
1160                actualPts, actualOffset, numPoints, mode, 0);
1161    }
1162
1163    /**
1164     * Asserts that coordinate values are equal to the expected ones within a positive delta.
1165     * If the comparison fails, the given message is completed with the expected and actual
1166     * values, and the index of the coordinate where the failure was found.
1167     *
1168     * @param message         the message to print in case of failure.
1169     * @param dimension       the dimension of each coordinate points in the arrays.
1170     * @param expectedPts     the array of expected coordinate values.
1171     * @param expectedOffset  index of the first valid ordinate in the {@code expectedPts} array.
1172     * @param actualPts       the array of coordinate values to check against the expected ones.
1173     * @param actualOffset    index of the first valid ordinate in the {@code actualPts} array.
1174     * @param numPoints       number of coordinate points to compare.
1175     * @param mode            indicates if the coordinates being compared are the result of a direct
1176     *                        or inverse transform, or if strict equality is requested.
1177     * @throws TransformFailure if at least one ordinate value is not equal to the expected value.
1178     */
1179    protected final void assertCoordinatesEqual(
1180            final String   message,     final int dimension,
1181            final double[] expectedPts, final int expectedOffset,
1182            final double[] actualPts,   final int actualOffset,
1183            final int      numPoints,   final CalculationType mode) throws TransformFailure
1184    {
1185        assertCoordinatesEqual(message, dimension, expectedPts, expectedOffset,
1186                actualPts, actualOffset, numPoints, mode, 0);
1187    }
1188
1189    /**
1190     * Implementation of public assertion methods with the addition of the coordinate
1191     * index to be reported in error message.
1192     *
1193     * @param message         the header part of the message to format in case of failure.
1194     * @param dimension       the dimension of each coordinate points in the arrays.
1195     * @param expectedPts     the {@code float[]} or {@code double[]} array of expected coordinate values.
1196     * @param expectedOffset  index of the first valid ordinate in the {@code expectedPts} array.
1197     * @param actualPts       the {@code float[]} or {@code double[]} array of coordinate values to check against the expected ones.
1198     * @param actualOffset    index of the first valid ordinate in the {@code actualPts} array.
1199     * @param numPoints       number of coordinate points to compare.
1200     * @param mode            indicates if the coordinates being compared are the result of a direct
1201     *                        or inverse transform, or if strict equality is requested.
1202     * @param reportedIndex   in case of failure, index of the point (not ordinate) to report in the error message.
1203     * @throws TransformFailure if at least one ordinate value is not equal to the expected value.
1204     */
1205    private void assertCoordinatesEqual(
1206            final String   message,     final int dimension,
1207            final Object   expectedPts, int expectedOffset,
1208            final Object   actualPts,   int actualOffset,
1209            final int      numPoints,   final CalculationType mode, final int reportedIndex)
1210            throws TransformFailure
1211    {
1212        final boolean useDouble = isDoubleArray(expectedPts) && isDoubleArray(actualPts);
1213        final SimpleDirectPosition actual   = new SimpleDirectPosition(dimension);
1214        final SimpleDirectPosition expected = new SimpleDirectPosition(dimension);
1215        final double[] tolerances = new double[dimension];
1216        final ToleranceModifier modifier = getToleranceModifier();
1217        for (int i=0; i<numPoints; i++) {
1218            actual  .setCoordinate(actualPts,   actualOffset,   useDouble);
1219            expected.setCoordinate(expectedPts, expectedOffset, useDouble);
1220            normalize(expected, actual, mode);
1221            Arrays.fill(tolerances, (mode != CalculationType.IDENTITY) ? tolerance : 0);
1222            if (modifier != null) {
1223                modifier.adjust(tolerances, expected, mode);
1224            }
1225            for (int mismatch=0; mismatch<dimension; mismatch++) {
1226                final double a = actual  .getOrdinate(mismatch);
1227                final double e = expected.getOrdinate(mismatch);
1228                /*
1229                 * This method uses !(a <= b) expressions instead than (a > b) for catching NaN.
1230                 * The next condition working on bit patterns is for NaN and Infinity values.
1231                 */
1232                final double delta = abs(e - a);
1233                final double tol = tolerances[mismatch];
1234                if (!(delta <= tol) && Double.doubleToLongBits(a) != Double.doubleToLongBits(e)) {
1235                    /*
1236                     * Format an error message with the coordinate values followed by the
1237                     * difference with the expected value.
1238                     */
1239                    final String lineSeparator = System.getProperty("line.separator", "\n");
1240                    final StringBuilder buffer = new StringBuilder(1000);
1241                    appendErrorHeader(buffer, message);
1242                    buffer.append(lineSeparator)
1243                            .append("• DirectPosition").append(dimension).append("D[").append(reportedIndex + i)
1244                            .append("]: Expected ").append(expected).append(" but got ").append(actual).append('.')
1245                            .append(lineSeparator).append("• The delta at ordinate ").append(mismatch).append(" is ");
1246                    if (useDouble) {
1247                        buffer.append(delta);
1248                    } else {
1249                        buffer.append((float) delta);
1250                    }
1251                    buffer.append(" which is ").append((float) (delta / tol)).append(" times the tolerance threshold.");
1252                    if (modifier != null) {
1253                        buffer.append(lineSeparator).append("• The tolerance were calculated by ").append(modifier);
1254                    }
1255                    String wkt = null;
1256                    try {
1257                        wkt = transform.toWKT();
1258                    } catch (Exception ignore) {
1259                        // WKT formatting is optional, so ignore.
1260                    }
1261                    if (wkt != null) {
1262                        buffer.append(lineSeparator).append("• The transform Well-Known Text (WKT) is below:")
1263                              .append(lineSeparator).append(wkt);
1264                    }
1265                    throw new TransformFailure(buffer.toString());
1266                }
1267            }
1268            expectedOffset += dimension;
1269            actualOffset   += dimension;
1270        }
1271    }
1272
1273    /**
1274     * Returns the tolerance modifier to use for comparing coordinate values. The user-specified
1275     * value in {@link #toleranceModifier} is merged with any implementation-specific modifiers,
1276     * and the result is cached in {@link #cachedModifier} for reuse.
1277     *
1278     * @see #cachedModifier
1279     * @see #modifierUsedByCache
1280     * @see #transformUsedByCache
1281     */
1282    private ToleranceModifier getToleranceModifier() {
1283        if (cachedModifier == null || modifierUsedByCache != toleranceModifier || transformUsedByCache != transform) {
1284            transformUsedByCache = transform;
1285            modifierUsedByCache = toleranceModifier;
1286            final ToleranceModifier foundOnTheClasspath = ToleranceModifiers.maximum(
1287                    ToleranceModifiers.getImplementationSpecific(transform));
1288            isToleranceRelaxed |= (foundOnTheClasspath != null);
1289            cachedModifier = ToleranceModifiers.concatenate(toleranceModifier, foundOnTheClasspath);
1290        }
1291        return cachedModifier;
1292    }
1293
1294    /**
1295     * @deprecated The {@code boolean} argument has been replaced by a {@link CalculationType} argument.
1296     *
1297     * @param message   the message to print in case of failure.
1298     * @param expected  the array of expected ordinate values.
1299     * @param actual    the array of ordinate values to check against the expected ones.
1300     * @param index     the index of the coordinate point being compared, for message formatting.
1301     * @param strict    {@code true} for ignoring the {@linkplain #tolerance(double) tolerance} threshold.
1302     *                  In such case, ordinate values are checked for strict equality.
1303     */
1304    @Deprecated
1305    protected final void assertCoordinateEquals(final String message, final float[] expected,
1306            final float[] actual, final int index, final boolean strict)
1307    {
1308        assertCoordinateEquals(message, expected, actual, index,
1309                strict ? CalculationType.IDENTITY : CalculationType.DIRECT_TRANSFORM);
1310    }
1311
1312    /**
1313     * @deprecated The {@code boolean} argument has been replaced by a {@link CalculationType} argument.
1314     *
1315     * @param message   the message to print in case of failure.
1316     * @param expected  the array of expected ordinate values.
1317     * @param actual    the array of ordinate values to check against the expected ones.
1318     * @param index     the index of the coordinate point being compared, for message formatting.
1319     * @param strict    {@code true} for ignoring the {@linkplain #tolerance(double) tolerance} threshold.
1320     *                  In such case, ordinate values are checked for strict equality.
1321     */
1322    @Deprecated
1323    protected final void assertCoordinateEquals(final String message, final float[] expected,
1324            final double[] actual, final int index, final boolean strict)
1325    {
1326        assertCoordinateEquals(message, expected, actual, index,
1327                strict ? CalculationType.IDENTITY : CalculationType.DIRECT_TRANSFORM);
1328    }
1329
1330    /**
1331     * @deprecated The {@code boolean} argument has been replaced by a {@link CalculationType} argument.
1332     *
1333     * @param message   the message to print in case of failure.
1334     * @param expected  the array of expected ordinate values.
1335     * @param actual    the array of ordinate values to check against the expected ones.
1336     * @param index     the index of the coordinate point being compared, for message formatting.
1337     * @param strict    {@code true} for ignoring the {@linkplain #tolerance(double) tolerance} threshold.
1338     *                  In such case, ordinate values are checked for strict equality.
1339     */
1340    @Deprecated
1341    protected final void assertCoordinateEquals(final String message, final double[] expected,
1342            final float[] actual, final int index, final boolean strict)
1343    {
1344        assertCoordinateEquals(message, expected, actual, index,
1345                strict ? CalculationType.IDENTITY : CalculationType.DIRECT_TRANSFORM);
1346    }
1347
1348    /**
1349     * @deprecated The {@code boolean} argument has been replaced by a {@link CalculationType} argument.
1350     *
1351     * @param message   the message to print in case of failure.
1352     * @param expected  the array of expected ordinate values.
1353     * @param actual    the array of ordinate values to check against the expected ones.
1354     * @param index     he index of the coordinate point being compared, for message formatting.
1355     * @param strict    {@code true} for ignoring the {@linkplain #tolerance(double) tolerance} threshold.
1356     *                  In such case, ordinate values are checked for strict equality.
1357     */
1358    @Deprecated
1359    protected final void assertCoordinateEquals(final String message, final double[] expected,
1360            final double[] actual, final int index, final boolean strict)
1361    {
1362        assertCoordinateEquals(message, expected, actual, index,
1363                strict ? CalculationType.IDENTITY : CalculationType.DIRECT_TRANSFORM);
1364    }
1365
1366    /**
1367     * @deprecated The {@code boolean} argument has been replaced by a {@link CalculationType} argument.
1368     *
1369     * @param message         the message to print in case of failure.
1370     * @param dimension       the dimension of each coordinate points in the arrays.
1371     * @param expectedPts     the array of expected coordinate values.
1372     * @param expectedOffset  index of the first valid ordinate in the {@code expectedPts} array.
1373     * @param actualPts       the array of coordinate values to check against the expected ones.
1374     * @param actualOffset    index of the first valid ordinate in the {@code actualPts} array.
1375     * @param numPoints       number of coordinate points to compare.
1376     * @param strict          {@code true} for ignoring the {@linkplain #tolerance(double) tolerance} threshold.
1377     *                        In such case, ordinate values are checked for strict equality.
1378     */
1379    @Deprecated
1380    protected final void assertCoordinatesEqual(
1381            final String  message,     final int dimension,
1382            final float[] expectedPts, final int expectedOffset,
1383            final float[] actualPts,   final int actualOffset,
1384            final int     numPoints,   final boolean strict)
1385    {
1386        assertCoordinatesEqual(message, dimension,
1387                expectedPts, expectedOffset, actualPts, actualOffset, numPoints,
1388                strict ? CalculationType.IDENTITY : CalculationType.DIRECT_TRANSFORM);
1389    }
1390
1391    /**
1392     * @deprecated The {@code boolean} argument has been replaced by a {@link CalculationType} argument.
1393     *
1394     * @param message         the message to print in case of failure.
1395     * @param dimension       the dimension of each coordinate points in the arrays.
1396     * @param expectedPts     the array of expected coordinate values.
1397     * @param expectedOffset  index of the first valid ordinate in the {@code expectedPts} array.
1398     * @param actualPts       the array of coordinate values to check against the expected ones.
1399     * @param actualOffset    index of the first valid ordinate in the {@code actualPts} array.
1400     * @param numPoints       number of coordinate points to compare.
1401     * @param strict          {@code true} for ignoring the {@linkplain #tolerance(double) tolerance} threshold.
1402     *                        In such case, ordinate values are checked for strict equality.
1403     */
1404    @Deprecated
1405    protected final void assertCoordinatesEqual(
1406            final String   message,     final int dimension,
1407            final float[]  expectedPts, final int expectedOffset,
1408            final double[] actualPts,   final int actualOffset,
1409            final int numPoints,        final boolean strict)
1410    {
1411        assertCoordinatesEqual(message, dimension,
1412                expectedPts, expectedOffset, actualPts, actualOffset, numPoints,
1413                strict ? CalculationType.IDENTITY : CalculationType.DIRECT_TRANSFORM);
1414    }
1415
1416    /**
1417     * @deprecated The {@code boolean} argument has been replaced by a {@link CalculationType} argument.
1418     *
1419     * @param message         the message to print in case of failure.
1420     * @param dimension       the dimension of each coordinate points in the arrays.
1421     * @param expectedPts     the array of expected coordinate values.
1422     * @param expectedOffset  index of the first valid ordinate in the {@code expectedPts} array.
1423     * @param actualPts       the array of coordinate values to check against the expected ones.
1424     * @param actualOffset    index of the first valid ordinate in the {@code actualPts} array.
1425     * @param numPoints       number of coordinate points to compare.
1426     * @param strict          {@code true} for ignoring the {@linkplain #tolerance(double) tolerance} threshold.
1427     *                        In such case, ordinate values are checked for strict equality.
1428     */
1429    @Deprecated
1430    protected final void assertCoordinatesEqual(
1431            final String   message,     final int dimension,
1432            final double[] expectedPts, final int expectedOffset,
1433            final float [] actualPts,   final int actualOffset,
1434            final int      numPoints,   final boolean strict)
1435    {
1436        assertCoordinatesEqual(message, dimension,
1437                expectedPts, expectedOffset, actualPts, actualOffset, numPoints,
1438                strict ? CalculationType.IDENTITY : CalculationType.DIRECT_TRANSFORM);
1439    }
1440
1441    /**
1442     * @deprecated The {@code boolean} argument has been replaced by a {@link CalculationType} argument.
1443     *
1444     * @param message         the message to print in case of failure.
1445     * @param dimension       the dimension of each coordinate points in the arrays.
1446     * @param expectedPts     the array of expected coordinate values.
1447     * @param expectedOffset  index of the first valid ordinate in the {@code expectedPts} array.
1448     * @param actualPts       the array of coordinate values to check against the expected ones.
1449     * @param actualOffset    index of the first valid ordinate in the {@code actualPts} array.
1450     * @param numPoints       number of coordinate points to compare.
1451     * @param strict          {@code true} for ignoring the {@linkplain #tolerance(double) tolerance} threshold.
1452     *                        In such case, ordinate values are checked for strict equality.
1453     */
1454    @Deprecated
1455    protected final void assertCoordinatesEqual(
1456            final String   message,     final int dimension,
1457            final double[] expectedPts, final int expectedOffset,
1458            final double[] actualPts,   final int actualOffset,
1459            final int      numPoints,   final boolean strict)
1460    {
1461        assertCoordinatesEqual(message, dimension,
1462                expectedPts, expectedOffset, actualPts, actualOffset, numPoints,
1463                strict ? CalculationType.IDENTITY : CalculationType.DIRECT_TRANSFORM);
1464    }
1465
1466    /**
1467     * Asserts that a matrix of derivatives is equals to the expected ones within a positive delta.
1468     * If the comparison fails, the given message is completed with the expected and actual matrixes
1469     * values.
1470     *
1471     * <p>For each matrix element, the tolerance value is given by the corresponding element in the
1472     * {@code tolmat} matrix. This tolerance matrix is initialized by the
1473     * {@link #verifyDerivative(double[])} method to the differences found between the 3 forms of
1474     * finite difference (<cite>forward</cite>, <cite>backward</cite>, <cite>central</cite>).
1475     * Developers can override this method and overwrite the {@code tolmat} elements if they
1476     * wish different tolerance values.</p>
1477     *
1478     * @param  message   the message to print in case of failure.
1479     * @param  expected  the expected matrix of derivative values, estimated by finite differences.
1480     * @param  actual    the actual matrix computed by the transform to be tested.
1481     * @param  tolmat    the tolerance value for each matrix elements, or {@code null} for a strict comparison.
1482     * @throws DerivativeFailure if at least one matrix element is not equal to the expected value.
1483     *
1484     * @see #verifyDerivative(double[])
1485     * @see org.opengis.test.Assert#assertMatrixEquals(String, Matrix, Matrix, double)
1486     *
1487     * @since 3.1
1488     */
1489    protected void assertMatrixEquals(final String message, final Matrix expected, final Matrix actual, final Matrix tolmat)
1490            throws DerivativeFailure
1491    {
1492        final int numRow = expected.getNumRow();
1493        final int numCol = expected.getNumCol();
1494        assertEquals("Wrong number of rows.",    numRow, actual.getNumRow());
1495        assertEquals("Wrong number of columns.", numCol, actual.getNumCol());
1496        for (int i=0; i<numCol; i++) {
1497            for (int j=0; j<numRow; j++) {
1498                final double e = expected.getElement(j, i);
1499                final double a = actual  .getElement(j, i);
1500                final double d = abs(e - a);
1501                final double tol = (tolmat != null) ? tolmat.getElement(j, i) : 0;
1502                if (!(d <= tol) && Double.doubleToLongBits(a) != Double.doubleToLongBits(e)) {
1503                    final String lineSeparator = System.getProperty("line.separator", "\n");
1504                    final StringBuilder buffer = new StringBuilder(1000);
1505                    appendErrorHeader(buffer, message);
1506                    buffer.append(lineSeparator).append("Matrix(").append(j).append(',').append(i)
1507                            .append("): expected ").append(e).append(" but got ").append(a)
1508                            .append(" (a difference of ").append(d).append(')').append(lineSeparator)
1509                            .append("Expected matrix (may be approximate):").append(lineSeparator);
1510                    SimpleMatrix.toString(expected, buffer, lineSeparator);
1511                    buffer.append(lineSeparator).append("Actual matrix:").append(lineSeparator);
1512                    SimpleMatrix.toString(actual, buffer, lineSeparator);
1513                    if (tolmat != null) {
1514                        buffer.append(lineSeparator).append("Tolerance matrix:").append(lineSeparator);
1515                        SimpleMatrix.toString(tolmat, buffer, lineSeparator);
1516                    }
1517                    throw new DerivativeFailure(buffer.toString());
1518                }
1519            }
1520        }
1521    }
1522
1523    /**
1524     * Invoked for preparing the header of a test failure message. The default implementation
1525     * just append the given message. Subclasses can override this message in order to provide
1526     * additional information.
1527     *
1528     * @param buffer   the buffer in which to append the header.
1529     * @param message  user-supplied message to append, or {@code null}.
1530     */
1531    void appendErrorHeader(final StringBuilder buffer, final String message) {
1532        if (message != null) {
1533            buffer.append(message.trim());
1534        }
1535    }
1536
1537    /**
1538     * Returns {@code true} if the given array is an array of {@code double} primitive types.
1539     */
1540    private static boolean isDoubleArray(final Object array) {
1541        return array.getClass().getComponentType() == Double.TYPE;
1542    }
1543
1544    /**
1545     * Invoked by all {@code assertCoordinateEqual(…)} methods before two positions are compared.
1546     * This method allows subclasses to replace some equivalent ordinate values by a unique value.
1547     * For example implementations may ensure that longitude values are contained in the ±180°
1548     * range, applying 360° shifts if needed.
1549     *
1550     * <p>The default implementation does nothing. Subclasses can modify the {@code actual} ordinate
1551     * values directly using the {@link DirectPosition#setOrdinate(int, double)} method.</p>
1552     *
1553     * @param expected  the expected ordinate value provided by the test case.
1554     * @param actual    the ordinate value computed by the {@linkplain #transform} being tested.
1555     * @param mode      indicates if the coordinates being compared are the result of a direct
1556     *                  or inverse transform, or if strict equality is requested.
1557     *
1558     * @since 3.1
1559     */
1560    protected void normalize(DirectPosition expected, DirectPosition actual, CalculationType mode) {
1561    }
1562}