blob: 614da83860b31912885ff2483564ca838ba13502 [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.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());
}
}