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

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

sonar - squid:S2184 - Math operands should be cast before assignment

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