source: josm/trunk/src/org/openstreetmap/josm/data/projection/CustomProjection.java@ 5634

Last change on this file since 5634 was 5548, checked in by bastiK, 11 years ago

remove Projection classes (replaced by data/epsg file)

concludes the projection rework from ealier this year

  • Property svn:eol-style set to native
File size: 18.3 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.data.projection;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.util.ArrayList;
7import java.util.HashMap;
8import java.util.List;
9import java.util.Map;
10import java.util.regex.Matcher;
11import java.util.regex.Pattern;
12
13import org.openstreetmap.josm.data.Bounds;
14import org.openstreetmap.josm.data.coor.LatLon;
15import org.openstreetmap.josm.data.projection.datum.CentricDatum;
16import org.openstreetmap.josm.data.projection.datum.Datum;
17import org.openstreetmap.josm.data.projection.datum.NTV2Datum;
18import org.openstreetmap.josm.data.projection.datum.NTV2GridShiftFileWrapper;
19import org.openstreetmap.josm.data.projection.datum.NullDatum;
20import org.openstreetmap.josm.data.projection.datum.SevenParameterDatum;
21import org.openstreetmap.josm.data.projection.datum.ThreeParameterDatum;
22import org.openstreetmap.josm.data.projection.datum.WGS84Datum;
23import org.openstreetmap.josm.data.projection.proj.Mercator;
24import org.openstreetmap.josm.data.projection.proj.Proj;
25import org.openstreetmap.josm.data.projection.proj.ProjParameters;
26import org.openstreetmap.josm.tools.Utils;
27
28/**
29 * Custom projection
30 *
31 * Inspired by PROJ.4 and Proj4J.
32 */
33public class CustomProjection extends AbstractProjection {
34
35 /**
36 * pref String that defines the projection
37 *
38 * null means fall back mode (Mercator)
39 */
40 protected String pref;
41 protected String name;
42 protected String code;
43 protected String cacheDir;
44 protected Bounds bounds;
45
46 protected static enum Param {
47
48 x_0("x_0", true),
49 y_0("y_0", true),
50 lon_0("lon_0", true),
51 k_0("k_0", true),
52 ellps("ellps", true),
53 a("a", true),
54 es("es", true),
55 rf("rf", true),
56 f("f", true),
57 b("b", true),
58 datum("datum", true),
59 towgs84("towgs84", true),
60 nadgrids("nadgrids", true),
61 proj("proj", true),
62 lat_0("lat_0", true),
63 lat_1("lat_1", true),
64 lat_2("lat_2", true),
65 wktext("wktext", false), // ignored
66 units("units", true), // ignored
67 no_defs("no_defs", false),
68 init("init", true),
69 // JOSM extension, not present in PROJ.4
70 bounds("bounds", true);
71
72 public String key;
73 public boolean hasValue;
74
75 public final static Map<String, Param> paramsByKey = new HashMap<String, Param>();
76 static {
77 for (Param p : Param.values()) {
78 paramsByKey.put(p.key, p);
79 }
80 }
81
82 Param(String key, boolean hasValue) {
83 this.key = key;
84 this.hasValue = hasValue;
85 }
86 }
87
88 public CustomProjection() {
89 }
90
91 public CustomProjection(String pref) {
92 this(null, null, pref, null);
93 }
94
95 /**
96 * Constructor.
97 *
98 * @param name describe projection in one or two words
99 * @param code unique code for this projection - may be null
100 * @param pref the string that defines the custom projection
101 * @param cacheDir cache directory name
102 */
103 public CustomProjection(String name, String code, String pref, String cacheDir) {
104 this.name = name;
105 this.code = code;
106 this.pref = pref;
107 this.cacheDir = cacheDir;
108 try {
109 update(pref);
110 } catch (ProjectionConfigurationException ex) {
111 try {
112 update(null);
113 } catch (ProjectionConfigurationException ex1) {
114 throw new RuntimeException();
115 }
116 }
117 }
118
119 public void update(String pref) throws ProjectionConfigurationException {
120 this.pref = pref;
121 if (pref == null) {
122 ellps = Ellipsoid.WGS84;
123 datum = WGS84Datum.INSTANCE;
124 proj = new Mercator();
125 bounds = new Bounds(
126 new LatLon(-85.05112877980659, -180.0),
127 new LatLon(85.05112877980659, 180.0), true);
128 } else {
129 Map<String, String> parameters = parseParameterList(pref);
130 ellps = parseEllipsoid(parameters);
131 datum = parseDatum(parameters, ellps);
132 proj = parseProjection(parameters, ellps);
133 String s = parameters.get(Param.x_0.key);
134 if (s != null) {
135 this.x_0 = parseDouble(s, Param.x_0.key);
136 }
137 s = parameters.get(Param.y_0.key);
138 if (s != null) {
139 this.y_0 = parseDouble(s, Param.y_0.key);
140 }
141 s = parameters.get(Param.lon_0.key);
142 if (s != null) {
143 this.lon_0 = parseAngle(s, Param.lon_0.key);
144 }
145 s = parameters.get(Param.k_0.key);
146 if (s != null) {
147 this.k_0 = parseDouble(s, Param.k_0.key);
148 }
149 s = parameters.get(Param.bounds.key);
150 if (s != null) {
151 this.bounds = parseBounds(s);
152 }
153 }
154 }
155
156 private Map<String, String> parseParameterList(String pref) throws ProjectionConfigurationException {
157 Map<String, String> parameters = new HashMap<String, String>();
158 String[] parts = pref.trim().split("\\s+");
159 if (pref.trim().isEmpty()) {
160 parts = new String[0];
161 }
162 for (int i = 0; i < parts.length; i++) {
163 String part = parts[i];
164 if (part.isEmpty() || part.charAt(0) != '+')
165 throw new ProjectionConfigurationException(tr("Parameter must begin with a ''+'' character (found ''{0}'')", part));
166 Matcher m = Pattern.compile("\\+([a-zA-Z0-9_]+)(=(.*))?").matcher(part);
167 if (m.matches()) {
168 String key = m.group(1);
169 // alias
170 if (key.equals("k")) {
171 key = Param.k_0.key;
172 }
173 String value = null;
174 if (m.groupCount() >= 3) {
175 value = m.group(3);
176 // same aliases
177 if (key.equals(Param.proj.key)) {
178 if (value.equals("longlat") || value.equals("latlon") || value.equals("latlong")) {
179 value = "lonlat";
180 }
181 }
182 }
183 if (!Param.paramsByKey.containsKey(key))
184 throw new ProjectionConfigurationException(tr("Unkown parameter: ''{0}''.", key));
185 if (Param.paramsByKey.get(key).hasValue && value == null)
186 throw new ProjectionConfigurationException(tr("Value expected for parameter ''{0}''.", key));
187 if (!Param.paramsByKey.get(key).hasValue && value != null)
188 throw new ProjectionConfigurationException(tr("No value expected for parameter ''{0}''.", key));
189 parameters.put(key, value);
190 } else
191 throw new ProjectionConfigurationException(tr("Unexpected parameter format (''{0}'')", part));
192 }
193 // recursive resolution of +init includes
194 String initKey = parameters.get(Param.init.key);
195 if (initKey != null) {
196 String init = Projections.getInit(initKey);
197 if (init == null)
198 throw new ProjectionConfigurationException(tr("Value ''{0}'' for option +init not supported.", initKey));
199 Map<String, String> initp = null;
200 try {
201 initp = parseParameterList(init);
202 } catch (ProjectionConfigurationException ex) {
203 throw new ProjectionConfigurationException(tr(initKey+": "+ex.getMessage()));
204 }
205 for (Map.Entry<String, String> e : parameters.entrySet()) {
206 initp.put(e.getKey(), e.getValue());
207 }
208 return initp;
209 }
210 return parameters;
211 }
212
213 public Ellipsoid parseEllipsoid(Map<String, String> parameters) throws ProjectionConfigurationException {
214 String code = parameters.get(Param.ellps.key);
215 if (code != null) {
216 Ellipsoid ellipsoid = Projections.getEllipsoid(code);
217 if (ellipsoid == null) {
218 throw new ProjectionConfigurationException(tr("Ellipsoid ''{0}'' not supported.", code));
219 } else {
220 return ellipsoid;
221 }
222 }
223 String s = parameters.get(Param.a.key);
224 if (s != null) {
225 double a = parseDouble(s, Param.a.key);
226 if (parameters.get(Param.es.key) != null) {
227 double es = parseDouble(parameters, Param.es.key);
228 return Ellipsoid.create_a_es(a, es);
229 }
230 if (parameters.get(Param.rf.key) != null) {
231 double rf = parseDouble(parameters, Param.rf.key);
232 return Ellipsoid.create_a_rf(a, rf);
233 }
234 if (parameters.get(Param.f.key) != null) {
235 double f = parseDouble(parameters, Param.f.key);
236 return Ellipsoid.create_a_f(a, f);
237 }
238 if (parameters.get(Param.b.key) != null) {
239 double b = parseDouble(parameters, Param.b.key);
240 return Ellipsoid.create_a_b(a, b);
241 }
242 }
243 if (parameters.containsKey(Param.a.key) ||
244 parameters.containsKey(Param.es.key) ||
245 parameters.containsKey(Param.rf.key) ||
246 parameters.containsKey(Param.f.key) ||
247 parameters.containsKey(Param.b.key))
248 throw new ProjectionConfigurationException(tr("Combination of ellipsoid parameters is not supported."));
249 if (parameters.containsKey(Param.no_defs.key))
250 throw new ProjectionConfigurationException(tr("Ellipsoid required (+ellps=* or +a=*, +b=*)"));
251 // nothing specified, use WGS84 as default
252 return Ellipsoid.WGS84;
253 }
254
255 public Datum parseDatum(Map<String, String> parameters, Ellipsoid ellps) throws ProjectionConfigurationException {
256 String nadgridsId = parameters.get(Param.nadgrids.key);
257 if (nadgridsId != null) {
258 if (nadgridsId.startsWith("@")) {
259 nadgridsId = nadgridsId.substring(1);
260 }
261 if (nadgridsId.equals("null"))
262 return new NullDatum(null, ellps);
263 NTV2GridShiftFileWrapper nadgrids = Projections.getNTV2Grid(nadgridsId);
264 if (nadgrids == null)
265 throw new ProjectionConfigurationException(tr("Grid shift file ''{0}'' for option +nadgrids not supported.", nadgridsId));
266 return new NTV2Datum(nadgridsId, null, ellps, nadgrids);
267 }
268
269 String towgs84 = parameters.get(Param.towgs84.key);
270 if (towgs84 != null)
271 return parseToWGS84(towgs84, ellps);
272
273 String datumId = parameters.get(Param.datum.key);
274 if (datumId != null) {
275 Datum datum = Projections.getDatum(datumId);
276 if (datum == null) throw new ProjectionConfigurationException(tr("Unkown datum identifier: ''{0}''", datumId));
277 return datum;
278 }
279 if (parameters.containsKey(Param.no_defs.key))
280 throw new ProjectionConfigurationException(tr("Datum required (+datum=*, +towgs84=* or +nadgirds=*)"));
281 return new CentricDatum(null, null, ellps);
282 }
283
284 public Datum parseToWGS84(String paramList, Ellipsoid ellps) throws ProjectionConfigurationException {
285 String[] numStr = paramList.split(",");
286
287 if (numStr.length != 3 && numStr.length != 7)
288 throw new ProjectionConfigurationException(tr("Unexpected number of arguments for parameter ''towgs84'' (must be 3 or 7)"));
289 List<Double> towgs84Param = new ArrayList<Double>();
290 for (int i = 0; i < numStr.length; i++) {
291 try {
292 towgs84Param.add(Double.parseDouble(numStr[i]));
293 } catch (NumberFormatException e) {
294 throw new ProjectionConfigurationException(tr("Unable to parse value of parameter ''towgs84'' (''{0}'')", numStr[i]));
295 }
296 }
297 boolean isCentric = true;
298 for (int i = 0; i<towgs84Param.size(); i++) {
299 if (towgs84Param.get(i) != 0.0) {
300 isCentric = false;
301 break;
302 }
303 }
304 if (isCentric)
305 return new CentricDatum(null, null, ellps);
306 boolean is3Param = true;
307 for (int i = 3; i<towgs84Param.size(); i++) {
308 if (towgs84Param.get(i) != 0.0) {
309 is3Param = false;
310 break;
311 }
312 }
313 if (is3Param)
314 return new ThreeParameterDatum(null, null, ellps,
315 towgs84Param.get(0),
316 towgs84Param.get(1),
317 towgs84Param.get(2));
318 else
319 return new SevenParameterDatum(null, null, ellps,
320 towgs84Param.get(0),
321 towgs84Param.get(1),
322 towgs84Param.get(2),
323 towgs84Param.get(3),
324 towgs84Param.get(4),
325 towgs84Param.get(5),
326 towgs84Param.get(6));
327 }
328
329 public Proj parseProjection(Map<String, String> parameters, Ellipsoid ellps) throws ProjectionConfigurationException {
330 String id = (String) parameters.get(Param.proj.key);
331 if (id == null) throw new ProjectionConfigurationException(tr("Projection required (+proj=*)"));
332
333 Proj proj = Projections.getBaseProjection(id);
334 if (proj == null) throw new ProjectionConfigurationException(tr("Unkown projection identifier: ''{0}''", id));
335
336 ProjParameters projParams = new ProjParameters();
337
338 projParams.ellps = ellps;
339
340 String s;
341 s = parameters.get(Param.lat_0.key);
342 if (s != null) {
343 projParams.lat_0 = parseAngle(s, Param.lat_0.key);
344 }
345 s = parameters.get(Param.lat_1.key);
346 if (s != null) {
347 projParams.lat_1 = parseAngle(s, Param.lat_1.key);
348 }
349 s = parameters.get(Param.lat_2.key);
350 if (s != null) {
351 projParams.lat_2 = parseAngle(s, Param.lat_2.key);
352 }
353 proj.initialize(projParams);
354 return proj;
355 }
356
357 public static Bounds parseBounds(String boundsStr) throws ProjectionConfigurationException {
358 String[] numStr = boundsStr.split(",");
359 if (numStr.length != 4)
360 throw new ProjectionConfigurationException(tr("Unexpected number of arguments for parameter ''+bounds'' (must be 4)"));
361 return new Bounds(parseAngle(numStr[1], "minlat (+bounds)"),
362 parseAngle(numStr[0], "minlon (+bounds)"),
363 parseAngle(numStr[3], "maxlat (+bounds)"),
364 parseAngle(numStr[2], "maxlon (+bounds)"), false);
365 }
366
367 public static double parseDouble(Map<String, String> parameters, String parameterName) throws ProjectionConfigurationException {
368 String doubleStr = parameters.get(parameterName);
369 if (doubleStr == null && parameters.containsKey(parameterName))
370 throw new ProjectionConfigurationException(
371 tr("Expected number argument for parameter ''{0}''", parameterName));
372 return parseDouble(doubleStr, parameterName);
373 }
374
375 public static double parseDouble(String doubleStr, String parameterName) throws ProjectionConfigurationException {
376 try {
377 return Double.parseDouble(doubleStr);
378 } catch (NumberFormatException e) {
379 throw new ProjectionConfigurationException(
380 tr("Unable to parse value ''{1}'' of parameter ''{0}'' as number.", parameterName, doubleStr));
381 }
382 }
383
384 public static double parseAngle(String angleStr, String parameterName) throws ProjectionConfigurationException {
385 String s = angleStr;
386 double value = 0;
387 boolean neg = false;
388 Matcher m = Pattern.compile("^-").matcher(s);
389 if (m.find()) {
390 neg = true;
391 s = s.substring(m.end());
392 }
393 final String FLOAT = "(\\d+(\\.\\d*)?)";
394 boolean dms = false;
395 double deg = 0.0, min = 0.0, sec = 0.0;
396 // degrees
397 m = Pattern.compile("^"+FLOAT+"d").matcher(s);
398 if (m.find()) {
399 s = s.substring(m.end());
400 deg = Double.parseDouble(m.group(1));
401 dms = true;
402 }
403 // minutes
404 m = Pattern.compile("^"+FLOAT+"'").matcher(s);
405 if (m.find()) {
406 s = s.substring(m.end());
407 min = Double.parseDouble(m.group(1));
408 dms = true;
409 }
410 // seconds
411 m = Pattern.compile("^"+FLOAT+"\"").matcher(s);
412 if (m.find()) {
413 s = s.substring(m.end());
414 sec = Double.parseDouble(m.group(1));
415 dms = true;
416 }
417 // plain number (in degrees)
418 if (dms) {
419 value = deg + (min/60.0) + (sec/3600.0);
420 } else {
421 m = Pattern.compile("^"+FLOAT).matcher(s);
422 if (m.find()) {
423 s = s.substring(m.end());
424 value += Double.parseDouble(m.group(1));
425 }
426 }
427 m = Pattern.compile("^(N|E)", Pattern.CASE_INSENSITIVE).matcher(s);
428 if (m.find()) {
429 s = s.substring(m.end());
430 } else {
431 m = Pattern.compile("^(S|W)", Pattern.CASE_INSENSITIVE).matcher(s);
432 if (m.find()) {
433 s = s.substring(m.end());
434 neg = !neg;
435 }
436 }
437 if (neg) {
438 value = -value;
439 }
440 if (!s.isEmpty()) {
441 throw new ProjectionConfigurationException(
442 tr("Unable to parse value ''{1}'' of parameter ''{0}'' as coordinate value.", parameterName, angleStr));
443 }
444 return value;
445 }
446
447 @Override
448 public Integer getEpsgCode() {
449 if (code != null && code.startsWith("EPSG:")) {
450 try {
451 return Integer.parseInt(code.substring(5));
452 } catch (NumberFormatException e) {}
453 }
454 return null;
455 }
456
457 @Override
458 public String toCode() {
459 return code != null ? code : "proj:" + (pref == null ? "ERROR" : pref);
460 }
461
462 @Override
463 public String getCacheDirectoryName() {
464 return cacheDir != null ? cacheDir : "proj-"+Utils.md5Hex(pref == null ? "" : pref).substring(0, 4);
465 }
466
467 @Override
468 public Bounds getWorldBoundsLatLon() {
469 if (bounds != null) return bounds;
470 return new Bounds(
471 new LatLon(-90.0, -180.0),
472 new LatLon(90.0, 180.0));
473 }
474
475 @Override
476 public String toString() {
477 return name != null ? name : tr("Custom Projection");
478 }
479}
Note: See TracBrowser for help on using the repository browser.