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

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

separate preference gui from core projection code (there may be bugs)

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