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

Last change on this file since 5266 was 5239, checked in by bastiK, 12 years ago

projections: minor stuff

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