1 | // License: GPL. For details, see LICENSE file.
|
---|
2 | package org.openstreetmap.josm.data.projection;
|
---|
3 |
|
---|
4 | import static org.openstreetmap.josm.tools.I18n.tr;
|
---|
5 |
|
---|
6 | import java.io.BufferedReader;
|
---|
7 | import java.io.BufferedWriter;
|
---|
8 | import java.io.File;
|
---|
9 | import java.io.FileNotFoundException;
|
---|
10 | import java.io.FileReader;
|
---|
11 | import java.io.FileWriter;
|
---|
12 | import java.io.IOException;
|
---|
13 | import java.util.ArrayList;
|
---|
14 | import java.util.Arrays;
|
---|
15 | import java.util.Collection;
|
---|
16 | import java.util.Collections;
|
---|
17 | import java.util.HashMap;
|
---|
18 | import java.util.HashSet;
|
---|
19 | import java.util.LinkedHashMap;
|
---|
20 | import java.util.List;
|
---|
21 | import java.util.Map;
|
---|
22 | import java.util.Map.Entry;
|
---|
23 | import java.util.Random;
|
---|
24 | import java.util.Set;
|
---|
25 |
|
---|
26 | import org.junit.BeforeClass;
|
---|
27 | import org.junit.Test;
|
---|
28 | import org.openstreetmap.josm.Main;
|
---|
29 | import org.openstreetmap.josm.data.Bounds;
|
---|
30 | import org.openstreetmap.josm.data.Preferences;
|
---|
31 | import org.openstreetmap.josm.data.coor.EastNorth;
|
---|
32 | import org.openstreetmap.josm.data.coor.LatLon;
|
---|
33 | import org.openstreetmap.josm.tools.Utils;
|
---|
34 | import org.openstreetmap.josm.tools.Pair;
|
---|
35 |
|
---|
36 | /**
|
---|
37 | * This test is used to monitor changes in projection code.
|
---|
38 | *
|
---|
39 | * It keeps a record of test data in the file data_nodist/projection-regression-test-data.csv.
|
---|
40 | * This record is generated from the current Projection classes available in JOSM. It needs to
|
---|
41 | * be updated, whenever a projection is added / removed or an algorithm is changed, such that
|
---|
42 | * the computed values are numerically different. There is no error threshold, every change is reported.
|
---|
43 | *
|
---|
44 | * So when this test fails, first check if the change is intended. Then update the regression
|
---|
45 | * test data, by running the main method of this class and commit the new data file.
|
---|
46 | */
|
---|
47 | public class ProjectionRegressionTest {
|
---|
48 |
|
---|
49 | public static final String PROJECTION_DATA_FILE = "data_nodist/projection-regression-test-data.csv";
|
---|
50 |
|
---|
51 | private static class TestData {
|
---|
52 | public String code;
|
---|
53 | public LatLon ll;
|
---|
54 | public EastNorth en;
|
---|
55 | public LatLon ll2;
|
---|
56 | }
|
---|
57 |
|
---|
58 | public static void main(String[] args) throws IOException, FileNotFoundException {
|
---|
59 | setUp();
|
---|
60 | Map<String, Projection> allCodes = new LinkedHashMap<String, Projection>();
|
---|
61 | List<Projection> projs = Projections.getProjections();
|
---|
62 | for (Projection p: projs) {
|
---|
63 | if (p instanceof ProjectionSubPrefs) {
|
---|
64 | for (String code : ((ProjectionSubPrefs)p).allCodes()) {
|
---|
65 | ProjectionSubPrefs projSub = recreateProj((ProjectionSubPrefs)p);
|
---|
66 | Collection<String> prefs = projSub.getPreferencesFromCode(code);
|
---|
67 | projSub.setPreferences(prefs);
|
---|
68 | allCodes.put(code, projSub);
|
---|
69 | }
|
---|
70 | } else {
|
---|
71 | allCodes.put(p.toCode(), p);
|
---|
72 | }
|
---|
73 | }
|
---|
74 |
|
---|
75 | List<TestData> prevData = new ArrayList<TestData>();
|
---|
76 | if (new File(PROJECTION_DATA_FILE).exists()) {
|
---|
77 | prevData = readData();
|
---|
78 | }
|
---|
79 | Map<String,TestData> prevCodes = new HashMap<String,TestData>();
|
---|
80 | for (TestData data : prevData) {
|
---|
81 | prevCodes.put(data.code, data);
|
---|
82 | }
|
---|
83 |
|
---|
84 | Random rand = new Random();
|
---|
85 | BufferedWriter out = new BufferedWriter(new FileWriter(PROJECTION_DATA_FILE));
|
---|
86 | out.write("# Data for test/unit/org/openstreetmap/josm/data/projection/ProjectionRegressionTest.java\n");
|
---|
87 | out.write("# Format: 1. Projection code; 2. lat/lon; 3. lat/lon projected -> east/north; 4. east/north (3.) inverse projected\n");
|
---|
88 | for (Entry<String, Projection> e : allCodes.entrySet()) {
|
---|
89 | Projection proj = e.getValue();
|
---|
90 | Bounds b = proj.getWorldBoundsLatLon();
|
---|
91 | double lat, lon;
|
---|
92 | TestData prev = prevCodes.get(proj.toCode());
|
---|
93 | if (prev != null) {
|
---|
94 | lat = prev.ll.lat();
|
---|
95 | lon = prev.ll.lon();
|
---|
96 | } else {
|
---|
97 | lat = b.getMin().lat() + rand.nextDouble() * (b.getMax().lat() - b.getMin().lat());
|
---|
98 | lon = b.getMin().lon() + rand.nextDouble() * (b.getMax().lon() - b.getMin().lon());
|
---|
99 | }
|
---|
100 | EastNorth en = proj.latlon2eastNorth(new LatLon(lat, lon));
|
---|
101 | LatLon ll2 = proj.eastNorth2latlon(en);
|
---|
102 | 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()));
|
---|
103 | }
|
---|
104 | out.close();
|
---|
105 | }
|
---|
106 |
|
---|
107 | private static ProjectionSubPrefs recreateProj(ProjectionSubPrefs proj) {
|
---|
108 | try {
|
---|
109 | return proj.getClass().newInstance();
|
---|
110 | } catch (Exception e) {
|
---|
111 | throw new IllegalStateException(
|
---|
112 | tr("Cannot instantiate projection ''{0}''", proj.getClass().toString()), e);
|
---|
113 | }
|
---|
114 | }
|
---|
115 |
|
---|
116 | private static List<TestData> readData() throws IOException, FileNotFoundException {
|
---|
117 | BufferedReader in = new BufferedReader(new FileReader(PROJECTION_DATA_FILE));
|
---|
118 | List<TestData> result = new ArrayList<TestData>();
|
---|
119 | String line;
|
---|
120 | while ((line = in.readLine()) != null) {
|
---|
121 | if (line.startsWith("#")) {
|
---|
122 | continue;
|
---|
123 | }
|
---|
124 | TestData next = new TestData();
|
---|
125 |
|
---|
126 | Pair<Double,Double> ll = readLine("ll", in.readLine());
|
---|
127 | Pair<Double,Double> en = readLine("en", in.readLine());
|
---|
128 | Pair<Double,Double> ll2 = readLine("ll2", in.readLine());
|
---|
129 |
|
---|
130 | next.code = line;
|
---|
131 | next.ll = new LatLon(ll.a, ll.b);
|
---|
132 | next.en = new EastNorth(en.a, en.b);
|
---|
133 | next.ll2 = new LatLon(ll2.a, ll2.b);
|
---|
134 |
|
---|
135 | result.add(next);
|
---|
136 | }
|
---|
137 | in.close();
|
---|
138 | return result;
|
---|
139 | }
|
---|
140 |
|
---|
141 | private static Pair<Double,Double> readLine(String expectedName, String input) {
|
---|
142 | String[] fields = input.trim().split("[ ]+");
|
---|
143 | if (fields.length != 3) throw new AssertionError();
|
---|
144 | if (!fields[0].equals(expectedName)) throw new AssertionError();
|
---|
145 | double a = Double.parseDouble(fields[1]);
|
---|
146 | double b = Double.parseDouble(fields[2]);
|
---|
147 | return Pair.create(a, b);
|
---|
148 | }
|
---|
149 |
|
---|
150 | @BeforeClass
|
---|
151 | public static void setUp() {
|
---|
152 | Main.pref = new Preferences();
|
---|
153 | }
|
---|
154 |
|
---|
155 | @Test
|
---|
156 | public void regressionTest() throws IOException, FileNotFoundException {
|
---|
157 | List<TestData> allData = readData();
|
---|
158 | Set<String> dataCodes = new HashSet<String>();
|
---|
159 | for (TestData data : allData) {
|
---|
160 | dataCodes.add(data.code);
|
---|
161 | }
|
---|
162 |
|
---|
163 | StringBuilder fail = new StringBuilder();
|
---|
164 |
|
---|
165 | List<Projection> projs = Projections.getProjections();
|
---|
166 | for (Projection p: projs) {
|
---|
167 | Collection<String> codes = null;
|
---|
168 | if (p instanceof ProjectionSubPrefs) {
|
---|
169 | codes = Arrays.asList(((ProjectionSubPrefs)p).allCodes());
|
---|
170 | } else {
|
---|
171 | codes = Collections.singleton(p.toCode());
|
---|
172 | }
|
---|
173 | for (String code : codes) {
|
---|
174 | if (!dataCodes.contains(code)) {
|
---|
175 | fail.append("Did not find projection "+code+" in test data!\n");
|
---|
176 | }
|
---|
177 | }
|
---|
178 | }
|
---|
179 |
|
---|
180 | for (TestData data : allData) {
|
---|
181 | Projection proj = ProjectionInfo.getProjectionByCode(data.code);
|
---|
182 | if (proj == null) {
|
---|
183 | fail.append("Projection "+data.code+" from test data was not found!\n");
|
---|
184 | continue;
|
---|
185 | }
|
---|
186 | EastNorth en = proj.latlon2eastNorth(data.ll);
|
---|
187 | if (!en.equals(data.en)) {
|
---|
188 | String error = String.format("%s (%s): Projecting latlon(%s,%s):\n" +
|
---|
189 | " expected: eastnorth(%s,%s),\n" +
|
---|
190 | " but got: eastnorth(%s,%s)!\n",
|
---|
191 | proj.toString(), data.code, data.ll.lat(), data.ll.lon(), data.en.east(), data.en.north(), en.east(), en.north());
|
---|
192 | fail.append(error);
|
---|
193 | }
|
---|
194 | LatLon ll2 = proj.eastNorth2latlon(data.en);
|
---|
195 | if (!ll2.equals(data.ll2)) {
|
---|
196 | String error = String.format("%s (%s): Inverse projecting eastnorth(%s,%s):\n" +
|
---|
197 | " expected: latlon(%s,%s),\n" +
|
---|
198 | " but got: latlon(%s,%s)!\n",
|
---|
199 | proj.toString(), data.code, data.en.east(), data.en.north(), data.ll2.lat(), data.ll2.lon(), ll2.lat(), ll2.lon());
|
---|
200 | fail.append(error);
|
---|
201 | }
|
---|
202 | }
|
---|
203 |
|
---|
204 | if (fail.length() > 0) {
|
---|
205 | System.err.println(fail.toString());
|
---|
206 | throw new AssertionError(fail.toString());
|
---|
207 | }
|
---|
208 |
|
---|
209 | }
|
---|
210 | }
|
---|