[5065] | 1 | // License: GPL. For details, see LICENSE file.
|
---|
| 2 | package org.openstreetmap.josm.data.projection;
|
---|
| 3 |
|
---|
| 4 | import java.io.BufferedReader;
|
---|
| 5 | import java.io.BufferedWriter;
|
---|
| 6 | import java.io.File;
|
---|
[6995] | 7 | import java.io.FileInputStream;
|
---|
[5065] | 8 | import java.io.FileNotFoundException;
|
---|
[6995] | 9 | import java.io.FileOutputStream;
|
---|
[5065] | 10 | import java.io.IOException;
|
---|
[6995] | 11 | import java.io.InputStreamReader;
|
---|
| 12 | import java.io.OutputStreamWriter;
|
---|
[7082] | 13 | import java.nio.charset.StandardCharsets;
|
---|
[5065] | 14 | import java.util.ArrayList;
|
---|
| 15 | import java.util.HashMap;
|
---|
| 16 | import java.util.HashSet;
|
---|
| 17 | import java.util.List;
|
---|
| 18 | import java.util.Map;
|
---|
| 19 | import java.util.Random;
|
---|
| 20 | import java.util.Set;
|
---|
[8793] | 21 | import java.util.TreeSet;
|
---|
[5065] | 22 |
|
---|
[5073] | 23 | import org.junit.BeforeClass;
|
---|
[5065] | 24 | import org.junit.Test;
|
---|
[7081] | 25 | import org.openstreetmap.josm.JOSMFixture;
|
---|
[8793] | 26 | import org.openstreetmap.josm.TestUtils;
|
---|
[5065] | 27 | import org.openstreetmap.josm.data.Bounds;
|
---|
| 28 | import org.openstreetmap.josm.data.coor.EastNorth;
|
---|
| 29 | import org.openstreetmap.josm.data.coor.LatLon;
|
---|
[5236] | 30 | import org.openstreetmap.josm.gui.preferences.projection.ProjectionChoice;
|
---|
| 31 | import org.openstreetmap.josm.gui.preferences.projection.ProjectionPreference;
|
---|
[5065] | 32 | import org.openstreetmap.josm.tools.Pair;
|
---|
| 33 |
|
---|
| 34 | /**
|
---|
| 35 | * This test is used to monitor changes in projection code.
|
---|
| 36 | *
|
---|
| 37 | * It keeps a record of test data in the file data_nodist/projection-regression-test-data.csv.
|
---|
| 38 | * This record is generated from the current Projection classes available in JOSM. It needs to
|
---|
| 39 | * be updated, whenever a projection is added / removed or an algorithm is changed, such that
|
---|
| 40 | * the computed values are numerically different. There is no error threshold, every change is reported.
|
---|
| 41 | *
|
---|
| 42 | * So when this test fails, first check if the change is intended. Then update the regression
|
---|
| 43 | * test data, by running the main method of this class and commit the new data file.
|
---|
| 44 | */
|
---|
| 45 | public class ProjectionRegressionTest {
|
---|
| 46 |
|
---|
[8457] | 47 | private static final String PROJECTION_DATA_FILE = "data_nodist/projection-regression-test-data.csv";
|
---|
[8793] | 48 | private static final String PROJECTION_DATA_FILE_JAVA_9 = "data_nodist/projection-regression-test-data-java9.csv";
|
---|
[5065] | 49 |
|
---|
| 50 | private static class TestData {
|
---|
| 51 | public String code;
|
---|
| 52 | public LatLon ll;
|
---|
| 53 | public EastNorth en;
|
---|
| 54 | public LatLon ll2;
|
---|
| 55 | }
|
---|
| 56 |
|
---|
[8793] | 57 | private static String getProjectionDataFile() {
|
---|
| 58 | return TestUtils.getJavaVersion() >= 1.9 ? PROJECTION_DATA_FILE_JAVA_9 : PROJECTION_DATA_FILE;
|
---|
| 59 | }
|
---|
| 60 |
|
---|
| 61 | public static void main(String[] args) throws IOException {
|
---|
[5073] | 62 | setUp();
|
---|
[5236] | 63 |
|
---|
[7005] | 64 | Map<String, Projection> supportedCodesMap = new HashMap<>();
|
---|
[7944] | 65 | for (String code : Projections.getAllProjectionCodes()) {
|
---|
| 66 | supportedCodesMap.put(code, Projections.getProjectionByCode(code));
|
---|
[5065] | 67 | }
|
---|
| 68 |
|
---|
[7005] | 69 | List<TestData> prevData = new ArrayList<>();
|
---|
[8793] | 70 | if (new File(getProjectionDataFile()).exists()) {
|
---|
[5065] | 71 | prevData = readData();
|
---|
| 72 | }
|
---|
[8510] | 73 | Map<String, TestData> prevCodesMap = new HashMap<>();
|
---|
[5065] | 74 | for (TestData data : prevData) {
|
---|
[5236] | 75 | prevCodesMap.put(data.code, data);
|
---|
[5065] | 76 | }
|
---|
| 77 |
|
---|
[8793] | 78 | Set<String> codesToWrite = new TreeSet<>();
|
---|
[5236] | 79 | for (TestData data : prevData) {
|
---|
| 80 | if (supportedCodesMap.containsKey(data.code)) {
|
---|
| 81 | codesToWrite.add(data.code);
|
---|
| 82 | }
|
---|
| 83 | }
|
---|
| 84 | for (String code : supportedCodesMap.keySet()) {
|
---|
| 85 | if (!codesToWrite.contains(code)) {
|
---|
| 86 | codesToWrite.add(code);
|
---|
| 87 | }
|
---|
| 88 | }
|
---|
| 89 |
|
---|
[5065] | 90 | Random rand = new Random();
|
---|
[8540] | 91 | try (BufferedWriter out = new BufferedWriter(new OutputStreamWriter(
|
---|
[8793] | 92 | new FileOutputStream(getProjectionDataFile()), StandardCharsets.UTF_8))) {
|
---|
[7030] | 93 | out.write("# Data for test/unit/org/openstreetmap/josm/data/projection/ProjectionRegressionTest.java\n");
|
---|
| 94 | out.write("# Format: 1. Projection code; 2. lat/lon; 3. lat/lon projected -> east/north; 4. east/north (3.) inverse projected\n");
|
---|
| 95 | for (String code : codesToWrite) {
|
---|
| 96 | Projection proj = supportedCodesMap.get(code);
|
---|
| 97 | Bounds b = proj.getWorldBoundsLatLon();
|
---|
| 98 | double lat, lon;
|
---|
| 99 | TestData prev = prevCodesMap.get(proj.toCode());
|
---|
| 100 | if (prev != null) {
|
---|
| 101 | lat = prev.ll.lat();
|
---|
| 102 | lon = prev.ll.lon();
|
---|
| 103 | } else {
|
---|
| 104 | lat = b.getMin().lat() + rand.nextDouble() * (b.getMax().lat() - b.getMin().lat());
|
---|
| 105 | lon = b.getMin().lon() + rand.nextDouble() * (b.getMax().lon() - b.getMin().lon());
|
---|
| 106 | }
|
---|
| 107 | EastNorth en = proj.latlon2eastNorth(new LatLon(lat, lon));
|
---|
| 108 | LatLon ll2 = proj.eastNorth2latlon(en);
|
---|
[8509] | 109 | out.write(String.format(
|
---|
| 110 | "%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] | 111 | }
|
---|
| 112 | }
|
---|
| 113 | }
|
---|
| 114 |
|
---|
| 115 | private static List<TestData> readData() throws IOException, FileNotFoundException {
|
---|
[8793] | 116 | try (BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream(getProjectionDataFile()),
|
---|
| 117 | StandardCharsets.UTF_8))) {
|
---|
[7030] | 118 | List<TestData> result = new ArrayList<>();
|
---|
| 119 | String line;
|
---|
| 120 | while ((line = in.readLine()) != null) {
|
---|
| 121 | if (line.startsWith("#")) {
|
---|
| 122 | continue;
|
---|
| 123 | }
|
---|
| 124 | TestData next = new TestData();
|
---|
[7081] | 125 |
|
---|
[8510] | 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());
|
---|
[7081] | 129 |
|
---|
[7030] | 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);
|
---|
[7081] | 134 |
|
---|
[7030] | 135 | result.add(next);
|
---|
[5065] | 136 | }
|
---|
[7030] | 137 | return result;
|
---|
[5065] | 138 | }
|
---|
| 139 | }
|
---|
| 140 |
|
---|
[8510] | 141 | private static Pair<Double, Double> readLine(String expectedName, String input) {
|
---|
[5065] | 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 |
|
---|
[6881] | 150 | /**
|
---|
| 151 | * Setup test.
|
---|
| 152 | */
|
---|
[5073] | 153 | @BeforeClass
|
---|
| 154 | public static void setUp() {
|
---|
[7081] | 155 | JOSMFixture.createUnitTestFixture().init();
|
---|
[5073] | 156 | }
|
---|
| 157 |
|
---|
[5065] | 158 | @Test
|
---|
| 159 | public void regressionTest() throws IOException, FileNotFoundException {
|
---|
| 160 | List<TestData> allData = readData();
|
---|
[7005] | 161 | Set<String> dataCodes = new HashSet<>();
|
---|
[5065] | 162 | for (TestData data : allData) {
|
---|
| 163 | dataCodes.add(data.code);
|
---|
| 164 | }
|
---|
| 165 |
|
---|
| 166 | StringBuilder fail = new StringBuilder();
|
---|
| 167 |
|
---|
[5236] | 168 | for (ProjectionChoice pc : ProjectionPreference.getProjectionChoices()) {
|
---|
| 169 | for (String code : pc.allCodes()) {
|
---|
[5065] | 170 | if (!dataCodes.contains(code)) {
|
---|
| 171 | fail.append("Did not find projection "+code+" in test data!\n");
|
---|
| 172 | }
|
---|
| 173 | }
|
---|
| 174 | }
|
---|
| 175 |
|
---|
| 176 | for (TestData data : allData) {
|
---|
[5554] | 177 | Projection proj = Projections.getProjectionByCode(data.code);
|
---|
[5065] | 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 | if (!en.equals(data.en)) {
|
---|
[6334] | 184 | String error = String.format("%s (%s): Projecting latlon(%s,%s):%n" +
|
---|
| 185 | " expected: eastnorth(%s,%s),%n" +
|
---|
| 186 | " but got: eastnorth(%s,%s)!%n",
|
---|
[5065] | 187 | proj.toString(), data.code, data.ll.lat(), data.ll.lon(), data.en.east(), data.en.north(), en.east(), en.north());
|
---|
| 188 | fail.append(error);
|
---|
| 189 | }
|
---|
| 190 | LatLon ll2 = proj.eastNorth2latlon(data.en);
|
---|
| 191 | if (!ll2.equals(data.ll2)) {
|
---|
[6334] | 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",
|
---|
[5065] | 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 | }
|
---|