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.Set; 021import java.util.Iterator; 022import org.opengis.referencing.cs.*; 023import org.opengis.test.ValidatorContainer; 024 025import static org.junit.jupiter.api.Assertions.*; 026import static org.opengis.test.Assertions.assertBetween; 027import static org.opengis.test.Assertions.assertValidRange; 028import static org.opengis.test.Assertions.assertStrictlyPositive; 029import static org.opengis.test.referencing.Utilities.getAxisDirections; 030 031 032/** 033 * Validates {@link CoordinateSystem} and related objects from the {@code org.opengis.referencing.cs} 034 * package. 035 * 036 * <p>This class is provided for users wanting to override the validation methods. When the default 037 * behavior is sufficient, the {@link org.opengis.test.Validators} static methods provide a more 038 * convenient way to validate various kinds of objects.</p> 039 * 040 * @author Martin Desruisseaux (Geomatys) 041 * @version 3.1 042 * @since 2.2 043 * 044 * @todo Add checks for Unit of Measurement depending on the coordinate system type. 045 * For example, {@link EllipsoidalCS} expects two angular values and one linear value (if 3D). 046 */ 047public class CSValidator extends ReferencingValidator { 048 /** 049 * The space in which an axis orientation is defined. 050 * Orientations in the same space can be compared. 051 */ 052 static enum Space { 053 GEOGRAPHIC, TEMPORAL, ENGINEERING, MATRIX, DISPLAY; 054 } 055 056 /** 057 * Orientation of an {@link AxisDirection}, used to detect if axes are perpendicular. 058 */ 059 static final class Orientation { 060 /** Amount of degrees in one angle unit. */ static final double ANGLE_UNIT = 22.5; 061 /** Geographic, matrix or display. */ final Space category; 062 /** Orientation as a multiple of 22.5°. */ final int orientation; 063 064 /** 065 * Creates a new {@code Orientation}. 066 * 067 * @param category geographic, matrix or display. 068 * @param orientation orientation as a multiple of 22.5°. 069 */ 070 Orientation(final Space category, final int orientation) { 071 this.category = category; 072 this.orientation = orientation; 073 } 074 075 /** {@return a string representation for debugging purpose only}. */ 076 @Override public String toString() { 077 return category.name() + ':' + (orientation * ANGLE_UNIT) + '°'; 078 } 079 } 080 081 /** 082 * The orientation of {@link AxisDirection} enumeration elements for which the direction will be verified. 083 * This array does not need to contain all axis directions, because it is used only by 084 * {@link #validate(CartesianCS)} for asserting that axes are perpendicular. 085 */ 086 static final Orientation[] ORIENTATIONS = new Orientation[34]; 087 static { 088 ORIENTATIONS[AxisDirection.NORTH .ordinal()] = new Orientation(Space.GEOGRAPHIC, 0); 089 ORIENTATIONS[AxisDirection.NORTH_NORTH_EAST .ordinal()] = new Orientation(Space.GEOGRAPHIC, 1); 090 ORIENTATIONS[AxisDirection.NORTH_EAST .ordinal()] = new Orientation(Space.GEOGRAPHIC, 2); 091 ORIENTATIONS[AxisDirection.EAST_NORTH_EAST .ordinal()] = new Orientation(Space.GEOGRAPHIC, 3); 092 ORIENTATIONS[AxisDirection.EAST .ordinal()] = new Orientation(Space.GEOGRAPHIC, 4); 093 ORIENTATIONS[AxisDirection.EAST_SOUTH_EAST .ordinal()] = new Orientation(Space.GEOGRAPHIC, 5); 094 ORIENTATIONS[AxisDirection.SOUTH_EAST .ordinal()] = new Orientation(Space.GEOGRAPHIC, 6); 095 ORIENTATIONS[AxisDirection.SOUTH_SOUTH_EAST .ordinal()] = new Orientation(Space.GEOGRAPHIC, 7); 096 ORIENTATIONS[AxisDirection.SOUTH .ordinal()] = new Orientation(Space.GEOGRAPHIC, 8); 097 ORIENTATIONS[AxisDirection.SOUTH_SOUTH_WEST .ordinal()] = new Orientation(Space.GEOGRAPHIC, 9); 098 ORIENTATIONS[AxisDirection.SOUTH_WEST .ordinal()] = new Orientation(Space.GEOGRAPHIC, 10); 099 ORIENTATIONS[AxisDirection.WEST_SOUTH_WEST .ordinal()] = new Orientation(Space.GEOGRAPHIC, 11); 100 ORIENTATIONS[AxisDirection.WEST .ordinal()] = new Orientation(Space.GEOGRAPHIC, 12); 101 ORIENTATIONS[AxisDirection.WEST_NORTH_WEST .ordinal()] = new Orientation(Space.GEOGRAPHIC, 13); 102 ORIENTATIONS[AxisDirection.NORTH_WEST .ordinal()] = new Orientation(Space.GEOGRAPHIC, 14); 103 ORIENTATIONS[AxisDirection.NORTH_NORTH_WEST .ordinal()] = new Orientation(Space.GEOGRAPHIC, 15); 104 ORIENTATIONS[AxisDirection.ROW_NEGATIVE .ordinal()] = new Orientation(Space.MATRIX, 0); 105 ORIENTATIONS[AxisDirection.COLUMN_POSITIVE .ordinal()] = new Orientation(Space.MATRIX, 4); 106 ORIENTATIONS[AxisDirection.ROW_POSITIVE .ordinal()] = new Orientation(Space.MATRIX, 8); 107 ORIENTATIONS[AxisDirection.COLUMN_NEGATIVE .ordinal()] = new Orientation(Space.MATRIX, 12); 108 ORIENTATIONS[AxisDirection.DISPLAY_UP .ordinal()] = new Orientation(Space.DISPLAY, 0); 109 ORIENTATIONS[AxisDirection.DISPLAY_RIGHT .ordinal()] = new Orientation(Space.DISPLAY, 4); 110 ORIENTATIONS[AxisDirection.DISPLAY_DOWN .ordinal()] = new Orientation(Space.DISPLAY, 8); 111 ORIENTATIONS[AxisDirection.DISPLAY_LEFT .ordinal()] = new Orientation(Space.DISPLAY, 12); 112 ORIENTATIONS[AxisDirection.FORWARD .ordinal()] = new Orientation(Space.ENGINEERING, 0); 113 ORIENTATIONS[AxisDirection.STARBOARD .ordinal()] = new Orientation(Space.ENGINEERING, 4); 114 ORIENTATIONS[AxisDirection.AFT .ordinal()] = new Orientation(Space.ENGINEERING, 8); 115 ORIENTATIONS[AxisDirection.PORT .ordinal()] = new Orientation(Space.ENGINEERING, 12); 116 } 117 118 /** 119 * Creates a new validator instance. 120 * 121 * @param container the set of validators to use for validating other kinds of objects 122 * (see {@linkplain #container field javadoc}). 123 */ 124 public CSValidator(final ValidatorContainer container) { 125 super(container, "org.opengis.referencing.cs"); 126 } 127 128 /** 129 * For each interface implemented by the given object, invokes the corresponding 130 * {@code validate(…)} method defined in this class (if any). 131 * 132 * @param object the object to dispatch to {@code validate(…)} methods, or {@code null}. 133 * @return number of {@code validate(…)} methods invoked in this class for the given object. 134 */ 135 @SuppressWarnings("deprecation") 136 public int dispatch(final CoordinateSystem object) { 137 int n = 0; 138 if (object != null) { 139 if (object instanceof CartesianCS) {validate((CartesianCS) object); n++;} 140 if (object instanceof EllipsoidalCS) {validate((EllipsoidalCS) object); n++;} 141 if (object instanceof SphericalCS) {validate((SphericalCS) object); n++;} 142 if (object instanceof CylindricalCS) {validate((CylindricalCS) object); n++;} 143 if (object instanceof PolarCS) {validate((PolarCS) object); n++;} 144 if (object instanceof LinearCS) {validate((LinearCS) object); n++;} 145 if (object instanceof VerticalCS) {validate((VerticalCS) object); n++;} 146 if (object instanceof TimeCS) {validate((TimeCS) object); n++;} 147 if (object instanceof UserDefinedCS) {validate((UserDefinedCS) object); n++;} 148 if (n == 0) { 149 validateIdentifiedObject(object); 150 validateAxes(object); 151 } 152 } 153 return n; 154 } 155 156 /** 157 * Validates the given axis. 158 * 159 * @param object the object to validate, or {@code null}. 160 */ 161 public void validate(final CoordinateSystemAxis object) { 162 if (object == null) { 163 return; 164 } 165 validateIdentifiedObject(object); 166 mandatory(object.getAbbreviation(), "CoordinateSystemAxis: abbreviation is mandatory."); 167 mandatory(object.getUnit(), "CoordinateSystemAxis: unit is mandatory."); 168 assertValidRange(object.getMinimumValue(), object.getMaximumValue(), 169 "CoordinateSystemAxis: expected maximum >= minimum."); 170 } 171 172 /** 173 * Validates the given coordinate system. This method ensures that 174 * {@linkplain CoordinateSystemAxis#getDirection() axis directions} 175 * are perpendicular to each other. Only known or compatibles directions are compared 176 * (e.g. {@code NORTH} with {@code EAST}). Unknown or incompatible directions 177 * (e.g. {@code NORTH} with {@code FUTURE}) are ignored. 178 * 179 * @param object the object to validate, or {@code null}. 180 */ 181 public void validate(final CartesianCS object) { 182 if (object == null) { 183 return; 184 } 185 validateIdentifiedObject(object); 186 validateAxes(object); 187 final Set<AxisDirection> axes = getAxisDirections(object); 188 validate(axes); 189 assertPerpendicularAxes(axes); 190 } 191 192 /** 193 * Validates the given coordinate system. 194 * 195 * @param object the object to validate, or {@code null}. 196 */ 197 public void validate(final EllipsoidalCS object) { 198 if (object == null) { 199 return; 200 } 201 validateIdentifiedObject(object); 202 validateAxes(object); 203 final int dimension = object.getDimension(); 204 assertBetween(2, 3, dimension, "EllipsoidalCS: wrong number of dimensions."); 205 } 206 207 /** 208 * Validates the given coordinate system. 209 * 210 * @param object the object to validate, or {@code null}. 211 */ 212 public void validate(final SphericalCS object) { 213 if (object == null) { 214 return; 215 } 216 validateIdentifiedObject(object); 217 validateAxes(object); 218 final int dimension = object.getDimension(); 219 assertEquals(3, dimension, "SphericalCS: wrong number of dimensions."); 220 } 221 222 /** 223 * Validates the given coordinate system. 224 * 225 * @param object the object to validate, or {@code null}. 226 */ 227 public void validate(final CylindricalCS object) { 228 if (object == null) { 229 return; 230 } 231 validateIdentifiedObject(object); 232 validateAxes(object); 233 final int dimension = object.getDimension(); 234 assertEquals(3, dimension, "CylindricalCS: wrong number of dimensions."); 235 } 236 237 /** 238 * Validates the given coordinate system. 239 * 240 * @param object the object to validate, or {@code null}. 241 */ 242 public void validate(final PolarCS object) { 243 if (object == null) { 244 return; 245 } 246 validateIdentifiedObject(object); 247 validateAxes(object); 248 final int dimension = object.getDimension(); 249 assertEquals(2, dimension, "PolarCS: wrong number of dimensions."); 250 } 251 252 /** 253 * Validates the given coordinate system. 254 * 255 * @param object the object to validate, or {@code null}. 256 */ 257 public void validate(final LinearCS object) { 258 if (object == null) { 259 return; 260 } 261 validateIdentifiedObject(object); 262 validateAxes(object); 263 final int dimension = object.getDimension(); 264 assertEquals(1, dimension, "LinearCS: wrong number of dimensions."); 265 } 266 267 /** 268 * Validates the given coordinate system. 269 * 270 * @param object the object to validate, or {@code null}. 271 */ 272 public void validate(final VerticalCS object) { 273 if (object == null) { 274 return; 275 } 276 validateIdentifiedObject(object); 277 validateAxes(object); 278 final int dimension = object.getDimension(); 279 assertEquals(1, dimension, "VerticalCS: wrong number of dimensions."); 280 } 281 282 /** 283 * Validates the given coordinate system. 284 * 285 * @param object the object to validate, or {@code null}. 286 */ 287 public void validate(final TimeCS object) { 288 if (object == null) { 289 return; 290 } 291 validateIdentifiedObject(object); 292 validateAxes(object); 293 final int dimension = object.getDimension(); 294 assertEquals(1, dimension, "TimeCS: wrong number of dimensions."); 295 } 296 297 /** 298 * Validates the given coordinate system. 299 * 300 * @param object the object to validate, or {@code null}. 301 */ 302 @Deprecated(since="3.1") 303 public void validate(final UserDefinedCS object) { 304 if (object == null) { 305 return; 306 } 307 validateIdentifiedObject(object); 308 validateAxes(object); 309 final int dimension = object.getDimension(); 310 assertBetween(2, 3, dimension, "UserDefinedCS: wrong number of dimensions."); 311 } 312 313 /** 314 * Performs the validation that are common to all coordinate systems. This method is 315 * invoked by {@code validate} methods after they have determined the type of their 316 * argument. 317 * 318 * @param object the object to validate (cannot be null). 319 */ 320 private void validateAxes(final CoordinateSystem object) { 321 final int dimension = object.getDimension(); 322 assertStrictlyPositive(dimension, "CoordinateSystem: dimension must be greater than zero."); 323 for (int i=0; i<dimension; i++) { 324 final CoordinateSystemAxis axis = object.getAxis(i); 325 mandatory(axis, "CoordinateSystem: axis cannot be null."); 326 validate(axis); 327 } 328 } 329 330 /** 331 * Asserts that the given set of axis directions are perpendicular. 332 * Only known or compatibles directions are compared (e.g. {@code NORTH} with {@code EAST}). 333 * Unknown or incompatible directions (e.g. {@code NORTH} with {@code FUTURE}) are ignored. 334 * 335 * <p>The given collection will be modified; do not pass a valuable collection!</p> 336 * 337 * @param directions the axis directions to verify. 338 */ 339 static void assertPerpendicularAxes(final Iterable<AxisDirection> directions) { 340 Iterator<AxisDirection> it; 341 while ((it = directions.iterator()).hasNext()) { 342 AxisDirection refDirection = null; 343 Orientation ref = null; 344 do { 345 final AxisDirection direction = it.next(); 346 if (direction.ordinal() < ORIENTATIONS.length) { 347 final Orientation other = ORIENTATIONS[direction.ordinal()]; 348 if (other != null) { 349 if (ref == null) { 350 ref = other; 351 refDirection = direction; 352 } else { 353 /* 354 * At this point, we got a pair of orientations to compare. 355 * We will perform the comparison only if they are compatible. 356 */ 357 if (ref.category.equals(other.category)) { 358 // Get the angle as a multiple of 22.5°. 359 // An angle of 4 units is 90°. 360 final int angle = other.orientation - ref.orientation; 361 if ((angle % 4) != 0) { 362 fail("Found an angle of " + (angle * Orientation.ANGLE_UNIT) + 363 "° between axis directions " + refDirection.name() + 364 " and " + direction.name() + '.'); 365 } 366 } 367 /* 368 * Do not remove the `other` axis direction, because 369 * we want to compare it again in other pairs of axes. 370 */ 371 continue; 372 } 373 } 374 } 375 it.remove(); 376 } while (it.hasNext()); 377 } 378 } 379}