source: josm/trunk/test/unit/org/openstreetmap/josm/data/projection/ProjectionRegressionTest.java@ 16445

Last change on this file since 16445 was 16445, checked in by simon04, 4 years ago

see #19251 - Java 8: use Stream

  • Property svn:eol-style set to native
File size: 8.8 KB
RevLine 
[5065]1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.data.projection;
3
4import java.io.BufferedReader;
5import java.io.BufferedWriter;
6import java.io.File;
7import java.io.IOException;
[6995]8import java.io.OutputStreamWriter;
[7082]9import java.nio.charset.StandardCharsets;
[15034]10import java.nio.file.Files;
11import java.nio.file.Paths;
[11324]12import java.security.SecureRandom;
[5065]13import java.util.ArrayList;
14import java.util.List;
15import java.util.Map;
16import java.util.Random;
17import java.util.Set;
[8793]18import java.util.TreeSet;
[16436]19import java.util.stream.Collectors;
[5065]20
[5073]21import org.junit.BeforeClass;
[5065]22import org.junit.Test;
[7081]23import org.openstreetmap.josm.JOSMFixture;
[5065]24import org.openstreetmap.josm.data.Bounds;
25import org.openstreetmap.josm.data.coor.EastNorth;
26import org.openstreetmap.josm.data.coor.LatLon;
27import org.openstreetmap.josm.tools.Pair;
[12130]28import org.openstreetmap.josm.tools.Utils;
[5065]29
30/**
31 * This test is used to monitor changes in projection code.
32 *
[16006]33 * It keeps a record of test data in the file nodist/data/projection/projection-regression-test-data.
[5065]34 * This record is generated from the current Projection classes available in JOSM. It needs to
35 * be updated, whenever a projection is added / removed or an algorithm is changed, such that
36 * the computed values are numerically different. There is no error threshold, every change is reported.
37 *
38 * So when this test fails, first check if the change is intended. Then update the regression
39 * test data, by running the main method of this class and commit the new data file.
40 */
41public class ProjectionRegressionTest {
42
[16006]43 private static final String PROJECTION_DATA_FILE = "nodist/data/projection/projection-regression-test-data";
[5065]44
45 private static class TestData {
46 public String code;
47 public LatLon ll;
48 public EastNorth en;
49 public LatLon ll2;
50 }
51
[9908]52 /**
53 * Program entry point to update reference projection file.
54 * @param args not used
55 * @throws IOException if any I/O errors occurs
56 */
[8793]57 public static void main(String[] args) throws IOException {
[5073]58 setUp();
[5236]59
[16445]60 Map<String, Projection> supportedCodesMap = Projections.getAllProjectionCodes().stream()
61 .collect(Collectors.toMap(code -> code, Projections::getProjectionByCode));
[5065]62
[7005]63 List<TestData> prevData = new ArrayList<>();
[10060]64 if (new File(PROJECTION_DATA_FILE).exists()) {
[5065]65 prevData = readData();
66 }
[16445]67 Map<String, TestData> prevCodesMap = prevData.stream()
68 .collect(Collectors.toMap(data -> data.code, data -> data));
[5065]69
[16445]70 Set<String> codesToWrite = new TreeSet<>(supportedCodesMap.keySet());
71 prevData.stream()
72 .filter(data -> supportedCodesMap.containsKey(data.code)).map(data -> data.code)
73 .forEach(codesToWrite::add);
[5236]74
[11324]75 Random rand = new SecureRandom();
[8540]76 try (BufferedWriter out = new BufferedWriter(new OutputStreamWriter(
[15034]77 Files.newOutputStream(Paths.get(PROJECTION_DATA_FILE)), StandardCharsets.UTF_8))) {
[7030]78 out.write("# Data for test/unit/org/openstreetmap/josm/data/projection/ProjectionRegressionTest.java\n");
79 out.write("# Format: 1. Projection code; 2. lat/lon; 3. lat/lon projected -> east/north; 4. east/north (3.) inverse projected\n");
80 for (String code : codesToWrite) {
81 Projection proj = supportedCodesMap.get(code);
82 Bounds b = proj.getWorldBoundsLatLon();
83 double lat, lon;
84 TestData prev = prevCodesMap.get(proj.toCode());
85 if (prev != null) {
86 lat = prev.ll.lat();
87 lon = prev.ll.lon();
88 } else {
89 lat = b.getMin().lat() + rand.nextDouble() * (b.getMax().lat() - b.getMin().lat());
90 lon = b.getMin().lon() + rand.nextDouble() * (b.getMax().lon() - b.getMin().lon());
91 }
92 EastNorth en = proj.latlon2eastNorth(new LatLon(lat, lon));
93 LatLon ll2 = proj.eastNorth2latlon(en);
[8509]94 out.write(String.format(
95 "%s%n ll %s %s%n en %s %s%n ll2 %s %s%n", proj.toCode(), lat, lon, en.east(), en.north(), ll2.lat(), ll2.lon()));
[5065]96 }
97 }
[9133]98 System.out.println("Update successful.");
[5065]99 }
100
[16099]101 private static List<TestData> readData() throws IOException {
102 try (BufferedReader in = Files.newBufferedReader(Paths.get(PROJECTION_DATA_FILE), StandardCharsets.UTF_8)) {
[7030]103 List<TestData> result = new ArrayList<>();
104 String line;
105 while ((line = in.readLine()) != null) {
106 if (line.startsWith("#")) {
107 continue;
108 }
109 TestData next = new TestData();
[7081]110
[8510]111 Pair<Double, Double> ll = readLine("ll", in.readLine());
112 Pair<Double, Double> en = readLine("en", in.readLine());
113 Pair<Double, Double> ll2 = readLine("ll2", in.readLine());
[7081]114
[7030]115 next.code = line;
116 next.ll = new LatLon(ll.a, ll.b);
117 next.en = new EastNorth(en.a, en.b);
118 next.ll2 = new LatLon(ll2.a, ll2.b);
[7081]119
[7030]120 result.add(next);
[5065]121 }
[7030]122 return result;
[5065]123 }
124 }
125
[8510]126 private static Pair<Double, Double> readLine(String expectedName, String input) {
[5065]127 String[] fields = input.trim().split("[ ]+");
128 if (fields.length != 3) throw new AssertionError();
129 if (!fields[0].equals(expectedName)) throw new AssertionError();
130 double a = Double.parseDouble(fields[1]);
131 double b = Double.parseDouble(fields[2]);
132 return Pair.create(a, b);
133 }
134
[6881]135 /**
136 * Setup test.
137 */
[5073]138 @BeforeClass
139 public static void setUp() {
[7081]140 JOSMFixture.createUnitTestFixture().init();
[5073]141 }
142
[9908]143 /**
144 * Non-regression unit test.
145 * @throws IOException if any I/O error occurs
146 */
[5065]147 @Test
[10758]148 public void testNonRegression() throws IOException {
[5065]149 List<TestData> allData = readData();
[16436]150 Set<String> dataCodes = allData.stream().map(data -> data.code).collect(Collectors.toSet());
[5065]151
152 StringBuilder fail = new StringBuilder();
153
[9133]154 for (String code : Projections.getAllProjectionCodes()) {
155 if (!dataCodes.contains(code)) {
156 fail.append("Did not find projection "+code+" in test data!\n");
157 }
[5065]158 }
159
[12130]160 final boolean java9 = Utils.getJavaVersion() >= 9;
[5065]161 for (TestData data : allData) {
[5554]162 Projection proj = Projections.getProjectionByCode(data.code);
[5065]163 if (proj == null) {
164 fail.append("Projection "+data.code+" from test data was not found!\n");
165 continue;
166 }
167 EastNorth en = proj.latlon2eastNorth(data.ll);
[10065]168 LatLon ll2 = proj.eastNorth2latlon(data.en);
[12001]169 if (!(java9 ? equalsJava9(en, data.en) : en.equals(data.en))) {
[6334]170 String error = String.format("%s (%s): Projecting latlon(%s,%s):%n" +
171 " expected: eastnorth(%s,%s),%n" +
172 " but got: eastnorth(%s,%s)!%n",
[5065]173 proj.toString(), data.code, data.ll.lat(), data.ll.lon(), data.en.east(), data.en.north(), en.east(), en.north());
174 fail.append(error);
175 }
[12001]176 if (!(java9 ? equalsJava9(ll2, data.ll2) : ll2.equals(data.ll2))) {
[6334]177 String error = String.format("%s (%s): Inverse projecting eastnorth(%s,%s):%n" +
178 " expected: latlon(%s,%s),%n" +
179 " but got: latlon(%s,%s)!%n",
[5065]180 proj.toString(), data.code, data.en.east(), data.en.north(), data.ll2.lat(), data.ll2.lon(), ll2.lat(), ll2.lon());
181 fail.append(error);
182 }
183 }
184
185 if (fail.length() > 0) {
186 System.err.println(fail.toString());
187 throw new AssertionError(fail.toString());
188 }
189 }
[11999]190
191 private static boolean equalsDoubleMaxUlp(double d1, double d2) {
[12021]192 // Due to error accumulation in projection computation, the difference can reach hundreds of ULPs
[13659]193 // The worst error is 1168 ULP (followed by 816 ULP then 512 ULP) with:
194 // NAD83 / Colorado South (EPSG:26955): Projecting latlon(32.24604527892822,-125.93039495227096):
195 // expected: eastnorth(-1004398.8994415681,24167.8944844745),
196 // but got: eastnorth(-1004398.8994415683,24167.894484478747)!
197 return Math.abs(d1 - d2) <= 1200 * Math.ulp(d1);
[11999]198 }
199
200 private static boolean equalsJava9(EastNorth en1, EastNorth en2) {
201 return equalsDoubleMaxUlp(en1.east(), en2.east()) &&
202 equalsDoubleMaxUlp(en1.north(), en2.north());
203 }
204
205 private static boolean equalsJava9(LatLon ll1, LatLon ll2) {
206 return equalsDoubleMaxUlp(ll1.lat(), ll2.lat()) &&
207 equalsDoubleMaxUlp(ll1.lon(), ll2.lon());
208 }
[5065]209}
Note: See TracBrowser for help on using the repository browser.