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

Last change on this file since 7025 was 7005, checked in by Don-vip, 10 years ago

see #8465 - use diamond operator where applicable

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