001    // License: GPL. Copyright 2007 by Immanuel Scholz and others
002    package org.openstreetmap.josm.data.projection;
003    
004    import java.io.BufferedReader;
005    import java.io.IOException;
006    import java.io.InputStream;
007    import java.io.InputStreamReader;
008    import java.util.Collection;
009    import java.util.HashMap;
010    import java.util.Map;
011    import java.util.regex.Matcher;
012    import java.util.regex.Pattern;
013    
014    import org.openstreetmap.josm.Main;
015    import org.openstreetmap.josm.data.coor.EastNorth;
016    import org.openstreetmap.josm.data.coor.LatLon;
017    import org.openstreetmap.josm.data.projection.datum.Datum;
018    import org.openstreetmap.josm.data.projection.datum.NTV2GridShiftFileWrapper;
019    import org.openstreetmap.josm.data.projection.datum.WGS84Datum;
020    import org.openstreetmap.josm.data.projection.proj.ClassProjFactory;
021    import org.openstreetmap.josm.data.projection.proj.LambertConformalConic;
022    import org.openstreetmap.josm.data.projection.proj.LonLat;
023    import org.openstreetmap.josm.data.projection.proj.Proj;
024    import org.openstreetmap.josm.data.projection.proj.ProjFactory;
025    import org.openstreetmap.josm.data.projection.proj.SwissObliqueMercator;
026    import org.openstreetmap.josm.data.projection.proj.TransverseMercator;
027    import org.openstreetmap.josm.gui.preferences.projection.ProjectionChoice;
028    import org.openstreetmap.josm.gui.preferences.projection.ProjectionPreference;
029    import org.openstreetmap.josm.io.MirroredInputStream;
030    import org.openstreetmap.josm.tools.Pair;
031    
032    /**
033     * Class to handle projections
034     *
035     */
036    public class Projections {
037    
038        public static EastNorth project(LatLon ll) {
039            if (ll == null) return null;
040            return Main.getProjection().latlon2eastNorth(ll);
041        }
042    
043        public static LatLon inverseProject(EastNorth en) {
044            if (en == null) return null;
045            return Main.getProjection().eastNorth2latlon(en);
046        }
047    
048        /*********************************
049         * Registry for custom projection
050         *
051         * should be compatible to PROJ.4
052         */
053        final public static Map<String, ProjFactory> projs = new HashMap<String, ProjFactory>();
054        final public static Map<String, Ellipsoid> ellipsoids = new HashMap<String, Ellipsoid>();
055        final public static Map<String, Datum> datums = new HashMap<String, Datum>();
056        final public static Map<String, NTV2GridShiftFileWrapper> nadgrids = new HashMap<String, NTV2GridShiftFileWrapper>();
057        final public static Map<String, Pair<String, String>> inits = new HashMap<String, Pair<String, String>>();
058    
059        static {
060            registerBaseProjection("lonlat", LonLat.class, "core");
061            registerBaseProjection("josm:smerc", org.openstreetmap.josm.data.projection.proj.Mercator.class, "core");
062            registerBaseProjection("lcc", LambertConformalConic.class, "core");
063            registerBaseProjection("somerc", SwissObliqueMercator.class, "core");
064            registerBaseProjection("tmerc", TransverseMercator.class, "core");
065    
066            ellipsoids.put("intl", Ellipsoid.hayford);
067            ellipsoids.put("GRS80", Ellipsoid.GRS80);
068            ellipsoids.put("WGS84", Ellipsoid.WGS84);
069            ellipsoids.put("bessel", Ellipsoid.Bessel1841);
070    
071            datums.put("WGS84", WGS84Datum.INSTANCE);
072    
073            nadgrids.put("BETA2007.gsb", NTV2GridShiftFileWrapper.BETA2007);
074            nadgrids.put("ntf_r93_b.gsb", NTV2GridShiftFileWrapper.ntf_rgf93);
075    
076            loadInits();
077        }
078    
079        /**
080         * Plugins can register additional base projections.
081         *
082         * @param id The "official" PROJ.4 id. In case the projection is not supported
083         * by PROJ.4, use some prefix, e.g. josm:myproj or gdal:otherproj.
084         * @param fac The base projection factory.
085         * @param origin Multiple plugins may implement the same base projection.
086         * Provide plugin name or similar string, so it be differentiated.
087         */
088        public static void registerBaseProjection(String id, ProjFactory fac, String origin) {
089            projs.put(id, fac);
090        }
091    
092        public static void registerBaseProjection(String id, Class<? extends Proj> projClass, String origin) {
093            registerBaseProjection(id, new ClassProjFactory(projClass), origin);
094        }
095    
096        public static Proj getBaseProjection(String id) {
097            ProjFactory fac = projs.get(id);
098            if (fac == null) return null;
099            return fac.createInstance();
100        }
101    
102        public static Ellipsoid getEllipsoid(String id) {
103            return ellipsoids.get(id);
104        }
105    
106        public static Datum getDatum(String id) {
107            return datums.get(id);
108        }
109    
110        public static NTV2GridShiftFileWrapper getNTV2Grid(String id) {
111            return nadgrids.get(id);
112        }
113    
114        public static String getInit(String id) {
115            return inits.get(id.toLowerCase()).b;
116        }
117    
118        /**
119         * Load +init "presets" from file
120         */
121        private static void loadInits() {
122            Pattern epsgPattern = Pattern.compile("<(\\d+)>(.*)<>");
123            try {
124                InputStream in = new MirroredInputStream("resource://data/epsg");
125                BufferedReader r = new BufferedReader(new InputStreamReader(in));
126                String line, lastline = "";
127                while ((line = r.readLine()) != null) {
128                    line = line.trim();
129                    if (!line.startsWith("#") && !line.isEmpty()) {
130                        if (!lastline.startsWith("#")) throw new AssertionError();
131                        String name = lastline.substring(1).trim();
132                        Matcher m = epsgPattern.matcher(line);
133                        if (m.matches()) {
134                            inits.put("epsg:" + m.group(1), Pair.create(name, m.group(2).trim()));
135                        } else {
136                            System.err.println("Warning: failed to parse line from the epsg projection definition: "+line);
137                        }
138                    }
139                    lastline = line;
140                }
141            } catch (IOException ex) {
142                throw new RuntimeException();
143            }
144        }
145    
146        private final static Map<String, ProjectionChoice> allCodesPC = new HashMap<String, ProjectionChoice>();
147        private final static Map<String, Projection> allCodes = new HashMap<String, Projection>();
148    
149        static {
150            // FIXME: use {@link #inits}, because it may contain more codes in future
151            // than exposed by the ProjectionChoices
152            for (ProjectionChoice pc : ProjectionPreference.getProjectionChoices()) {
153                for (String code : pc.allCodes()) {
154                    allCodesPC.put(code, pc);
155                }
156            }
157        }
158    
159        public static Projection getProjectionByCode(String code) {
160            Projection p = allCodes.get(code);
161            if (p != null) return p;
162            ProjectionChoice pc = allCodesPC.get(code);
163            if (pc == null) return null;
164            Collection<String> pref = pc.getPreferencesFromCode(code);
165            pc.setPreferences(pref);
166            p = pc.getProjection();
167            allCodes.put(code, p);
168            return p;
169        }
170    
171    }