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

Last change on this file since 6362 was 6310, checked in by Don-vip, 11 years ago

Sonar/FindBugs - Nested blocks of code should not be left empty

  • 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.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 extension, not present in PROJ.4
71 bounds("bounds", true);
72
73 public String key;
74 public boolean hasValue;
75
76 public final static Map<String, Param> paramsByKey = new HashMap<String, Param>();
77 static {
78 for (Param p : Param.values()) {
79 paramsByKey.put(p.key, p);
80 }
81 }
82
83 Param(String key, boolean hasValue) {
84 this.key = key;
85 this.hasValue = hasValue;
86 }
87 }
88
89 public CustomProjection() {
90 }
91
92 public CustomProjection(String pref) {
93 this(null, null, pref, null);
94 }
95
96 /**
97 * Constructor.
98 *
99 * @param name describe projection in one or two words
100 * @param code unique code for this projection - may be null
101 * @param pref the string that defines the custom projection
102 * @param cacheDir cache directory name
103 */
104 public CustomProjection(String name, String code, String pref, String cacheDir) {
105 this.name = name;
106 this.code = code;
107 this.pref = pref;
108 this.cacheDir = cacheDir;
109 try {
110 update(pref);
111 } catch (ProjectionConfigurationException ex) {
112 try {
113 update(null);
114 } catch (ProjectionConfigurationException ex1) {
115 throw new RuntimeException();
116 }
117 }
118 }
119
120 public void update(String pref) throws ProjectionConfigurationException {
121 this.pref = pref;
122 if (pref == null) {
123 ellps = Ellipsoid.WGS84;
124 datum = WGS84Datum.INSTANCE;
125 proj = new Mercator();
126 bounds = new Bounds(
127 -85.05112877980659, -180.0,
128 85.05112877980659, 180.0, true);
129 } else {
130 Map<String, String> parameters = parseParameterList(pref);
131 ellps = parseEllipsoid(parameters);
132 datum = parseDatum(parameters, ellps);
133 proj = parseProjection(parameters, ellps);
134 String s = parameters.get(Param.x_0.key);
135 if (s != null) {
136 this.x_0 = parseDouble(s, Param.x_0.key);
137 }
138 s = parameters.get(Param.y_0.key);
139 if (s != null) {
140 this.y_0 = parseDouble(s, Param.y_0.key);
141 }
142 s = parameters.get(Param.lon_0.key);
143 if (s != null) {
144 this.lon_0 = parseAngle(s, Param.lon_0.key);
145 }
146 s = parameters.get(Param.k_0.key);
147 if (s != null) {
148 this.k_0 = parseDouble(s, Param.k_0.key);
149 }
150 s = parameters.get(Param.bounds.key);
151 if (s != null) {
152 this.bounds = parseBounds(s);
153 }
154 }
155 }
156
157 private Map<String, String> parseParameterList(String pref) throws ProjectionConfigurationException {
158 Map<String, String> parameters = new HashMap<String, String>();
159 String[] parts = pref.trim().split("\\s+");
160 if (pref.trim().isEmpty()) {
161 parts = new String[0];
162 }
163 for (String part : parts) {
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 (String str : numStr) {
291 try {
292 towgs84Param.add(Double.parseDouble(str));
293 } catch (NumberFormatException e) {
294 throw new ProjectionConfigurationException(tr("Unable to parse value of parameter ''towgs84'' (''{0}'')", str));
295 }
296 }
297 boolean isCentric = true;
298 for (Double param : towgs84Param) {
299 if (param != 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 = 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 if (!parameters.containsKey(parameterName))
369 throw new IllegalArgumentException(tr("Unknown parameter ''{0}''", parameterName));
370 String doubleStr = parameters.get(parameterName);
371 if (doubleStr == null)
372 throw new ProjectionConfigurationException(
373 tr("Expected number argument for parameter ''{0}''", parameterName));
374 return parseDouble(doubleStr, parameterName);
375 }
376
377 public static double parseDouble(String doubleStr, String parameterName) throws ProjectionConfigurationException {
378 try {
379 return Double.parseDouble(doubleStr);
380 } catch (NumberFormatException e) {
381 throw new ProjectionConfigurationException(
382 tr("Unable to parse value ''{1}'' of parameter ''{0}'' as number.", parameterName, doubleStr));
383 }
384 }
385
386 public static double parseAngle(String angleStr, String parameterName) throws ProjectionConfigurationException {
387 String s = angleStr;
388 double value = 0;
389 boolean neg = false;
390 Matcher m = Pattern.compile("^-").matcher(s);
391 if (m.find()) {
392 neg = true;
393 s = s.substring(m.end());
394 }
395 final String FLOAT = "(\\d+(\\.\\d*)?)";
396 boolean dms = false;
397 double deg = 0.0, min = 0.0, sec = 0.0;
398 // degrees
399 m = Pattern.compile("^"+FLOAT+"d").matcher(s);
400 if (m.find()) {
401 s = s.substring(m.end());
402 deg = Double.parseDouble(m.group(1));
403 dms = true;
404 }
405 // minutes
406 m = Pattern.compile("^"+FLOAT+"'").matcher(s);
407 if (m.find()) {
408 s = s.substring(m.end());
409 min = Double.parseDouble(m.group(1));
410 dms = true;
411 }
412 // seconds
413 m = Pattern.compile("^"+FLOAT+"\"").matcher(s);
414 if (m.find()) {
415 s = s.substring(m.end());
416 sec = Double.parseDouble(m.group(1));
417 dms = true;
418 }
419 // plain number (in degrees)
420 if (dms) {
421 value = deg + (min/60.0) + (sec/3600.0);
422 } else {
423 m = Pattern.compile("^"+FLOAT).matcher(s);
424 if (m.find()) {
425 s = s.substring(m.end());
426 value += Double.parseDouble(m.group(1));
427 }
428 }
429 m = Pattern.compile("^(N|E)", Pattern.CASE_INSENSITIVE).matcher(s);
430 if (m.find()) {
431 s = s.substring(m.end());
432 } else {
433 m = Pattern.compile("^(S|W)", Pattern.CASE_INSENSITIVE).matcher(s);
434 if (m.find()) {
435 s = s.substring(m.end());
436 neg = !neg;
437 }
438 }
439 if (neg) {
440 value = -value;
441 }
442 if (!s.isEmpty()) {
443 throw new ProjectionConfigurationException(
444 tr("Unable to parse value ''{1}'' of parameter ''{0}'' as coordinate value.", parameterName, angleStr));
445 }
446 return value;
447 }
448
449 @Override
450 public Integer getEpsgCode() {
451 if (code != null && code.startsWith("EPSG:")) {
452 try {
453 return Integer.parseInt(code.substring(5));
454 } catch (NumberFormatException e) {
455 Main.warn(e);
456 }
457 }
458 return null;
459 }
460
461 @Override
462 public String toCode() {
463 return code != null ? code : "proj:" + (pref == null ? "ERROR" : pref);
464 }
465
466 @Override
467 public String getCacheDirectoryName() {
468 return cacheDir != null ? cacheDir : "proj-"+Utils.md5Hex(pref == null ? "" : pref).substring(0, 4);
469 }
470
471 @Override
472 public Bounds getWorldBoundsLatLon() {
473 if (bounds != null) return bounds;
474 return new Bounds(
475 new LatLon(-90.0, -180.0),
476 new LatLon(90.0, 180.0));
477 }
478
479 @Override
480 public String toString() {
481 return name != null ? name : tr("Custom Projection");
482 }
483}
Note: See TracBrowser for help on using the repository browser.