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