Changeset 13598 in josm


Ignore:
Timestamp:
2018-04-05T19:03:04+02:00 (3 weeks ago)
Author:
Don-vip
Message:

see #16129 - add new projections and support for new format of ESRI file

Location:
trunk
Files:
2 added
8 edited

Legend:

Unmodified
Added
Removed
  • trunk/CONTRIBUTION

    r13588 r13598  
    9393Swiss CHENYX06 NTV2 grid: Source: Swiss Federal Office of Topography
    9494
     95ESRI projection definitions: Environmental Systems Research Institute
     96
    9597------------------------------------ ICONS ------------------------------------
    9698
  • trunk/scripts/BuildProjectionDefinitions.java

    r13583 r13598  
    77import java.io.OutputStreamWriter;
    88import java.nio.charset.StandardCharsets;
    9 import java.util.HashMap;
     9import java.util.Arrays;
    1010import java.util.LinkedHashMap;
     11import java.util.List;
     12import java.util.Locale;
    1113import java.util.Map;
     14import java.util.TreeMap;
    1215
    1316import org.openstreetmap.josm.data.projection.CustomProjection;
     17import org.openstreetmap.josm.data.projection.CustomProjection.Param;
    1418import org.openstreetmap.josm.data.projection.ProjectionConfigurationException;
    1519import org.openstreetmap.josm.data.projection.Projections;
     
    2327public class BuildProjectionDefinitions {
    2428
    25     private static final String JOSM_EPSG_FILE = "data_nodist/projection/josm-epsg";
    26     private static final String PROJ4_EPSG_FILE = "data_nodist/projection/epsg";
    27     private static final String PROJ4_ESRI_FILE = "data_nodist/projection/esri";
     29    private static final String PROJ_DIR = "data_nodist/projection";
     30    private static final String JOSM_EPSG_FILE = "josm-epsg";
     31    private static final String PROJ4_EPSG_FILE = "epsg";
     32    private static final String PROJ4_ESRI_FILE = "esri";
    2833    private static final String OUTPUT_EPSG_FILE = "data/projection/custom-epsg";
    2934
     
    4045    private static int noGeocent = 0;
    4146    private static int noBaseProjection = 0;
    42     private static final Map<String, Integer> baseProjectionMap = new HashMap<>();
     47    private static int noEllipsoid = 0;
     48    private static int noNadgrid = 0;
    4349    private static int noDatumgrid = 0;
    4450    private static int noJosm = 0;
     
    4753    private static int noOmercNoBounds = 0;
    4854    private static int noEquatorStereo = 0;
     55
     56    private static final Map<String, Integer> baseProjectionMap = new TreeMap<>();
     57    private static final Map<String, Integer> ellipsoidMap = new TreeMap<>();
     58    private static final Map<String, Integer> nadgridMap = new TreeMap<>();
     59    private static final Map<String, Integer> datumgridMap = new TreeMap<>();
     60
     61    private static List<String> knownGeoidgrids;
     62    private static List<String> knownNadgrids;
    4963
    5064    /**
     
    5771    }
    5872
     73    static List<String> initList(String baseDir, String ext) {
     74        return Arrays.asList(new File(baseDir + File.separator + PROJ_DIR)
     75                .list((dir, name) -> !name.contains(".") || name.toLowerCase(Locale.ENGLISH).endsWith(ext)));
     76    }
     77
    5978    static void initMap(String baseDir, String file, Map<String, ProjectionDefinition> map) throws IOException {
    60         for (ProjectionDefinition pd : Projections.loadProjectionDefinitions(baseDir + File.separator + file)) {
     79        for (ProjectionDefinition pd : Projections.loadProjectionDefinitions(
     80                baseDir + File.separator + PROJ_DIR + File.separator + file)) {
    6181            map.put(pd.code, pd);
    6282        }
     
    6787        initMap(baseDir, PROJ4_EPSG_FILE, epsgProj4);
    6888        initMap(baseDir, PROJ4_ESRI_FILE, esriProj4);
     89
     90        knownGeoidgrids = initList(baseDir, ".gtx");
     91        knownNadgrids = initList(baseDir, ".gsb");
    6992
    7093        try (FileOutputStream output = new FileOutputStream(baseDir + File.separator + OUTPUT_EPSG_FILE);
     
    104127            System.out.println("some entries from proj.4 have not been included:");
    105128            System.out.println(String.format(" * already in the maintained JOSM list: %d entries", noInJosm));
    106             System.out.println(String.format(" * ESRI already in the standard EPSG list: %d entries", noInProj4));
     129            if (noInProj4 > 0) {
     130                System.out.println(String.format(" * ESRI already in the standard EPSG list: %d entries", noInProj4));
     131            }
    107132            System.out.println(String.format(" * deprecated: %d entries", noDeprecated));
    108133            System.out.println(String.format(" * using +proj=geocent, which is 3D (X,Y,Z) and not useful in JOSM: %d entries", noGeocent));
    109             System.out.println(String.format(" * unsupported base projection: %d entries", noBaseProjection));
    110             System.out.println("   in particular: " + baseProjectionMap);
    111             System.out.println(String.format(" * requires data file for datum conversion: %d entries", noDatumgrid));
     134            if (noEllipsoid > 0) {
     135                System.out.println(String.format(" * unsupported ellipsoids: %d entries", noEllipsoid));
     136                System.out.println("   in particular: " + ellipsoidMap);
     137            }
     138            if (noBaseProjection > 0) {
     139                System.out.println(String.format(" * unsupported base projection: %d entries", noBaseProjection));
     140                System.out.println("   in particular: " + baseProjectionMap);
     141            }
     142            if (noDatumgrid > 0) {
     143                System.out.println(String.format(" * requires data file for vertical datum conversion: %d entries", noDatumgrid));
     144                System.out.println("   in particular: " + datumgridMap);
     145            }
     146            if (noNadgrid > 0) {
     147                System.out.println(String.format(" * requires data file for datum conversion: %d entries", noNadgrid));
     148                System.out.println("   in particular: " + nadgridMap);
     149            }
    112150            if (noOmercNoBounds > 0) {
    113151                System.out.println(String.format(" * projection is Oblique Mercator (requires bounds), but no bounds specified: %d entries", noOmercNoBounds));
     
    147185        }
    148186
    149         // exclude deprecated projections
     187        // exclude deprecated/discontinued projections
    150188        // EPSG:4296 is also deprecated, but this is not mentioned in the name
    151         if (pd.name.contains("deprecated") || pd.code.equals("EPSG:4296")) {
     189        String lowName = pd.name.toLowerCase(Locale.ENGLISH);
     190        if (lowName.contains("deprecated") || lowName.contains("discontinued") || pd.code.equals("EPSG:4296")) {
    152191            result = false;
    153192            noDeprecated++;
     
    176215        }
    177216
    178         // requires datum conversion database
    179         if (parameters.containsKey("geoidgrids")) {
     217        // requires vertical datum conversion database (.gtx)
     218        String geoidgrids = parameters.get("geoidgrids");
     219        if (geoidgrids != null && !"@null".equals(geoidgrids) && !knownGeoidgrids.contains(geoidgrids)) {
    180220            result = false;
    181221            noDatumgrid++;
     222            incMap(datumgridMap, geoidgrids);
     223        }
     224
     225        // requires datum conversion database (.gsb)
     226        String nadgrids = parameters.get("nadgrids");
     227        if (nadgrids != null && !"@null".equals(nadgrids) && !knownNadgrids.contains(nadgrids)) {
     228            result = false;
     229            noNadgrid++;
     230            incMap(nadgridMap, nadgrids);
    182231        }
    183232
     
    188237            noBaseProjection++;
    189238            if (!"geocent".equals(proj)) {
    190                 if (!baseProjectionMap.containsKey(proj)) {
    191                     baseProjectionMap.put(proj, 0);
     239                incMap(baseProjectionMap, proj);
     240            }
     241        }
     242
     243        // exclude entries where we don't support the base ellipsoid
     244        String ellps = parameters.get("ellps");
     245        if (result && ellps != null && Projections.getEllipsoid(ellps) == null) {
     246            result = false;
     247            noEllipsoid++;
     248            incMap(ellipsoidMap, ellps);
     249        }
     250
     251        if (result && "omerc".equals(proj) && !parameters.containsKey(CustomProjection.Param.bounds.key)) {
     252            result = false;
     253            noOmercNoBounds++;
     254        }
     255
     256        final double EPS10 = 1.e-10;
     257
     258        String lat0 = parameters.get("lat_0");
     259        if (lat0 != null) {
     260            try {
     261                final double latitudeOfOrigin = Math.toRadians(CustomProjection.parseAngle(lat0, Param.lat_0.key));
     262                // TODO: implement equatorial stereographic, see https://josm.openstreetmap.de/ticket/15970
     263                if (result && "stere".equals(proj) && Math.abs(latitudeOfOrigin) < EPS10) {
     264                    result = false;
     265                    noEquatorStereo++;
    192266                }
    193                 baseProjectionMap.put(proj, baseProjectionMap.get(proj)+1);
    194             }
    195         }
    196 
    197         if (result && "omerc".equals(proj) && !parameters.containsKey(CustomProjection.Param.bounds.key)) {
    198             result = false;
    199             noOmercNoBounds++;
    200         }
    201         // TODO: implement equatorial stereographic, see https://josm.openstreetmap.de/ticket/15970
    202         if (result && "stere".equals(proj) && "0".equals(parameters.get(CustomProjection.Param.lat_0.key))) {
    203             result = false;
    204             noEquatorStereo++;
     267
     268                // exclude entries which need geodesic computation (equatorial/oblique azimuthal equidistant)
     269                if (result && "aeqd".equals(proj)) {
     270                    final double HALF_PI = Math.PI / 2;
     271                    if (Math.abs(latitudeOfOrigin - HALF_PI) >= EPS10 &&
     272                        Math.abs(latitudeOfOrigin + HALF_PI) >= EPS10) {
     273                        // See https://josm.openstreetmap.de/ticket/16129#comment:21
     274                        result = false;
     275                    }
     276                }
     277            } catch (NumberFormatException | ProjectionConfigurationException e) {
     278                e.printStackTrace();
     279                result = false;
     280            }
     281        }
     282
     283        if (result && "0.0".equals(parameters.get("rf"))) {
     284            // Proj fails with "reciprocal flattening (1/f) = 0" for
     285            result = false; // FIXME Only for some projections?
     286        }
     287
     288        String k_0 = parameters.get("k_0");
     289        if (result && k_0 != null && k_0.startsWith("-")) {
     290            // Proj fails with "k <= 0" for ESRI:102470
     291            result = false;
    205292        }
    206293
    207294        return result;
    208295    }
     296
     297    private static void incMap(Map<String, Integer> map, String key) {
     298        map.putIfAbsent(key, 0);
     299        map.put(key, map.get(key)+1);
     300    }
    209301}
  • trunk/src/org/openstreetmap/josm/data/projection/CustomProjection.java

    r13182 r13598  
    296296            }
    297297            s = parameters.get(Param.bounds.key);
    298             if (s != null) {
    299                 this.bounds = parseBounds(s);
    300             }
     298            this.bounds = s != null ? parseBounds(s) : null;
    301299            s = parameters.get(Param.wmssrs.key);
    302300            if (s != null) {
     
    590588            projParams.gamma = parseAngle(s, Param.gamma.key);
    591589        }
     590        s = parameters.get(Param.lon_0.key);
     591        if (s != null) {
     592            projParams.lon0 = parseAngle(s, Param.lon_0.key);
     593        }
    592594        s = parameters.get(Param.lon_1.key);
    593595        if (s != null) {
  • trunk/src/org/openstreetmap/josm/data/projection/Projections.java

    r13582 r13598  
    2525import org.openstreetmap.josm.data.projection.datum.WGS84Datum;
    2626import org.openstreetmap.josm.data.projection.proj.AlbersEqualArea;
     27import org.openstreetmap.josm.data.projection.proj.AzimuthalEquidistant;
    2728import org.openstreetmap.josm.data.projection.proj.CassiniSoldner;
    2829import org.openstreetmap.josm.data.projection.proj.ClassProjFactory;
    2930import org.openstreetmap.josm.data.projection.proj.DoubleStereographic;
     31import org.openstreetmap.josm.data.projection.proj.EquidistantCylindrical;
    3032import org.openstreetmap.josm.data.projection.proj.LambertAzimuthalEqualArea;
    3133import org.openstreetmap.josm.data.projection.proj.LambertConformalConic;
     
    5557     */
    5658    public static class ProjectionDefinition {
     59        /**
     60         * EPSG code
     61         */
    5762        public final String code;
     63        /**
     64         * Projection name
     65         */
    5866        public final String name;
     67        /**
     68         * projection definition (EPSG format)
     69         */
    5970        public final String definition;
    6071
     
    89100    static {
    90101        registerBaseProjection("aea", AlbersEqualArea.class, "core");
     102        registerBaseProjection("aeqd", AzimuthalEquidistant.class, "core");
    91103        registerBaseProjection("cass", CassiniSoldner.class, "core");
     104        registerBaseProjection("eqc", EquidistantCylindrical.class, "core");
    92105        registerBaseProjection("laea", LambertAzimuthalEqualArea.class, "core");
    93106        registerBaseProjection("lcc", LambertConformalConic.class, "core");
     
    199212    }
    200213
     214    /**
     215     * Plugins can register additional base projections.
     216     *
     217     * @param id The "official" PROJ.4 id. In case the projection is not supported
     218     * by PROJ.4, use some prefix, e.g. josm:myproj or gdal:otherproj.
     219     * @param projClass The base projection class.
     220     * @param origin Multiple plugins may implement the same base projection.
     221     * Provide plugin name or similar string, so it be differentiated.
     222     */
    201223    public static void registerBaseProjection(String id, Class<? extends Proj> projClass, String origin) {
    202224        registerBaseProjection(id, new ClassProjFactory(projClass), origin);
     
    293315        List<ProjectionDefinition> result = new ArrayList<>();
    294316        Pattern epsgPattern = Pattern.compile("<(\\d+)>(.*)<>");
    295         String line, lastline = "";
     317        StringBuilder sb = new StringBuilder();
     318        String line;
    296319        while ((line = r.readLine()) != null) {
    297320            line = line.trim();
    298             if (!line.startsWith("#") && !line.isEmpty()) {
    299                 if (!lastline.startsWith("#")) throw new AssertionError("EPSG file seems corrupted");
    300                 String name = lastline.substring(1).trim();
    301                 Matcher m = epsgPattern.matcher(line);
    302                 if (m.matches()) {
    303                     String code = "EPSG:" + m.group(1);
    304                     String definition = m.group(2).trim();
    305                     result.add(new ProjectionDefinition(code, name, definition));
    306                 } else {
    307                     Logging.warn("Failed to parse line from the EPSG projection definition: "+line);
     321            if (!line.isEmpty()) {
     322                if (!line.startsWith("#")) {
     323                    Matcher m = epsgPattern.matcher(line);
     324                    if (m.matches()) {
     325                        String code = "EPSG:" + m.group(1);
     326                        String definition = m.group(2).trim();
     327                        result.add(new ProjectionDefinition(code, sb.toString(), definition));
     328                    } else {
     329                        Logging.warn("Failed to parse line from the EPSG projection definition: "+line);
     330                    }
     331                    sb.setLength(0);
     332                } else if (!line.startsWith("# area: ")) {
     333                    if (sb.length() == 0) {
     334                        sb.append(line.substring(1).trim());
     335                    } else {
     336                        sb.append('(').append(line.substring(1).trim()).append(')');
     337                    }
    308338                }
    309339            }
    310             lastline = line;
    311         }
     340        }
     341        if (result.isEmpty())
     342            throw new AssertionError("EPSG file seems corrupted");
    312343        return result;
    313344    }
  • trunk/src/org/openstreetmap/josm/data/projection/proj/AbstractProj.java

    r10748 r13598  
    155155    }
    156156
     157    /**
     158     * Tolerant asin that will just return the limits of its output range if the input is out of range
     159     * @param v the value whose arc sine is to be returned.
     160     * @return the arc sine of the argument.
     161     */
     162    protected final double aasin(double v) {
     163        double av = Math.abs(v);
     164        if (av >= 1.) {
     165            return (v < 0. ? -Math.PI / 2 : Math.PI / 2);
     166        }
     167        return Math.asin(v);
     168    }
     169
    157170    // Iteratively solve equation (7-9) from Snyder.
    158171    final double cphi2(final double ts) {
  • trunk/src/org/openstreetmap/josm/data/projection/proj/ProjParameters.java

    r9565 r13598  
    22package org.openstreetmap.josm.data.projection.proj;
    33
     4import org.openstreetmap.josm.data.projection.CustomProjection.Param;
    45import org.openstreetmap.josm.data.projection.Ellipsoid;
    56
    67/**
    78 * Parameters to initialize a Proj object.
     9 * @since 5066
    810 */
    911public class ProjParameters {
    1012
     13    /** {@code +ellps} */
    1114    public Ellipsoid ellps;
    1215
     16    /** {@link Param#lat_0} */
    1317    public Double lat0;
     18    /** {@link Param#lat_1} */
    1419    public Double lat1;
     20    /** {@link Param#lat_2} */
    1521    public Double lat2;
    1622
    1723    // Polar Stereographic and Mercator
     24    /** {@link Param#lat_ts} */
    1825    public Double lat_ts;
    1926
     27    // Azimuthal Equidistant
     28    /** {@link Param#lon_0} */
     29    public Double lon0;
     30
    2031    // Oblique Mercator
     32    /** {@link Param#lonc} */
    2133    public Double lonc;
     34    /** {@link Param#alpha} */
    2235    public Double alpha;
     36    /** {@link Param#gamma} */
    2337    public Double gamma;
     38    /** {@link Param#no_off} */
    2439    public Boolean no_off;
     40    /** {@link Param#lon_1} */
    2541    public Double lon1;
     42    /** {@link Param#lon_2} */
    2643    public Double lon2;
    2744}
  • trunk/test/unit/org/openstreetmap/josm/data/projection/ProjectionRefTest.java

    r12795 r13598  
    7979    static Random rand = new SecureRandom();
    8080
     81    static boolean debug;
     82
    8183    /**
    8284     * Setup test.
     
    9294     */
    9395    public static void main(String[] args) throws IOException {
     96        debug = args.length > 0 && "debug".equals(args[0]);
    9497        Collection<RefEntry> refs = readData();
    9598        refs = updateData(refs);
     
    236239        pb.environment().put("PROJ_LIB", new File(PROJ_LIB_DIR).getAbsolutePath());
    237240
    238         String output;
     241        String output = "";
    239242        try {
    240243            Process process = pb.start();
    241244            OutputStream stdin = process.getOutputStream();
    242245            InputStream stdout = process.getInputStream();
     246            InputStream stderr = process.getErrorStream();
    243247            try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(stdin, StandardCharsets.UTF_8))) {
    244                 writer.write(String.format("%.9f %.9f%n", ll.lon(), ll.lat()));
     248                String s = String.format("%.9f %.9f%n", ll.lon(), ll.lat());
     249                if (debug) {
     250                    System.out.println("\n" + String.join(" ", args) + "\n" + s);
     251                }
     252                writer.write(s);
    245253            }
    246254            try (BufferedReader reader = new BufferedReader(new InputStreamReader(stdout, StandardCharsets.UTF_8))) {
    247                 output = reader.readLine();
     255                String line;
     256                while (null != (line = reader.readLine())) {
     257                    if (debug) {
     258                        System.out.println("> " + line);
     259                    }
     260                    output = line;
     261                }
     262            }
     263            try (BufferedReader reader = new BufferedReader(new InputStreamReader(stderr, StandardCharsets.UTF_8))) {
     264                String line;
     265                while (null != (line = reader.readLine())) {
     266                    System.err.println("! " + line);
     267                }
    248268            }
    249269        } catch (IOException e) {
  • trunk/test/unit/org/openstreetmap/josm/data/projection/ProjectionTest.java

    r11324 r13598  
    2424    String text;
    2525
     26    /**
     27     * Tests that projections are numerically stable in their definition bounds (round trip error &lt; 1e-5)
     28     */
    2629    @Test
    2730    public void testProjections() {
     
    5558            testProjection(Projections.getProjectionByCode("EPSG:"+Integer.toString(3942+i))); // Lambert CC9 Zones France
    5659        }
     60
     61        for (int i = 0; i <= 17; ++i) {
     62            testProjection(Projections.getProjectionByCode("EPSG:"+Integer.toString(102421+i))); // WGS_1984_ARC_System Zones
     63        }
     64
     65        testProjection(Projections.getProjectionByCode("EPSG:102016")); // North Pole
     66        testProjection(Projections.getProjectionByCode("EPSG:102019")); // South Pole
    5767
    5868        if (error) {
     
    104114    Collection<String> projIds;
    105115
     116    /**
     117     * Tests that projections are numerically stable in their definition bounds (round trip error &lt; epsilon)
     118     */
    106119    @Test
    107120    public void testProjs() {
     
    127140        testProj("merc", 1e-5, "");
    128141        testProj("sinu", 1e-4, "");
     142        testProj("aeqd", 1e-5, "+lon_0=0dE +lat_0=90dN");
     143        testProj("aeqd", 1e-5, "+lon_0=0dE +lat_0=90dS");
     144        testProj("eqc", 1e-5, "");
    129145
    130146        if (error2) {
Note: See TracChangeset for help on using the changeset viewer.