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

Last change on this file since 5065 was 5065, checked in by bastiK, 12 years ago

add projection regression test

This test fails whenever the numerical value of the projected coordinates changes in future versions of JOSM.
There is no error threshold, when the change is intended, the test data has to be updated as well.

  • Property svn:eol-style set to native
File size: 8.1 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.data.projection;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.io.BufferedReader;
7import java.io.BufferedWriter;
8import java.io.File;
9import java.io.FileNotFoundException;
10import java.io.FileReader;
11import java.io.FileWriter;
12import java.io.IOException;
13import java.util.ArrayList;
14import java.util.Arrays;
15import java.util.Collection;
16import java.util.Collections;
17import java.util.HashMap;
18import java.util.HashSet;
19import java.util.LinkedHashMap;
20import java.util.List;
21import java.util.Map;
22import java.util.Map.Entry;
23import java.util.Random;
24import java.util.Set;
25
26import org.junit.Test;
27import org.openstreetmap.josm.data.Bounds;
28import org.openstreetmap.josm.data.coor.EastNorth;
29import org.openstreetmap.josm.data.coor.LatLon;
30import org.openstreetmap.josm.tools.Utils;
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-regression-test-data.csv.
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 public static final String PROJECTION_DATA_FILE = "data_nodist/projection-regression-test-data.csv";
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 public static void main(String[] args) throws IOException, FileNotFoundException {
56 Map<String, Projection> allCodes = new LinkedHashMap<String, Projection>();
57 List<Projection> projs = Projections.getProjections();
58 for (Projection p: projs) {
59 if (p instanceof ProjectionSubPrefs) {
60 for (String code : ((ProjectionSubPrefs)p).allCodes()) {
61 ProjectionSubPrefs projSub = recreateProj((ProjectionSubPrefs)p);
62 Collection<String> prefs = projSub.getPreferencesFromCode(code);
63 projSub.setPreferences(prefs);
64 allCodes.put(code, projSub);
65 }
66 } else {
67 allCodes.put(p.toCode(), p);
68 }
69 }
70
71 List<TestData> prevData = new ArrayList<TestData>();
72 if (new File(PROJECTION_DATA_FILE).exists()) {
73 prevData = readData();
74 }
75 Map<String,TestData> prevCodes = new HashMap<String,TestData>();
76 for (TestData data : prevData) {
77 prevCodes.put(data.code, data);
78 }
79
80 Random rand = new Random();
81 BufferedWriter out = new BufferedWriter(new FileWriter(PROJECTION_DATA_FILE));
82 out.write("# Data for test/unit/org/openstreetmap/josm/data/projection/ProjectionRegressionTest.java\n");
83 out.write("# Format: 1. Projection code; 2. lat/lon; 3. lat/lon projected -> east/north; 4. east/north (3.) inverse projected\n");
84 for (Entry<String, Projection> e : allCodes.entrySet()) {
85 Projection proj = e.getValue();
86 Bounds b = proj.getWorldBoundsLatLon();
87 double lat, lon;
88 TestData prev = prevCodes.get(proj.toCode());
89 if (prev != null) {
90 lat = prev.ll.lat();
91 lon = prev.ll.lon();
92 } else {
93 lat = b.getMin().lat() + rand.nextDouble() * (b.getMax().lat() - b.getMin().lat());
94 lon = b.getMin().lon() + rand.nextDouble() * (b.getMax().lon() - b.getMin().lon());
95 }
96 EastNorth en = proj.latlon2eastNorth(new LatLon(lat, lon));
97 LatLon ll2 = proj.eastNorth2latlon(en);
98 out.write(String.format("%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()));
99 }
100 out.close();
101 }
102
103 private static ProjectionSubPrefs recreateProj(ProjectionSubPrefs proj) {
104 try {
105 return proj.getClass().newInstance();
106 } catch (Exception e) {
107 throw new IllegalStateException(
108 tr("Cannot instantiate projection ''{0}''", proj.getClass().toString()), e);
109 }
110 }
111
112 private static List<TestData> readData() throws IOException, FileNotFoundException {
113 BufferedReader in = new BufferedReader(new FileReader(PROJECTION_DATA_FILE));
114 List<TestData> result = new ArrayList<TestData>();
115 String line;
116 while ((line = in.readLine()) != null) {
117 if (line.startsWith("#")) {
118 continue;
119 }
120 TestData next = new TestData();
121
122 Pair<Double,Double> ll = readLine("ll", in.readLine());
123 Pair<Double,Double> en = readLine("en", in.readLine());
124 Pair<Double,Double> ll2 = readLine("ll2", in.readLine());
125
126 next.code = line;
127 next.ll = new LatLon(ll.a, ll.b);
128 next.en = new EastNorth(en.a, en.b);
129 next.ll2 = new LatLon(ll2.a, ll2.b);
130
131 result.add(next);
132 }
133 in.close();
134 return result;
135 }
136
137 private static Pair<Double,Double> readLine(String expectedName, String input) {
138 String[] fields = input.trim().split("[ ]+");
139 if (fields.length != 3) throw new AssertionError();
140 if (!fields[0].equals(expectedName)) throw new AssertionError();
141 double a = Double.parseDouble(fields[1]);
142 double b = Double.parseDouble(fields[2]);
143 return Pair.create(a, b);
144 }
145
146 @Test
147 public void regressionTest() throws IOException, FileNotFoundException {
148 List<TestData> allData = readData();
149 Set<String> dataCodes = new HashSet<String>();
150 for (TestData data : allData) {
151 dataCodes.add(data.code);
152 }
153
154 StringBuilder fail = new StringBuilder();
155
156 List<Projection> projs = Projections.getProjections();
157 for (Projection p: projs) {
158 Collection<String> codes = null;
159 if (p instanceof ProjectionSubPrefs) {
160 codes = Arrays.asList(((ProjectionSubPrefs)p).allCodes());
161 } else {
162 codes = Collections.singleton(p.toCode());
163 }
164 for (String code : codes) {
165 if (!dataCodes.contains(code)) {
166 fail.append("Did not find projection "+code+" in test data!\n");
167 }
168 }
169 }
170
171 for (TestData data : allData) {
172 Projection proj = ProjectionInfo.getProjectionByCode(data.code);
173 if (proj == null) {
174 fail.append("Projection "+data.code+" from test data was not found!\n");
175 continue;
176 }
177 EastNorth en = proj.latlon2eastNorth(data.ll);
178 if (!en.equals(data.en)) {
179 String error = String.format("%s (%s): Projecting latlon(%s,%s):\n" +
180 " expected: eastnorth(%s,%s),\n" +
181 " but got: eastnorth(%s,%s)!\n",
182 proj.toString(), data.code, data.ll.lat(), data.ll.lon(), data.en.east(), data.en.north(), en.east(), en.north());
183 fail.append(error);
184 }
185 LatLon ll2 = proj.eastNorth2latlon(data.en);
186 if (!ll2.equals(data.ll2)) {
187 String error = String.format("%s (%s): Inverse projecting eastnorth(%s,%s):\n" +
188 " expected: latlon(%s,%s),\n" +
189 " but got: latlon(%s,%s)!\n",
190 proj.toString(), data.code, data.en.east(), data.en.north(), data.ll2.lat(), data.ll2.lon(), ll2.lat(), ll2.lon());
191 fail.append(error);
192 }
193 }
194
195 if (fail.length() > 0) {
196 System.err.println(fail.toString());
197 throw new AssertionError(fail.toString());
198 }
199
200 }
201}
Note: See TracBrowser for help on using the repository browser.