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.geometry; 033 034import java.util.Arrays; 035 036import org.opengis.geometry.*; 037import org.opengis.referencing.cs.CoordinateSystem; 038import org.opengis.referencing.cs.CoordinateSystemAxis; 039import org.opengis.referencing.crs.CoordinateReferenceSystem; 040import org.opengis.referencing.cs.RangeMeaning; 041 042import org.opengis.test.Validator; 043import org.opengis.test.ValidatorContainer; 044 045import static java.lang.Double.NaN; 046import static java.lang.Double.isNaN; 047import static org.opengis.test.Assert.*; 048 049 050/** 051 * Validates {@link Geometry} and related objects from the {@code org.opengis.geometry} 052 * package. 053 * 054 * <p>This class is provided for users wanting to override the validation methods. When the default 055 * behavior is sufficient, the {@link org.opengis.test.Validators} static methods provide a more 056 * convenient way to validate various kinds of objects.</p> 057 * 058 * @author Martin Desruisseaux (Geomatys) 059 * @version 3.1 060 * @since 2.2 061 */ 062public class GeometryValidator extends Validator { 063 /** 064 * Small relative tolerance values for comparisons of floating point numbers. 065 * The default value is {@value org.opengis.test.Validator#DEFAULT_TOLERANCE}. 066 * Implementors can change this value before to run the tests. 067 */ 068 public double tolerance = DEFAULT_TOLERANCE; 069 070 /** 071 * Creates a new validator instance. 072 * 073 * @param container the set of validators to use for validating other kinds of objects 074 * (see {@linkplain #container field javadoc}). 075 */ 076 public GeometryValidator(final ValidatorContainer container) { 077 super(container, "org.opengis.geometry"); 078 } 079 080 /** 081 * Returns {@code true} if the given range is [+0 … -0]. Such range is used by some implementations 082 * for representing a 360° turn around the Earth. Such convention is of course not mandatory, but 083 * some tests in this class must be aware of it. 084 */ 085 private static boolean isPositiveToNegativeZero(final double lower, final double upper) { 086 return Double.doubleToRawLongBits(lower) == 0L && // Positive zero 087 Double.doubleToRawLongBits(upper) == Long.MIN_VALUE; // Negative zero 088 } 089 090 /** 091 * Validates the given envelope. 092 * This method performs the following verifications: 093 * 094 * <ul> 095 * <li>Envelope and corners dimension shall be the same.</li> 096 * <li>Envelope and corners CRS shall be the same, ignoring {@code null} values.</li> 097 * <li>Lower, upper and median ordinate values shall be inside the [minimum … maximum] range.</li> 098 * <li>Lower > upper ordinate values are allowed only on axis having wraparound range meaning.</li> 099 * <li>For the usual lower < upper case, compares the minimum, maximum, median and span values 100 * against values computed from the lower and upper ordinates.</li> 101 * </ul> 102 * 103 * @param object the object to validate, or {@code null}. 104 */ 105 public void validate(final Envelope object) { 106 if (object == null) { 107 return; 108 } 109 final int dimension = object.getDimension(); 110 assertPositive("Envelope: dimension can not be negative.", dimension); 111 final CoordinateReferenceSystem crs = object.getCoordinateReferenceSystem(); 112 container.validate(crs); // May be null. 113 CoordinateSystem cs = null; 114 if (crs != null) { 115 cs = crs.getCoordinateSystem(); 116 if (cs != null) { 117 assertEquals("Envelope: CRS dimension shall be equal to the envelope dimension", 118 dimension, cs.getDimension()); 119 } 120 } 121 /* 122 * Validates the corners as DirectPosition objects, 123 * then checks Coordinate Reference Systems and dimensions. 124 */ 125 final DirectPosition lowerCorner = object.getLowerCorner(); 126 final DirectPosition upperCorner = object.getUpperCorner(); 127 mandatory("Envelope: shall have a lower corner.", lowerCorner); 128 mandatory("Envelope: shall have an upper corner.", upperCorner); 129 validate(lowerCorner); 130 validate(upperCorner); 131 CoordinateReferenceSystem lowerCRS = null; 132 CoordinateReferenceSystem upperCRS = null; 133 if (lowerCorner != null) { 134 lowerCRS = lowerCorner.getCoordinateReferenceSystem(); 135 assertEquals("Envelope: lower corner dimension shall be equal to the envelope dimension.", 136 dimension, lowerCorner.getDimension()); 137 } 138 if (upperCorner != null) { 139 upperCRS = upperCorner.getCoordinateReferenceSystem(); 140 assertEquals("Envelope: upper corner dimension shall be equal to the envelope dimension.", 141 dimension, upperCorner.getDimension()); 142 } 143 if (crs != null) { 144 if (lowerCRS != null) assertSame("Envelope: lower CRS shall be the same than the envelope CRS.", crs, lowerCRS); 145 if (upperCRS != null) assertSame("Envelope: upper CRS shall be the same than the envelope CRS.", crs, upperCRS); 146 } else if (lowerCRS != null && upperCRS != null) { 147 assertSame("Envelope: the two corners shall have the same CRS.", lowerCRS, upperCRS); 148 } 149 /* 150 * Verifies the consistency of lower, upper, minimum, maximum, median and span values. 151 * The tests are relaxed in the case of ranges spanning the wraparound limit (e.g. the 152 * anti-meridian). 153 */ 154 for (int i=0; i<dimension; i++) { 155 RangeMeaning meaning = null; 156 if (cs != null) { 157 final CoordinateSystemAxis axis = cs.getAxis(i); 158 if (axis != null) { // Should never be null, but this is not this test's job to ensure that. 159 meaning = axis.getRangeMeaning(); 160 } 161 } 162 final double lower = (lowerCorner != null) ? lowerCorner.getOrdinate(i) : NaN; 163 final double upper = (upperCorner != null) ? upperCorner.getOrdinate(i) : NaN; 164 final double minimum = object.getMinimum(i); 165 final double maximum = object.getMaximum(i); 166 final double median = object.getMedian (i); 167 final double span = object.getSpan (i); 168 if (!isNaN(minimum) && !isNaN(maximum)) { 169 if (lower <= upper && !isPositiveToNegativeZero(lower, upper)) { // Do not accept NaN in this block. 170 final double eps = (upper - lower) * tolerance; 171 assertEquals("Envelope: minimum value shall be equal to the lower corner ordinate.", lower, minimum, eps); 172 assertEquals("Envelope: maximum value shall be equal to the upper corner ordinate.", upper, maximum, eps); 173 assertEquals("Envelope: unexpected span value.", (maximum - minimum), span, eps); 174 assertEquals("Envelope: unexpected median value.", (maximum + minimum)/2, median, eps); 175 } else if (RangeMeaning.EXACT.equals(meaning)) { 176 // assertBetween(…) tolerates NaN values, which is what we want. 177 assertValidRange("Envelope: invalid minimum or maximum.", minimum, maximum); 178 assertBetween ("Envelope: invalid lower ordinate.", minimum, maximum, lower); 179 assertBetween ("Envelope: invalid upper ordinate.", minimum, maximum, upper); 180 assertBetween ("Envelope: invalid median ordinate.", minimum, maximum, median); 181 } 182 } 183 if (meaning != null && (lower > upper || isPositiveToNegativeZero(lower, upper))) { 184 assertEquals("Envelope: lower ordinate value may be greater than upper ordinate value " 185 + "only on axis having wrappround range.", RangeMeaning.WRAPAROUND, meaning); 186 } 187 } 188 } 189 190 /** 191 * Validates the given position. 192 * This method ensures that the following hold: 193 * 194 * <ul> 195 * <li>The number of dimension can not be negative.</li> 196 * <li>If the position is associated to a CRS, then their number of dimensions must be equal.</li> 197 * <li>Length of {@link DirectPosition#getCoordinate()} must be equals to the number of dimensions.</li> 198 * <li>Values of above array must be equals to values returned by {@link DirectPosition#getOrdinate(int)}.</li> 199 * <li>If the position is associated to a CRS and the axis range meaning is {@link RangeMeaning#EXACT}, 200 * then the ordinate values must be between the minimum and maximum axis value.</li> 201 * </ul> 202 * 203 * @param object the object to validate, or {@code null}. 204 */ 205 public void validate(final DirectPosition object) { 206 if (object == null) { 207 return; 208 } 209 /* 210 * Checks coordinate consistency. 211 */ 212 final int dimension = object.getDimension(); 213 assertPositive("DirectPosition: dimension can not be negative.", dimension); 214 final double[] coordinate = object.getCoordinate(); 215 mandatory("DirectPosition: coordinate array can not be null.", coordinate); 216 if (coordinate != null) { 217 assertEquals("DirectPosition: coordinate array length shall be equal to the dimension.", 218 dimension, coordinate.length); 219 for (int i=0; i<dimension; i++) { 220 assertEquals("DirectPosition: getOrdinate(i) shall be the same than coordinate[i].", 221 coordinate[i], object.getOrdinate(i), 0.0); // No tolerance - we want exact match. 222 } 223 } 224 /* 225 * Checks coordinate validity in the CRS. 226 */ 227 final CoordinateReferenceSystem crs = object.getCoordinateReferenceSystem(); 228 container.validate(crs); // May be null. 229 int hashCode = 0; 230 if (crs != null) { 231 final CoordinateSystem cs = crs.getCoordinateSystem(); // Assume already validated. 232 if (cs != null) { 233 assertEquals("DirectPosition: CRS dimension must matches the position dimension.", 234 dimension, cs.getDimension()); 235 for (int i=0; i<dimension; i++) { 236 final CoordinateSystemAxis axis = cs.getAxis(i); // Assume already validated. 237 if (axis != null && RangeMeaning.EXACT.equals(axis.getRangeMeaning())) { 238 final double ordinate = coordinate[i]; 239 final double minimum = axis.getMinimumValue(); 240 final double maximum = axis.getMaximumValue(); 241 assertBetween("DirectPosition: ordinate out of axis bounds.", minimum, maximum, ordinate); 242 } 243 } 244 } 245 hashCode = crs.hashCode(); 246 } 247 /* 248 * Tests hash code values. It must be compliant to DirectPosition.hashCode() 249 * contract stated in the javadoc. 250 */ 251 hashCode += Arrays.hashCode(coordinate); 252 assertEquals("DirectPosition: hashCode shall be compliant to the contract given in javadoc.", 253 hashCode, object.hashCode()); 254 assertTrue("DirectPosition: shall be equal to itself.", object.equals(object)); 255 /* 256 * Ensures that the array returned by DirectPosition.getCoordinate() is a clone. 257 */ 258 for (int i=0; i<dimension; i++) { 259 final double oldValue = coordinate[i]; 260 coordinate[i] *= 2; 261 assertEquals("DirectPosition: coordinate array shall be cloned.", 262 oldValue, object.getOrdinate(i), 0.0); // No tolerance - we want exact match. 263 } 264 } 265}