| /* |
| * 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.factory; |
| |
| import java.util.List; |
| import java.util.Set; |
| import java.util.HashSet; |
| import java.awt.geom.AffineTransform; |
| import java.awt.geom.NoninvertibleTransformException; |
| import org.opengis.util.FactoryException; |
| import org.opengis.metadata.citation.Citation; |
| import org.opengis.parameter.ParameterValueGroup; |
| import org.opengis.referencing.NoSuchAuthorityCodeException; |
| import org.opengis.referencing.crs.CoordinateReferenceSystem; |
| import org.opengis.referencing.crs.EngineeringCRS; |
| import org.opengis.referencing.crs.GeographicCRS; |
| import org.opengis.referencing.crs.ProjectedCRS; |
| import org.opengis.referencing.crs.VerticalCRS; |
| import org.opengis.referencing.crs.TemporalCRS; |
| import org.opengis.referencing.cs.AxisDirection; |
| import org.opengis.referencing.datum.Datum; |
| import org.apache.sis.util.privy.Constants; |
| import org.apache.sis.referencing.IdentifiedObjects; |
| import org.apache.sis.referencing.CommonCRS; |
| import org.apache.sis.referencing.operation.provider.TransverseMercator; |
| import org.apache.sis.referencing.operation.transform.LinearTransform; |
| import org.apache.sis.metadata.iso.citation.Citations; |
| import org.apache.sis.io.wkt.Convention; |
| import org.apache.sis.measure.Units; |
| |
| // Test dependencies |
| import org.junit.jupiter.api.Disabled; |
| import org.junit.jupiter.api.Test; |
| import static org.junit.jupiter.api.Assertions.*; |
| import org.apache.sis.test.TestCase; |
| import static org.apache.sis.test.Assertions.assertSetEquals; |
| import static org.apache.sis.test.Assertions.assertNotDeepEquals; |
| import static org.apache.sis.referencing.Assertions.assertWktEqualsRegex; |
| import static org.apache.sis.test.TestUtilities.getSingleton; |
| |
| // Specific to the main branch: |
| import static org.apache.sis.test.GeoapiAssert.assertAxisDirectionsEqual; |
| |
| |
| /** |
| * Tests {@link CommonAuthorityFactory}. |
| * |
| * @author Martin Desruisseaux (IRD, Geomatys) |
| */ |
| public final class CommonAuthorityFactoryTest extends TestCase { |
| /** |
| * The factory to test. |
| */ |
| private final CommonAuthorityFactory factory; |
| |
| /** |
| * Initializes the factory to test. |
| */ |
| public CommonAuthorityFactoryTest() { |
| factory = new CommonAuthorityFactory(); |
| } |
| |
| /** |
| * Tests {@link CommonAuthorityFactory#getAuthorityCodes(Class)}. |
| * |
| * @throws FactoryException if an error occurred while fetching the set of codes. |
| */ |
| @Test |
| public void testGetAuthorityCodes() throws FactoryException { |
| assertTrue(factory.getAuthorityCodes(Datum.class).isEmpty()); |
| assertSetEquals(List.of("CRS:1", "CRS:27", "CRS:83", "CRS:84", "CRS:88", |
| "AUTO2:42001", "AUTO2:42002", "AUTO2:42003", "AUTO2:42004", "AUTO2:42005", |
| "OGC:JulianDate", "OGC:TruncatedJulianDate", "OGC:UnixTime"), |
| factory.getAuthorityCodes(CoordinateReferenceSystem.class)); |
| assertSetEquals(List.of("AUTO2:42001", "AUTO2:42002", "AUTO2:42003", "AUTO2:42004", "AUTO2:42005"), |
| factory.getAuthorityCodes(ProjectedCRS.class)); |
| assertSetEquals(List.of("CRS:27", "CRS:83", "CRS:84"), |
| factory.getAuthorityCodes(GeographicCRS.class)); |
| assertSetEquals(List.of("CRS:88"), |
| factory.getAuthorityCodes(VerticalCRS.class)); |
| assertSetEquals(List.of("OGC:JulianDate", "OGC:TruncatedJulianDate", "OGC:UnixTime"), |
| factory.getAuthorityCodes(TemporalCRS.class)); |
| assertSetEquals(List.of("CRS:1"), |
| factory.getAuthorityCodes(EngineeringCRS.class)); |
| |
| final Set<String> codes = factory.getAuthorityCodes(GeographicCRS.class); |
| assertFalse(codes.contains("CRS:1")); |
| assertTrue (codes.contains("CRS:27")); |
| assertTrue (codes.contains("CRS:83")); |
| assertTrue (codes.contains("CRS:84")); |
| assertFalse(codes.contains("CRS:88")); |
| assertTrue (codes.contains("0084")); |
| assertFalse(codes.contains("0088")); |
| assertTrue (codes.contains("OGC:CRS084")); |
| } |
| |
| /** |
| * Verifies that the names of temporal CRS (including namespace) |
| * are identical to the one defined by {@link CommonCRS.Temporal}. |
| * |
| * @throws FactoryException if an error occurred while fetching the set of codes. |
| */ |
| @Test |
| public void verifyTemporalCodes() throws FactoryException { |
| final Set<String> expected = new HashSet<>(); |
| for (final CommonCRS.Temporal e : CommonCRS.Temporal.values()) { |
| assertTrue(expected.add(IdentifiedObjects.toString(getSingleton(e.crs().getIdentifiers())))); |
| } |
| assertFalse(expected.isEmpty()); |
| for (final String code : factory.getAuthorityCodes(TemporalCRS.class)) { |
| assertTrue(expected.remove(code), code); |
| } |
| for (final String remaining : expected) { |
| assertFalse(remaining.startsWith(Constants.OGC), remaining); |
| } |
| } |
| |
| /** |
| * Tests {@link CommonAuthorityFactory#getDescriptionText(Class, String)}. |
| * |
| * @throws FactoryException if an error occurred while creating a CRS. |
| */ |
| @Test |
| public void testDescription() throws FactoryException { |
| assertDescriptionEquals("WGS 84", "CRS:84"); |
| assertDescriptionEquals("WGS 84 / Auto UTM", "AUTO:42001"); |
| assertDescriptionEquals("WGS 84 / UTM zone 10S", "AUTO:42001,-124,-10"); |
| } |
| |
| /** |
| * Asserts that the description is equal to the expected value. |
| * |
| * @param expected the expected description. |
| * @param code the code of the object for which to fetch the description. |
| */ |
| private void assertDescriptionEquals(String expected, String code) throws FactoryException { |
| assertEquals(expected, factory.getDescriptionText(CoordinateReferenceSystem.class, code).orElseThrow().toString()); |
| } |
| |
| /** |
| * Checks the value returned by {@link CommonAuthorityFactory#getAuthority()}. |
| */ |
| @Test |
| public void testAuthority() { |
| final Citation authority = factory.getAuthority(); |
| assertTrue (Citations.identifierMatches(authority, "WMS")); |
| assertFalse(Citations.identifierMatches(authority, "OGP")); |
| assertFalse(Citations.identifierMatches(authority, "EPSG")); |
| assertEquals(Constants.OGC, Citations.toCodeSpace(authority)); |
| } |
| |
| /** |
| * Tests {@link CommonAuthorityFactory#createGeographicCRS(String)} with the {@code "CRS:84"} code. |
| * |
| * @throws FactoryException if an error occurred while creating a CRS. |
| */ |
| @Test |
| public void testCRS84() throws FactoryException { |
| GeographicCRS crs = factory.createGeographicCRS("CRS:84"); |
| assertSame (crs, factory.createGeographicCRS("84")); |
| assertSame (crs, factory.createGeographicCRS("CRS84")); |
| assertSame (crs, factory.createGeographicCRS("CRS:CRS84")); |
| assertSame (crs, factory.createGeographicCRS("crs : crs84")); |
| assertSame (crs, factory.createGeographicCRS("OGC:84")); // Not in real use as far as I know. |
| assertSame (crs, factory.createGeographicCRS("OGC:CRS84")); |
| assertNotSame(crs, factory.createGeographicCRS("CRS:83")); |
| assertAxisDirectionsEqual(crs.getCoordinateSystem(), AxisDirection.EAST, AxisDirection.NORTH); |
| } |
| |
| /** |
| * Tests {@link CommonAuthorityFactory#createGeographicCRS(String)} with the {@code "CRS:83"} code. |
| * |
| * @throws FactoryException if an error occurred while creating a CRS. |
| */ |
| @Test |
| public void testCRS83() throws FactoryException { |
| GeographicCRS crs = factory.createGeographicCRS("CRS:83"); |
| assertSame (crs, factory.createGeographicCRS("83")); |
| assertSame (crs, factory.createGeographicCRS("CRS83")); |
| assertSame (crs, factory.createGeographicCRS("CRS:CRS83")); |
| assertNotSame(crs, factory.createGeographicCRS("CRS:84")); |
| assertNotDeepEquals(CommonCRS.WGS84.normalizedGeographic(), crs); |
| assertAxisDirectionsEqual(crs.getCoordinateSystem(), AxisDirection.EAST, AxisDirection.NORTH); |
| } |
| |
| /** |
| * Tests {@link CommonAuthorityFactory#createVerticalCRS(String)} with the {@code "CRS:88"} code. |
| * |
| * @throws FactoryException if an error occurred while creating a CRS. |
| */ |
| @Test |
| public void testCRS88() throws FactoryException { |
| VerticalCRS crs = factory.createVerticalCRS("CRS:88"); |
| assertSame (crs, factory.createVerticalCRS("88")); |
| assertSame (crs, factory.createVerticalCRS("CRS88")); |
| assertSame (crs, factory.createVerticalCRS("CRS:CRS 88")); |
| assertAxisDirectionsEqual(crs.getCoordinateSystem(), AxisDirection.UP); |
| } |
| |
| /** |
| * Tests {@link CommonAuthorityFactory#createEngineeringCRS(String)} with the {@code "CRS:1"} code. |
| * |
| * @throws FactoryException if an error occurred while creating a CRS. |
| */ |
| @Test |
| public void testCRS1() throws FactoryException { |
| EngineeringCRS crs = factory.createEngineeringCRS("CRS:1"); |
| assertSame (crs, factory.createEngineeringCRS("1")); |
| assertSame (crs, factory.createEngineeringCRS("CRS1")); |
| assertSame (crs, factory.createEngineeringCRS("CRS:CRS 1")); |
| assertAxisDirectionsEqual(crs.getCoordinateSystem(), AxisDirection.EAST, AxisDirection.SOUTH); |
| } |
| |
| /** |
| * Tests {@link CommonAuthorityFactory#createProjectedCRS(String)} with the {@code "AUTO:42001"} code. |
| * |
| * @throws FactoryException if an error occurred while creating a CRS. |
| */ |
| @Test |
| public void testAuto42001() throws FactoryException { |
| final ProjectedCRS crs = factory.createProjectedCRS("AUTO:42001,-123,0"); |
| assertSame(crs, factory.createProjectedCRS("AUTO : 42001, -122, 10 ")); |
| assertSame(crs, factory.createProjectedCRS(" 42001, -122 , 10 ")); |
| assertSame(crs, factory.createProjectedCRS("AUTO2 : 42001, 1, -122 , 10 ")); |
| assertSame(crs, factory.createProjectedCRS("AUTO1 : 42001, 9001, -122 , 10 ")); |
| assertSame(crs, factory.createProjectedCRS("AUTO:42001,9001,-122,10")); |
| assertSame(crs, factory.createProjectedCRS("AUTO2:42002,1,-123,0"), |
| "When the given parameters match exactly the UTM central meridian and latitude of origin," |
| + " the CRS created by AUTO:42002 should be the same as the CRS created by AUTO:42001."); |
| /* |
| * Do not use `assertEpsgNameAndIdentifierEqual(…)` because the "EPSG" authority is missing |
| * (actually is "Subset of EPSG") if the CRS was built from the fallback factory. |
| */ |
| assertEquals("WGS 84 / UTM zone 10N", crs.getName().getCode()); |
| assertEquals("32610", getSingleton(crs.getIdentifiers()).getCode()); |
| final ParameterValueGroup p = crs.getConversionFromBase().getParameterValues(); |
| assertEquals(TransverseMercator.NAME, crs.getConversionFromBase().getMethod().getName().getCode()); |
| assertAxisDirectionsEqual(crs.getCoordinateSystem(), AxisDirection.EAST, AxisDirection.NORTH); |
| assertEquals(-123, p.parameter(Constants.CENTRAL_MERIDIAN) .doubleValue()); |
| assertEquals( 0, p.parameter(Constants.LATITUDE_OF_ORIGIN).doubleValue()); |
| assertEquals( 0, p.parameter(Constants.FALSE_NORTHING) .doubleValue()); |
| assertEquals(Units.METRE, crs.getCoordinateSystem().getAxis(0).getUnit()); |
| |
| var e = assertThrows(NoSuchAuthorityCodeException.class, () -> factory.createObject("AUTO:42001"), |
| "Should not have accepted incomplete code."); |
| assertEquals("42001", e.getAuthorityCode()); |
| } |
| |
| /** |
| * Tests {@link CommonAuthorityFactory#createProjectedCRS(String)} with the same {@code "AUTO:42001"} code |
| * than {@link #testAuto42001()} except that axes are feet. |
| * |
| * @throws FactoryException if an error occurred while creating a CRS. |
| */ |
| @Test |
| public void testAuto42001_foot() throws FactoryException { |
| final ProjectedCRS crs = factory.createProjectedCRS("AUTO2:42001, 0.3048, -123, 0"); |
| assertSame(crs, factory.createProjectedCRS("AUTO:42001,9002,-123,0"), "Legacy namespace."); |
| assertEquals("WGS 84 / UTM zone 10N", crs.getName().getCode()); |
| assertTrue(crs.getIdentifiers().isEmpty(), "Expected no EPSG identifier because the axes are not in metres."); |
| assertEquals(Units.FOOT, crs.getCoordinateSystem().getAxis(0).getUnit()); |
| } |
| |
| /** |
| * Tests {@link CommonAuthorityFactory#createProjectedCRS(String)} with the {@code "AUTO:42002"} code. |
| * |
| * @throws FactoryException if an error occurred while creating a CRS. |
| */ |
| @Test |
| public void testAuto42002() throws FactoryException { |
| final ProjectedCRS crs = factory.createProjectedCRS("AUTO:42002,-122,10"); |
| assertSame(crs, factory.createProjectedCRS(" 42002, -122 , 10 "), "Omitting namespace."); |
| assertSame(crs, factory.createProjectedCRS("AUTO2 : 42002, 1, -122 , 10 "), "With explicit unit."); |
| assertEquals("Transverse Mercator", crs.getName().getCode()); |
| assertTrue(crs.getIdentifiers().isEmpty(), "Expected no EPSG identifier."); |
| |
| final ParameterValueGroup p = crs.getConversionFromBase().getParameterValues(); |
| assertEquals(TransverseMercator.NAME, crs.getConversionFromBase().getMethod().getName().getCode()); |
| assertAxisDirectionsEqual(crs.getCoordinateSystem(), AxisDirection.EAST, AxisDirection.NORTH); |
| assertEquals(-122, p.parameter(Constants.CENTRAL_MERIDIAN) .doubleValue()); |
| assertEquals( 10, p.parameter(Constants.LATITUDE_OF_ORIGIN).doubleValue()); |
| assertEquals( 0, p.parameter(Constants.FALSE_NORTHING) .doubleValue()); |
| } |
| |
| /** |
| * Tests {@link CommonAuthorityFactory#createProjectedCRS(String)} with the {@code "AUTO:42003"} code. |
| * |
| * @throws FactoryException if an error occurred while creating a CRS. |
| */ |
| @Test |
| @Disabled("Pending the port of Orthographic projection.") |
| public void testAuto42003() throws FactoryException { |
| final ProjectedCRS crs = factory.createProjectedCRS("AUTO:42003,9001,10,45"); |
| final ParameterValueGroup p = crs.getConversionFromBase().getParameterValues(); |
| assertAxisDirectionsEqual(crs.getCoordinateSystem(), AxisDirection.EAST, AxisDirection.NORTH); |
| assertEquals(10, p.parameter(Constants.CENTRAL_MERIDIAN) .doubleValue()); |
| assertEquals(45, p.parameter(Constants.LATITUDE_OF_ORIGIN).doubleValue()); |
| } |
| |
| /** |
| * Tests {@link CommonAuthorityFactory#createProjectedCRS(String)} with the {@code "AUTO:42004"} code. |
| * |
| * @throws FactoryException if an error occurred while creating a CRS. |
| */ |
| @Test |
| public void testAuto42004() throws FactoryException { |
| final ProjectedCRS crs = factory.createProjectedCRS("AUTO2:42004,1,10,45"); |
| final ParameterValueGroup p = crs.getConversionFromBase().getParameterValues(); |
| assertAxisDirectionsEqual(crs.getCoordinateSystem(), AxisDirection.EAST, AxisDirection.NORTH); |
| assertEquals(10, p.parameter(Constants.CENTRAL_MERIDIAN) .doubleValue()); |
| assertEquals(45, p.parameter(Constants.STANDARD_PARALLEL_1).doubleValue()); |
| assertInstanceOf(LinearTransform.class, crs.getConversionFromBase().getMathTransform(), |
| "Opportunistic check: in the special case of Equirectangular projection, " |
| + "SIS should have optimized the MathTransform as an affine transform."); |
| } |
| |
| /** |
| * Tests {@link CommonAuthorityFactory#createProjectedCRS(String)} with the {@code "AUTO:42005"} code. |
| * |
| * @throws FactoryException if an error occurred while creating a CRS. |
| */ |
| @Test |
| public void testAuto42005() throws FactoryException { |
| final ProjectedCRS crs = factory.createProjectedCRS("AUTO:42005,9001,10,45"); |
| final ParameterValueGroup p = crs.getConversionFromBase().getParameterValues(); |
| assertAxisDirectionsEqual(crs.getCoordinateSystem(), AxisDirection.EAST, AxisDirection.NORTH); |
| assertEquals(10, p.parameter(Constants.CENTRAL_MERIDIAN) .doubleValue()); |
| } |
| |
| /** |
| * Tests two {@code "AUTO:42004"} (Equirectangular projection) case built in such a way that the conversion |
| * from one to the other should be the conversion factor from metres to feet. |
| * |
| * This is an integration test. |
| * |
| * @throws FactoryException if an error occurred while creating a CRS. |
| * @throws NoninvertibleTransformException Should never happen. |
| */ |
| @Test |
| public void testUnits() throws FactoryException, NoninvertibleTransformException { |
| AffineTransform tr1, tr2; |
| tr1 = (AffineTransform) factory.createProjectedCRS("AUTO:42004,9001,0,35").getConversionFromBase().getMathTransform(); |
| tr2 = (AffineTransform) factory.createProjectedCRS("AUTO:42004,9002,0,35").getConversionFromBase().getMathTransform(); |
| tr2 = tr2.createInverse(); |
| tr2.concatenate(tr1); |
| assertEquals(0, tr2.getType() & ~AffineTransform.TYPE_MASK_SCALE, "Expected any kind of scale."); |
| assertEquals(0.3048, tr2.getScaleX(), 1E-9, "Expected the conversion factor from foot to metre."); |
| assertEquals(0.3048, tr2.getScaleY(), 1E-9, "Expected the conversion factor from foot to metre."); |
| } |
| |
| /** |
| * Tests the WKT formatting. The main purpose of this test is to ensure that |
| * the authority name is "CRS" and not "Web Map Service CRS". |
| * |
| * @throws FactoryException if an error occurred while creating the CRS. |
| */ |
| @Test |
| public void testWKT() throws FactoryException { |
| final String WGS84 = "“WGS\\E\\s?(?:19)?\\Q84”"; // Accept "WGS 84" or "WGS 1984" |
| GeographicCRS crs = factory.createGeographicCRS("CRS:84"); |
| assertWktEqualsRegex(Convention.WKT1, "(?m)\\Q" + // Multilines |
| "GEOGCS[" + WGS84 + ",\n" + |
| " DATUM[“World Geodetic System 1984”,\n" + |
| " SPHEROID[" + WGS84 + ", 6378137.0, 298.257223563]],\n" + |
| " PRIMEM[“Greenwich”, 0.0],\n" + |
| " UNIT[“degree”, 0.017453292519943295],\n" + |
| " AXIS[“Longitude”, EAST],\n" + |
| " AXIS[“Latitude”, NORTH],\n" + |
| " AUTHORITY[“CRS”, “84”]]\\E", crs); |
| |
| assertWktEqualsRegex(Convention.WKT2, "(?m)\\Q" + |
| "GEODCRS[" + WGS84 + ",\n" + |
| " DATUM[“World Geodetic System 1984”,\n" + |
| " ELLIPSOID[" + WGS84 + ", 6378137.0, 298.257223563, LENGTHUNIT[“metre”, 1]]],\n" + |
| " PRIMEM[“Greenwich”, 0.0, ANGLEUNIT[“degree”, 0.017453292519943295]],\n" + |
| " CS[ellipsoidal, 2],\n" + |
| " AXIS[“Longitude (L)”, east, ORDER[1]],\n" + |
| " AXIS[“Latitude (B)”, north, ORDER[2]],\n" + |
| " ANGLEUNIT[“degree”, 0.017453292519943295],\n" + |
| "\\E(?: SCOPE\\[“.+”\\],\n)?\\Q" + // Ignore SCOPE[…] if present. |
| " AREA[“World\\E.*\\Q”],\n" + |
| " BBOX[-90.00, -180.00, 90.00, 180.00],\n" + |
| " ID[“CRS”, 84, CITATION[“WMS”], URI[“urn:ogc:def:crs:OGC:1.3:CRS84”]]" + |
| "\\E(?:,\n REMARK\\[“.+”\\])?\\]", // Ignore trailing REMARK[…] if present. |
| crs); |
| /* |
| * Note: the WKT specification defines the ID element as: |
| * |
| * ID[authority, code, (version), (authority citation), (URI)] |
| * |
| * where everything after the code is optional. The difference between "authority" and "authority citation" |
| * is unclear. The only example found in OGC 12-063r5 uses CITATION[…] as the source of an EPSG definition |
| * (so we could almost said "the authority of the authority"). |
| */ |
| } |
| } |