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.NTV2GridShiftFileWrapper; 023import org.openstreetmap.josm.data.projection.datum.WGS84Datum; 024import org.openstreetmap.josm.data.projection.proj.ClassProjFactory; 025import org.openstreetmap.josm.data.projection.proj.LambertConformalConic; 026import org.openstreetmap.josm.data.projection.proj.LonLat; 027import org.openstreetmap.josm.data.projection.proj.Mercator; 028import org.openstreetmap.josm.data.projection.proj.Proj; 029import org.openstreetmap.josm.data.projection.proj.ProjFactory; 030import org.openstreetmap.josm.data.projection.proj.SwissObliqueMercator; 031import org.openstreetmap.josm.data.projection.proj.TransverseMercator; 032import org.openstreetmap.josm.gui.preferences.projection.ProjectionChoice; 033import org.openstreetmap.josm.gui.preferences.projection.ProjectionPreference; 034import org.openstreetmap.josm.io.CachedFile; 035import org.openstreetmap.josm.tools.Pair; 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 public static final Map<String, ProjFactory> projs = new HashMap<>(); 063 public static final Map<String, Ellipsoid> ellipsoids = new HashMap<>(); 064 public static final Map<String, Datum> datums = new HashMap<>(); 065 public static final Map<String, NTV2GridShiftFileWrapper> nadgrids = new HashMap<>(); 066 public static final Map<String, Pair<String, String>> inits = new HashMap<>(); 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 try ( 135 InputStream in = new CachedFile("resource://data/projection/epsg").getInputStream(); 136 BufferedReader r = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8)); 137 ) { 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("EPSG file seems corrupted"); 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 } 156 } 157 158 private static final Set<String> allCodes = new HashSet<>(); 159 private static final Map<String, ProjectionChoice> allProjectionChoicesByCode = new HashMap<>(); 160 private static final Map<String, Projection> projectionsByCode_cache = new HashMap<>(); 161 162 static { 163 for (ProjectionChoice pc : ProjectionPreference.getProjectionChoices()) { 164 for (String code : pc.allCodes()) { 165 allProjectionChoicesByCode.put(code, pc); 166 } 167 } 168 allCodes.addAll(inits.keySet()); 169 allCodes.addAll(allProjectionChoicesByCode.keySet()); 170 } 171 172 public static Projection getProjectionByCode(String code) { 173 Projection proj = projectionsByCode_cache.get(code); 174 if (proj != null) return proj; 175 ProjectionChoice pc = allProjectionChoicesByCode.get(code); 176 if (pc != null) { 177 Collection<String> pref = pc.getPreferencesFromCode(code); 178 pc.setPreferences(pref); 179 try { 180 proj = pc.getProjection(); 181 } catch (Exception e) { 182 String cause = e.getMessage(); 183 Main.warn("Unable to get projection "+code+" with "+pc + (cause != null ? ". "+cause : "")); 184 } 185 } 186 if (proj == null) { 187 Pair<String, String> pair = inits.get(code); 188 if (pair == null) return null; 189 String name = pair.a; 190 String init = pair.b; 191 proj = new CustomProjection(name, code, init, null); 192 } 193 projectionsByCode_cache.put(code, proj); 194 return proj; 195 } 196 197 public static Collection<String> getAllProjectionCodes() { 198 return Collections.unmodifiableCollection(allCodes); 199 } 200}