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.nio.charset.StandardCharsets; 009import java.util.ArrayList; 010import java.util.Collection; 011import java.util.Collections; 012import java.util.HashMap; 013import java.util.HashSet; 014import java.util.List; 015import java.util.Locale; 016import java.util.Map; 017import java.util.Set; 018import java.util.regex.Matcher; 019import java.util.regex.Pattern; 020 021import org.openstreetmap.josm.Main; 022import org.openstreetmap.josm.data.coor.EastNorth; 023import org.openstreetmap.josm.data.coor.LatLon; 024import org.openstreetmap.josm.data.projection.datum.Datum; 025import org.openstreetmap.josm.data.projection.datum.GRS80Datum; 026import org.openstreetmap.josm.data.projection.datum.NTV2GridShiftFileWrapper; 027import org.openstreetmap.josm.data.projection.datum.WGS84Datum; 028import org.openstreetmap.josm.data.projection.proj.ClassProjFactory; 029import org.openstreetmap.josm.data.projection.proj.LambertConformalConic; 030import org.openstreetmap.josm.data.projection.proj.LonLat; 031import org.openstreetmap.josm.data.projection.proj.Mercator; 032import org.openstreetmap.josm.data.projection.proj.Proj; 033import org.openstreetmap.josm.data.projection.proj.ProjFactory; 034import org.openstreetmap.josm.data.projection.proj.SwissObliqueMercator; 035import org.openstreetmap.josm.data.projection.proj.TransverseMercator; 036import org.openstreetmap.josm.gui.preferences.projection.ProjectionChoice; 037import org.openstreetmap.josm.gui.preferences.projection.ProjectionPreference; 038import org.openstreetmap.josm.io.CachedFile; 039import org.openstreetmap.josm.tools.Pair; 040import org.openstreetmap.josm.tools.Utils; 041 042/** 043 * Class to handle projections 044 * 045 */ 046public final class Projections { 047 048 private Projections() { 049 // Hide default constructor for utils classes 050 } 051 052 public static EastNorth project(LatLon ll) { 053 if (ll == null) return null; 054 return Main.getProjection().latlon2eastNorth(ll); 055 } 056 057 public static LatLon inverseProject(EastNorth en) { 058 if (en == null) return null; 059 return Main.getProjection().eastNorth2latlon(en); 060 } 061 062 /********************************* 063 * Registry for custom projection 064 * 065 * should be compatible to PROJ.4 066 */ 067 static final Map<String, ProjFactory> projs = new HashMap<>(); 068 static final Map<String, Ellipsoid> ellipsoids = new HashMap<>(); 069 static final Map<String, Datum> datums = new HashMap<>(); 070 static final Map<String, NTV2GridShiftFileWrapper> nadgrids = new HashMap<>(); 071 static final Map<String, Pair<String, String>> inits = new HashMap<>(); 072 073 static { 074 registerBaseProjection("lonlat", LonLat.class, "core"); 075 registerBaseProjection("josm:smerc", Mercator.class, "core"); 076 registerBaseProjection("lcc", LambertConformalConic.class, "core"); 077 registerBaseProjection("somerc", SwissObliqueMercator.class, "core"); 078 registerBaseProjection("tmerc", TransverseMercator.class, "core"); 079 080 ellipsoids.put("airy", Ellipsoid.Airy); 081 ellipsoids.put("mod_airy", Ellipsoid.AiryMod); 082 ellipsoids.put("aust_SA", Ellipsoid.AustSA); 083 ellipsoids.put("bessel", Ellipsoid.Bessel1841); 084 ellipsoids.put("clrk66", Ellipsoid.Clarke1866); 085 ellipsoids.put("clarkeIGN", Ellipsoid.ClarkeIGN); 086 ellipsoids.put("intl", Ellipsoid.Hayford); 087 ellipsoids.put("helmert", Ellipsoid.Helmert); 088 ellipsoids.put("krass", Ellipsoid.Krassowsky); 089 ellipsoids.put("GRS67", Ellipsoid.GRS67); 090 ellipsoids.put("GRS80", Ellipsoid.GRS80); 091 ellipsoids.put("WGS72", Ellipsoid.WGS72); 092 ellipsoids.put("WGS84", Ellipsoid.WGS84); 093 094 datums.put("WGS84", WGS84Datum.INSTANCE); 095 datums.put("GRS80", GRS80Datum.INSTANCE); 096 097 nadgrids.put("BETA2007.gsb", NTV2GridShiftFileWrapper.BETA2007); 098 nadgrids.put("ntf_r93_b.gsb", NTV2GridShiftFileWrapper.ntf_rgf93); 099 100 loadInits(); 101 } 102 103 /** 104 * Plugins can register additional base projections. 105 * 106 * @param id The "official" PROJ.4 id. In case the projection is not supported 107 * by PROJ.4, use some prefix, e.g. josm:myproj or gdal:otherproj. 108 * @param fac The base projection factory. 109 * @param origin Multiple plugins may implement the same base projection. 110 * Provide plugin name or similar string, so it be differentiated. 111 */ 112 public static void registerBaseProjection(String id, ProjFactory fac, String origin) { 113 projs.put(id, fac); 114 } 115 116 public static void registerBaseProjection(String id, Class<? extends Proj> projClass, String origin) { 117 registerBaseProjection(id, new ClassProjFactory(projClass), origin); 118 } 119 120 public static Proj getBaseProjection(String id) { 121 ProjFactory fac = projs.get(id); 122 if (fac == null) return null; 123 return fac.createInstance(); 124 } 125 126 public static Ellipsoid getEllipsoid(String id) { 127 return ellipsoids.get(id); 128 } 129 130 public static Datum getDatum(String id) { 131 return datums.get(id); 132 } 133 134 public static NTV2GridShiftFileWrapper getNTV2Grid(String id) { 135 return nadgrids.get(id); 136 } 137 138 /** 139 * Get the projection definition string for the given id. 140 * @param id the id 141 * @return the string that can be processed by #{link CustomProjection}. 142 * Null, if the id isn't supported. 143 */ 144 public static String getInit(String id) { 145 Pair<String, String> r = inits.get(id.toUpperCase(Locale.ENGLISH)); 146 if (r == null) return null; 147 return r.b; 148 } 149 150 /** 151 * Load +init "presets" from file 152 */ 153 private static void loadInits() { 154 Pattern epsgPattern = Pattern.compile("<(\\d+)>(.*)<>"); 155 try ( 156 InputStream in = new CachedFile("resource://data/projection/epsg").getInputStream(); 157 BufferedReader r = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8)); 158 ) { 159 String line, lastline = ""; 160 while ((line = r.readLine()) != null) { 161 line = line.trim(); 162 if (!line.startsWith("#") && !line.isEmpty()) { 163 if (!lastline.startsWith("#")) throw new AssertionError("EPSG file seems corrupted"); 164 String name = lastline.substring(1).trim(); 165 Matcher m = epsgPattern.matcher(line); 166 if (m.matches()) { 167 inits.put("EPSG:" + m.group(1), Pair.create(name, m.group(2).trim())); 168 } else { 169 Main.warn("Failed to parse line from the EPSG projection definition: "+line); 170 } 171 } 172 lastline = line; 173 } 174 } catch (IOException ex) { 175 throw new RuntimeException(ex); 176 } 177 } 178 179 private static final Set<String> allCodes = new HashSet<>(); 180 private static final Map<String, ProjectionChoice> allProjectionChoicesByCode = new HashMap<>(); 181 private static final Map<String, Projection> projectionsByCode_cache = new HashMap<>(); 182 183 static { 184 for (ProjectionChoice pc : ProjectionPreference.getProjectionChoices()) { 185 for (String code : pc.allCodes()) { 186 allProjectionChoicesByCode.put(code, pc); 187 } 188 } 189 allCodes.addAll(inits.keySet()); 190 allCodes.addAll(allProjectionChoicesByCode.keySet()); 191 } 192 193 public static Projection getProjectionByCode(String code) { 194 Projection proj = projectionsByCode_cache.get(code); 195 if (proj != null) return proj; 196 ProjectionChoice pc = allProjectionChoicesByCode.get(code); 197 if (pc != null) { 198 Collection<String> pref = pc.getPreferencesFromCode(code); 199 pc.setPreferences(pref); 200 try { 201 proj = pc.getProjection(); 202 } catch (Exception e) { 203 String cause = e.getMessage(); 204 Main.warn("Unable to get projection "+code+" with "+pc + (cause != null ? ". "+cause : "")); 205 } 206 } 207 if (proj == null) { 208 Pair<String, String> pair = inits.get(code); 209 if (pair == null) return null; 210 String name = pair.a; 211 String init = pair.b; 212 proj = new CustomProjection(name, code, init, null); 213 } 214 projectionsByCode_cache.put(code, proj); 215 return proj; 216 } 217 218 public static Collection<String> getAllProjectionCodes() { 219 return Collections.unmodifiableCollection(allCodes); 220 } 221 222 private static String listKeys(Map<String, ?> map) { 223 List<String> keys = new ArrayList<>(map.keySet()); 224 Collections.sort(keys); 225 return Utils.join(", ", keys); 226 } 227 228 /** 229 * Replies the list of projections as string (comma separated). 230 * @return the list of projections as string (comma separated) 231 * @since 8533 232 */ 233 public static String listProjs() { 234 return listKeys(projs); 235 } 236 237 /** 238 * Replies the list of ellipsoids as string (comma separated). 239 * @return the list of ellipsoids as string (comma separated) 240 * @since 8533 241 */ 242 public static String listEllipsoids() { 243 return listKeys(ellipsoids); 244 } 245 246 /** 247 * Replies the list of datums as string (comma separated). 248 * @return the list of datums as string (comma separated) 249 * @since 8533 250 */ 251 public static String listDatums() { 252 return listKeys(datums); 253 } 254 255 /** 256 * Replies the list of nadgrids as string (comma separated). 257 * @return the list of nadgrids as string (comma separated) 258 * @since 8533 259 */ 260 public static String listNadgrids() { 261 return listKeys(nadgrids); 262 } 263}