001/* 002 * GeoAPI - Java interfaces for OGC/ISO standards 003 * Copyright © 2011-2024 Open Geospatial Consortium, Inc. 004 * http://www.geoapi.org 005 * 006 * Licensed under the Apache License, Version 2.0 (the "License"); 007 * you may not use this file except in compliance with the License. 008 * You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018package org.opengis.test; 019 020import java.util.Set; 021import java.util.Arrays; 022import java.util.EnumSet; 023import org.opengis.geometry.DirectPosition; 024import org.opengis.referencing.operation.MathTransform; 025 026import static java.lang.StrictMath.*; 027 028 029/** 030 * A factory of various {@link ToleranceModifier} implementations. 031 * 032 * @author Martin Desruisseaux (Geomatys) 033 * @version 3.1 034 * @since 3.1 035 */ 036@SuppressWarnings("strictfp") // Because we still target Java 11. 037public strictfp final class ToleranceModifiers { 038 /** 039 * The standard length of one nautical mile, which is {@value} metres. This is the length 040 * of about one minute of arc of latitude along any meridian. This distance is used by 041 * {@linkplain ToleranceModifier#GEOGRAPHIC geographic tolerance modifiers} for converting 042 * linear units to angular units. 043 */ 044 public static final double NAUTICAL_MILE = 1852; 045 046 /** 047 * An empty array of tolerance modifiers, to be returned by 048 * {@link #getImplementationSpecific(MathTransform)} in the common case where 049 * there is no specific implementation needed for a given math transform. 050 */ 051 private static final ToleranceModifier[] EMPTY_ARRAY = new ToleranceModifier[0]; 052 053 /** 054 * Do not allow instantiation of this class. 055 */ 056 private ToleranceModifiers() { 057 } 058 059 /** 060 * Base implementation of all {@link ToleranceModifier} defined in the enclosing class. 061 */ 062 strictfp static abstract class Abstract implements ToleranceModifier { 063 /** Invoked by the public static method or field only. */ 064 Abstract() { 065 } 066 067 /** Compares this object with the given object for equality. */ 068 @Override 069 public boolean equals(final Object object) { 070 return (object != null) && object.getClass() == getClass(); 071 } 072 073 /** Returns a hash code value for this object. */ 074 @Override 075 public int hashCode() { 076 return getClass().hashCode() ^ 434184245; 077 } 078 079 /** Returns a string representation for debugging purpose. */ 080 @Override 081 public String toString() { 082 final StringBuilder buffer = new StringBuilder("ToleranceModifier.") 083 .append(getClass().getSimpleName()).append('['); 084 toString(buffer); 085 return buffer.append(']').toString(); 086 } 087 088 /** 089 * Overridden by subclasses for defining the inner part of {@code toString()}. 090 * 091 * @param buffer where to write the string representation of this tolerance modifier. 092 */ 093 void toString(final StringBuilder buffer) { 094 buffer.append('…'); 095 } 096 } 097 098 /** 099 * Implementation of {@link ToleranceModifier#RELATIVE}. 100 */ 101 strictfp static final class Relative extends Abstract { 102 /** Invoked by the public static method or field only. */ 103 Relative() { 104 } 105 106 /** Adjusts the tolerances as documented in the enclosing class. */ 107 @Override 108 public void adjust(final double[] tolerances, final DirectPosition coordinates, final CalculationType mode) { 109 for (int i=0; i<tolerances.length; i++) { 110 final double scale = abs(coordinates.getCoordinate(i)); 111 if (scale > 1) { 112 tolerances[i] *= scale; 113 } 114 } 115 } 116 }; 117 118 /** 119 * Converts λ and φ tolerance values from metres to degrees before comparing 120 * geographic coordinates. The tolerance for the longitude (λ) and latitude (φ) 121 * coordinate values are converted from metres to degrees using the standard length of one 122 * nautical mile ({@value #NAUTICAL_MILE} metres per minute of angle). Next, the λ 123 * tolerance is adjusted according the distance of the φ coordinate value to the pole. 124 * In the extreme case where the coordinate to compare is located at a pole, then the 125 * tolerance is 360° in longitude values. 126 * 127 * @param λDimension the dimension of longitude coordinate values (typically 0 or 1). 128 * @param φDimension the dimension of latitude coordinate values (typically 0 or 1). 129 * @return a tolerance modifier suitable for comparing geographic coordinates. 130 * 131 * @see ToleranceModifier#GEOGRAPHIC 132 * @see ToleranceModifier#GEOGRAPHIC_φλ 133 */ 134 public static ToleranceModifier geographic(final int λDimension, final int φDimension) { 135 if (λDimension == 0 && φDimension == 1) { 136 return ToleranceModifier.GEOGRAPHIC; 137 } 138 if (φDimension == 0 && λDimension == 1) { 139 return ToleranceModifier.GEOGRAPHIC_φλ; 140 } 141 return new Geographic(λDimension, φDimension); 142 } 143 144 /** 145 * Implementation of the value returned by {@link ToleranceModifiers#geographic(int,int)}. 146 */ 147 strictfp static class Geographic extends Abstract { 148 /** The dimension of the tolerance values to modify. */ 149 private final int λDimension, φDimension; 150 151 /** 152 * Invoked by the public static method or field only. 153 * 154 * @param λDimension dimension of longitude values. 155 * @param φDimension dimension of latitude values. 156 */ 157 Geographic(final int λDimension, final int φDimension) { 158 this.λDimension = λDimension; 159 this.φDimension = φDimension; 160 if (λDimension < 0) throw new IllegalArgumentException("Illegal λ dimension: " + λDimension); 161 if (φDimension < 0) throw new IllegalArgumentException("Illegal φ dimension: " + φDimension); 162 if (φDimension == λDimension) { 163 throw new IllegalArgumentException("λ and φ dimensions must be different."); 164 } 165 } 166 167 /** Adjusts the (λ,φ) tolerances as documented in the enclosing class. */ 168 @Override 169 public void adjust(final double[] tolerances, final DirectPosition coordinates, final CalculationType mode) { 170 tolerances[φDimension] /= (NAUTICAL_MILE * 60); // 1 nautical miles = 1852 metres in 1 minute of angle. 171 double tol = tolerances[λDimension]; 172 if (tol != 0) { 173 tol /= (NAUTICAL_MILE*60 * cos(toRadians(abs(coordinates.getCoordinate(φDimension))))); 174 if (!(tol <= 360)) { // !(a<=b) rather than (a>b) in order to catch NaN. 175 tol = 360; 176 } 177 tolerances[λDimension] = tol; 178 } 179 } 180 181 /** Compares this object with the given object for equality. */ 182 @Override 183 public boolean equals(final Object object) { 184 if (super.equals(object)) { 185 final Geographic other = (Geographic) object; 186 return other.λDimension == λDimension && other.φDimension == φDimension; 187 } 188 return false; 189 } 190 191 /** Returns a hash code value for this object. */ 192 @Override 193 public int hashCode() { 194 return (super.hashCode()*31 + λDimension)*31 + φDimension; 195 } 196 197 /** Formats λ and φ symbols at the position of their dimension. */ 198 @Override 199 final void toString(final StringBuilder buffer) { 200 final int n = max(λDimension, φDimension); 201 for (int i=0; i<=n; i++) { 202 buffer.append(i == λDimension ? 'λ' : (i == φDimension ? 'φ' : '·')).append(','); 203 } 204 super.toString(buffer); 205 } 206 }; 207 208 /** 209 * Converts λ and φ tolerance values from metres to degrees before comparing 210 * the result of an <i>reverse projection</i>. For <i>forward projections</i> 211 * and all other calculations, the tolerance values are left unchanged. 212 * 213 * <p>The modifier performs the work documented in {@link #geographic(int, int)} if and only if 214 * the {@link CalculationType} is {@link CalculationType#INVERSE_TRANSFORM INVERSE_TRANSFORM}. 215 * For all other cases, the modifier does nothing.</p> 216 * 217 * @param λDimension the dimension of longitude coordinate values (typically 0 or 1). 218 * @param φDimension the dimension of latitude coordinate values (typically 0 or 1). 219 * @return a tolerance modifier suitable for comparing projected coordinates. 220 * 221 * @see ToleranceModifier#PROJECTION 222 * @see ToleranceModifier#PROJECTION_FROM_φλ 223 */ 224 public static ToleranceModifier projection(final int λDimension, final int φDimension) { 225 if (λDimension == 0 && φDimension == 1) { 226 return ToleranceModifier.PROJECTION; 227 } 228 if (φDimension == 0 && λDimension == 1) { 229 return ToleranceModifier.PROJECTION_FROM_φλ; 230 } 231 return new Projection(λDimension, φDimension); 232 } 233 234 /** 235 * Implementation of the value returned by {@link ToleranceModifiers#projection(int,int)}. 236 */ 237 strictfp static final class Projection extends Geographic { 238 /** 239 * Invoked by the public static method or field only. 240 * 241 * @param λDimension dimension of longitude values. 242 * @param φDimension dimension of latitude values. 243 */ 244 Projection(final int λDimension, final int φDimension) { 245 super(λDimension, φDimension); 246 } 247 248 /** Adjusts the (λ,φ) tolerances as documented in the enclosing class. */ 249 @Override 250 public void adjust(final double[] tolerances, final DirectPosition coordinates, final CalculationType mode) { 251 if (mode == CalculationType.INVERSE_TRANSFORM) { 252 super.adjust(tolerances, coordinates, mode); 253 } 254 } 255 }; 256 257 /** 258 * Multiplies tolerance values by the given factors. For every dimension <var>i</var>, this 259 * modifier multiplies <code>tolerance[<var>i</var>]</code> by <code>factors[<var>i</var>]</code>. 260 * 261 * <p>If the tolerance array is longer than the factors array, all extra tolerance values are left 262 * unchanged. If the tolerance array is shorter than the factors array, the extra factors values 263 * are ignored.</p> 264 * 265 * @param types the calculation types for which to apply the given scale factors. 266 * @param factors the factors by which to multiply the tolerance values. 267 * @return a tolerance modifier that scale the tolerance thresholds, or {@code null} if 268 * the given set or array is empty or all the given scale factors are equals to 1. 269 */ 270 public static ToleranceModifier scale(Set<CalculationType> types, final double... factors) { 271 types = EnumSet.copyOf(types); 272 if (types.isEmpty()) { 273 return null; 274 } 275 int upper = 0; 276 for (int i=0; i<factors.length;) { 277 final double factor = factors[i]; 278 if (!(factor >= 0)) { // !(a >= 0) instead of (a < 0) for catching NaN. 279 throw new IllegalArgumentException("Illegal scale: factors[" + i + "] = " + factor); 280 } 281 i++; 282 if (factor != 1) { 283 upper = i; 284 } 285 } 286 return (upper != 0) ? new Scale(types, Arrays.copyOf(factors, upper)) : null; 287 } 288 289 /** 290 * Implementation of the value returned by {@link ToleranceModifiers#scale(double[])}. 291 */ 292 strictfp static final class Scale extends Abstract { 293 /** The types for which to apply the scale factors. */ 294 private final Set<CalculationType> types; 295 296 /** The scale factors. */ 297 private final double[] factors; 298 299 /** 300 * Invoked by the public static method only. 301 * 302 * @param types the types for which to apply the scale factors. 303 * @param factors the scale factors. 304 */ 305 Scale(final Set<CalculationType> types, final double[] factors) { 306 this.types = types; 307 this.factors = factors; 308 } 309 310 /** Gets the scaled tolerance threshold as documented in the enclosing class. */ 311 @Override 312 public void adjust(final double[] tolerances, final DirectPosition coordinates, final CalculationType mode) { 313 if (types.contains(mode)) { 314 for (int i=min(tolerances.length, factors.length); --i>=0;) { 315 tolerances[i] *= factors[i]; 316 } 317 } 318 } 319 320 /** Compares this object with the given object for equality. */ 321 @Override 322 public boolean equals(final Object object) { 323 if (super.equals(object)) { 324 final Scale other = (Scale) object; 325 return types.equals(other.types) && Arrays.equals(factors, other.factors); 326 } 327 return false; 328 } 329 330 /** Returns a hash code value for this object. */ 331 @Override 332 public int hashCode() { 333 return super.hashCode() + 31*(types.hashCode() + 31*Arrays.hashCode(factors)); 334 } 335 336 /** Appends the scale factors. */ 337 @Override 338 void toString(final StringBuilder buffer) { 339 boolean more = false; 340 for (final CalculationType type : types) { 341 if (more) buffer.append(','); 342 buffer.append(type); 343 more = true; 344 } 345 buffer.append(':'); 346 for (final double factor : factors) { 347 if (factor == 1) { 348 buffer.append('·'); 349 } else { 350 buffer.append('×'); 351 final int casted = (int) factor; 352 if (casted == factor) { 353 buffer.append(casted); 354 } else { 355 buffer.append(factor); 356 } 357 } 358 buffer.append(','); 359 } 360 super.toString(buffer); 361 } 362 } 363 364 /** 365 * Returns a modifier which will return the maximal tolerance threshold of all the given 366 * modifiers for each dimension. 367 * 368 * @param modifiers the modifiers to iterate over. 369 * @return a filter for the maximal tolerance threshold of all the given modifiers, 370 * or {@code null} if the given {@code modifiers} array is empty. 371 */ 372 public static ToleranceModifier maximum(final ToleranceModifier... modifiers) { 373 final int length = modifiers.length; 374 switch (length) { 375 case 0: return null; 376 case 1: return modifiers[0]; 377 } 378 /* 379 * If any element of the given modifiers array is another instance of the 380 * 'Maximum' modifier, concatenate all modifier arrays into a single array. 381 */ 382 ToleranceModifier[] expanded = new ToleranceModifier[length]; 383 for (int i=0,t=0; i<length; i++) { 384 final ToleranceModifier modifier = modifiers[i]; 385 if (modifier == null) { 386 throw new NullPointerException("modifiers[" + i + "] is null."); 387 } 388 if (modifier instanceof Maximum) { 389 final ToleranceModifier[] insert = ((Maximum) modifier).modifiers; 390 expanded = Arrays.copyOf(expanded, expanded.length + insert.length-1); 391 System.arraycopy(insert, 0, expanded, t, insert.length); 392 t += insert.length; 393 } else { 394 expanded[t++] = modifier; 395 } 396 } 397 return new Maximum(expanded); 398 } 399 400 /** 401 * The implementation of {@link ToleranceModifiers#maximum(ToleranceModifier[])}. 402 */ 403 private strictfp static final class Maximum extends Abstract { 404 /** The modifiers from which to get the maximal tolerance. */ 405 private final ToleranceModifier[] modifiers; 406 407 /** 408 * Invoked by the public static method only. 409 * 410 * @param modifiers the modifiers from which to get the maximal tolerance. 411 */ 412 Maximum(final ToleranceModifier[] modifiers) { 413 this.modifiers = modifiers; 414 } 415 416 /** Gets the maximal tolerance thresholds as documented in the enclosing class. */ 417 @Override 418 public void adjust(final double[] tolerances, final DirectPosition coordinates, final CalculationType mode) { 419 final double[] original = tolerances.clone(); 420 final double[] copy = new double[original.length]; 421 for (final ToleranceModifier modifier : modifiers) { 422 System.arraycopy(original, 0, copy, 0, original.length); 423 modifier.adjust(copy, coordinates, mode); 424 for (int i=0; i<copy.length; i++) { 425 final double tol = copy[i]; 426 if (tol > tolerances[i]) { 427 tolerances[i] = tol; 428 } 429 } 430 } 431 } 432 433 /** Compares this object with the given object for equality. */ 434 @Override 435 public boolean equals(final Object object) { 436 return super.equals(object) && Arrays.equals(((Maximum) object).modifiers, modifiers); 437 } 438 439 /** Returns a hash code value for this object. */ 440 @Override 441 public int hashCode() { 442 return super.hashCode() ^ Arrays.hashCode(modifiers); 443 } 444 445 /** Concatenates the string representations of the enclosed modifiers. */ 446 @Override 447 void toString(final StringBuilder buffer) { 448 Concatenate.toString(buffer, ", ", modifiers); 449 } 450 } 451 452 /** 453 * Returns a concatenation of two existing modifiers. The tolerance threshold are first 454 * adjusted according the first modifier, then the result is given to the second modifier 455 * for an additional adjustment. 456 * 457 * @param first the first modifier, or {@code null}. 458 * @param second the second modifier, or {@code null}. 459 * @return the concatenation of the two given identifiers, or {@code null} if both identifiers are {@code null}. 460 */ 461 public static ToleranceModifier concatenate(final ToleranceModifier first, final ToleranceModifier second) { 462 if (first == null) return second; 463 if (second == null) return first; 464 return new Concatenate(first, second); 465 } 466 467 /** 468 * The implementation of {@link ToleranceModifiers#concatenate(ToleranceModifier, ToleranceModifier)}. 469 */ 470 private strictfp static final class Concatenate extends Abstract { 471 /** The modifiers to concatenate. */ 472 private final ToleranceModifier first, second; 473 474 /** 475 * Invoked by the public static method only. 476 * 477 * @param first first modifier to concatenate. 478 * @param second second modifier to concatenate. 479 */ 480 Concatenate(final ToleranceModifier first, final ToleranceModifier second) { 481 this.first = first; 482 this.second = second; 483 } 484 485 /** Gets the concatenated thresholds as documented in the enclosing class. */ 486 @Override 487 public void adjust(final double[] tolerances, final DirectPosition coordinates, final CalculationType mode) { 488 first .adjust(tolerances, coordinates, mode); 489 second.adjust(tolerances, coordinates, mode); 490 } 491 492 /** Compares this object with the given object for equality. */ 493 @Override 494 public boolean equals(final Object object) { 495 if (super.equals(object)) { 496 final Concatenate other = (Concatenate) object; 497 return first.equals(other.first) && second.equals(other.second); 498 } 499 return false; 500 } 501 502 /** Returns a hash code value for this object. */ 503 @Override 504 public int hashCode() { 505 return (super.hashCode()*31 + first.hashCode())*31 + second.hashCode(); 506 } 507 508 /** Concatenates the string representations of the enclosed modifiers. */ 509 @Override 510 void toString(final StringBuilder buffer) { 511 toString(buffer, " → ", first, second); 512 } 513 514 /** 515 * Concatenates the string representations of the given modifiers. 516 * 517 * @param buffer where to write the string representations. 518 * @param separator separator between tolerance modifiers. 519 * @param modifiers the tolerance modifiers to format. 520 */ 521 static void toString(final StringBuilder buffer, final String separator, final ToleranceModifier... modifiers) { 522 String next = ""; 523 for (final ToleranceModifier modifier : modifiers) { 524 String st = modifier.toString(); 525 if (modifier instanceof Abstract) { 526 st = st.substring(st.indexOf('.') + 1); 527 } 528 buffer.append(next).append(st); 529 next = separator; 530 } 531 } 532 } 533}