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
RevLine 
[5072]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;
[5227]7import java.util.Arrays;
[5072]8import java.util.HashMap;
[5227]9import java.util.HashSet;
[5072]10import java.util.List;
11import java.util.Map;
[5227]12import java.util.Set;
[5072]13import java.util.regex.Matcher;
14import java.util.regex.Pattern;
15
16import org.openstreetmap.josm.data.Bounds;
17import org.openstreetmap.josm.data.coor.LatLon;
[5227]18import org.openstreetmap.josm.data.projection.CustomProjection.Param;
[5072]19import org.openstreetmap.josm.data.projection.datum.CentricDatum;
20import org.openstreetmap.josm.data.projection.datum.Datum;
[5226]21import org.openstreetmap.josm.data.projection.datum.NTV2Datum;
22import org.openstreetmap.josm.data.projection.datum.NTV2GridShiftFileWrapper;
[5072]23import org.openstreetmap.josm.data.projection.datum.SevenParameterDatum;
24import org.openstreetmap.josm.data.projection.datum.ThreeParameterDatum;
[5226]25import org.openstreetmap.josm.data.projection.datum.WGS84Datum;
[5072]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 */
[5226]35public class CustomProjection extends AbstractProjection {
[5072]36
[5226]37 /**
38 * pref String that defines the projection
39 *
40 * null means fall back mode (Mercator)
41 */
[5234]42 protected String pref;
[5228]43 protected Bounds bounds;
[5072]44
[5227]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);
[5228]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);
[5227]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,
[5228]75 nadgrids, proj, lat_0, lat_1, lat_2, wktext, units, no_defs,
76 init, bounds
[5227]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
[5234]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
[5226]109 public void update(String pref) throws ProjectionConfigurationException {
[5228]110 this.pref = pref;
[5226]111 if (pref == null) {
112 ellps = Ellipsoid.WGS84;
113 datum = WGS84Datum.INSTANCE;
114 proj = new org.openstreetmap.josm.data.projection.proj.Mercator();
[5228]115 bounds = new Bounds(
116 new LatLon(-85.05112877980659, -180.0),
117 new LatLon(85.05112877980659, 180.0));
[5226]118 } else {
[5228]119 Map<String, String> parameters = parseParameterList(pref);
[5072]120 ellps = parseEllipsoid(parameters);
121 datum = parseDatum(parameters, ellps);
122 proj = parseProjection(parameters, ellps);
[5227]123 String s = parameters.get(Param.x_0.key);
[5072]124 if (s != null) {
[5227]125 this.x_0 = parseDouble(s, Param.x_0.key);
[5072]126 }
[5227]127 s = parameters.get(Param.y_0.key);
[5072]128 if (s != null) {
[5228]129 this.y_0 = parseDouble(s, Param.y_0.key);
[5072]130 }
[5227]131 s = parameters.get(Param.lon_0.key);
[5072]132 if (s != null) {
[5228]133 this.lon_0 = parseAngle(s, Param.lon_0.key);
[5072]134 }
[5227]135 s = parameters.get(Param.k_0.key);
[5072]136 if (s != null) {
[5228]137 this.k_0 = parseDouble(s, Param.k_0.key);
[5072]138 }
[5228]139 s = parameters.get(Param.bounds.key);
140 if (s != null) {
141 this.bounds = parseBounds(s);
142 }
[5072]143 }
144 }
145
[5228]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
[5072]203 public Ellipsoid parseEllipsoid(Map<String, String> parameters) throws ProjectionConfigurationException {
[5227]204 String code = parameters.get(Param.ellps.key);
[5072]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 }
[5227]213 String s = parameters.get(Param.a.key);
[5072]214 if (s != null) {
[5227]215 double a = parseDouble(s, Param.a.key);
216 if (parameters.get(Param.es.key) != null) {
217 double es = parseDouble(parameters, Param.es.key);
[5072]218 return Ellipsoid.create_a_es(a, es);
219 }
[5227]220 if (parameters.get(Param.rf.key) != null) {
221 double rf = parseDouble(parameters, Param.rf.key);
[5072]222 return Ellipsoid.create_a_rf(a, rf);
223 }
[5227]224 if (parameters.get(Param.f.key) != null) {
225 double f = parseDouble(parameters, Param.f.key);
[5072]226 return Ellipsoid.create_a_f(a, f);
227 }
[5227]228 if (parameters.get(Param.b.key) != null) {
229 double b = parseDouble(parameters, Param.b.key);
[5072]230 return Ellipsoid.create_a_b(a, b);
231 }
232 }
[5227]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))
[5072]238 throw new ProjectionConfigurationException(tr("Combination of ellipsoid parameters is not supported."));
[5228]239 if (parameters.containsKey(Param.no_defs.key))
240 throw new ProjectionConfigurationException(tr("Ellipsoid required (+ellps=* or +a=*, +b=*)"));
[5072]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 {
[5227]246 String nadgridsId = parameters.get(Param.nadgrids.key);
[5226]247 if (nadgridsId != null) {
[5228]248 NTV2GridShiftFileWrapper nadgrids = Projections.getNTV2Grid(nadgridsId);
[5226]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
[5227]254 String towgs84 = parameters.get(Param.towgs84.key);
[5072]255 if (towgs84 != null)
256 return parseToWGS84(towgs84, ellps);
257
[5227]258 String datumId = parameters.get(Param.datum.key);
[5072]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;
[5228]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);
[5072]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 {
[5227]311 String id = (String) parameters.get(Param.proj.key);
[5072]312 if (id == null) throw new ProjectionConfigurationException(tr("Projection required (+proj=*)"));
313
[5227]314 Proj proj = Projections.getBaseProjection(id);
[5072]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;
[5227]322 s = parameters.get(Param.lat_0.key);
[5072]323 if (s != null) {
[5227]324 projParams.lat_0 = parseAngle(s, Param.lat_0.key);
[5072]325 }
[5227]326 s = parameters.get(Param.lat_1.key);
[5072]327 if (s != null) {
[5227]328 projParams.lat_1 = parseAngle(s, Param.lat_1.key);
[5072]329 }
[5227]330 s = parameters.get(Param.lat_2.key);
[5072]331 if (s != null) {
[5227]332 projParams.lat_2 = parseAngle(s, Param.lat_2.key);
[5072]333 }
334 proj.initialize(projParams);
335 return proj;
[5228]336 }
[5072]337
[5228]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)"));
[5072]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(
[5202]361 tr("Unable to parse value ''{1}'' of parameter ''{0}'' as number.", parameterName, doubleStr));
[5072]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(
[5202]420 tr("Unable to parse value ''{1}'' of parameter ''{0}'' as coordinate value.", parameterName, angleStr));
[5072]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() {
[5226]442 return "proj:" + (pref == null ? "ERROR" : pref);
[5072]443 }
444
445 @Override
446 public String getCacheDirectoryName() {
[5226]447 return "proj-"+Utils.md5Hex(pref == null ? "" : pref).substring(0, 4);
[5072]448 }
449
450 @Override
451 public Bounds getWorldBoundsLatLon() {
[5228]452 if (bounds != null) return bounds;
[5072]453 return new Bounds(
[5228]454 new LatLon(-90.0, -180.0),
455 new LatLon(90.0, 180.0));
[5072]456 }
457
458 @Override
459 public String toString() {
[5227]460 return tr("Custom Projection");
[5072]461 }
462}
Note: See TracBrowser for help on using the repository browser.