blob: a5ca080a4254480a9615c7e4796782b4f37f8b73 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.sis.referencing;
import org.opengis.metadata.Identifier;
import org.opengis.metadata.extent.Extent;
import org.opengis.metadata.extent.GeographicExtent;
import org.opengis.metadata.extent.GeographicBoundingBox;
import org.opengis.referencing.datum.Ellipsoid;
import org.opengis.referencing.datum.PrimeMeridian;
import org.opengis.referencing.datum.GeodeticDatum;
import org.opengis.referencing.datum.VerticalDatum;
import org.opengis.referencing.cs.CoordinateSystem;
import org.opengis.referencing.cs.CoordinateSystemAxis;
import org.opengis.referencing.cs.AxisDirection;
import org.opengis.referencing.cs.CartesianCS;
import org.opengis.referencing.cs.EllipsoidalCS;
import org.opengis.referencing.cs.RangeMeaning;
import org.opengis.referencing.crs.GeodeticCRS;
import org.apache.sis.internal.metadata.AxisNames;
import org.apache.sis.measure.Units;
import static org.apache.sis.test.Assert.*;
/**
* Verifies the values of some geodetic objects. Methods in this class ignore most textual properties like remarks,
* because IOGP allows implementations to modify non-essential properties.
*
* @author Martin Desruisseaux (Geomatys)
* @version 0.8
* @since 0.4
* @module
*
* @todo Move this class to GeoAPI.
*/
public final strictfp class GeodeticObjectVerifier {
/**
* The tolerance threshold for strict comparisons of floating point values.
*/
private static final double STRICT = 0;
/**
* Creates a new test case.
*/
private GeodeticObjectVerifier() {
}
/**
* Asserts that all {@link GeographicBoundingBox}, if any,
* {@linkplain #assertIsWorld(GeographicBoundingBox) encompasses the world}.
*
* <p><b>Note:</b> a future version of this method may accept other kinds of extent,
* for example a polygon encompassing the world.</p>
*
* @param extent the extent to verify, or {@code null} if none.
* @param isMandatory {@code true} if an absence of world extent is a failure.
*/
private static void assertIsWorld(final Extent extent, boolean isMandatory) {
if (extent != null) {
for (final GeographicExtent element : extent.getGeographicElements()) {
if (element instanceof GeographicBoundingBox) {
assertIsWorld((GeographicBoundingBox) element);
isMandatory = false;
}
}
}
if (isMandatory) {
fail("Expected a world extent element.");
}
}
/**
* Asserts that the given geographic bounding box encompasses the world.
* This method verifies the following properties:
*
* <table class="sis">
* <caption>Verified properties</caption>
* <tr><th>Property</th> <th>Expected value</th></tr>
* <tr><td>{@linkplain GeographicBoundingBox#getInclusion() Inclusion}</td><td>Absent or {@link Boolean#TRUE}</td></tr>
* <tr><td>{@linkplain GeographicBoundingBox#getWestBoundLongitude() West bound longitude}</td><td>-180</td></tr>
* <tr><td>{@linkplain GeographicBoundingBox#getEastBoundLongitude() East bound longitude}</td><td>+180</td></tr>
* <tr><td>{@linkplain GeographicBoundingBox#getSouthBoundLatitude() South bound latitude}</td><td> -90</td></tr>
* <tr><td>{@linkplain GeographicBoundingBox#getNorthBoundLatitude() North bound latitude}</td><td> +90</td></tr>
* </table>
*
* @param box the geographic bounding box to verify.
*/
public static void assertIsWorld(final GeographicBoundingBox box) {
final Boolean inclusion = box.getInclusion();
if (inclusion != null) {
assertEquals("inclusion", Boolean.TRUE, inclusion);
}
assertEquals("westBoundLongitude", -180, box.getWestBoundLongitude(), STRICT);
assertEquals("eastBoundLongitude", +180, box.getEastBoundLongitude(), STRICT);
assertEquals("southBoundLatitude", -90, box.getSouthBoundLatitude(), STRICT);
assertEquals("northBoundLatitude", +90, box.getNorthBoundLatitude(), STRICT);
}
/**
* Asserts that the given prime meridian is the Greenwich one.
* This method verifies the following properties:
*
* <table class="sis">
* <caption>Verified properties</caption>
* <tr><th>Property</th> <th>Expected value</th></tr>
* <tr><td>{@linkplain Identifier#getCode() Code} of the {@linkplain PrimeMeridian#getName() name}</td>
* <td>{@code "Greenwich"}</td></tr>
* <tr><td>{@linkplain PrimeMeridian#getGreenwichLongitude() Greenwich longitude}</td>
* <td>0</td></tr>
* <tr><td>{@linkplain PrimeMeridian#getAngularUnit() Angular unit}</td>
* <td>{@link Units#DEGREE}</td></tr>
* </table>
*
* @param meridian the prime meridian to verify.
*/
public static void assertIsGreenwich(final PrimeMeridian meridian) {
assertEquals("name", "Greenwich", meridian.getName().getCode());
assertEquals("greenwichLongitude", 0, meridian.getGreenwichLongitude(), STRICT);
assertEquals("angularUnit", Units.DEGREE, meridian.getAngularUnit());
}
/**
* Asserts that the given prime meridian is the Paris one.
* This method verifies the following properties:
*
* <table class="sis">
* <caption>Verified properties</caption>
* <tr><th>Property</th> <th>Expected value</th></tr>
* <tr><td>{@linkplain Identifier#getCode() Code} of the {@linkplain PrimeMeridian#getName() name}</td>
* <td>{@code "Paris"}</td></tr>
* <tr><td>{@linkplain PrimeMeridian#getGreenwichLongitude() Greenwich longitude}</td>
* <td>2.5969213</td></tr>
* <tr><td>{@linkplain PrimeMeridian#getAngularUnit() Angular unit}</td>
* <td>{@link Units#GRAD}</td></tr>
* </table>
*
* @param meridian the prime meridian to verify.
*/
public static void assertIsParis(final PrimeMeridian meridian) {
assertEquals("name", "Paris", meridian.getName().getCode());
assertEquals("greenwichLongitude", 2.5969213, meridian.getGreenwichLongitude(), STRICT);
assertEquals("angularUnit", Units.GRAD, meridian.getAngularUnit());
}
/**
* Asserts that the given ellipsoid is the WGS 84 one.
* This method verifies the following properties:
*
* <table class="sis">
* <caption>Verified properties</caption>
* <tr><th>Property</th> <th>Expected value</th></tr>
* <tr><td>{@linkplain Identifier#getCode() Code} of the {@linkplain Ellipsoid#getName() name}</td>
* <td>{@code "WGS 84"}</td></tr>
* <tr><td>{@linkplain Ellipsoid#getAxisUnit() Axis unit}</td>
* <td>{@link Units#METRE}</td></tr>
* <tr><td>{@linkplain Ellipsoid#getSemiMajorAxis() Semi-major axis}</td>
* <td>6378137</td></tr>
* <tr><td>{@linkplain Ellipsoid#getSemiMinorAxis() Semi-minor axis}</td>
* <td>6356752.314245179 ± 0.001</td></tr>
* <tr><td>{@linkplain Ellipsoid#getInverseFlattening() Inverse flattening}</td>
* <td>298.257223563</td></tr>
* <tr><td>{@linkplain Ellipsoid#isIvfDefinitive() is IVF definitive}</td>
* <td>{@code true}</td></tr>
* </table>
*
* @param ellipsoid the ellipsoid to verify.
*/
public static void assertIsWGS84(final Ellipsoid ellipsoid) {
assertEquals("name", "WGS 84", ellipsoid.getName().getCode());
assertEquals("axisUnit", Units.METRE, ellipsoid.getAxisUnit());
assertEquals("semiMajorAxis", 6378137, ellipsoid.getSemiMajorAxis(), STRICT);
assertEquals("semiMinorAxis", 6356752.314245179, ellipsoid.getSemiMinorAxis(), 0.001);
assertEquals("inverseFlattening", 298.257223563, ellipsoid.getInverseFlattening(), STRICT);
assertTrue ("isIvfDefinitive", ellipsoid.isIvfDefinitive());
}
/**
* Asserts that the given datum is the WGS 84 one.
* This method verifies the following properties:
*
* <table class="sis">
* <caption>Verified properties</caption>
* <tr><th>Property</th> <th>Expected value</th></tr>
* <tr><td>{@linkplain Identifier#getCode() Code} of the {@linkplain GeodeticDatum#getName() name}</td>
* <td>{@code "World Geodetic System 1984"}</td></tr>
* <tr><td>{@linkplain GeodeticDatum#getDomainOfValidity() Domain of validity}</td>
* <td>{@linkplain #assertIsWorld(GeographicBoundingBox) Is world} or absent</td></tr>
* <tr><td>{@linkplain GeodeticDatum#getPrimeMeridian() Prime meridian}</td>
* <td>{@linkplain #assertIsGreenwich(PrimeMeridian) Is Greenwich}</td></tr>
* <tr><td>{@linkplain GeodeticDatum#getEllipsoid() Ellipsoid}</td>
* <td>{@linkplain #assertIsWGS84(Ellipsoid) Is WGS84}</td></tr>
* </table>
*
* @param datum the datum to verify.
* @param isExtentMandatory {@code true} if the domain of validity is required to contain an
* {@code Extent} element for the world, or {@code false} if optional.
*/
public static void assertIsWGS84(final GeodeticDatum datum, final boolean isExtentMandatory) {
assertEquals("name", "World Geodetic System 1984", datum.getName().getCode());
assertIsWorld (datum.getDomainOfValidity(), isExtentMandatory);
assertIsGreenwich(datum.getPrimeMeridian());
assertIsWGS84 (datum.getEllipsoid());
}
/**
* Asserts that the given CRS is the WGS 84 one.
* This method verifies the following properties:
*
* <table class="sis">
* <caption>Verified properties</caption>
* <tr><th>Property</th> <th>Expected value</th></tr>
* <tr><td>{@linkplain Identifier#getCode() Code} of the {@linkplain GeodeticCRS#getName() name}</td>
* <td>{@code "WGS 84"}</td></tr>
* <tr><td>{@linkplain GeodeticCRS#getDomainOfValidity() Domain of validity}</td>
* <td>{@linkplain #assertIsWorld(GeographicBoundingBox) Is world} or absent</td></tr>
* <tr><td>{@linkplain GeodeticCRS#getDatum() Datum}</td>
* <td>{@linkplain #assertIsWGS84(GeodeticDatum, boolean) Is WGS84}</td></tr>
* <tr><td>{@linkplain GeodeticCRS#getCoordinateSystem() Coordinate system}</td>
* <td>{@linkplain #assertIsGeodetic2D(EllipsoidalCS, boolean) Is for a 2D geodetic CRS}</td></tr>
* </table>
*
* @param crs the coordinate reference system to verify.
* @param isExtentMandatory {@code true} if the CRS and datum domains of validity are required to contain an
* {@code Extent} element for the world, or {@code false} if optional.
* @param isRangeMandatory {@code true} if the coordinate system axes range and range meaning properties
* shall be defined, or {@code false} if they are optional.
*/
public static void assertIsWGS84(final GeodeticCRS crs, final boolean isExtentMandatory, final boolean isRangeMandatory) {
assertEquals("name", "WGS 84", crs.getName().getCode());
assertIsWorld(crs.getDomainOfValidity(), isExtentMandatory);
assertIsWGS84(crs.getDatum(), isExtentMandatory);
final CoordinateSystem cs = crs.getCoordinateSystem();
assertInstanceOf("coordinateSystem", EllipsoidalCS.class, cs);
assertIsGeodetic2D((EllipsoidalCS) cs, isRangeMandatory);
}
/**
* Asserts that the given datum is the Mean Sea Level one.
* This method verifies the following properties:
*
* <table class="sis">
* <caption>Verified properties</caption>
* <tr><th>Property</th> <th>Expected value</th></tr>
* <tr><td>{@linkplain Identifier#getCode() Code} of the {@linkplain GeodeticDatum#getName() name}</td>
* <td>{@code "Mean Sea Level"}</td></tr>
* <tr><td>{@linkplain GeodeticDatum#getDomainOfValidity() Domain of validity}</td>
* <td>{@linkplain #assertIsWorld(GeographicBoundingBox) Is world} or absent</td></tr>
* </table>
*
* @param datum the datum to verify.
* @param isExtentMandatory {@code true} if the domain of validity is required to contain an
* {@code Extent} element for the world, or {@code false} if optional.
*/
public static void assertIsMeanSeaLevel(final VerticalDatum datum, final boolean isExtentMandatory) {
assertEquals("name", "Mean Sea Level", datum.getName().getCode());
assertIsWorld(datum.getDomainOfValidity(), isExtentMandatory);
}
/**
* Asserts that the given coordinate system contains the (easting, northing) axes in metres.
* This method verifies the following properties:
*
* <table class="sis">
* <caption>Verified properties</caption>
* <tr><th>Property</th> <th colspan="2">Expected value</th></tr>
* <tr><td>{@linkplain CartesianCS#getDimension() Dimension}</td>
* <td colspan="2">2</td></tr>
* <tr><td>Axes {@linkplain Identifier#getCode() Code} of the {@linkplain GeodeticDatum#getName() name}</td>
* <td>{@code "Easting"}</td>
* <td>{@code "Northing"}</td></tr>
* <tr><td>Axes {@linkplain CoordinateSystemAxis#getAbbreviation() abbreviation}</td>
* <td>{@code "E"}</td>
* <td>{@code "N"}</td></tr>
* <tr><td>Axes {@linkplain CoordinateSystemAxis#getDirection() direction}</td>
* <td>{@link AxisDirection#EAST EAST}</td>
* <td>{@link AxisDirection#NORTH NORTH}</td></tr>
* <tr><td>Axes {@linkplain CoordinateSystemAxis#getUnit() units}</td>
* <td>{@link Units#METRE}</td>
* <td>{@link Units#METRE}</td></tr>
* <tr><td>Axes range</td>
* <td>[−∞ … ∞]</td>
* <td>[−∞ … ∞]</td></tr>
* <tr><td>Axes range meaning</td>
* <td>{@code null}</td>
* <td>{@code null}</td>
* </table>
*
* @param cs the coordinate system to verify.
*/
public static void assertIsProjected2D(final CartesianCS cs) {
assertEquals("dimension", 2, cs.getDimension());
final CoordinateSystemAxis E = cs.getAxis(0);
final CoordinateSystemAxis N = cs.getAxis(1);
assertNotNull("axis", E);
assertNotNull("axis", N);
assertEquals("axis[0].name", AxisNames.EASTING, E.getName().getCode());
assertEquals("axis[1].name", AxisNames.NORTHING, N.getName().getCode());
assertEquals("axis[0].abbreviation", "E", E.getAbbreviation());
assertEquals("axis[1].abbreviation", "N", N.getAbbreviation());
assertEquals("axis[0].direction", AxisDirection.EAST, E.getDirection());
assertEquals("axis[1].direction", AxisDirection.NORTH, N.getDirection());
assertEquals("axis[0].unit", Units.METRE, E.getUnit());
assertEquals("axis[1].unit", Units.METRE, N.getUnit());
verifyRange(E, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, null, true);
verifyRange(N, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, null, true);
}
/**
* Asserts that the given coordinate system contains the geodetic (latitude, longitude) axes in degrees.
* This method verifies the following properties:
*
* <table class="sis">
* <caption>Verified properties</caption>
* <tr><th>Property</th> <th colspan="2">Expected value</th></tr>
* <tr><td>{@linkplain EllipsoidalCS#getDimension() Dimension}</td>
* <td colspan="2">2</td></tr>
* <tr><td>Axes {@linkplain Identifier#getCode() Code} of the {@linkplain GeodeticDatum#getName() name}</td>
* <td>{@code "Geodetic latitude"}</td>
* <td>{@code "Geodetic longitude"}</td></tr>
* <tr><td>Axes {@linkplain CoordinateSystemAxis#getDirection() direction}</td>
* <td>{@link AxisDirection#NORTH NORTH}</td>
* <td>{@link AxisDirection#EAST EAST}</td></tr>
* <tr><td>Axes {@linkplain CoordinateSystemAxis#getUnit() units}</td>
* <td>{@link Units#DEGREE}</td>
* <td>{@link Units#DEGREE}</td></tr>
* <tr><td>Axes range</td>
* <td>[-90 … 90] (see below)</td>
* <td>[-180 … 180] (see below)</td></tr>
* <tr><td>Axes range meaning</td>
* <td>{@link RangeMeaning#EXACT} or missing</td>
* <td>{@link RangeMeaning#WRAPAROUND} or missing</td></tr>
* </table>
*
* <b>Notes:</b>
* <ul>
* <li>The axes range may be missing if and only if the range meaning is also missing.</li>
* <li>This method does not verify {@linkplain CoordinateSystemAxis#getAbbreviation() abbreviations}
* because the classical symbols (φ,λ) are often replaced by (lat,long).</li>
* </ul>
*
* @param cs the coordinate system to verify.
* @param isRangeMandatory {@code true} if the axes range and range meaning properties shall be defined,
* or {@code false} if they are optional.
*/
public static void assertIsGeodetic2D(final EllipsoidalCS cs, final boolean isRangeMandatory) {
assertEquals("dimension", 2, cs.getDimension());
final CoordinateSystemAxis latitude = cs.getAxis(0);
final CoordinateSystemAxis longitude = cs.getAxis(1);
assertNotNull("axis", latitude);
assertNotNull("axis", longitude);
assertEquals("axis[0].name", AxisNames.GEODETIC_LATITUDE, latitude .getName().getCode());
assertEquals("axis[1].name", AxisNames.GEODETIC_LONGITUDE, longitude.getName().getCode());
assertEquals("axis[0].direction", AxisDirection.NORTH, latitude .getDirection());
assertEquals("axis[1].direction", AxisDirection.EAST, longitude.getDirection());
assertEquals("axis[0].unit", Units.DEGREE, latitude .getUnit());
assertEquals("axis[1].unit", Units.DEGREE, longitude.getUnit());
verifyRange(latitude, -90, +90, RangeMeaning.EXACT, isRangeMandatory);
verifyRange(longitude, -180, +180, RangeMeaning.WRAPAROUND, isRangeMandatory);
}
/**
* Asserts that the axis range is either fully missing, or defined to exactly the given properties.
*/
private static void verifyRange(final CoordinateSystemAxis axis,
final double min, final double max, final RangeMeaning expected, final boolean isMandatory)
{
final double minimumValue = axis.getMinimumValue();
final double maximumValue = axis.getMaximumValue();
final RangeMeaning rangeMeaning = axis.getRangeMeaning();
if (isMandatory || rangeMeaning != null ||
minimumValue != Double.NEGATIVE_INFINITY ||
maximumValue != Double.POSITIVE_INFINITY)
{
assertEquals("axis.minimumValue", min, minimumValue, STRICT);
assertEquals("axis.maximumValue", max, maximumValue, STRICT);
assertEquals("axis.rangeMeaning", expected, rangeMeaning);
}
}
}