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<<var>Operation</var>>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<</code><var>Operation</var><code>>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<</code><var>Operation</var><code>>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}