File: BuildProjectionDefinitions.java

package info (click to toggle)
josm 0.0.svn14760%2Bdfsg-1
  • links: PTS, VCS
  • area: main
  • in suites: buster
  • size: 187,192 kB
  • sloc: java: 317,260; xml: 197,001; perl: 10,125; jsp: 250; sh: 112; makefile: 94; python: 29
file content (357 lines) | stat: -rw-r--r-- 17,000 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
// License: GPL. For details, see LICENSE file.

import java.io.BufferedReader;
import java.io.IOException;
import java.io.Writer;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.TreeMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import org.openstreetmap.josm.data.projection.CustomProjection;
import org.openstreetmap.josm.data.projection.CustomProjection.Param;
import org.openstreetmap.josm.data.projection.ProjectionConfigurationException;
import org.openstreetmap.josm.data.projection.Projections;
import org.openstreetmap.josm.data.projection.Projections.ProjectionDefinition;
import org.openstreetmap.josm.data.projection.proj.Proj;

/**
 * Generates the list of projections by combining two sources: The list from the
 * proj.4 project and a list maintained by the JOSM team.
 */
public final class BuildProjectionDefinitions {

    private static final String PROJ_DIR = "data_nodist/projection";
    private static final String JOSM_EPSG_FILE = "josm-epsg";
    private static final String PROJ4_EPSG_FILE = "epsg";
    private static final String PROJ4_ESRI_FILE = "esri";
    private static final String OUTPUT_EPSG_FILE = "data/projection/custom-epsg";

    private static final Map<String, ProjectionDefinition> epsgProj4 = new LinkedHashMap<>();
    private static final Map<String, ProjectionDefinition> esriProj4 = new LinkedHashMap<>();
    private static final Map<String, ProjectionDefinition> epsgJosm = new LinkedHashMap<>();

    private static final boolean printStats = false;

    // statistics:
    private static int noInJosm;
    private static int noInProj4;
    private static int noDeprecated;
    private static int noGeocent;
    private static int noBaseProjection;
    private static int noEllipsoid;
    private static int noNadgrid;
    private static int noDatumgrid;
    private static int noJosm;
    private static int noProj4;
    private static int noEsri;
    private static int noOmercNoBounds;
    private static int noEquatorStereo;

    private static final Map<String, Integer> baseProjectionMap = new TreeMap<>();
    private static final Map<String, Integer> ellipsoidMap = new TreeMap<>();
    private static final Map<String, Integer> nadgridMap = new TreeMap<>();
    private static final Map<String, Integer> datumgridMap = new TreeMap<>();

    private static List<String> knownGeoidgrids;
    private static List<String> knownNadgrids;

    private BuildProjectionDefinitions() {
    }

    /**
     * Program entry point
     * @param args command line arguments (not used)
     * @throws IOException if any I/O error occurs
     */
    public static void main(String[] args) throws IOException {
        buildList(args[0]);
    }

    static List<String> initList(String baseDir, String ext) throws IOException {
        return Files.list(Paths.get(baseDir).resolve(PROJ_DIR))
                .map(path -> path.getFileName().toString())
                .filter(name -> !name.contains(".") || name.toLowerCase(Locale.ENGLISH).endsWith(ext))
                .collect(Collectors.toList());
    }

    static void initMap(String baseDir, String file, Map<String, ProjectionDefinition> map) throws IOException {
        final Path path = Paths.get(baseDir).resolve(PROJ_DIR).resolve(file);
        final List<ProjectionDefinition> list;
        try (BufferedReader reader = Files.newBufferedReader(path, StandardCharsets.UTF_8)) {
            list = Projections.loadProjectionDefinitions(reader);
        }
        if (list.isEmpty())
            throw new AssertionError("EPSG file seems corrupted");
        Pattern badDmsPattern = Pattern.compile("(\\d+(?:\\.\\d+)?d\\d+(?:\\.\\d+)?')(N|S|E|W)");
        for (ProjectionDefinition pd : list) {
            // DMS notation without second causes problems with cs2cs, add 0"
            Matcher matcher = badDmsPattern.matcher(pd.definition);
            StringBuffer sb = new StringBuffer();
            while (matcher.find()) {
                matcher.appendReplacement(sb, matcher.group(1) + "0\"" + matcher.group(2));
            }
            matcher.appendTail(sb);
            map.put(pd.code, new ProjectionDefinition(pd.code, pd.name, sb.toString()));
        }
    }

