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

Last change on this file since 12022 was 12022, checked in by Don-vip, 7 years ago

see #13387 - forgot debug code

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