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

Last change on this file since 16099 was 16099, checked in by simon04, 5 years ago

ProjectionRegressionTest: use Files.newBufferedReader

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