| /* |
| * 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"); |
| } |
| } |
| } |