blob: 63c6dfb55a232c6a2ac17afcbfdede9f7dbbaf94 [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.metadata.privy.AxisNames;
import org.apache.sis.measure.Units;
// Test dependencies
import static org.junit.jupiter.api.Assertions.*;
/**
* 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)
*
* @todo Move this class to GeoAPI.
*/
public final class GeodeticObjectVerifier {
/**
* 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 bbox) {
assertIsWorld(bbox);
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(Boolean.TRUE, inclusion, "inclusion");
}
assertEquals(-180, box.getWestBoundLongitude(), "westBoundLongitude");
assertEquals(+180, box.getEastBoundLongitude(), "eastBoundLongitude");
assertEquals( -90, box.getSouthBoundLatitude(), "southBoundLatitude");
assertEquals( +90, box.getNorthBoundLatitude(), "northBoundLatitude");
}
/**
* 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("Greenwich", meridian.getName().getCode(), "name");
assertEquals(0, meridian.getGreenwichLongitude(), "greenwichLongitude");
assertEquals(Units.DEGREE, meridian.getAngularUnit(), "angularUnit");
}
/**
* 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("Paris", meridian.getName().getCode(), "name");
assertEquals(2.5969213, meridian.getGreenwichLongitude(), "greenwichLongitude");
assertEquals(Units.GRAD, meridian.getAngularUnit(), "angularUnit");
}
/**
* 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) {
assertTrue (ellipsoid.getName().getCode().matches("WGS\\s?(?:19)?84"), "name");
assertEquals(Units.METRE, ellipsoid.getAxisUnit(), "axisUnit");
assertEquals(6378137, ellipsoid.getSemiMajorAxis(), "semiMajorAxis");
assertEquals(6356752.314245179, ellipsoid.getSemiMinorAxis(), 0.001, "semiMinorAxis");
assertEquals(298.257223563, ellipsoid.getInverseFlattening(), "inverseFlattening");
assertTrue ( ellipsoid.isIvfDefinitive() , "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("World Geodetic System 1984", datum.getName().getCode(), "name");
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("WGS 84", crs.getName().getCode(), "name");
assertIsWorld(crs.getDomainOfValidity(), isExtentMandatory);
assertIsWGS84(crs.getDatum(), isExtentMandatory);
final CoordinateSystem cs = crs.getCoordinateSystem();
assertInstanceOf(EllipsoidalCS.class, cs, "coordinateSystem");
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("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(2, cs.getDimension(), "dimension");
final CoordinateSystemAxis E = cs.getAxis(0);
final CoordinateSystemAxis N = cs.getAxis(1);
assertNotNull(E, "axis");
assertNotNull(N, "axis");
assertEquals(AxisNames.EASTING, E.getName().getCode(), "axis[0].name");
assertEquals(AxisNames.NORTHING, N.getName().getCode(), "axis[1].name");
assertEquals("E", E.getAbbreviation(), "axis[0].abbreviation");
assertEquals("N", N.getAbbreviation(), "axis[1].abbreviation");
assertEquals(AxisDirection.EAST, E.getDirection(), "axis[0].direction");
assertEquals(AxisDirection.NORTH, N.getDirection(), "axis[1].direction");
assertEquals(Units.METRE, E.getUnit(), "axis[0].unit");
assertEquals(Units.METRE, N.getUnit(), "axis[1].unit");
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(2, cs.getDimension(), "dimension");
final CoordinateSystemAxis latitude = cs.getAxis(0);
final CoordinateSystemAxis longitude = cs.getAxis(1);
assertNotNull(latitude, "axis");
assertNotNull(longitude, "axis");
assertEquals(AxisNames.GEODETIC_LATITUDE, latitude .getName().getCode(), "axis[0].name");
assertEquals(AxisNames.GEODETIC_LONGITUDE, longitude.getName().getCode(), "axis[1].name");
assertEquals(AxisDirection.NORTH, latitude .getDirection(), "axis[0].direction");
assertEquals(AxisDirection.EAST, longitude.getDirection(), "axis[1].direction");
assertEquals(Units.DEGREE, latitude .getUnit(), "axis[0].unit");
assertEquals(Units.DEGREE, longitude.getUnit(), "axis[1].unit");
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(min, minimumValue, "axis.minimumValue");
assertEquals(max, maximumValue, "axis.maximumValue");
assertEquals(expected, rangeMeaning, "axis.rangeMeaning");
}
}
}