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

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

fix #9024 - bbox/bounds memory optimizations (modified patch by shinigami) + javadoc

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