    static void buildList(String baseDir) throws IOException {
        initMap(baseDir, JOSM_EPSG_FILE, epsgJosm);
        initMap(baseDir, PROJ4_EPSG_FILE, epsgProj4);
        initMap(baseDir, PROJ4_ESRI_FILE, esriProj4);

        knownGeoidgrids = initList(baseDir, ".gtx");
        knownNadgrids = initList(baseDir, ".gsb");

        try (Writer out = Files.newBufferedWriter(Paths.get(baseDir).resolve(OUTPUT_EPSG_FILE), StandardCharsets.UTF_8)) {
            out.write("## This file is autogenerated, do not edit!\n");
            out.write("## Run ant task \"epsg\" to rebuild.\n");
            out.write(String.format("## Source files are %s (can be changed), %s and %s (copied from the proj.4 project).%n",
                    JOSM_EPSG_FILE, PROJ4_EPSG_FILE, PROJ4_ESRI_FILE));
            out.write("##\n");
            out.write("## Entries checked and maintained by the JOSM team:\n");
            for (ProjectionDefinition pd : epsgJosm.values()) {
                write(out, pd);
                noJosm++;
            }
            out.write("## Other supported projections (source: proj.4):\n");
            for (ProjectionDefinition pd : epsgProj4.values()) {
                if (doInclude(pd, true, false)) {
                    write(out, pd);
                    noProj4++;
                }
            }
            out.write("## ESRI-specific projections (source: ESRI):\n");
            for (ProjectionDefinition pd : esriProj4.values()) {
                pd = new ProjectionDefinition(pd.code, "ESRI: " + pd.name, pd.definition);
                if (doInclude(pd, true, true)) {
                    write(out, pd);
                    noEsri++;
                }
            }
        }

        if (printStats) {
            System.out.println(String.format("loaded %d entries from %s", epsgJosm.size(), JOSM_EPSG_FILE));
            System.out.println(String.format("loaded %d entries from %s", epsgProj4.size(), PROJ4_EPSG_FILE));
            System.out.println(String.format("loaded %d entries from %s", esriProj4.size(), PROJ4_ESRI_FILE));
            System.out.println();
            System.out.println("some entries from proj.4 have not been included:");
            System.out.println(String.format(" * already in the maintained JOSM list: %d entries", noInJosm));
            if (noInProj4 > 0) {
                System.out.println(String.format(" * ESRI already in the standard EPSG list: %d entries", noInProj4));
            }
            System.out.println(String.format(" * deprecated: %d entries", noDeprecated));
            System.out.println(String.format(" * using +proj=geocent, which is 3D (X,Y,Z) and not useful in JOSM: %d entries", noGeocent));
            if (noEllipsoid > 0) {
                System.out.println(String.format(" * unsupported ellipsoids: %d entries", noEllipsoid));
                System.out.println("   in particular: " + ellipsoidMap);
            }
            if (noBaseProjection > 0) {
                System.out.println(String.format(" * unsupported base projection: %d entries", noBaseProjection));
                System.out.println("   in particular: " + baseProjectionMap);
            }
            if (noDatumgrid > 0) {
                System.out.println(String.format(" * requires data file for vertical datum conversion: %d entries", noDatumgrid));
                System.out.println("   in particular: " + datumgridMap);
            }
            if (noNadgrid > 0) {
                System.out.println(String.format(" * requires data file for datum conversion: %d entries", noNadgrid));
                System.out.println("   in particular: " + nadgridMap);
            }
            if (noOmercNoBounds > 0) {
                System.out.println(String.format(
                        " * projection is Oblique Mercator (requires bounds), but no bounds specified: %d entries", noOmercNoBounds));
            }
            if (noEquatorStereo > 0) {
                System.out.println(String.format(" * projection is Equatorial Stereographic (see #15970): %d entries", noEquatorStereo));
            }
            System.out.println();
            System.out.println(String.format("written %d entries from %s", noJosm, JOSM_EPSG_FILE));
            System.out.println(String.format("written %d entries from %s", noProj4, PROJ4_EPSG_FILE));
            System.out.println(String.format("written %d entries from %s", noEsri, PROJ4_ESRI_FILE));
        }
    }

