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}