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

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

improved custom projection

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