    static void write(Writer out, ProjectionDefinition pd) throws IOException {
        out.write("# " + pd.name + "\n");
        out.write("<"+pd.code.substring("EPSG:".length())+"> "+pd.definition+" <>\n");
    }

    static boolean doInclude(ProjectionDefinition pd, boolean noIncludeJosm, boolean noIncludeProj4) {

        boolean result = true;

        if (noIncludeJosm) {
            // we already have this projection
            if (epsgJosm.containsKey(pd.code)) {
                result = false;
                noInJosm++;
            }
        }
        if (noIncludeProj4) {
            // we already have this projection
            if (epsgProj4.containsKey(pd.code)) {
                result = false;
                noInProj4++;
            }
        }

        // exclude deprecated/discontinued projections
        // EPSG:4296 is also deprecated, but this is not mentioned in the name
        String lowName = pd.name.toLowerCase(Locale.ENGLISH);
        if (lowName.contains("deprecated") || lowName.contains("discontinued") || pd.code.equals("EPSG:4296")) {
            result = false;
            noDeprecated++;
        }

        // exclude projections failing
        // CHECKSTYLE.OFF: LineLength
        if (Arrays.asList(
                // Unsuitable parameters 'lat_1' and 'lat_2' for two point method
                "EPSG:53025", "EPSG:54025", "EPSG:65062",
                // ESRI projection defined as UTM 55N but covering a much bigger area
                "EPSG:102449",
                // Others: errors to investigate
                "EPSG:102061", // omerc/evrst69 - Everest_Modified_1969_RSO_Malaya_Meters [Everest Modified 1969 RSO Malaya Meters]
                "EPSG:102062", // omerc/evrst48 - Kertau_RSO_Malaya_Meters [Kertau RSO Malaya Meters]
                "EPSG:102121", // omerc/NAD83   - NAD_1983_Michigan_GeoRef_Feet_US [NAD 1983 Michigan GeoRef (US Survey Feet)]
                "EPSG:102212", // lcc/NAD83     - NAD_1983_WyLAM [NAD 1983 WyLAM]
                "EPSG:102366", // omerc/GRS80   - NAD_1983_CORS96_StatePlane_Alaska_1_FIPS_5001 [NAD 1983 (CORS96) SPCS Alaska Zone 1]
                "EPSG:102445", // omerc/GRS80   - NAD_1983_2011_StatePlane_Alaska_1_FIPS_5001_Feet [NAD 1983 2011 SPCS Alaska Zone 1 (US Feet)]
                "EPSG:102491", // lcc/clrk80ign - Nord_Algerie_Ancienne_Degree [Voirol 1875 (degrees) Nord Algerie Ancienne]
                "EPSG:102591", // lcc           - Nord_Algerie_Degree [Voirol Unifie (degrees) Nord Algerie]
                "EPSG:102631", // omerc/NAD83   - NAD_1983_StatePlane_Alaska_1_FIPS_5001_Feet [NAD 1983 SPCS Alaska 1 (Feet)]
                "EPSG:103232", // lcc/GRS80     - NAD_1983_CORS96_StatePlane_California_I_FIPS_0401 [NAD 1983 (CORS96) SPCS California I]
                "EPSG:103235", // lcc/GRS80     - NAD_1983_CORS96_StatePlane_California_IV_FIPS_0404 [NAD 1983 (CORS96) SPCS California IV]
                "EPSG:103238", // lcc/GRS80     - NAD_1983_CORS96_StatePlane_California_I_FIPS_0401_Ft_US [NAD 1983 (CORS96) SPCS California I (US Feet)]
                "EPSG:103241", // lcc/GRS80     - NAD_1983_CORS96_StatePlane_California_IV_FIPS_0404_Ft_US [NAD 1983 (CORS96) SPCS California IV (US Feet)]
                "EPSG:103371", // lcc/GRS80     - NAD_1983_HARN_WISCRS_Wood_County_Meters [NAD 1983 HARN Wisconsin CRS Wood (meters)]
                "EPSG:103471", // lcc/GRS80     - NAD_1983_HARN_WISCRS_Wood_County_Feet [NAD 1983 HARN Wisconsin CRS Wood (US feet)]
                "EPSG:103474", // lcc/GRS80     - NAD_1983_CORS96_StatePlane_Nebraska_FIPS_2600 [NAD 1983 (CORS96) SPCS Nebraska]
                "EPSG:103475"  // lcc/GRS80     - NAD_1983_CORS96_StatePlane_Nebraska_FIPS_2600_Ft_US [NAD 1983 (CORS96) SPCS Nebraska (US Feet)]
                ).contains(pd.code)) {
            result = false;
        }
        // CHECKSTYLE.ON: LineLength

        Map<String, String> parameters;
        try {
            parameters = CustomProjection.parseParameterList(pd.definition, true);
        } catch (ProjectionConfigurationException ex) {
            throw new IllegalStateException(pd.code + ":" + ex, ex);
        }
        String proj = parameters.get(CustomProjection.Param.proj.key);
        if (proj == null) {
            result = false;
        }

        // +proj=geocent is 3D (X,Y,Z) "projection" - this is not useful in
        // JOSM as we only deal with 2D maps
        if ("geocent".equals(proj)) {
            result = false;
            noGeocent++;
        }

        // no support for NAD27 datum, as it requires a conversion database
        String datum = parameters.get(CustomProjection.Param.datum.key);
        if ("NAD27".equals(datum)) {
            result = false;
            noDatumgrid++;
        }

        // requires vertical datum conversion database (.gtx)
        String geoidgrids = parameters.get("geoidgrids");
        if (geoidgrids != null && !"@null".equals(geoidgrids) && !knownGeoidgrids.contains(geoidgrids)) {
            result = false;
            noDatumgrid++;
            incMap(datumgridMap, geoidgrids);
        }

        // requires datum conversion database (.gsb)
        String nadgrids = parameters.get("nadgrids");
        if (nadgrids != null && !"@null".equals(nadgrids) && !knownNadgrids.contains(nadgrids)) {
            result = false;
            noNadgrid++;
            incMap(nadgridMap, nadgrids);
        }

        // exclude entries where we don't support the base projection
        Proj bp = Projections.getBaseProjection(proj);
        if (result && !"utm".equals(proj) && bp == null) {
            result = false;
            noBaseProjection++;
            if (!"geocent".equals(proj)) {
                incMap(baseProjectionMap, proj);
            }
        }

        // exclude entries where we don't support the base ellipsoid
        String ellps = parameters.get("ellps");
        if (result && ellps != null && Projections.getEllipsoid(ellps) == null) {
            result = false;
            noEllipsoid++;
            incMap(ellipsoidMap, ellps);
        }

        if (result && "omerc".equals(proj) && !parameters.containsKey(CustomProjection.Param.bounds.key)) {
            result = false;
            noOmercNoBounds++;
        }

        final double eps10 = 1.e-10;

        String lat0 = parameters.get("lat_0");
        if (lat0 != null) {
            try {
                final double latitudeOfOrigin = Math.toRadians(CustomProjection.parseAngle(lat0, Param.lat_0.key));
                // TODO: implement equatorial stereographic, see https://josm.openstreetmap.de/ticket/15970
                if (result && "stere".equals(proj) && Math.abs(latitudeOfOrigin) < eps10) {
                    result = false;
                    noEquatorStereo++;
                }

                // exclude entries which need geodesic computation (equatorial/oblique azimuthal equidistant)
                if (result && "aeqd".equals(proj)) {
                    final double halfPi = Math.PI / 2;
                    if (Math.abs(latitudeOfOrigin - halfPi) >= eps10 &&
                        Math.abs(latitudeOfOrigin + halfPi) >= eps10) {
                        // See https://josm.openstreetmap.de/ticket/16129#comment:21
                        result = false;
                    }
                }
            } catch (NumberFormatException | ProjectionConfigurationException e) {
                e.printStackTrace();
                result = false;
            }
        }

        if (result && "0.0".equals(parameters.get("rf"))) {
            // Proj fails with "reciprocal flattening (1/f) = 0" for
            result = false; // FIXME Only for some projections?
        }

        String k0 = parameters.get("k_0");
        if (result && k0 != null && k0.startsWith("-")) {
            // Proj fails with "k <= 0" for ESRI:102470
            result = false;
        }

        return result;
    }

    private static void incMap(Map<String, Integer> map, String key) {
        map.putIfAbsent(key, 0);
        map.put(key, map.get(key)+1);
    }
}