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.Collection; 021import java.util.function.Consumer; 022import java.util.function.Supplier; 023import javax.measure.Unit; 024import javax.measure.quantity.Angle; 025import javax.measure.quantity.Length; 026 027import org.opengis.referencing.datum.*; 028import org.opengis.referencing.IdentifiedObject; 029import org.opengis.test.ValidatorContainer; 030 031import static java.lang.Double.POSITIVE_INFINITY; 032import static org.junit.jupiter.api.Assertions.*; 033import static org.opengis.test.Assertions.assertBetween; 034 035 036/** 037 * Validates {@link Datum} and related objects from the {@code org.opengis.datum} package. 038 * 039 * <p>This class is provided for users wanting to override the validation methods. When the default 040 * behavior is sufficient, the {@link org.opengis.test.Validators} static methods provide a more 041 * convenient way to validate various kinds of objects.</p> 042 * 043 * @author Martin Desruisseaux (Geomatys) 044 * @version 3.1 045 * @since 2.2 046 */ 047public class DatumValidator extends ReferencingValidator { 048 /** 049 * Creates a new validator instance. 050 * 051 * @param container the set of validators to use for validating other kinds of objects 052 * (see {@linkplain #container field javadoc}). 053 */ 054 public DatumValidator(ValidatorContainer container) { 055 super(container, "org.opengis.referencing.datum"); 056 } 057 058 /** 059 * For each interface implemented by the given object, invokes the corresponding 060 * {@code validate(…)} method defined in this class (if any). 061 * 062 * @param object the object to dispatch to {@code validate(…)} methods, or {@code null}. 063 * @return number of {@code validate(…)} methods invoked in this class for the given object. 064 */ 065 @SuppressWarnings("deprecation") 066 public int dispatch(final Datum object) { 067 int n = 0; 068 if (object != null) { 069 if (object instanceof GeodeticDatum) {validate((GeodeticDatum) object); n++;} 070 if (object instanceof VerticalDatum) {validate((VerticalDatum) object); n++;} 071 if (object instanceof TemporalDatum) {validate((TemporalDatum) object); n++;} 072 if (object instanceof ImageDatum) {validate((ImageDatum) object); n++;} 073 if (object instanceof EngineeringDatum) {validate((EngineeringDatum) object); n++;} 074 if (object instanceof DatumEnsemble) {validate((DatumEnsemble) object); n++;} 075 if (n == 0) { 076 validateIdentifiedObject(object); 077 } 078 } 079 return n; 080 } 081 082 /** 083 * Validates the given prime meridian. 084 * 085 * @param object the object to validate, or {@code null}. 086 */ 087 public void validate(final PrimeMeridian object) { 088 if (object == null) { 089 return; 090 } 091 validateIdentifiedObject(object); 092 final Unit<Angle> unit = object.getAngularUnit(); 093 mandatory(unit, "PrimeMeridian: shall have a unit of measurement."); 094 double longitude = object.getGreenwichLongitude(); 095 if (unit != null) { 096 final Unit<Angle> degree = units.degree(); 097 assertTrue(unit.isCompatible(degree), "PrimeMeridian: unit must be compatible with degrees."); 098 longitude = unit.getConverterTo(degree).convert(longitude); 099 } 100 assertBetween(-180, +180, longitude, "PrimeMeridian: expected longitude in [-180 … +180]° range."); 101 } 102 103 /** 104 * Validates the given ellipsoid. 105 * This method checks the following conditions: 106 * 107 * <ul> 108 * <li>{@linkplain Ellipsoid#getAxisUnit() Axis unit} is defined and is linear.</li> 109 * <li>{@linkplain Ellipsoid#getSemiMinorAxis() semi-minor} <= {@linkplain Ellipsoid#getSemiMajorAxis() semi-major}.</li> 110 * <li>{@linkplain Ellipsoid#getInverseFlattening() inverse flattening} > 0.</li> 111 * <li>Consistency of semi-minor axis length with inverse flattening factor.</li> 112 * </ul> 113 * 114 * @param object the object to validate, or {@code null}. 115 */ 116 public void validate(final Ellipsoid object) { 117 if (object == null) { 118 return; 119 } 120 validateIdentifiedObject(object); 121 final Unit<Length> unit = object.getAxisUnit(); 122 mandatory(unit, "Ellipsoid: shall have a unit of measurement."); 123 if (unit != null) { 124 assertTrue(unit.isCompatible(units.metre()), "Ellipsoid: unit must be compatible with metres."); 125 } 126 final double semiMajor = object.getSemiMajorAxis(); 127 final double semiMinor = object.getSemiMinorAxis(); 128 final double inverseFlattening = object.getInverseFlattening(); 129 assertTrue(semiMajor > 0, invalidAxisLength("semi-major", '>', 0, semiMajor)); 130 assertTrue(semiMinor > 0, invalidAxisLength("semi-minor", '>', 0, semiMinor)); 131 assertTrue(semiMajor < POSITIVE_INFINITY, invalidAxisLength("semi-major", '<', POSITIVE_INFINITY, semiMajor)); 132 assertTrue(semiMinor <= semiMajor, invalidAxisLength("semi-minor", '≤', semiMajor, semiMinor)); 133 assertTrue(inverseFlattening > 0, "Ellipsoid: expected inverse flattening > 0."); 134 object.getSemiMedianAxis().ifPresent((semiMedian) -> { 135 assertTrue(semiMedian > semiMinor, invalidAxisLength("semi-median", '>', semiMinor, semiMedian)); 136 assertTrue(semiMedian < semiMajor, invalidAxisLength("semi-median", '<', semiMajor, semiMedian)); 137 }); 138 if (!object.isSphere()) { 139 assertEquals(semiMajor - semiMajor/inverseFlattening, semiMinor, semiMinor*DEFAULT_TOLERANCE, 140 "Ellipsoid: inconsistent semi-major axis length."); 141 assertEquals(semiMajor / (semiMajor-semiMinor), inverseFlattening, inverseFlattening*DEFAULT_TOLERANCE, 142 "Ellipsoid: inconsistent inverse flattening."); 143 } 144 } 145 146 /** 147 * Returns a supplier of message to format in case of error while verifying the length of an ellipsoid axis. 148 * 149 * @param name name of the ellipsoid axis to verify. 150 * @param operator the mathematical operator used for comparing the actual value with the limit. 151 * @param limit minimum or maximum expected value. 152 * @param actual the actual value found in the ellipsoid. 153 * @return the message to format if the assertion fails. 154 */ 155 private static Supplier<String> invalidAxisLength(String name, char operator, double limit, double actual) { 156 return () -> "Ellipsoid: expected " + name + " axis length " + operator + ' ' + limit + " but got " + actual + '.'; 157 } 158 159 /** 160 * Validates the given datum. 161 * 162 * @param object the object to validate, or {@code null}. 163 */ 164 public void validate(final GeodeticDatum object) { 165 if (object == null) { 166 return; 167 } 168 validateIdentifiedObject(object); 169 final PrimeMeridian meridian = object.getPrimeMeridian(); 170 mandatory(meridian, "GeodeticDatum: shall have a prime meridian."); 171 validate(meridian); 172 173 final Ellipsoid ellipsoid = object.getEllipsoid(); 174 mandatory(ellipsoid, "GeodeticDatum: shall have an ellipsoid."); 175 validate(ellipsoid); 176 } 177 178 /** 179 * Validates the given datum. 180 * 181 * @param object the object to validate, or {@code null}. 182 */ 183 public void validate(final VerticalDatum object) { 184 if (object == null) { 185 return; 186 } 187 validateIdentifiedObject(object); 188 } 189 190 /** 191 * Validates the given datum. 192 * 193 * @param object the object to validate, or {@code null}. 194 */ 195 public void validate(final TemporalDatum object) { 196 if (object == null) { 197 return; 198 } 199 validateIdentifiedObject(object); 200 final var origin = object.getOrigin(); 201 mandatory(origin, "TemporalDatum: expected an origin."); 202 } 203 204 /** 205 * Validates the given datum. 206 * 207 * @param object the object to validate, or {@code null}. 208 * 209 * @deprecated Replaced by {@link EngineeringDatum} as of ISO 19111:2019. 210 */ 211 @Deprecated(since="3.1") 212 public void validate(final ImageDatum object) { 213 if (object == null) { 214 return; 215 } 216 validateIdentifiedObject(object); 217 final PixelInCell pc = object.getPixelInCell(); 218 mandatory(pc, "ImageDatum: shall specify PixelInCell."); 219 } 220 221 /** 222 * Validates the given datum. 223 * 224 * @param object the object to validate, or {@code null}. 225 */ 226 public void validate(final EngineeringDatum object) { 227 if (object == null) { 228 return; 229 } 230 validateIdentifiedObject(object); 231 } 232 233 /** 234 * Validates the given datum ensemble. 235 * 236 * @param object the object to validate, or {@code null}. 237 * 238 * @since 3.1 239 */ 240 public void validate(final DatumEnsemble<?> object) { 241 if (object == null) { 242 return; 243 } 244 validateIdentifiedObject(object); 245 final Collection<? extends Datum> members = object.getMembers(); 246 assertNotNull(members, "DatumEnsemble: shall have at least two members."); 247 assertTrue(members.size() >= 2, "DatumEnsemble: shall have at least two members."); 248 final Consumer<IdentifiedObject> c = new Consumer<>() { 249 /** The first conventional RS found in datum. */ 250 private IdentifiedObject conventionalRS; 251 252 /** Verifies that the given conventional RS is the same as in previous datum. */ 253 @Override public void accept(final IdentifiedObject rs) { 254 if (conventionalRS == null) { 255 conventionalRS = rs; 256 } else { 257 assertEquals(conventionalRS, rs, "Members of datum ensemble shall share the same conventional RS."); 258 } 259 } 260 }; 261 for (final Datum member : members) { 262 dispatch(member); 263 member.getConventionalRS().ifPresent(c); 264 } 265 container.validate(object.getEnsembleAccuracy()); 266 } 267}