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

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

make fields final

  • Property svn:eol-style set to native
File size: 20.4 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 }
123
124 /**
125 * Constructs a new {@code CustomProjection} with given parameters.
126 * @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")
127 */
128 public CustomProjection(String pref) {
129 this(null, null, pref, null);
130 }
131
132 /**
133 * Constructs a new {@code CustomProjection} with given name, code and parameters.
134 *
135 * @param name describe projection in one or two words
136 * @param code unique code for this projection - may be null
137 * @param pref the string that defines the custom projection
138 * @param cacheDir cache directory name
139 */
140 public CustomProjection(String name, String code, String pref, String cacheDir) {
141 this.name = name;
142 this.code = code;
143 this.pref = pref;
144 this.cacheDir = cacheDir;
145 try {
146 update(pref);
147 } catch (ProjectionConfigurationException ex) {
148 try {
149 update(null);
150 } catch (ProjectionConfigurationException ex1) {
151 throw new RuntimeException(ex1);
152 }
153 }
154 }
155
156 /**
157 * Updates this {@code CustomProjection} with given parameters.
158 * @param pref String containing projection parameters (ex: "+proj=lonlat +ellps=WGS84 +datum=WGS84 +bounds=-180,-90,180,90")
159 * @throws ProjectionConfigurationException if {@code pref} cannot be parsed properly
160 */
161 public final void update(String pref) throws ProjectionConfigurationException {
162 this.pref = pref;
163 if (pref == null) {
164 ellps = Ellipsoid.WGS84;
165 datum = WGS84Datum.INSTANCE;
166 proj = new Mercator();
167 bounds = new Bounds(
168 -85.05112877980659, -180.0,
169 85.05112877980659, 180.0, true);
170 } else {
171 Map<String, String> parameters = parseParameterList(pref);
172 ellps = parseEllipsoid(parameters);
173 datum = parseDatum(parameters, ellps);
174 proj = parseProjection(parameters, ellps);
175 String s = parameters.get(Param.x_0.key);
176 if (s != null) {
177 this.x_0 = parseDouble(s, Param.x_0.key);
178 }
179 s = parameters.get(Param.y_0.key);
180 if (s != null) {
181 this.y_0 = parseDouble(s, Param.y_0.key);
182 }
183 s = parameters.get(Param.lon_0.key);
184 if (s != null) {
185 this.lon_0 = parseAngle(s, Param.lon_0.key);
186 }
187 s = parameters.get(Param.k_0.key);
188 if (s != null) {
189 this.k_0 = parseDouble(s, Param.k_0.key);
190 }
191 s = parameters.get(Param.bounds.key);
192 if (s != null) {
193 this.bounds = parseBounds(s);
194 }
195 s = parameters.get(Param.wmssrs.key);
196 if (s != null) {
197 this.code = s;
198 }
199 }
200 }
201
202 private Map<String, String> parseParameterList(String pref) throws ProjectionConfigurationException {
203 Map<String, String> parameters = new HashMap<>();
204 String[] parts = Utils.WHITE_SPACES_PATTERN.split(pref.trim());
205 if (pref.trim().isEmpty()) {
206 parts = new String[0];
207 }
208 for (String part : parts) {
209 if (part.isEmpty() || part.charAt(0) != '+')
210 throw new ProjectionConfigurationException(tr("Parameter must begin with a ''+'' character (found ''{0}'')", part));
211 Matcher m = Pattern.compile("\\+([a-zA-Z0-9_]+)(=(.*))?").matcher(part);
212 if (m.matches()) {
213 String key = m.group(1);
214 // alias
215 if ("k".equals(key)) {
216 key = Param.k_0.key;
217 }
218 String value = null;
219 if (m.groupCount() >= 3) {
220 value = m.group(3);
221 // some aliases
222 if (key.equals(Param.proj.key)) {
223 if ("longlat".equals(value) || "latlon".equals(value) || "latlong".equals(value)) {
224 value = "lonlat";
225 }
226 }
227 }
228 if (!Param.paramsByKey.containsKey(key))
229 throw new ProjectionConfigurationException(tr("Unknown parameter: ''{0}''.", key));
230 if (Param.paramsByKey.get(key).hasValue && value == null)
231 throw new ProjectionConfigurationException(tr("Value expected for parameter ''{0}''.", key));
232 if (!Param.paramsByKey.get(key).hasValue && value != null)
233 throw new ProjectionConfigurationException(tr("No value expected for parameter ''{0}''.", key));
234 parameters.put(key, value);
235 } else
236 throw new ProjectionConfigurationException(tr("Unexpected parameter format (''{0}'')", part));
237 }
238 // recursive resolution of +init includes
239 String initKey = parameters.get(Param.init.key);
240 if (initKey != null) {
241 String init = Projections.getInit(initKey);
242 if (init == null)
243 throw new ProjectionConfigurationException(tr("Value ''{0}'' for option +init not supported.", initKey));
244 Map<String, String> initp = null;
245 try {
246 initp = parseParameterList(init);
247 } catch (ProjectionConfigurationException ex) {
248 throw new ProjectionConfigurationException(tr(initKey+": "+ex.getMessage()), ex);
249 }
250 for (Map.Entry<String, String> e : parameters.entrySet()) {
251 initp.put(e.getKey(), e.getValue());
252 }
253 return initp;
254 }
255 return parameters;
256 }
257
258 public Ellipsoid parseEllipsoid(Map<String, String> parameters) throws ProjectionConfigurationException {
259 String code = parameters.get(Param.ellps.key);
260 if (code != null) {
261 Ellipsoid ellipsoid = Projections.getEllipsoid(code);
262 if (ellipsoid == null) {
263 throw new ProjectionConfigurationException(tr("Ellipsoid ''{0}'' not supported.", code));
264 } else {
265 return ellipsoid;
266 }
267 }
268 String s = parameters.get(Param.a.key);
269 if (s != null) {
270 double a = parseDouble(s, Param.a.key);
271 if (parameters.get(Param.es.key) != null) {
272 double es = parseDouble(parameters, Param.es.key);
273 return Ellipsoid.create_a_es(a, es);
274 }
275 if (parameters.get(Param.rf.key) != null) {
276 double rf = parseDouble(parameters, Param.rf.key);
277 return Ellipsoid.create_a_rf(a, rf);
278 }
279 if (parameters.get(Param.f.key) != null) {
280 double f = parseDouble(parameters, Param.f.key);
281 return Ellipsoid.create_a_f(a, f);
282 }
283 if (parameters.get(Param.b.key) != null) {
284 double b = parseDouble(parameters, Param.b.key);
285 return Ellipsoid.create_a_b(a, b);
286 }
287 }
288 if (parameters.containsKey(Param.a.key) ||
289 parameters.containsKey(Param.es.key) ||
290 parameters.containsKey(Param.rf.key) ||
291 parameters.containsKey(Param.f.key) ||
292 parameters.containsKey(Param.b.key))
293 throw new ProjectionConfigurationException(tr("Combination of ellipsoid parameters is not supported."));
294 if (parameters.containsKey(Param.no_defs.key))
295 throw new ProjectionConfigurationException(tr("Ellipsoid required (+ellps=* or +a=*, +b=*)"));
296 // nothing specified, use WGS84 as default
297 return Ellipsoid.WGS84;
298 }
299
300 public Datum parseDatum(Map<String, String> parameters, Ellipsoid ellps) throws ProjectionConfigurationException {
301 String nadgridsId = parameters.get(Param.nadgrids.key);
302 if (nadgridsId != null) {
303 if (nadgridsId.startsWith("@")) {
304 nadgridsId = nadgridsId.substring(1);
305 }
306 if ("null".equals(nadgridsId))
307 return new NullDatum(null, ellps);
308 NTV2GridShiftFileWrapper nadgrids = Projections.getNTV2Grid(nadgridsId);
309 if (nadgrids == null)
310 throw new ProjectionConfigurationException(tr("Grid shift file ''{0}'' for option +nadgrids not supported.", nadgridsId));
311 return new NTV2Datum(nadgridsId, null, ellps, nadgrids);
312 }
313
314 String towgs84 = parameters.get(Param.towgs84.key);
315 if (towgs84 != null)
316 return parseToWGS84(towgs84, ellps);
317
318 String datumId = parameters.get(Param.datum.key);
319 if (datumId != null) {
320 Datum datum = Projections.getDatum(datumId);
321 if (datum == null) throw new ProjectionConfigurationException(tr("Unknown datum identifier: ''{0}''", datumId));
322 return datum;
323 }
324 if (parameters.containsKey(Param.no_defs.key))
325 throw new ProjectionConfigurationException(tr("Datum required (+datum=*, +towgs84=* or +nadgrids=*)"));
326 return new CentricDatum(null, null, ellps);
327 }
328
329 public Datum parseToWGS84(String paramList, Ellipsoid ellps) throws ProjectionConfigurationException {
330 String[] numStr = paramList.split(",");
331
332 if (numStr.length != 3 && numStr.length != 7)
333 throw new ProjectionConfigurationException(tr("Unexpected number of arguments for parameter ''towgs84'' (must be 3 or 7)"));
334 List<Double> towgs84Param = new ArrayList<>();
335 for (String str : numStr) {
336 try {
337 towgs84Param.add(Double.parseDouble(str));
338 } catch (NumberFormatException e) {
339 throw new ProjectionConfigurationException(tr("Unable to parse value of parameter ''towgs84'' (''{0}'')", str), e);
340 }
341 }
342 boolean isCentric = true;
343 for (Double param : towgs84Param) {
344 if (param != 0.0) {
345 isCentric = false;
346 break;
347 }
348 }
349 if (isCentric)
350 return new CentricDatum(null, null, ellps);
351 boolean is3Param = true;
352 for (int i = 3; i<towgs84Param.size(); i++) {
353 if (towgs84Param.get(i) != 0.0) {
354 is3Param = false;
355 break;
356 }
357 }
358 if (is3Param)
359 return new ThreeParameterDatum(null, null, ellps,
360 towgs84Param.get(0),
361 towgs84Param.get(1),
362 towgs84Param.get(2));
363 else
364 return new SevenParameterDatum(null, null, ellps,
365 towgs84Param.get(0),
366 towgs84Param.get(1),
367 towgs84Param.get(2),
368 towgs84Param.get(3),
369 towgs84Param.get(4),
370 towgs84Param.get(5),
371 towgs84Param.get(6));
372 }
373
374 public Proj parseProjection(Map<String, String> parameters, Ellipsoid ellps) throws ProjectionConfigurationException {
375 String id = parameters.get(Param.proj.key);
376 if (id == null) throw new ProjectionConfigurationException(tr("Projection required (+proj=*)"));
377
378 Proj proj = Projections.getBaseProjection(id);
379 if (proj == null) throw new ProjectionConfigurationException(tr("Unknown projection identifier: ''{0}''", id));
380
381 ProjParameters projParams = new ProjParameters();
382
383 projParams.ellps = ellps;
384
385 String s;
386 s = parameters.get(Param.lat_0.key);
387 if (s != null) {
388 projParams.lat_0 = parseAngle(s, Param.lat_0.key);
389 }
390 s = parameters.get(Param.lat_1.key);
391 if (s != null) {
392 projParams.lat_1 = parseAngle(s, Param.lat_1.key);
393 }
394 s = parameters.get(Param.lat_2.key);
395 if (s != null) {
396 projParams.lat_2 = parseAngle(s, Param.lat_2.key);
397 }
398 proj.initialize(projParams);
399 return proj;
400 }
401
402 public static Bounds parseBounds(String boundsStr) throws ProjectionConfigurationException {
403 String[] numStr = boundsStr.split(",");
404 if (numStr.length != 4)
405 throw new ProjectionConfigurationException(tr("Unexpected number of arguments for parameter ''+bounds'' (must be 4)"));
406 return new Bounds(parseAngle(numStr[1], "minlat (+bounds)"),
407 parseAngle(numStr[0], "minlon (+bounds)"),
408 parseAngle(numStr[3], "maxlat (+bounds)"),
409 parseAngle(numStr[2], "maxlon (+bounds)"), false);
410 }
411
412 public static double parseDouble(Map<String, String> parameters, String parameterName) throws ProjectionConfigurationException {
413 if (!parameters.containsKey(parameterName))
414 throw new IllegalArgumentException(tr("Unknown parameter ''{0}''", parameterName));
415 String doubleStr = parameters.get(parameterName);
416 if (doubleStr == null)
417 throw new ProjectionConfigurationException(
418 tr("Expected number argument for parameter ''{0}''", parameterName));
419 return parseDouble(doubleStr, parameterName);
420 }
421
422 public static double parseDouble(String doubleStr, String parameterName) throws ProjectionConfigurationException {
423 try {
424 return Double.parseDouble(doubleStr);
425 } catch (NumberFormatException e) {
426 throw new ProjectionConfigurationException(
427 tr("Unable to parse value ''{1}'' of parameter ''{0}'' as number.", parameterName, doubleStr), e);
428 }
429 }
430
431 public static double parseAngle(String angleStr, String parameterName) throws ProjectionConfigurationException {
432 String s = angleStr;
433 double value = 0;
434 boolean neg = false;
435 Matcher m = Pattern.compile("^-").matcher(s);
436 if (m.find()) {
437 neg = true;
438 s = s.substring(m.end());
439 }
440 final String FLOAT = "(\\d+(\\.\\d*)?)";
441 boolean dms = false;
442 double deg = 0.0, min = 0.0, sec = 0.0;
443 // degrees
444 m = Pattern.compile("^"+FLOAT+"d").matcher(s);
445 if (m.find()) {
446 s = s.substring(m.end());
447 deg = Double.parseDouble(m.group(1));
448 dms = true;
449 }
450 // minutes
451 m = Pattern.compile("^"+FLOAT+"'").matcher(s);
452 if (m.find()) {
453 s = s.substring(m.end());
454 min = Double.parseDouble(m.group(1));
455 dms = true;
456 }
457 // seconds
458 m = Pattern.compile("^"+FLOAT+"\"").matcher(s);
459 if (m.find()) {
460 s = s.substring(m.end());
461 sec = Double.parseDouble(m.group(1));
462 dms = true;
463 }
464 // plain number (in degrees)
465 if (dms) {
466 value = deg + (min/60.0) + (sec/3600.0);
467 } else {
468 m = Pattern.compile("^"+FLOAT).matcher(s);
469 if (m.find()) {
470 s = s.substring(m.end());
471 value += Double.parseDouble(m.group(1));
472 }
473 }
474 m = Pattern.compile("^(N|E)", Pattern.CASE_INSENSITIVE).matcher(s);
475 if (m.find()) {
476 s = s.substring(m.end());
477 } else {
478 m = Pattern.compile("^(S|W)", Pattern.CASE_INSENSITIVE).matcher(s);
479 if (m.find()) {
480 s = s.substring(m.end());
481 neg = !neg;
482 }
483 }
484 if (neg) {
485 value = -value;
486 }
487 if (!s.isEmpty()) {
488 throw new ProjectionConfigurationException(
489 tr("Unable to parse value ''{1}'' of parameter ''{0}'' as coordinate value.", parameterName, angleStr));
490 }
491 return value;
492 }
493
494 @Override
495 public Integer getEpsgCode() {
496 if (code != null && code.startsWith("EPSG:")) {
497 try {
498 return Integer.parseInt(code.substring(5));
499 } catch (NumberFormatException e) {
500 Main.warn(e);
501 }
502 }
503 return null;
504 }
505
506 @Override
507 public String toCode() {
508 return code != null ? code : "proj:" + (pref == null ? "ERROR" : pref);
509 }
510
511 @Override
512 public String getCacheDirectoryName() {
513 return cacheDir != null ? cacheDir : "proj-"+Utils.md5Hex(pref == null ? "" : pref).substring(0, 4);
514 }
515
516 @Override
517 public Bounds getWorldBoundsLatLon() {
518 if (bounds != null) return bounds;
519 return new Bounds(
520 new LatLon(-90.0, -180.0),
521 new LatLon(90.0, 180.0));
522 }
523
524 @Override
525 public String toString() {
526 return name != null ? name : tr("Custom Projection");
527 }
528}
Note: See TracBrowser for help on using the repository browser.