| /* |
| * 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.test.integration; |
| |
| import java.util.Arrays; |
| import java.util.HashSet; |
| import java.util.Set; |
| import java.text.ParseException; |
| import javax.measure.Quantity; |
| import javax.measure.Unit; |
| import javax.measure.UnitConverter; |
| import org.opengis.metadata.Identifier; |
| import org.opengis.util.FactoryException; |
| import org.opengis.util.NoSuchIdentifierException; |
| import org.opengis.referencing.crs.CoordinateReferenceSystem; |
| import org.apache.sis.referencing.factory.FactoryDataException; |
| import org.apache.sis.referencing.factory.UnavailableFactoryException; |
| import org.apache.sis.referencing.IdentifiedObjects; |
| import org.apache.sis.referencing.CRS; |
| import org.apache.sis.io.wkt.Convention; |
| import org.apache.sis.io.wkt.Warnings; |
| import org.apache.sis.io.wkt.WKTFormat; |
| import org.apache.sis.io.TableAppender; |
| import org.apache.sis.io.wkt.UnformattableObjectException; |
| import org.apache.sis.util.ComparisonMode; |
| import org.apache.sis.util.CharSequences; |
| import org.apache.sis.util.Utilities; |
| import org.apache.sis.test.DependsOn; |
| import org.apache.sis.test.TestCase; |
| import org.junit.Test; |
| |
| import static org.junit.Assert.*; |
| import static org.junit.Assume.assumeTrue; |
| |
| |
| /** |
| * Performs consistency checks on all CRS given by {@link CRS#getAuthorityFactory(String)}. |
| * The consistency checks include: |
| * |
| * <ul> |
| * <li>Format in WKT, parse, reformat again and verify that we get the same WKT string.</li> |
| * </ul> |
| * |
| * This test is executed only if {@link #RUN_EXTENSIVE_TESTS} is {@code true}. |
| * |
| * @author Martin Desruisseaux (Geomatys) |
| * @version 1.0 |
| * @since 0.7 |
| * @module |
| */ |
| @DependsOn({ |
| org.apache.sis.referencing.CRSTest.class, |
| org.apache.sis.io.wkt.WKTFormatTest.class |
| }) |
| public final strictfp class ConsistencyTest extends TestCase { |
| /** |
| * Codes to exclude for now. |
| */ |
| private static final Set<String> EXCLUDES = new HashSet<>(Arrays.asList( |
| "CRS:1", // Computer display: WKT parser alters the (i,j) axis names. |
| "EPSG:5819", // EPSG topocentric example A: error while parsing WKT. |
| "AUTO2:42001", // This projection requires parameters, but we provide none. |
| "AUTO2:42002", // This projection requires parameters, but we provide none. |
| "AUTO2:42003", // This projection requires parameters, but we provide none. |
| "AUTO2:42004", // This projection requires parameters, but we provide none. |
| "AUTO2:42005" // This projection requires parameters, but we provide none. |
| )); |
| |
| /** |
| * Width of the code columns in the warnings formatted by {@link #print(String, String, Object)}. |
| * We begin with an arbitrary width and will expand if necessary. |
| */ |
| private int codeWidth = 15; |
| |
| /** |
| * Specialization of {@link #testCoordinateReferenceSystems()} for specific cases that were known to fail. |
| * This is used for debugging purposes only; not included in normal test execution because it is redundant |
| * with {@link #testCoordinateReferenceSystems()}. |
| * |
| * @throws FactoryException if the coordinate reference system can not be created. |
| * |
| * @see <a href="https://issues.apache.org/jira/browse/SIS-433">SIS-433</a> |
| * @see <a href="https://issues.apache.org/jira/browse/SIS-434">SIS-434</a> |
| */ |
| public void debug() throws FactoryException { |
| final String code = "EPSG::29871"; |
| final CoordinateReferenceSystem crs = CRS.forCode(code); |
| final WKTFormat format = new WKTFormat(null, null); |
| format.setConvention(Convention.WKT2); |
| lookup(parseAndFormat(format, code, crs), crs); |
| } |
| |
| /** |
| * Verifies the WKT consistency of all CRS instances. |
| * |
| * @throws FactoryException if an error other than "unsupported operation method" occurred. |
| */ |
| @Test |
| public void testCoordinateReferenceSystems() throws FactoryException { |
| assumeTrue("Extensive tests not enabled.", RUN_EXTENSIVE_TESTS); |
| final WKTFormat v1 = new WKTFormat(null, null); |
| final WKTFormat v1c = new WKTFormat(null, null); |
| final WKTFormat v2 = new WKTFormat(null, null); |
| final WKTFormat v2s = new WKTFormat(null, null); |
| v1 .setConvention(Convention.WKT1); |
| v1c.setConvention(Convention.WKT1_COMMON_UNITS); |
| v2 .setConvention(Convention.WKT2); |
| v2s.setConvention(Convention.WKT2_SIMPLIFIED); |
| for (final String code : CRS.getAuthorityFactory(null).getAuthorityCodes(CoordinateReferenceSystem.class)) { |
| if (!EXCLUDES.contains(code) && !code.startsWith("Proj4:")) { |
| final CoordinateReferenceSystem crs; |
| try { |
| crs = CRS.forCode(code); |
| } catch (UnavailableFactoryException | NoSuchIdentifierException | FactoryDataException e) { |
| print(code, "WARNING", e.getLocalizedMessage()); |
| continue; |
| } |
| lookup(parseAndFormat(v2, code, crs), crs); |
| lookup(parseAndFormat(v2s, code, crs), crs); |
| /* |
| * There is more information lost in WKT 1 than in WKT 2, so we can not test everything. |
| * For example we can not format fully three-dimensional geographic CRS because the unit |
| * is not the same for all axes. We can not format neither some axis directions. |
| */ |
| try { |
| parseAndFormat(v1, code, crs); |
| } catch (UnformattableObjectException e) { |
| print(code, "WARNING", e.getLocalizedMessage()); |
| continue; |
| } |
| parseAndFormat(v1c, code, crs); |
| } |
| } |
| } |
| |
| /** |
| * Prints the given code followed by spaces and the given {@code "ERROR"} or {@code "WARNING"} word, |
| * then the given message. |
| */ |
| private void print(final String code, final String word, final Object message) { |
| final int currentWidth = code.length(); |
| if (currentWidth >= codeWidth) { |
| codeWidth = currentWidth + 1; |
| } |
| out.print(code); |
| out.print(CharSequences.spaces(codeWidth - currentWidth)); |
| out.print(word); |
| out.print(": "); |
| out.println(message); |
| } |
| |
| /** |
| * Formats the given CRS using the given formatter, parses it and reformats again. |
| * Then the two WKT are compared. |
| * |
| * @param f the formatter to use. |
| * @param code the authority code, used only in case of errors. |
| * @param crs the CRS to test. |
| * @return the parsed CRS. |
| */ |
| private CoordinateReferenceSystem parseAndFormat(final WKTFormat f, |
| final String code, final CoordinateReferenceSystem crs) |
| { |
| String wkt = f.format(crs); |
| final Warnings warnings = f.getWarnings(); |
| if (warnings != null && !warnings.getExceptions().isEmpty()) { |
| print(code, "WARNING", warnings.getException(0)); |
| } |
| final CoordinateReferenceSystem parsed; |
| try { |
| parsed = (CoordinateReferenceSystem) f.parseObject(wkt); |
| } catch (ParseException e) { |
| print(code, "ERROR", "Can not parse the WKT below."); |
| out.println(wkt); |
| out.println(); |
| e.printStackTrace(out); |
| fail(e.getLocalizedMessage()); |
| return null; |
| } |
| final String again = f.format(parsed); |
| final CharSequence[] expectedLines = CharSequences.splitOnEOL(wkt); |
| final CharSequence[] actualLines = CharSequences.splitOnEOL(again); |
| /* |
| * WKT 2 contains a line like below: |
| * |
| * METHOD["Transverse Mercator", ID["EPSG", 9807, "8.9"]] |
| * |
| * But after parsing, the version number disaspear: |
| * |
| * METHOD["Transverse Mercator", ID["EPSG", 9807]] |
| * |
| * This is a side effect of the fact that operation method are hard-coded in Java code. |
| * This is normal for our implementation, so remove the version number from the expected lines. |
| */ |
| if (f.getConvention().majorVersion() >= 2) { |
| for (int i=0; i < expectedLines.length; i++) { |
| final CharSequence line = expectedLines[i]; |
| int p = line.length(); |
| int s = CharSequences.skipLeadingWhitespaces(line, 0, p); |
| if (CharSequences.regionMatches(line, s, "METHOD[\"")) { |
| assertEquals(code, ',', line.charAt(--p)); |
| assertEquals(code, ']', line.charAt(--p)); |
| assertEquals(code, ']', line.charAt(--p)); |
| if (line.charAt(--p) == '"') { |
| p = CharSequences.lastIndexOf(line, ',', 0, p); |
| expectedLines[i] = line.subSequence(0, p) + "]],"; |
| } |
| } |
| } |
| } |
| /* |
| * Now compare the WKT line-by-line. |
| */ |
| final int length = StrictMath.min(expectedLines.length, actualLines.length); |
| try { |
| for (int i=0; i<length; i++) { |
| assertEquals(code, expectedLines[i], actualLines[i]); |
| } |
| } catch (AssertionError e) { |
| print(code, "ERROR", "WKT are not equal."); |
| final TableAppender table = new TableAppender(); |
| table.nextLine('═'); |
| table.setMultiLinesCells(true); |
| table.append("Original WKT:"); |
| table.nextColumn(); |
| table.append("CRS parsed from the WKT:"); |
| table.nextLine(); |
| table.appendHorizontalSeparator(); |
| table.append(wkt); |
| table.nextColumn(); |
| table.append(again); |
| table.nextLine(); |
| table.nextLine('═'); |
| out.println(table); |
| throw e; |
| } |
| assertEquals("Unexpected number of lines.", expectedLines.length, actualLines.length); |
| return parsed; |
| } |
| |
| /** |
| * Verifies that {@code IdentifiedObjects.lookupURN(…)} on the parsed CRS can find back the original CRS. |
| */ |
| private void lookup(final CoordinateReferenceSystem parsed, final CoordinateReferenceSystem crs) throws FactoryException { |
| final Identifier id = IdentifiedObjects.getIdentifier(crs, null); |
| final String urn = IdentifiedObjects.toURN(crs.getClass(), id); |
| assertNotNull(crs.getName().getCode(), urn); |
| /* |
| * Lookup operation is not going to work if the CRS are not approximately equal. |
| * However in current Apache SIS implementation, we can perform this check only |
| * if the scale factor of units of measurement have the exact same value. |
| * |
| * This check can be removed after the following issue is resolved: |
| * https://issues.apache.org/jira/browse/SIS-433 |
| */ |
| if (toStandardUnit(crs .getCoordinateSystem().getAxis(0).getUnit()).equals( |
| toStandardUnit(parsed.getCoordinateSystem().getAxis(0).getUnit()))) |
| { |
| assertTrue(urn, Utilities.deepEquals(crs, parsed, ComparisonMode.DEBUG)); |
| /* |
| * Now test the lookup operation. Since the parsed CRS has an identifier, |
| * that lookup operation should not do a lot of work actually. |
| */ |
| final String lookup = IdentifiedObjects.lookupURN(parsed, null); |
| assertEquals("Failed to lookup the parsed CRS.", urn, lookup); |
| } else { |
| print(id.getCode(), "SKIPPED", "Unit conversion factors differ."); |
| } |
| } |
| |
| /** |
| * Returns the converter to standard unit. |
| */ |
| private static <Q extends Quantity<Q>> UnitConverter toStandardUnit(final Unit<Q> unit) { |
| return unit.getConverterTo(unit.getSystemUnit()); |
| } |
| } |