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

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

sonar - squid:S00100 - Method names should comply with a naming convention

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