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.List; 035 036import org.opengis.referencing.operation.*; 037import org.opengis.parameter.ParameterValueGroup; 038import org.opengis.referencing.crs.CoordinateReferenceSystem; 039 040import org.opengis.test.ValidatorContainer; 041import static org.opengis.test.Assert.*; 042 043 044/** 045 * Validates {@link CoordinateOperation} and related objects from the 046 * {@code org.opengis.referencing.operation} package. 047 * 048 * <p>This class is provided for users wanting to override the validation methods. When the default 049 * behavior is sufficient, the {@link org.opengis.test.Validators} static methods provide a more 050 * convenient way to validate various kinds of objects.</p> 051 * 052 * @author Martin Desruisseaux (Geomatys) 053 * @version 3.1 054 * @since 2.2 055 */ 056public class OperationValidator extends ReferencingValidator { 057 /** 058 * Creates a new validator instance. 059 * 060 * @param container the set of validators to use for validating other kinds of objects 061 * (see {@linkplain #container field javadoc}). 062 */ 063 public OperationValidator(final ValidatorContainer container) { 064 super(container, "org.opengis.referencing.operation"); 065 } 066 067 /** 068 * For each interface implemented by the given object, invokes the corresponding 069 * {@code validate(…)} method defined in this class (if any). 070 * 071 * @param object the object to dispatch to {@code validate(…)} methods, or {@code null}. 072 * @return number of {@code validate(…)} methods invoked in this class for the given object. 073 */ 074 public int dispatch(final CoordinateOperation object) { 075 int n = 0; 076 if (object != null) { 077 if (object instanceof Conversion) {validate((Conversion) object); n++;} 078 if (object instanceof Transformation) {validate((Transformation) object); n++;} 079 if (object instanceof ConcatenatedOperation) {validate((ConcatenatedOperation) object); n++;} 080 if (object instanceof PassThroughOperation) {validate((PassThroughOperation) object); n++;} 081 if (n == 0) { 082 if (object instanceof SingleOperation) { 083 validateOperation((SingleOperation) object); 084 } else { 085 validateCoordinateOperation(object); 086 } 087 } 088 } 089 return n; 090 } 091 092 /** 093 * Validates the given "pass through" operation. 094 * 095 * @param object the object to validate, or {@code null}. 096 */ 097 public void validate(final PassThroughOperation object) { 098 if (object == null) { 099 return; 100 } 101 validateCoordinateOperation(object); 102 final MathTransform transform = object.getMathTransform(); 103 mandatory("PassThroughOperation: shall have a MathTransform.", transform); 104 105 final CoordinateOperation operation = object.getOperation(); 106 mandatory("PassThroughOperation: getOperation() is mandatory.", operation); 107 assertNotSame("PassThroughOperation: getOperation() can't be this.", object, operation); 108 dispatch(operation); 109 110 final int[] index = object.getModifiedCoordinates(); 111 mandatory("PassThroughOperation: modified coordinates are mandatory.", index); 112 if (operation == null || index == null) { 113 return; 114 } 115 final int sourceDimension = transform.getSourceDimensions(); 116 for (int i : index) { 117 assertBetween("PassThroughOperation: invalid modified coordinate index.", 0, sourceDimension-1, i); 118 } 119 } 120 121 /** 122 * Validates the given concatenated operation. 123 * 124 * @param object the object to validate, or {@code null}. 125 */ 126 public void validate(final ConcatenatedOperation object) { 127 if (object == null) { 128 return; 129 } 130 validateCoordinateOperation(object); 131 final MathTransform transform = object.getMathTransform(); 132 mandatory("ConcatenatedOperation: shall have a MathTransform.", transform); 133 134 final List<? extends CoordinateOperation> operations = object.getOperations(); 135 mandatory("ConcatenatedOperation: shall provide a list of operations.", operations); 136 if (operations == null) { 137 return; 138 } 139 validate(operations); 140 CoordinateOperation first=null, last=null; 141 for (final CoordinateOperation single : operations) { 142 assertNotNull("ConcatenatedOperation: getOperations() can't contain null element.", single); 143 assertNotSame("ConcatenatedOperation: can't contain itself as a single element.", single, object); 144 dispatch(single); 145 if (first == null) { 146 first = single; 147 } else { 148 // Do not validate MathTransform since it is already done by dispatch(single). 149 final MathTransform lastMT = last.getMathTransform(); 150 final MathTransform thisMT = single.getMathTransform(); 151 if (lastMT != null && thisMT != null) { 152 assertEquals("ConcatenatedOperation: source dimension of a single operation " + 153 "must match the target dimension of the previous one.", 154 lastMT.getTargetDimensions(), thisMT.getSourceDimensions()); 155 } 156 // Do not validate CRS since it is already done by dispatch(single). 157 final CoordinateReferenceSystem targetCRS = last.getTargetCRS(); 158 final CoordinateReferenceSystem sourceCRS = single.getSourceCRS(); 159 if (targetCRS != null && sourceCRS != null) { 160 assertEquals("ConcatenatedOperation: source dimension of a single operation " + 161 "must match the target dimension of the previous one.", 162 dimension(targetCRS), dimension(sourceCRS)); 163 } 164 } 165 last = single; 166 } 167 assertNotNull("ConcatenatedOperation: shall contain at least one single operation.", last); 168 if (transform != null) { 169 final MathTransform firstMT = first.getMathTransform(); 170 final MathTransform lastMT = last .getMathTransform(); 171 if (firstMT != null) { 172 assertEquals("ConcatenatedOperation: source dimension must match " + 173 "the source dimension of the first single operation.", 174 firstMT.getSourceDimensions(), transform.getSourceDimensions()); 175 } 176 if (lastMT != null) { 177 assertEquals("ConcatenatedOperation: target dimension must match " + 178 "the target dimension of the last single operation.", 179 lastMT.getTargetDimensions(), transform.getTargetDimensions()); 180 } 181 } 182 final CoordinateReferenceSystem sourceCRS = object.getSourceCRS(); 183 final CoordinateReferenceSystem targetCRS = object.getTargetCRS(); 184 final CoordinateReferenceSystem firstCRS = first .getSourceCRS(); 185 final CoordinateReferenceSystem lastCRS = last .getTargetCRS(); 186 if (sourceCRS != null && firstCRS != null) { 187 assertSame("ConcatenatedOperation: sourceCRS must be the source " + 188 "of the first single operation.", firstCRS, sourceCRS); 189 } 190 if (targetCRS != null && lastCRS != null) { 191 assertSame("ConcatenatedOperation: targetCRS must be the target " + 192 "of the last single operation.", lastCRS, targetCRS); 193 } 194 } 195 196 /** 197 * Validates the given coordinate operation. This method is private because we 198 * choose to expose only non-ambiguous {@code validate} methods in public API. 199 * 200 * @param object the object to validate, or {@code null}. 201 */ 202 private void validateCoordinateOperation(final CoordinateOperation object) { 203 if (object == null) { 204 return; 205 } 206 assertFalse("CoordinateOperation: can't be both a ConcatenatedOperation and a SingleOperation.", 207 (object instanceof ConcatenatedOperation) && (object instanceof SingleOperation)); 208 validateIdentifiedObject(object); 209 container.validate(object.getScope()); 210 container.validate(object.getDomainOfValidity()); 211 212 final CoordinateReferenceSystem sourceCRS = object.getSourceCRS(); 213 final CoordinateReferenceSystem targetCRS = object.getTargetCRS(); 214 container.validate(sourceCRS); 215 container.validate(targetCRS); 216 217 // Note: MathTransform can be null in defining conversion. We will 218 // check for non-null value in more specific validation methods only. 219 final MathTransform transform = object.getMathTransform(); 220 validate(transform); 221 if (transform != null) { 222 if (sourceCRS != null) { 223 assertEquals("CoordinateOperation: MathTransform source dimension must match sourceCRS dimension.", 224 dimension(sourceCRS), transform.getSourceDimensions()); 225 } 226 if (targetCRS != null) { 227 assertEquals("CoordinateOperation: MathTransform target dimension must match targetCRS dimension.", 228 dimension(targetCRS), transform.getTargetDimensions()); 229 } 230 } 231 } 232 233 /** 234 * Validates the given operation. This method is private because we choose 235 * to expose only non-ambiguous {@code validate} methods in public API. 236 * 237 * @param object the object to validate, or {@code null}. 238 */ 239 @SuppressWarnings("UnnecessaryUnboxing") 240 private void validateOperation(final SingleOperation object) { 241 if (object == null) { 242 return; 243 } 244 validateCoordinateOperation(object); 245 assertFalse("Operation: can't be both a Conversion and a Transformation.", 246 (object instanceof Conversion) && (object instanceof Transformation)); 247 248 final OperationMethod method = object.getMethod(); 249 mandatory("Operation: OperationMethod is mandatory.", method); 250 if (method != null) { 251 validate(method); 252 final Integer opSourceDimension = method.getSourceDimensions(); 253 final Integer opTargetDimension = method.getTargetDimensions(); 254 final MathTransform transform = object.getMathTransform(); 255 // Do not validate because it is already done by validateCoordinateOperation(object). 256 if (transform != null) { 257 if (opSourceDimension != null) { 258 assertEquals("Operation: MathTransform source dimension must match OperationMethod source dimension.", 259 opSourceDimension.intValue(), transform.getSourceDimensions()); 260 } 261 if (opTargetDimension != null) { 262 assertEquals("Operation: MathTransform target dimension must match OperationMethod target dimension.", 263 opTargetDimension.intValue(), transform.getTargetDimensions()); 264 } 265 } 266 } 267 final ParameterValueGroup parameters = object.getParameterValues(); 268 mandatory("Operation: ParameterValues are mandatory.", method); 269 container.validate(parameters); 270 } 271 272 /** 273 * Validates the given conversion. 274 * 275 * @param object the object to validate, or {@code null}. 276 */ 277 public void validate(final Conversion object) { 278 if (object == null) { 279 return; 280 } 281 validateOperation(object); 282 assertFalse("Projection: can't be both planar and conic.", 283 (object instanceof PlanarProjection) && (object instanceof ConicProjection)); 284 assertFalse("Projection: can't be both planar and cylindrical.", 285 (object instanceof PlanarProjection) && (object instanceof CylindricalProjection)); 286 assertFalse("Projection: can't be both cylindrical and conic.", 287 (object instanceof CylindricalProjection) && (object instanceof ConicProjection)); 288 289 if (object.getMathTransform() != null) { 290 mandatory("Conversion: non-defining conversion should have a source CRS.", object.getSourceCRS()); 291 mandatory("Conversion: non-defining conversion should have a target CRS.", object.getTargetCRS()); 292 } 293 forbidden("Conversion: should not have operation version.", object.getOperationVersion()); 294 if (object.getMathTransform() == null) { 295 forbidden("Conversion: defining conversion should not have source CRS", object.getSourceCRS()); 296 forbidden("Conversion: defining conversion should not have target CRS", object.getTargetCRS()); 297 } 298 } 299 300 /** 301 * Validates the given transformation. 302 * 303 * @param object the object to validate, or {@code null}. 304 */ 305 public void validate(final Transformation object) { 306 if (object == null) { 307 return; 308 } 309 validateOperation(object); 310 mandatory("Transformation: operationVersion is a mandatory attribute.", object.getOperationVersion()); 311 mandatory("Transformation: sourceCRS is a mandatory attribute.", object.getSourceCRS()); 312 mandatory("Transformation: targetCRS is a mandatory attribute.", object.getTargetCRS()); 313 mandatory("Transformation: MathTransform is a mandatory attribute.", object.getMathTransform()); 314 } 315 316 /** 317 * Validates the given operation method. 318 * 319 * @param object the object to validate, or {@code null}. 320 */ 321 public void validate(final OperationMethod object) { 322 if (object == null) { 323 return; 324 } 325 final Integer sourceDimension = object.getSourceDimensions(); 326 final Integer targetDimension = object.getTargetDimensions(); 327 if (sourceDimension != null) { 328 assertStrictlyPositive("OperationMethod: source dimension must be greater than zero.", sourceDimension); 329 } 330 if (targetDimension != null) { 331 assertStrictlyPositive("OperationMethod: target dimension must be greater than zero.", targetDimension); 332 } 333 validate(object.getFormula()); 334 container.validate(object.getParameters()); 335 validateIdentifiedObject(object); 336 } 337 338 /** 339 * Validates the given formula. 340 * 341 * @param object the object to validate, or {@code null}. 342 */ 343 public void validate(final Formula object) { 344 if (object == null) { 345 return; 346 } 347 container.validate(object.getFormula()); 348 container.validate(object.getCitation()); 349 } 350 351 /** 352 * Validates the given math transform. 353 * 354 * @param object the object to validate, or {@code null}. 355 */ 356 public void validate(final MathTransform object) { 357 if (object == null) { 358 return; 359 } 360 final int sourceDimension = object.getSourceDimensions(); 361 final int targetDimension = object.getTargetDimensions(); 362 assertStrictlyPositive("MathTransform: source dimension must be greater than zero.", sourceDimension); 363 assertStrictlyPositive("MathTransform: target dimension must be greater than zero.", targetDimension); 364 if (object instanceof MathTransform1D) { 365 assertEquals("MathTransform1D: source dimension must be 1.", 1, sourceDimension); 366 assertEquals("MathTransform1D: target dimension must be 1.", 1, targetDimension); 367 } 368 if (object instanceof MathTransform2D) { 369 assertEquals("MathTransform2D: source dimension must be 2.", 2, sourceDimension); 370 assertEquals("MathTransform2D: target dimension must be 2.", 2, targetDimension); 371 } 372 if (object.isIdentity()) { 373 assertEquals("MathTransform: identity transforms must have the same source and target dimensions.", 374 sourceDimension, targetDimension); 375 } 376 } 377 378 /** 379 * Returns the dimension of the given CRS. 380 */ 381 private static int dimension(final CoordinateReferenceSystem crs) { 382 return crs.getCoordinateSystem().getDimension(); 383 } 384}