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