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

Last change on this file since 9562 was 9562, checked in by Don-vip, 8 years ago

see #12186 - checkstyle

  • Property svn:eol-style set to native
File size: 32.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.EnumMap;
8import java.util.HashMap;
9import java.util.List;
10import java.util.Map;
11import java.util.concurrent.ConcurrentHashMap;
12import java.util.regex.Matcher;
13import java.util.regex.Pattern;
14
15import org.openstreetmap.josm.Main;
16import org.openstreetmap.josm.data.Bounds;
17import org.openstreetmap.josm.data.ProjectionBounds;
18import org.openstreetmap.josm.data.coor.EastNorth;
19import org.openstreetmap.josm.data.coor.LatLon;
20import org.openstreetmap.josm.data.projection.datum.CentricDatum;
21import org.openstreetmap.josm.data.projection.datum.Datum;
22import org.openstreetmap.josm.data.projection.datum.NTV2Datum;
23import org.openstreetmap.josm.data.projection.datum.NTV2GridShiftFileWrapper;
24import org.openstreetmap.josm.data.projection.datum.NullDatum;
25import org.openstreetmap.josm.data.projection.datum.SevenParameterDatum;
26import org.openstreetmap.josm.data.projection.datum.ThreeParameterDatum;
27import org.openstreetmap.josm.data.projection.datum.WGS84Datum;
28import org.openstreetmap.josm.data.projection.proj.ICentralMeridianProvider;
29import org.openstreetmap.josm.data.projection.proj.Mercator;
30import org.openstreetmap.josm.data.projection.proj.Proj;
31import org.openstreetmap.josm.data.projection.proj.ProjParameters;
32import org.openstreetmap.josm.tools.Utils;
33
34/**
35 * Custom projection.
36 *
37 * Inspired by PROJ.4 and Proj4J.
38 * @since 5072
39 */
40public class CustomProjection extends AbstractProjection {
41
42 private static final double METER_PER_UNIT_DEGREE = 2 * Math.PI * 6370997 / 360;
43 private static final Map<String, Double> UNITS_TO_METERS = getUnitsToMeters();
44 private static final Map<String, Double> PRIME_MERIDANS = getPrimeMeridians();
45
46 /**
47 * pref String that defines the projection
48 *
49 * null means fall back mode (Mercator)
50 */
51 protected String pref;
52 protected String name;
53 protected String code;
54 protected String cacheDir;
55 protected Bounds bounds;
56 private double metersPerUnit = METER_PER_UNIT_DEGREE; // default to degrees
57 private String axis = "enu"; // default axis orientation is East, North, Up
58
59 /**
60 * Proj4-like projection parameters. See <a href="https://trac.osgeo.org/proj/wiki/GenParms">reference</a>.
61 * @since 7370 (public)
62 */
63 public enum Param {
64
65 /** False easting */
66 x_0("x_0", true),
67 /** False northing */
68 y_0("y_0", true),
69 /** Central meridian */
70 lon_0("lon_0", true),
71 /** Prime meridian */
72 pm("pm", true),
73 /** Scaling factor */
74 k_0("k_0", true),
75 /** Ellipsoid name (see {@code proj -le}) */
76 ellps("ellps", true),
77 /** Semimajor radius of the ellipsoid axis */
78 a("a", true),
79 /** Eccentricity of the ellipsoid squared */
80 es("es", true),
81 /** Reciprocal of the ellipsoid flattening term (e.g. 298) */
82 rf("rf", true),
83 /** Flattening of the ellipsoid = 1-sqrt(1-e^2) */
84 f("f", true),
85 /** Semiminor radius of the ellipsoid axis */
86 b("b", true),
87 /** Datum name (see {@code proj -ld}) */
88 datum("datum", true),
89 /** 3 or 7 term datum transform parameters */
90 towgs84("towgs84", true),
91 /** Filename of NTv2 grid file to use for datum transforms */
92 nadgrids("nadgrids", true),
93 /** Projection name (see {@code proj -l}) */
94 proj("proj", true),
95 /** Latitude of origin */
96 lat_0("lat_0", true),
97 /** Latitude of first standard parallel */
98 lat_1("lat_1", true),
99 /** Latitude of second standard parallel */
100 lat_2("lat_2", true),
101 /** Latitude of true scale (Polar Stereographic) */
102 lat_ts("lat_ts", true),
103 /** longitude of the center of the projection (Oblique Mercator) */
104 lonc("lonc", true),
105 /** azimuth (true) of the center line passing through the center of the
106 * projection (Oblique Mercator) */
107 alpha("alpha", true),
108 /** rectified bearing of the center line (Oblique Mercator) */
109 gamma("gamma", true),
110 /** select "Hotine" variant of Oblique Mercator */
111 no_off("no_off", false),
112 /** legacy alias for no_off */
113 no_uoff("no_uoff", false),
114 /** longitude of first point (Oblique Mercator) */
115 lon_1("lon_1", true),
116 /** longitude of second point (Oblique Mercator) */
117 lon_2("lon_2", true),
118 /** the exact proj.4 string will be preserved in the WKT representation */
119 wktext("wktext", false), // ignored
120 /** meters, US survey feet, etc. */
121 units("units", true),
122 /** Don't use the /usr/share/proj/proj_def.dat defaults file */
123 no_defs("no_defs", false),
124 init("init", true),
125 /** crs units to meter multiplier */
126 to_meter("to_meter", true),
127 /** definition of axis for projection */
128 axis("axis", true),
129 /** UTM zone */
130 zone("zone", true),
131 /** indicate southern hemisphere for UTM */
132 south("south", false),
133 /** vertical units - ignore, as we don't use height information */
134 vunits("vunits", true),
135 // JOSM extensions, not present in PROJ.4
136 wmssrs("wmssrs", true),
137 bounds("bounds", true);
138
139 /** Parameter key */
140 public final String key;
141 /** {@code true} if the parameter has a value */
142 public final boolean hasValue;
143
144 /** Map of all parameters by key */
145 static final Map<String, Param> paramsByKey = new ConcurrentHashMap<>();
146 static {
147 for (Param p : Param.values()) {
148 paramsByKey.put(p.key, p);
149 }
150 }
151
152 Param(String key, boolean hasValue) {
153 this.key = key;
154 this.hasValue = hasValue;
155 }
156 }
157
158 private enum Polarity { NORTH, SOUTH }
159
160 private EnumMap<Polarity, EastNorth> polesEN;
161 private EnumMap<Polarity, LatLon> polesLL;
162 {
163 polesLL = new EnumMap<>(Polarity.class);
164 polesLL.put(Polarity.NORTH, LatLon.NORTH_POLE);
165 polesLL.put(Polarity.SOUTH, LatLon.SOUTH_POLE);
166 }
167
168 /**
169 * Constructs a new empty {@code CustomProjection}.
170 */
171 public CustomProjection() {
172 // contents can be set later with update()
173 }
174
175 /**
176 * Constructs a new {@code CustomProjection} with given parameters.
177 * @param pref String containing projection parameters
178 * (ex: "+proj=tmerc +lon_0=-3 +k_0=0.9996 +x_0=500000 +ellps=WGS84 +datum=WGS84 +bounds=-8,-5,2,85")
179 */
180 public CustomProjection(String pref) {
181 this(null, null, pref, null);
182 }
183
184 /**
185 * Constructs a new {@code CustomProjection} with given name, code and parameters.
186 *
187 * @param name describe projection in one or two words
188 * @param code unique code for this projection - may be null
189 * @param pref the string that defines the custom projection
190 * @param cacheDir cache directory name
191 */
192 public CustomProjection(String name, String code, String pref, String cacheDir) {
193 this.name = name;
194 this.code = code;
195 this.pref = pref;
196 this.cacheDir = cacheDir;
197 try {
198 update(pref);
199 } catch (ProjectionConfigurationException ex) {
200 try {
201 update(null);
202 } catch (ProjectionConfigurationException ex1) {
203 throw new RuntimeException(ex1);
204 }
205 }
206 }
207
208 /**
209 * Updates this {@code CustomProjection} with given parameters.
210 * @param pref String containing projection parameters (ex: "+proj=lonlat +ellps=WGS84 +datum=WGS84 +bounds=-180,-90,180,90")
211 * @throws ProjectionConfigurationException if {@code pref} cannot be parsed properly
212 */
213 public final void update(String pref) throws ProjectionConfigurationException {
214 this.pref = pref;
215 if (pref == null) {
216 ellps = Ellipsoid.WGS84;
217 datum = WGS84Datum.INSTANCE;
218 proj = new Mercator();
219 bounds = new Bounds(
220 -85.05112877980659, -180.0,
221 85.05112877980659, 180.0, true);
222 } else {
223 Map<String, String> parameters = parseParameterList(pref, false);
224 parameters = resolveInits(parameters, false);
225 ellps = parseEllipsoid(parameters);
226 datum = parseDatum(parameters, ellps);
227 if (ellps == null) {
228 ellps = datum.getEllipsoid();
229 }
230 proj = parseProjection(parameters, ellps);
231 // "utm" is a shortcut for a set of parameters
232 if ("utm".equals(parameters.get(Param.proj.key))) {
233 String zoneStr = parameters.get(Param.zone.key);
234 Integer zone;
235 if (zoneStr == null)
236 throw new ProjectionConfigurationException(tr("UTM projection (''+proj=utm'') requires ''+zone=...'' parameter."));
237 try {
238 zone = Integer.valueOf(zoneStr);
239 } catch (NumberFormatException e) {
240 zone = null;
241 }
242 if (zone == null || zone < 1 || zone > 60)
243 throw new ProjectionConfigurationException(tr("Expected integer value in range 1-60 for ''+zone=...'' parameter."));
244 this.lon0 = 6 * zone - 183;
245 this.k0 = 0.9996;
246 this.x0 = 500000;
247 this.y0 = parameters.containsKey(Param.south.key) ? 10000000 : 0;
248 }
249 String s = parameters.get(Param.x_0.key);
250 if (s != null) {
251 this.x0 = parseDouble(s, Param.x_0.key);
252 }
253 s = parameters.get(Param.y_0.key);
254 if (s != null) {
255 this.y0 = parseDouble(s, Param.y_0.key);
256 }
257 s = parameters.get(Param.lon_0.key);
258 if (s != null) {
259 this.lon0 = parseAngle(s, Param.lon_0.key);
260 }
261 if (proj instanceof ICentralMeridianProvider) {
262 this.lon0 = ((ICentralMeridianProvider) proj).getCentralMeridian();
263 }
264 s = parameters.get(Param.pm.key);
265 if (s != null) {
266 if (PRIME_MERIDANS.containsKey(s)) {
267 this.pm = PRIME_MERIDANS.get(s);
268 } else {
269 this.pm = parseAngle(s, Param.pm.key);
270 }
271 }
272 s = parameters.get(Param.k_0.key);
273 if (s != null) {
274 this.k0 = parseDouble(s, Param.k_0.key);
275 }
276 s = parameters.get(Param.bounds.key);
277 if (s != null) {
278 this.bounds = parseBounds(s);
279 }
280 s = parameters.get(Param.wmssrs.key);
281 if (s != null) {
282 this.code = s;
283 }
284 s = parameters.get(Param.units.key);
285 if (s != null) {
286 s = Utils.strip(s, "\"");
287 if (UNITS_TO_METERS.containsKey(s)) {
288 this.metersPerUnit = UNITS_TO_METERS.get(s);
289 } else {
290 Main.warn("No metersPerUnit found for: " + s);
291 }
292 }
293 s = parameters.get(Param.to_meter.key);
294 if (s != null) {
295 this.metersPerUnit = parseDouble(s, Param.to_meter.key);
296 }
297 s = parameters.get(Param.axis.key);
298 if (s != null) {
299 this.axis = s;
300 }
301 }
302 }
303
304 /**
305 * Parse a parameter list to key=value pairs.
306 *
307 * @param pref the parameter list
308 * @param ignoreUnknownParameter true, if unknown parameter should not raise exception
309 * @return parameters map
310 * @throws ProjectionConfigurationException in case of invalid parameter
311 */
312 public static Map<String, String> parseParameterList(String pref, boolean ignoreUnknownParameter) throws ProjectionConfigurationException {
313 Map<String, String> parameters = new HashMap<>();
314 String[] parts = Utils.WHITE_SPACES_PATTERN.split(pref.trim());
315 if (pref.trim().isEmpty()) {
316 parts = new String[0];
317 }
318 for (String part : parts) {
319 if (part.isEmpty() || part.charAt(0) != '+')
320 throw new ProjectionConfigurationException(tr("Parameter must begin with a ''+'' character (found ''{0}'')", part));
321 Matcher m = Pattern.compile("\\+([a-zA-Z0-9_]+)(=(.*))?").matcher(part);
322 if (m.matches()) {
323 String key = m.group(1);
324 // alias
325 if ("k".equals(key)) {
326 key = Param.k_0.key;
327 }
328 String value = null;
329 if (m.groupCount() >= 3) {
330 value = m.group(3);
331 // some aliases
332 if (key.equals(Param.proj.key)) {
333 if ("longlat".equals(value) || "latlon".equals(value) || "latlong".equals(value)) {
334 value = "lonlat";
335 }
336 }
337 }
338 if (!Param.paramsByKey.containsKey(key)) {
339 if (!ignoreUnknownParameter)
340 throw new ProjectionConfigurationException(tr("Unknown parameter: ''{0}''.", key));
341 } else {
342 if (Param.paramsByKey.get(key).hasValue && value == null)
343 throw new ProjectionConfigurationException(tr("Value expected for parameter ''{0}''.", key));
344 if (!Param.paramsByKey.get(key).hasValue && value != null)
345 throw new ProjectionConfigurationException(tr("No value expected for parameter ''{0}''.", key));
346 }
347 parameters.put(key, value);
348 } else
349 throw new ProjectionConfigurationException(tr("Unexpected parameter format (''{0}'')", part));
350 }
351 return parameters;
352 }
353
354 /**
355 * Recursive resolution of +init includes.
356 *
357 * @param parameters parameters map
358 * @param ignoreUnknownParameter true, if unknown parameter should not raise exception
359 * @return parameters map with +init includes resolved
360 * @throws ProjectionConfigurationException in case of invalid parameter
361 */
362 public static Map<String, String> resolveInits(Map<String, String> parameters, boolean ignoreUnknownParameter)
363 throws ProjectionConfigurationException {
364 // recursive resolution of +init includes
365 String initKey = parameters.get(Param.init.key);
366 if (initKey != null) {
367 String init = Projections.getInit(initKey);
368 if (init == null)
369 throw new ProjectionConfigurationException(tr("Value ''{0}'' for option +init not supported.", initKey));
370 Map<String, String> initp;
371 try {
372 initp = parseParameterList(init, ignoreUnknownParameter);
373 initp = resolveInits(initp, ignoreUnknownParameter);
374 } catch (ProjectionConfigurationException ex) {
375 throw new ProjectionConfigurationException(initKey+": "+ex.getMessage(), ex);
376 }
377 initp.putAll(parameters);
378 return initp;
379 }
380 return parameters;
381 }
382
383 public Ellipsoid parseEllipsoid(Map<String, String> parameters) throws ProjectionConfigurationException {
384 String code = parameters.get(Param.ellps.key);
385 if (code != null) {
386 Ellipsoid ellipsoid = Projections.getEllipsoid(code);
387 if (ellipsoid == null) {
388 throw new ProjectionConfigurationException(tr("Ellipsoid ''{0}'' not supported.", code));
389 } else {
390 return ellipsoid;
391 }
392 }
393 String s = parameters.get(Param.a.key);
394 if (s != null) {
395 double a = parseDouble(s, Param.a.key);
396 if (parameters.get(Param.es.key) != null) {
397 double es = parseDouble(parameters, Param.es.key);
398 return Ellipsoid.create_a_es(a, es);
399 }
400 if (parameters.get(Param.rf.key) != null) {
401 double rf = parseDouble(parameters, Param.rf.key);
402 return Ellipsoid.create_a_rf(a, rf);
403 }
404 if (parameters.get(Param.f.key) != null) {
405 double f = parseDouble(parameters, Param.f.key);
406 return Ellipsoid.create_a_f(a, f);
407 }
408 if (parameters.get(Param.b.key) != null) {
409 double b = parseDouble(parameters, Param.b.key);
410 return Ellipsoid.create_a_b(a, b);
411 }
412 }
413 if (parameters.containsKey(Param.a.key) ||
414 parameters.containsKey(Param.es.key) ||
415 parameters.containsKey(Param.rf.key) ||
416 parameters.containsKey(Param.f.key) ||
417 parameters.containsKey(Param.b.key))
418 throw new ProjectionConfigurationException(tr("Combination of ellipsoid parameters is not supported."));
419 return null;
420 }
421
422 public Datum parseDatum(Map<String, String> parameters, Ellipsoid ellps) throws ProjectionConfigurationException {
423 String datumId = parameters.get(Param.datum.key);
424 if (datumId != null) {
425 Datum datum = Projections.getDatum(datumId);
426 if (datum == null) throw new ProjectionConfigurationException(tr("Unknown datum identifier: ''{0}''", datumId));
427 return datum;
428 }
429 if (ellps == null) {
430 if (parameters.containsKey(Param.no_defs.key))
431 throw new ProjectionConfigurationException(tr("Ellipsoid required (+ellps=* or +a=*, +b=*)"));
432 // nothing specified, use WGS84 as default
433 ellps = Ellipsoid.WGS84;
434 }
435
436 String nadgridsId = parameters.get(Param.nadgrids.key);
437 if (nadgridsId != null) {
438 if (nadgridsId.startsWith("@")) {
439 nadgridsId = nadgridsId.substring(1);
440 }
441 if ("null".equals(nadgridsId))
442 return new NullDatum(null, ellps);
443 NTV2GridShiftFileWrapper nadgrids = Projections.getNTV2Grid(nadgridsId);
444 if (nadgrids == null)
445 throw new ProjectionConfigurationException(tr("Grid shift file ''{0}'' for option +nadgrids not supported.", nadgridsId));
446 return new NTV2Datum(nadgridsId, null, ellps, nadgrids);
447 }
448
449 String towgs84 = parameters.get(Param.towgs84.key);
450 if (towgs84 != null)
451 return parseToWGS84(towgs84, ellps);
452
453 return new NullDatum(null, ellps);
454 }
455
456 public Datum parseToWGS84(String paramList, Ellipsoid ellps) throws ProjectionConfigurationException {
457 String[] numStr = paramList.split(",");
458
459 if (numStr.length != 3 && numStr.length != 7)
460 throw new ProjectionConfigurationException(tr("Unexpected number of arguments for parameter ''towgs84'' (must be 3 or 7)"));
461 List<Double> towgs84Param = new ArrayList<>();
462 for (String str : numStr) {
463 try {
464 towgs84Param.add(Double.valueOf(str));
465 } catch (NumberFormatException e) {
466 throw new ProjectionConfigurationException(tr("Unable to parse value of parameter ''towgs84'' (''{0}'')", str), e);
467 }
468 }
469 boolean isCentric = true;
470 for (Double param : towgs84Param) {
471 if (param != 0) {
472 isCentric = false;
473 break;
474 }
475 }
476 if (isCentric)
477 return new CentricDatum(null, null, ellps);
478 boolean is3Param = true;
479 for (int i = 3; i < towgs84Param.size(); i++) {
480 if (towgs84Param.get(i) != 0) {
481 is3Param = false;
482 break;
483 }
484 }
485 if (is3Param)
486 return new ThreeParameterDatum(null, null, ellps,
487 towgs84Param.get(0),
488 towgs84Param.get(1),
489 towgs84Param.get(2));
490 else
491 return new SevenParameterDatum(null, null, ellps,
492 towgs84Param.get(0),
493 towgs84Param.get(1),
494 towgs84Param.get(2),
495 towgs84Param.get(3),
496 towgs84Param.get(4),
497 towgs84Param.get(5),
498 towgs84Param.get(6));
499 }
500
501 public Proj parseProjection(Map<String, String> parameters, Ellipsoid ellps) throws ProjectionConfigurationException {
502 String id = parameters.get(Param.proj.key);
503 if (id == null) throw new ProjectionConfigurationException(tr("Projection required (+proj=*)"));
504
505 // "utm" is not a real projection, but a shortcut for a set of parameters
506 if ("utm".equals(id)) {
507 id = "tmerc";
508 }
509 Proj proj = Projections.getBaseProjection(id);
510 if (proj == null) throw new ProjectionConfigurationException(tr("Unknown projection identifier: ''{0}''", id));
511
512 ProjParameters projParams = new ProjParameters();
513
514 projParams.ellps = ellps;
515
516 String s;
517 s = parameters.get(Param.lat_0.key);
518 if (s != null) {
519 projParams.lat0 = parseAngle(s, Param.lat_0.key);
520 }
521 s = parameters.get(Param.lat_1.key);
522 if (s != null) {
523 projParams.lat1 = parseAngle(s, Param.lat_1.key);
524 }
525 s = parameters.get(Param.lat_2.key);
526 if (s != null) {
527 projParams.lat2 = parseAngle(s, Param.lat_2.key);
528 }
529 s = parameters.get(Param.lat_ts.key);
530 if (s != null) {
531 projParams.lat_ts = parseAngle(s, Param.lat_ts.key);
532 }
533 s = parameters.get(Param.lonc.key);
534 if (s != null) {
535 projParams.lonc = parseAngle(s, Param.lonc.key);
536 }
537 s = parameters.get(Param.alpha.key);
538 if (s != null) {
539 projParams.alpha = parseAngle(s, Param.alpha.key);
540 }
541 s = parameters.get(Param.gamma.key);
542 if (s != null) {
543 projParams.gamma = parseAngle(s, Param.gamma.key);
544 }
545 s = parameters.get(Param.lon_1.key);
546 if (s != null) {
547 projParams.lon1 = parseAngle(s, Param.lon_1.key);
548 }
549 s = parameters.get(Param.lon_2.key);
550 if (s != null) {
551 projParams.lon2 = parseAngle(s, Param.lon_2.key);
552 }
553 if (parameters.containsKey(Param.no_off.key) || parameters.containsKey(Param.no_uoff.key)) {
554 projParams.no_off = true;
555 }
556 proj.initialize(projParams);
557 return proj;
558 }
559
560 public static Bounds parseBounds(String boundsStr) throws ProjectionConfigurationException {
561 String[] numStr = boundsStr.split(",");
562 if (numStr.length != 4)
563 throw new ProjectionConfigurationException(tr("Unexpected number of arguments for parameter ''+bounds'' (must be 4)"));
564 return new Bounds(parseAngle(numStr[1], "minlat (+bounds)"),
565 parseAngle(numStr[0], "minlon (+bounds)"),
566 parseAngle(numStr[3], "maxlat (+bounds)"),
567 parseAngle(numStr[2], "maxlon (+bounds)"), false);
568 }
569
570 public static double parseDouble(Map<String, String> parameters, String parameterName) throws ProjectionConfigurationException {
571 if (!parameters.containsKey(parameterName))
572 throw new ProjectionConfigurationException(tr("Unknown parameter ''{0}''", parameterName));
573 String doubleStr = parameters.get(parameterName);
574 if (doubleStr == null)
575 throw new ProjectionConfigurationException(
576 tr("Expected number argument for parameter ''{0}''", parameterName));
577 return parseDouble(doubleStr, parameterName);
578 }
579
580 public static double parseDouble(String doubleStr, String parameterName) throws ProjectionConfigurationException {
581 try {
582 return Double.parseDouble(doubleStr);
583 } catch (NumberFormatException e) {
584 throw new ProjectionConfigurationException(
585 tr("Unable to parse value ''{1}'' of parameter ''{0}'' as number.", parameterName, doubleStr), e);
586 }
587 }
588
589 public static double parseAngle(String angleStr, String parameterName) throws ProjectionConfigurationException {
590 String s = angleStr;
591 double value = 0;
592 boolean neg = false;
593 Matcher m = Pattern.compile("^-").matcher(s);
594 if (m.find()) {
595 neg = true;
596 s = s.substring(m.end());
597 }
598 final String FLOAT = "(\\d+(\\.\\d*)?)";
599 boolean dms = false;
600 double deg = 0.0, min = 0.0, sec = 0.0;
601 // degrees
602 m = Pattern.compile("^"+FLOAT+"d").matcher(s);
603 if (m.find()) {
604 s = s.substring(m.end());
605 deg = Double.parseDouble(m.group(1));
606 dms = true;
607 }
608 // minutes
609 m = Pattern.compile("^"+FLOAT+"'").matcher(s);
610 if (m.find()) {
611 s = s.substring(m.end());
612 min = Double.parseDouble(m.group(1));
613 dms = true;
614 }
615 // seconds
616 m = Pattern.compile("^"+FLOAT+"\"").matcher(s);
617 if (m.find()) {
618 s = s.substring(m.end());
619 sec = Double.parseDouble(m.group(1));
620 dms = true;
621 }
622 // plain number (in degrees)
623 if (dms) {
624 value = deg + (min/60.0) + (sec/3600.0);
625 } else {
626 m = Pattern.compile("^"+FLOAT).matcher(s);
627 if (m.find()) {
628 s = s.substring(m.end());
629 value += Double.parseDouble(m.group(1));
630 }
631 }
632 m = Pattern.compile("^(N|E)", Pattern.CASE_INSENSITIVE).matcher(s);
633 if (m.find()) {
634 s = s.substring(m.end());
635 } else {
636 m = Pattern.compile("^(S|W)", Pattern.CASE_INSENSITIVE).matcher(s);
637 if (m.find()) {
638 s = s.substring(m.end());
639 neg = !neg;
640 }
641 }
642 if (neg) {
643 value = -value;
644 }
645 if (!s.isEmpty()) {
646 throw new ProjectionConfigurationException(
647 tr("Unable to parse value ''{1}'' of parameter ''{0}'' as coordinate value.", parameterName, angleStr));
648 }
649 return value;
650 }
651
652 @Override
653 public Integer getEpsgCode() {
654 if (code != null && code.startsWith("EPSG:")) {
655 try {
656 return Integer.valueOf(code.substring(5));
657 } catch (NumberFormatException e) {
658 Main.warn(e);
659 }
660 }
661 return null;
662 }
663
664 @Override
665 public String toCode() {
666 return code != null ? code : "proj:" + (pref == null ? "ERROR" : pref);
667 }
668
669 @Override
670 public String getCacheDirectoryName() {
671 return cacheDir != null ? cacheDir : "proj-"+Utils.md5Hex(pref == null ? "" : pref).substring(0, 4);
672 }
673
674 @Override
675 public Bounds getWorldBoundsLatLon() {
676 if (bounds != null) return bounds;
677 Bounds ab = proj.getAlgorithmBounds();
678 if (ab != null) {
679 double minlon = Math.max(ab.getMinLon() + lon0 + pm, -180);
680 double maxlon = Math.min(ab.getMaxLon() + lon0 + pm, 180);
681 return new Bounds(ab.getMinLat(), minlon, ab.getMaxLat(), maxlon, false);
682 } else {
683 return new Bounds(
684 new LatLon(-90.0, -180.0),
685 new LatLon(90.0, 180.0));
686 }
687 }
688
689 @Override
690 public String toString() {
691 return name != null ? name : tr("Custom Projection");
692 }
693
694 @Override
695 public double getMetersPerUnit() {
696 return metersPerUnit;
697 }
698
699 @Override
700 public boolean switchXY() {
701 // TODO: support for other axis orientation such as West South, and Up Down
702 return this.axis.startsWith("ne");
703 }
704
705 private static Map<String, Double> getUnitsToMeters() {
706 Map<String, Double> ret = new ConcurrentHashMap<>();
707 ret.put("km", 1000d);
708 ret.put("m", 1d);
709 ret.put("dm", 1d/10);
710 ret.put("cm", 1d/100);
711 ret.put("mm", 1d/1000);
712 ret.put("kmi", 1852.0);
713 ret.put("in", 0.0254);
714 ret.put("ft", 0.3048);
715 ret.put("yd", 0.9144);
716 ret.put("mi", 1609.344);
717 ret.put("fathom", 1.8288);
718 ret.put("chain", 20.1168);
719 ret.put("link", 0.201168);
720 ret.put("us-in", 1d/39.37);
721 ret.put("us-ft", 0.304800609601219);
722 ret.put("us-yd", 0.914401828803658);
723 ret.put("us-ch", 20.11684023368047);
724 ret.put("us-mi", 1609.347218694437);
725 ret.put("ind-yd", 0.91439523);
726 ret.put("ind-ft", 0.30479841);
727 ret.put("ind-ch", 20.11669506);
728 ret.put("degree", METER_PER_UNIT_DEGREE);
729 return ret;
730 }
731
732 private static Map<String, Double> getPrimeMeridians() {
733 Map<String, Double> ret = new ConcurrentHashMap<>();
734 try {
735 ret.put("greenwich", 0.0);
736 ret.put("lisbon", parseAngle("9d07'54.862\"W", null));
737 ret.put("paris", parseAngle("2d20'14.025\"E", null));
738 ret.put("bogota", parseAngle("74d04'51.3\"W", null));
739 ret.put("madrid", parseAngle("3d41'16.58\"W", null));
740 ret.put("rome", parseAngle("12d27'8.4\"E", null));
741 ret.put("bern", parseAngle("7d26'22.5\"E", null));
742 ret.put("jakarta", parseAngle("106d48'27.79\"E", null));
743 ret.put("ferro", parseAngle("17d40'W", null));
744 ret.put("brussels", parseAngle("4d22'4.71\"E", null));
745 ret.put("stockholm", parseAngle("18d3'29.8\"E", null));
746 ret.put("athens", parseAngle("23d42'58.815\"E", null));
747 ret.put("oslo", parseAngle("10d43'22.5\"E", null));
748 } catch (ProjectionConfigurationException ex) {
749 throw new RuntimeException();
750 }
751 return ret;
752 }
753
754 private EastNorth getPointAlong(int i, int N, ProjectionBounds r) {
755 double dEast = (r.maxEast - r.minEast) / N;
756 double dNorth = (r.maxNorth - r.minNorth) / N;
757 if (i < N) {
758 return new EastNorth(r.minEast + i * dEast, r.minNorth);
759 } else if (i < 2*N) {
760 i -= N;
761 return new EastNorth(r.maxEast, r.minNorth + i * dNorth);
762 } else if (i < 3*N) {
763 i -= 2*N;
764 return new EastNorth(r.maxEast - i * dEast, r.maxNorth);
765 } else if (i < 4*N) {
766 i -= 3*N;
767 return new EastNorth(r.minEast, r.maxNorth - i * dNorth);
768 } else {
769 throw new AssertionError();
770 }
771 }
772
773 private EastNorth getPole(Polarity whichPole) {
774 if (polesEN == null) {
775 polesEN = new EnumMap<>(Polarity.class);
776 for (Polarity p : Polarity.values()) {
777 polesEN.put(p, null);
778 LatLon ll = polesLL.get(p);
779 try {
780 EastNorth enPole = latlon2eastNorth(ll);
781 if (enPole.isValid()) {
782 // project back and check if the result is somewhat reasonable
783 LatLon llBack = eastNorth2latlon(enPole);
784 if (llBack.isValid() && ll.greatCircleDistance(llBack) < 1000) {
785 polesEN.put(p, enPole);
786 }
787 }
788 } catch (Exception e) {
789 Main.error(e);
790 }
791 }
792 }
793 return polesEN.get(whichPole);
794 }
795
796 @Override
797 public Bounds getLatLonBoundsBox(ProjectionBounds r) {
798 final int N = 10;
799 Bounds result = new Bounds(eastNorth2latlon(r.getMin()));
800 result.extend(eastNorth2latlon(r.getMax()));
801 LatLon llPrev = null;
802 for (int i = 0; i < 4*N; i++) {
803 LatLon llNow = eastNorth2latlon(getPointAlong(i, N, r));
804 result.extend(llNow);
805 // check if segment crosses 180th meridian and if so, make sure
806 // to extend bounds to +/-180 degrees longitude
807 if (llPrev != null) {
808 double lon1 = llPrev.lon();
809 double lon2 = llNow.lon();
810 if (90 < lon1 && lon1 < 180 && -180 < lon2 && lon2 < -90) {
811 result.extend(new LatLon(llPrev.lat(), 180));
812 result.extend(new LatLon(llNow.lat(), -180));
813 }
814 if (90 < lon2 && lon2 < 180 && -180 < lon1 && lon1 < -90) {
815 result.extend(new LatLon(llNow.lat(), 180));
816 result.extend(new LatLon(llPrev.lat(), -180));
817 }
818 }
819 llPrev = llNow;
820 }
821 // if the box contains one of the poles, the above method did not get
822 // correct min/max latitude value
823 for (Polarity p : Polarity.values()) {
824 EastNorth pole = getPole(p);
825 if (pole != null && r.contains(pole)) {
826 result.extend(polesLL.get(p));
827 }
828 }
829 return result;
830 }
831}
Note: See TracBrowser for help on using the repository browser.