001/* 002 * GeoAPI - Java interfaces for OGC/ISO standards 003 * http://www.geoapi.org 004 * 005 * Copyright (C) 2008-2019 Open Geospatial Consortium, Inc. 006 * All Rights Reserved. http://www.opengeospatial.org/ogc/legal 007 * 008 * Permission to use, copy, and modify this software and its documentation, with 009 * or without modification, for any purpose and without fee or royalty is hereby 010 * granted, provided that you include the following on ALL copies of the software 011 * and documentation or portions thereof, including modifications, that you make: 012 * 013 * 1. The full text of this NOTICE in a location viewable to users of the 014 * redistributed or derivative work. 015 * 2. Notice of any changes or modifications to the OGC files, including the 016 * date changes were made. 017 * 018 * THIS SOFTWARE AND DOCUMENTATION IS PROVIDED "AS IS," AND COPYRIGHT HOLDERS MAKE 019 * NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 020 * TO, WARRANTIES OF MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR PURPOSE OR THAT 021 * THE USE OF THE SOFTWARE OR DOCUMENTATION WILL NOT INFRINGE ANY THIRD PARTY 022 * PATENTS, COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS. 023 * 024 * COPYRIGHT HOLDERS WILL NOT BE LIABLE FOR ANY DIRECT, INDIRECT, SPECIAL OR 025 * CONSEQUENTIAL DAMAGES ARISING OUT OF ANY USE OF THE SOFTWARE OR DOCUMENTATION. 026 * 027 * The name and trademarks of copyright holders may NOT be used in advertising or 028 * publicity pertaining to the software without specific, written prior permission. 029 * Title to copyright in this software and any associated documentation will at all 030 * times remain with copyright holders. 031 */ 032package org.opengis.test.referencing; 033 034import java.util.Set; 035import java.util.Iterator; 036import org.opengis.referencing.cs.*; 037import org.opengis.test.ValidatorContainer; 038 039import static org.opengis.test.Assert.*; 040import static org.opengis.test.referencing.Utilities.*; 041 042 043/** 044 * Validates {@link CoordinateSystem} and related objects from the {@code org.opengis.referencing.cs} 045 * package. 046 * 047 * <p>This class is provided for users wanting to override the validation methods. When the default 048 * behavior is sufficient, the {@link org.opengis.test.Validators} static methods provide a more 049 * convenient way to validate various kinds of objects.</p> 050 * 051 * @author Martin Desruisseaux (Geomatys) 052 * @version 3.1 053 * @since 2.2 054 * 055 * @todo Add checks for Unit of Measurement depending on the coordinate system type. 056 * For example {@link EllipsoidalCS} expects two angular values and one linear 057 * value (if 3D). 058 */ 059public class CSValidator extends ReferencingValidator { 060 /** 061 * Orientation of an {@link AxisDirection}, used to detect if axes are perpendicular. 062 */ 063 static final class Orientation { 064 /** Amount of degrees in one angle unit. */ static final double ANGLE_UNIT = 22.5; 065 /** Geographic, matrix or display. */ final String category; 066 /** Orientation as a multiple of 22.5°. */ final int orientation; 067 /** Creates a new {@code Orientation}. */ 068 Orientation(final String category, final int orientation) { 069 this.category = category; 070 this.orientation = orientation; 071 } 072 073 /** String representation for debugging purpose only. */ 074 @Override public String toString() { 075 return category + ':' + (orientation * ANGLE_UNIT) + '°'; 076 } 077 } 078 079 /** 080 * The orientation of each {@link AxisDirection} enumeration elements. This is used 081 * by {@link #validate(CartesianCS)} for asserting that axes are perpendicular. 082 */ 083 static final Orientation[] ORIENTATIONS = new Orientation[32]; 084 static { 085 ORIENTATIONS[AxisDirection.NORTH .ordinal()] = new Orientation("geographic", 0); 086 ORIENTATIONS[AxisDirection.NORTH_NORTH_EAST .ordinal()] = new Orientation("geographic", 1); 087 ORIENTATIONS[AxisDirection.NORTH_EAST .ordinal()] = new Orientation("geographic", 2); 088 ORIENTATIONS[AxisDirection.EAST_NORTH_EAST .ordinal()] = new Orientation("geographic", 3); 089 ORIENTATIONS[AxisDirection.EAST .ordinal()] = new Orientation("geographic", 4); 090 ORIENTATIONS[AxisDirection.EAST_SOUTH_EAST .ordinal()] = new Orientation("geographic", 5); 091 ORIENTATIONS[AxisDirection.SOUTH_EAST .ordinal()] = new Orientation("geographic", 6); 092 ORIENTATIONS[AxisDirection.SOUTH_SOUTH_EAST .ordinal()] = new Orientation("geographic", 7); 093 ORIENTATIONS[AxisDirection.SOUTH .ordinal()] = new Orientation("geographic", 8); 094 ORIENTATIONS[AxisDirection.SOUTH_SOUTH_WEST .ordinal()] = new Orientation("geographic", 9); 095 ORIENTATIONS[AxisDirection.SOUTH_WEST .ordinal()] = new Orientation("geographic", 10); 096 ORIENTATIONS[AxisDirection.WEST_SOUTH_WEST .ordinal()] = new Orientation("geographic", 11); 097 ORIENTATIONS[AxisDirection.WEST .ordinal()] = new Orientation("geographic", 12); 098 ORIENTATIONS[AxisDirection.WEST_NORTH_WEST .ordinal()] = new Orientation("geographic", 13); 099 ORIENTATIONS[AxisDirection.NORTH_WEST .ordinal()] = new Orientation("geographic", 14); 100 ORIENTATIONS[AxisDirection.NORTH_NORTH_WEST .ordinal()] = new Orientation("geographic", 15); 101 ORIENTATIONS[AxisDirection.ROW_NEGATIVE .ordinal()] = new Orientation("matrix", 0); 102 ORIENTATIONS[AxisDirection.COLUMN_POSITIVE .ordinal()] = new Orientation("matrix", 4); 103 ORIENTATIONS[AxisDirection.ROW_POSITIVE .ordinal()] = new Orientation("matrix", 8); 104 ORIENTATIONS[AxisDirection.COLUMN_NEGATIVE .ordinal()] = new Orientation("matrix", 12); 105 ORIENTATIONS[AxisDirection.DISPLAY_UP .ordinal()] = new Orientation("display", 0); 106 ORIENTATIONS[AxisDirection.DISPLAY_RIGHT .ordinal()] = new Orientation("display", 4); 107 ORIENTATIONS[AxisDirection.DISPLAY_DOWN .ordinal()] = new Orientation("display", 8); 108 ORIENTATIONS[AxisDirection.DISPLAY_LEFT .ordinal()] = new Orientation("display", 12); 109 } 110 111 /** 112 * Creates a new validator instance. 113 * 114 * @param container the set of validators to use for validating other kinds of objects 115 * (see {@linkplain #container field javadoc}). 116 */ 117 public CSValidator(final ValidatorContainer container) { 118 super(container, "org.opengis.referencing.cs"); 119 } 120 121 /** 122 * For each interface implemented by the given object, invokes the corresponding 123 * {@code validate(…)} method defined in this class (if any). 124 * 125 * @param object the object to dispatch to {@code validate(…)} methods, or {@code null}. 126 * @return number of {@code validate(…)} methods invoked in this class for the given object. 127 */ 128 public int dispatch(final CoordinateSystem object) { 129 int n = 0; 130 if (object != null) { 131 if (object instanceof CartesianCS) {validate((CartesianCS) object); n++;} 132 if (object instanceof EllipsoidalCS) {validate((EllipsoidalCS) object); n++;} 133 if (object instanceof SphericalCS) {validate((SphericalCS) object); n++;} 134 if (object instanceof CylindricalCS) {validate((CylindricalCS) object); n++;} 135 if (object instanceof PolarCS) {validate((PolarCS) object); n++;} 136 if (object instanceof LinearCS) {validate((LinearCS) object); n++;} 137 if (object instanceof VerticalCS) {validate((VerticalCS) object); n++;} 138 if (object instanceof TimeCS) {validate((TimeCS) object); n++;} 139 if (object instanceof UserDefinedCS) {validate((UserDefinedCS) object); n++;} 140 if (n == 0) { 141 validateIdentifiedObject(object); 142 validateAxes(object); 143 } 144 } 145 return n; 146 } 147 148 /** 149 * Validates the given axis. 150 * 151 * @param object the object to validate, or {@code null}. 152 */ 153 public void validate(final CoordinateSystemAxis object) { 154 if (object == null) { 155 return; 156 } 157 validateIdentifiedObject(object); 158 mandatory("CoordinateSystemAxis: abbreviation is mandatory.", object.getAbbreviation()); 159 mandatory("CoordinateSystemAxis: unit is mandatory.", object.getUnit()); 160 assertValidRange("CoordinateSystemAxis: expected maximum >= minimum.", 161 object.getMinimumValue(), object.getMaximumValue()); 162 } 163 164 /** 165 * Validates the given coordinate system. This method ensures that 166 * {@linkplain CoordinateSystemAxis#getDirection() axis directions} 167 * are perpendicular to each other. Only known or compatibles directions are compared 168 * (e.g. {@code NORTH} with {@code EAST}). Unknown or incompatible directions 169 * (e.g. {@code NORTH} with {@code FUTURE}) are ignored. 170 * 171 * @param object the object to validate, or {@code null}. 172 */ 173 public void validate(final CartesianCS object) { 174 if (object == null) { 175 return; 176 } 177 validateIdentifiedObject(object); 178 validateAxes(object); 179 final Set<AxisDirection> axes = getAxisDirections(object); 180 validate(axes); 181 assertPerpendicularAxes(axes); 182 } 183 184 /** 185 * Validates the given coordinate system. 186 * 187 * @param object the object to validate, or {@code null}. 188 */ 189 public void validate(final EllipsoidalCS object) { 190 if (object == null) { 191 return; 192 } 193 validateIdentifiedObject(object); 194 validateAxes(object); 195 final int dimension = object.getDimension(); 196 assertBetween("EllipsoidalCS: wrong number of dimensions.", 2, 3, dimension); 197 } 198 199 /** 200 * Validates the given coordinate system. 201 * 202 * @param object the object to validate, or {@code null}. 203 */ 204 public void validate(final SphericalCS object) { 205 if (object == null) { 206 return; 207 } 208 validateIdentifiedObject(object); 209 validateAxes(object); 210 final int dimension = object.getDimension(); 211 assertEquals("SphericalCS: wrong number of dimensions.", 3, dimension); 212 } 213 214 /** 215 * Validates the given coordinate system. 216 * 217 * @param object the object to validate, or {@code null}. 218 */ 219 public void validate(final CylindricalCS object) { 220 if (object == null) { 221 return; 222 } 223 validateIdentifiedObject(object); 224 validateAxes(object); 225 final int dimension = object.getDimension(); 226 assertEquals("CylindricalCS: wrong number of dimensions.", 3, dimension); 227 } 228 229 /** 230 * Validates the given coordinate system. 231 * 232 * @param object the object to validate, or {@code null}. 233 */ 234 public void validate(final PolarCS object) { 235 if (object == null) { 236 return; 237 } 238 validateIdentifiedObject(object); 239 validateAxes(object); 240 final int dimension = object.getDimension(); 241 assertEquals("PolarCS: wrong number of dimensions.", 2, dimension); 242 } 243 244 /** 245 * Validates the given coordinate system. 246 * 247 * @param object the object to validate, or {@code null}. 248 */ 249 public void validate(final LinearCS object) { 250 if (object == null) { 251 return; 252 } 253 validateIdentifiedObject(object); 254 validateAxes(object); 255 final int dimension = object.getDimension(); 256 assertEquals("LinearCS: wrong number of dimensions.", 1, dimension); 257 } 258 259 /** 260 * Validates the given coordinate system. 261 * 262 * @param object the object to validate, or {@code null}. 263 */ 264 public void validate(final VerticalCS object) { 265 if (object == null) { 266 return; 267 } 268 validateIdentifiedObject(object); 269 validateAxes(object); 270 final int dimension = object.getDimension(); 271 assertEquals("VerticalCS: wrong number of dimensions.", 1, dimension); 272 } 273 274 /** 275 * Validates the given coordinate system. 276 * 277 * @param object the object to validate, or {@code null}. 278 */ 279 public void validate(final TimeCS object) { 280 if (object == null) { 281 return; 282 } 283 validateIdentifiedObject(object); 284 validateAxes(object); 285 final int dimension = object.getDimension(); 286 assertEquals("TimeCS: wrong number of dimensions.", 1, dimension); 287 } 288 289 /** 290 * Validates the given coordinate system. 291 * 292 * @param object the object to validate, or {@code null}. 293 */ 294 public void validate(final UserDefinedCS object) { 295 if (object == null) { 296 return; 297 } 298 validateIdentifiedObject(object); 299 validateAxes(object); 300 final int dimension = object.getDimension(); 301 assertBetween("UserDefinedCS: wrong number of dimensions.", 2, 3, dimension); 302 } 303 304 /** 305 * Performs the validation that are common to all coordinate systems. This method is 306 * invoked by {@code validate} methods after they have determined the type of their 307 * argument. 308 * 309 * @param object the object to validate (can not be null). 310 */ 311 private void validateAxes(final CoordinateSystem object) { 312 final int dimension = object.getDimension(); 313 assertStrictlyPositive("CoordinateSystem: dimension must be greater than zero.", dimension); 314 for (int i=0; i<dimension; i++) { 315 final CoordinateSystemAxis axis = object.getAxis(i); 316 mandatory("CoordinateSystem: axis can't be null.", axis); 317 validate(axis); 318 } 319 } 320 321 /** 322 * Asserts that the given set of axis directions are perpendicular. 323 * Only known or compatibles directions are compared (e.g. {@code NORTH} with {@code EAST}). 324 * Unknown or incompatible directions (e.g. {@code NORTH} with {@code FUTURE}) are ignored. 325 * 326 * <p>The given collection will be modified; do not pass a valuable collection!</p> 327 */ 328 static void assertPerpendicularAxes(final Iterable<AxisDirection> directions) { 329 Iterator<AxisDirection> it; 330 while ((it = directions.iterator()).hasNext()) { 331 AxisDirection refDirection = null; 332 Orientation ref = null; 333 do { 334 final AxisDirection direction = it.next(); 335 if (direction.ordinal() < ORIENTATIONS.length) { 336 final Orientation other = ORIENTATIONS[direction.ordinal()]; 337 if (other != null) { 338 if (ref == null) { 339 ref = other; 340 refDirection = direction; 341 } else { 342 // At this point, we got a pair of orientations to compare. 343 // We will perform the comparison only if they are compatible. 344 if (ref.category.equals(other.category)) { 345 // Get the angle as a multiple of 22.5°. 346 // An angle of 4 units is 90°. 347 final int angle = other.orientation - ref.orientation; 348 if ((angle % 4) != 0) { 349 fail("Found an angle of " + (angle * Orientation.ANGLE_UNIT) + 350 "° between axis directions " + refDirection.name() + 351 " and " + direction.name() + '.'); 352 } 353 } 354 // Do not remove the 'other' axis direction, since we 355 // want to compare it again in other pairs of axes. 356 continue; 357 } 358 } 359 } 360 it.remove(); 361 } while (it.hasNext()); 362 } 363 } 364}