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

Last change on this file since 9545 was 9532, checked in by bastiK, 8 years ago

see #12186 - add Oblique Mercator projection
(imports pieces of code from the Geotools project)

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