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