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

Last change on this file since 8419 was 8415, checked in by Don-vip, 9 years ago

code style/cleanup - Uncommented Empty Constructor

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