source: josm/trunk/src/org/openstreetmap/josm/data/coor/LatLon.java@ 12669

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

see #15182 - remove dependence on JMapViewer for package data.coor (only useful for imagery)

  • Property svn:eol-style set to native
File size: 26.2 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.data.coor;
3
4import static java.lang.Math.PI;
5import static java.lang.Math.asin;
6import static java.lang.Math.atan2;
7import static java.lang.Math.cos;
8import static java.lang.Math.sin;
9import static java.lang.Math.sqrt;
10import static org.openstreetmap.josm.data.projection.Ellipsoid.WGS84;
11import static org.openstreetmap.josm.tools.I18n.trc;
12import static org.openstreetmap.josm.tools.Utils.toRadians;
13
14import java.awt.geom.Area;
15import java.text.DecimalFormat;
16import java.text.NumberFormat;
17import java.util.ArrayList;
18import java.util.Arrays;
19import java.util.List;
20import java.util.Locale;
21import java.util.Objects;
22import java.util.regex.Matcher;
23import java.util.regex.Pattern;
24
25import org.openstreetmap.josm.Main;
26import org.openstreetmap.josm.data.Bounds;
27import org.openstreetmap.josm.tools.Logging;
28import org.openstreetmap.josm.tools.Utils;
29
30/**
31 * LatLon are unprojected latitude / longitude coordinates.
32 * <br>
33 * <b>Latitude</b> specifies the north-south position in degrees
34 * where valid values are in the [-90,90] and positive values specify positions north of the equator.
35 * <br>
36 * <b>Longitude</b> specifies the east-west position in degrees
37 * where valid values are in the [-180,180] and positive values specify positions east of the prime meridian.
38 * <br>
39 * <img alt="lat/lon" src="https://upload.wikimedia.org/wikipedia/commons/6/62/Latitude_and_Longitude_of_the_Earth.svg">
40 * <br>
41 * This class is immutable.
42 *
43 * @author Imi
44 */
45public class LatLon extends Coordinate implements ILatLon {
46
47 private static final long serialVersionUID = 1L;
48
49 /**
50 * Minimum difference in location to not be represented as the same position.
51 * The API returns 7 decimals.
52 */
53 public static final double MAX_SERVER_PRECISION = 1e-7;
54 /**
55 * The inverse of the server precision
56 * @see #MAX_SERVER_PRECISION
57 */
58 public static final double MAX_SERVER_INV_PRECISION = 1e7;
59
60 /**
61 * The (0,0) coordinates.
62 * @since 6178
63 */
64 public static final LatLon ZERO = new LatLon(0, 0);
65
66 /** North pole. */
67 public static final LatLon NORTH_POLE = new LatLon(90, 0);
68 /** South pole. */
69 public static final LatLon SOUTH_POLE = new LatLon(-90, 0);
70
71 private static DecimalFormat cDmsMinuteFormatter = new DecimalFormat("00");
72 private static DecimalFormat cDmsSecondFormatter = new DecimalFormat(
73 Main.pref == null ? "00.0" : Main.pref.get("latlon.dms.decimal-format", "00.0"));
74 private static DecimalFormat cDmMinuteFormatter = new DecimalFormat(
75 Main.pref == null ? "00.000" : Main.pref.get("latlon.dm.decimal-format", "00.000"));
76 /**
77 * The normal number format for server precision coordinates
78 */
79 public static final DecimalFormat cDdFormatter;
80 /**
81 * The number format used for high precision coordinates
82 */
83 public static final DecimalFormat cDdHighPecisionFormatter;
84 static {
85 // Don't use the localized decimal separator. This way we can present
86 // a comma separated list of coordinates.
87 cDdFormatter = (DecimalFormat) NumberFormat.getInstance(Locale.UK);
88 cDdFormatter.applyPattern("###0.0######");
89 cDdHighPecisionFormatter = (DecimalFormat) NumberFormat.getInstance(Locale.UK);
90 cDdHighPecisionFormatter.applyPattern("###0.0##########");
91 }
92
93 private static final String cDms60 = cDmsSecondFormatter.format(60.0);
94 private static final String cDms00 = cDmsSecondFormatter.format(0.0);
95 private static final String cDm60 = cDmMinuteFormatter.format(60.0);
96 private static final String cDm00 = cDmMinuteFormatter.format(0.0);
97
98 /** Character denoting South, as string */
99 public static final String SOUTH = trc("compass", "S");
100 /** Character denoting North, as string */
101 public static final String NORTH = trc("compass", "N");
102 /** Character denoting West, as string */
103 public static final String WEST = trc("compass", "W");
104 /** Character denoting East, as string */
105 public static final String EAST = trc("compass", "E");
106
107 private static final char N_TR = NORTH.charAt(0);
108 private static final char S_TR = SOUTH.charAt(0);
109 private static final char E_TR = EAST.charAt(0);
110 private static final char W_TR = WEST.charAt(0);
111
112 private static final String DEG = "\u00B0";
113 private static final String MIN = "\u2032";
114 private static final String SEC = "\u2033";
115
116 private static final Pattern P = Pattern.compile(
117 "([+|-]?\\d+[.,]\\d+)|" // (1)
118 + "([+|-]?\\d+)|" // (2)
119 + "("+DEG+"|o|deg)|" // (3)
120 + "('|"+MIN+"|min)|" // (4)
121 + "(\"|"+SEC+"|sec)|" // (5)
122 + "(,|;)|" // (6)
123 + "([NSEW"+N_TR+S_TR+E_TR+W_TR+"])|"// (7)
124 + "\\s+|"
125 + "(.+)", Pattern.CASE_INSENSITIVE);
126
127 private static final Pattern P_XML = Pattern.compile(
128 "lat=[\"']([+|-]?\\d+[.,]\\d+)[\"']\\s+lon=[\"']([+|-]?\\d+[.,]\\d+)[\"']");
129
130 /**
131 * Replies true if lat is in the range [-90,90]
132 *
133 * @param lat the latitude
134 * @return true if lat is in the range [-90,90]
135 */
136 public static boolean isValidLat(double lat) {
137 return lat >= -90d && lat <= 90d;
138 }
139
140 /**
141 * Replies true if lon is in the range [-180,180]
142 *
143 * @param lon the longitude
144 * @return true if lon is in the range [-180,180]
145 */
146 public static boolean isValidLon(double lon) {
147 return lon >= -180d && lon <= 180d;
148 }
149
150 /**
151 * Make sure longitude value is within <code>[-180, 180]</code> range.
152 * @param lon the longitude in degrees
153 * @return lon plus/minus multiples of <code>360</code>, as needed to get
154 * in <code>[-180, 180]</code> range
155 */
156 public static double normalizeLon(double lon) {
157 if (lon >= -180 && lon <= 180)
158 return lon;
159 else {
160 lon = lon % 360.0;
161 if (lon > 180) {
162 return lon - 360;
163 } else if (lon < -180) {
164 return lon + 360;
165 }
166 return lon;
167 }
168 }
169
170 /**
171 * Replies true if lat is in the range [-90,90] and lon is in the range [-180,180]
172 *
173 * @return true if lat is in the range [-90,90] and lon is in the range [-180,180]
174 */
175 public boolean isValid() {
176 return isValidLat(lat()) && isValidLon(lon());
177 }
178
179 /**
180 * Clamp the lat value to be inside the world.
181 * @param value The value
182 * @return The value clamped to the world.
183 */
184 public static double toIntervalLat(double value) {
185 return Utils.clamp(value, -90, 90);
186 }
187
188 /**
189 * Returns a valid OSM longitude [-180,+180] for the given extended longitude value.
190 * For example, a value of -181 will return +179, a value of +181 will return -179.
191 * @param value A longitude value not restricted to the [-180,+180] range.
192 * @return a valid OSM longitude [-180,+180]
193 */
194 public static double toIntervalLon(double value) {
195 if (isValidLon(value))
196 return value;
197 else {
198 int n = (int) (value + Math.signum(value)*180.0) / 360;
199 return value - n*360.0;
200 }
201 }
202
203 /**
204 * Replies the coordinate in degrees/minutes/seconds format
205 * @param pCoordinate The coordinate to convert
206 * @return The coordinate in degrees/minutes/seconds format
207 * @deprecated use {@link #degreesMinutesSeconds} instead
208 */
209 @Deprecated
210 public static String dms(double pCoordinate) {
211 return degreesMinutesSeconds(pCoordinate);
212 }
213
214 /**
215 * Replies the coordinate in degrees/minutes/seconds format
216 * @param pCoordinate The coordinate to convert
217 * @return The coordinate in degrees/minutes/seconds format
218 * @since 12561
219 */
220 public static String degreesMinutesSeconds(double pCoordinate) {
221
222 double tAbsCoord = Math.abs(pCoordinate);
223 int tDegree = (int) tAbsCoord;
224 double tTmpMinutes = (tAbsCoord - tDegree) * 60;
225 int tMinutes = (int) tTmpMinutes;
226 double tSeconds = (tTmpMinutes - tMinutes) * 60;
227
228 String sDegrees = Integer.toString(tDegree);
229 String sMinutes = cDmsMinuteFormatter.format(tMinutes);
230 String sSeconds = cDmsSecondFormatter.format(tSeconds);
231
232 if (cDms60.equals(sSeconds)) {
233 sSeconds = cDms00;
234 sMinutes = cDmsMinuteFormatter.format(tMinutes+1L);
235 }
236 if ("60".equals(sMinutes)) {
237 sMinutes = "00";
238 sDegrees = Integer.toString(tDegree+1);
239 }
240
241 return sDegrees + '\u00B0' + sMinutes + '\'' + sSeconds + '\"';
242 }
243
244 /**
245 * Replies the coordinate in degrees/minutes format
246 * @param pCoordinate The coordinate to convert
247 * @return The coordinate in degrees/minutes format
248 * @since 12537
249 */
250 public static String degreesMinutes(double pCoordinate) {
251
252 double tAbsCoord = Math.abs(pCoordinate);
253 int tDegree = (int) tAbsCoord;
254 double tMinutes = (tAbsCoord - tDegree) * 60;
255
256 String sDegrees = Integer.toString(tDegree);
257 String sMinutes = cDmMinuteFormatter.format(tMinutes);
258
259 if (sMinutes.equals(cDm60)) {
260 sMinutes = cDm00;
261 sDegrees = Integer.toString(tDegree+1);
262 }
263
264 return sDegrees + '\u00B0' + sMinutes + '\'';
265 }
266
267 /**
268 * Replies the coordinate in degrees/minutes format
269 * @param pCoordinate The coordinate to convert
270 * @return The coordinate in degrees/minutes format
271 * @deprecated use {@link #degreesMinutes(double)} instead
272 */
273 @Deprecated
274 public static String dm(double pCoordinate) {
275 return degreesMinutes(pCoordinate);
276 }
277
278 /**
279 * Constructs a new object representing the given latitude/longitude.
280 * @param lat the latitude, i.e., the north-south position in degrees
281 * @param lon the longitude, i.e., the east-west position in degrees
282 */
283 public LatLon(double lat, double lon) {
284 super(lon, lat);
285 }
286
287 /**
288 * Creates a new LatLon object for the given coordinate
289 * @param coor The coordinates to copy from.
290 */
291 public LatLon(ILatLon coor) {
292 super(coor.lon(), coor.lat());
293 }
294
295 @Override
296 public double lat() {
297 return y;
298 }
299
300 /**
301 * Formats the latitude part according to the given format
302 * @param d the coordinate format to use
303 * @return the formatted latitude
304 */
305 public String latToString(CoordinateFormat d) {
306 switch(d) {
307 case DECIMAL_DEGREES: return cDdFormatter.format(y);
308 case DEGREES_MINUTES_SECONDS: return degreesMinutesSeconds(y) + ((y < 0) ? SOUTH : NORTH);
309 case NAUTICAL: return degreesMinutes(y) + ((y < 0) ? SOUTH : NORTH);
310 case EAST_NORTH: return cDdFormatter.format(this.getEastNorth().north());
311 default: return "ERR";
312 }
313 }
314
315 @Override
316 public double lon() {
317 return x;
318 }
319
320 /**
321 * Formats the longitude part according to the given format
322 * @param d the coordinate format to use
323 * @return the formatted longitude
324 */
325 public String lonToString(CoordinateFormat d) {
326 switch(d) {
327 case DECIMAL_DEGREES: return cDdFormatter.format(x);
328 case DEGREES_MINUTES_SECONDS: return degreesMinutesSeconds(x) + ((x < 0) ? WEST : EAST);
329 case NAUTICAL: return degreesMinutes(x) + ((x < 0) ? WEST : EAST);
330 case EAST_NORTH: return cDdFormatter.format(this.getEastNorth().east());
331 default: return "ERR";
332 }
333 }
334
335 /**
336 * @param other other lat/lon
337 * @return <code>true</code> if the other point has almost the same lat/lon
338 * values, only differing by no more than 1 / {@link #MAX_SERVER_PRECISION MAX_SERVER_PRECISION}.
339 */
340 public boolean equalsEpsilon(LatLon other) {
341 double p = MAX_SERVER_PRECISION / 2;
342 return Math.abs(lat()-other.lat()) <= p && Math.abs(lon()-other.lon()) <= p;
343 }
344
345 /**
346 * Determines if this lat/lon is outside of the world
347 * @return <code>true</code>, if the coordinate is outside the world, compared by using lat/lon.
348 */
349 public boolean isOutSideWorld() {
350 Bounds b = Main.getProjection().getWorldBoundsLatLon();
351 return lat() < b.getMinLat() || lat() > b.getMaxLat() ||
352 lon() < b.getMinLon() || lon() > b.getMaxLon();
353 }
354
355 /**
356 * Determines if this lat/lon is within the given bounding box.
357 * @param b bounding box
358 * @return <code>true</code> if this is within the given bounding box.
359 */
360 public boolean isWithin(Bounds b) {
361 return b.contains(this);
362 }
363
364 /**
365 * Check if this is contained in given area or area is null.
366 *
367 * @param a Area
368 * @return <code>true</code> if this is contained in given area or area is null.
369 */
370 public boolean isIn(Area a) {
371 return a == null || a.contains(x, y);
372 }
373
374 /**
375 * Computes the distance between this lat/lon and another point on the earth.
376 * Uses Haversine formular.
377 * @param other the other point.
378 * @return distance in metres.
379 */
380 public double greatCircleDistance(LatLon other) {
381 double sinHalfLat = sin(toRadians(other.lat() - this.lat()) / 2);
382 double sinHalfLon = sin(toRadians(other.lon() - this.lon()) / 2);
383 double d = 2 * WGS84.a * asin(
384 sqrt(sinHalfLat*sinHalfLat +
385 cos(toRadians(this.lat()))*cos(toRadians(other.lat()))*sinHalfLon*sinHalfLon));
386 // For points opposite to each other on the sphere,
387 // rounding errors could make the argument of asin greater than 1
388 // (This should almost never happen.)
389 if (java.lang.Double.isNaN(d)) {
390 Logging.error("NaN in greatCircleDistance");
391 d = PI * WGS84.a;
392 }
393 return d;
394 }
395
396 /**
397 * Returns the heading that you have to use to get from this lat/lon to another.
398 *
399 * Angle starts from north and increases counterclockwise (!), PI/2 means west.
400 * You can get usual clockwise angle from {@link #bearing(LatLon)} method.
401 * This method is kept as deprecated because it is called from many plugins.
402 *
403 * (I don't know the original source of this formula, but see
404 * <a href="https://math.stackexchange.com/questions/720/how-to-calculate-a-heading-on-the-earths-surface">this question</a>
405 * for some hints how it is derived.)
406 *
407 * @deprecated see bearing method
408 * @param other the "destination" position
409 * @return heading in radians in the range 0 &lt;= hd &lt; 2*PI
410 */
411 @Deprecated
412 public double heading(LatLon other) {
413 double hd = atan2(sin(toRadians(this.lon() - other.lon())) * cos(toRadians(other.lat())),
414 cos(toRadians(this.lat())) * sin(toRadians(other.lat())) -
415 sin(toRadians(this.lat())) * cos(toRadians(other.lat())) * cos(toRadians(this.lon() - other.lon())));
416 hd %= 2 * PI;
417 if (hd < 0) {
418 hd += 2 * PI;
419 }
420 return hd;
421 }
422
423 /**
424 * Returns bearing from this point to another.
425 *
426 * Angle starts from north and increases clockwise, PI/2 means east.
427 * Old deprecated method {@link #heading(LatLon)} used unusual reverse angle.
428 *
429 * Please note that reverse bearing (from other point to this point) should NOT be
430 * calculated from return value of this method, because great circle path
431 * between the two points have different bearings at each position.
432 *
433 * To get bearing from another point to this point call other.bearing(this)
434 *
435 * @param other the "destination" position
436 * @return heading in radians in the range 0 &lt;= hd &lt; 2*PI
437 */
438 public double bearing(LatLon other) {
439 double lat1 = toRadians(this.lat());
440 double lat2 = toRadians(other.lat());
441 double dlon = toRadians(other.lon() - this.lon());
442 double bearing = atan2(
443 sin(dlon) * cos(lat2),
444 cos(lat1) * sin(lat2) - sin(lat1) * cos(lat2) * cos(dlon)
445 );
446 bearing %= 2 * PI;
447 if (bearing < 0) {
448 bearing += 2 * PI;
449 }
450 return bearing;
451 }
452
453 /**
454 * Returns this lat/lon pair in human-readable format.
455 *
456 * @return String in the format "lat=1.23456 deg, lon=2.34567 deg"
457 */
458 public String toDisplayString() {
459 NumberFormat nf = NumberFormat.getInstance();
460 nf.setMaximumFractionDigits(5);
461 return "lat=" + nf.format(lat()) + "\u00B0, lon=" + nf.format(lon()) + '\u00B0';
462 }
463
464 /**
465 * Returns this lat/lon pair in human-readable format separated by {@code separator}.
466 * @param separator values separator
467 * @return String in the format {@code "1.23456[separator]2.34567"}
468 */
469 public String toStringCSV(String separator) {
470 return Utils.join(separator, Arrays.asList(
471 latToString(CoordinateFormat.DECIMAL_DEGREES),
472 lonToString(CoordinateFormat.DECIMAL_DEGREES)
473 ));
474 }
475
476 /**
477 * Interpolate between this and a other latlon
478 * @param ll2 The other lat/lon object
479 * @param proportion The proportion to interpolate
480 * @return a new latlon at this position if proportion is 0, at the other position it proportion is 1 and lineary interpolated otherwise.
481 */
482 public LatLon interpolate(LatLon ll2, double proportion) {
483 // this is an alternate form of this.lat() + proportion * (ll2.lat() - this.lat()) that is slightly faster
484 return new LatLon((1 - proportion) * this.lat() + proportion * ll2.lat(),
485 (1 - proportion) * this.lon() + proportion * ll2.lon());
486 }
487
488 /**
489 * Get the center between two lat/lon points
490 * @param ll2 The other {@link LatLon}
491 * @return The center at the average coordinates of the two points. Does not take the 180° meridian into account.
492 */
493 public LatLon getCenter(LatLon ll2) {
494 // The JIT will inline this for us, it is as fast as the normal /2 approach
495 return interpolate(ll2, .5);
496 }
497
498 /**
499 * Returns the euclidean distance from this {@code LatLon} to a specified {@code LatLon}.
500 *
501 * @param ll the specified coordinate to be measured against this {@code LatLon}
502 * @return the euclidean distance from this {@code LatLon} to a specified {@code LatLon}
503 * @since 6166
504 */
505 public double distance(final LatLon ll) {
506 return super.distance(ll);
507 }
508
509 /**
510 * Returns the square of the euclidean distance from this {@code LatLon} to a specified {@code LatLon}.
511 *
512 * @param ll the specified coordinate to be measured against this {@code LatLon}
513 * @return the square of the euclidean distance from this {@code LatLon} to a specified {@code LatLon}
514 * @since 6166
515 */
516 public double distanceSq(final LatLon ll) {
517 return super.distanceSq(ll);
518 }
519
520 @Override
521 public String toString() {
522 return "LatLon[lat="+lat()+",lon="+lon()+']';
523 }
524
525 /**
526 * Returns the value rounded to OSM precisions, i.e. to {@link #MAX_SERVER_PRECISION}.
527 * @param value lat/lon value
528 *
529 * @return rounded value
530 */
531 public static double roundToOsmPrecision(double value) {
532 return Math.round(value * MAX_SERVER_INV_PRECISION) / MAX_SERVER_INV_PRECISION;
533 }
534
535 /**
536 * Replies a clone of this lat LatLon, rounded to OSM precisions, i.e. to {@link #MAX_SERVER_PRECISION}
537 *
538 * @return a clone of this lat LatLon
539 */
540 public LatLon getRoundedToOsmPrecision() {
541 return new LatLon(
542 roundToOsmPrecision(lat()),
543 roundToOsmPrecision(lon())
544 );
545 }
546
547 @Override
548 public int hashCode() {
549 return Objects.hash(x, y);
550 }
551
552 @Override
553 public boolean equals(Object obj) {
554 if (this == obj) return true;
555 if (obj == null || getClass() != obj.getClass()) return false;
556 LatLon that = (LatLon) obj;
557 return Double.compare(that.x, x) == 0 &&
558 Double.compare(that.y, y) == 0;
559 }
560
561 private static class LatLonHolder {
562 private double lat = Double.NaN;
563 private double lon = Double.NaN;
564 }
565
566 private static void setLatLonObj(final LatLonHolder latLon,
567 final Object coord1deg, final Object coord1min, final Object coord1sec, final Object card1,
568 final Object coord2deg, final Object coord2min, final Object coord2sec, final Object card2) {
569
570 setLatLon(latLon,
571 (Double) coord1deg, (Double) coord1min, (Double) coord1sec, (String) card1,
572 (Double) coord2deg, (Double) coord2min, (Double) coord2sec, (String) card2);
573 }
574
575 private static void setLatLon(final LatLonHolder latLon,
576 final double coord1deg, final double coord1min, final double coord1sec, final String card1,
577 final double coord2deg, final double coord2min, final double coord2sec, final String card2) {
578
579 setLatLon(latLon, coord1deg, coord1min, coord1sec, card1);
580 setLatLon(latLon, coord2deg, coord2min, coord2sec, card2);
581 if (Double.isNaN(latLon.lat) || Double.isNaN(latLon.lon)) {
582 throw new IllegalArgumentException("invalid lat/lon parameters");
583 }
584 }
585
586 private static void setLatLon(final LatLonHolder latLon, final double coordDeg, final double coordMin, final double coordSec,
587 final String card) {
588 if (coordDeg < -180 || coordDeg > 180 || coordMin < 0 || coordMin >= 60 || coordSec < 0 || coordSec > 60) {
589 throw new IllegalArgumentException("out of range");
590 }
591
592 double coord = (coordDeg < 0 ? -1 : 1) * (Math.abs(coordDeg) + coordMin / 60 + coordSec / 3600);
593 coord = "N".equals(card) || "E".equals(card) ? coord : -coord;
594 if ("N".equals(card) || "S".equals(card)) {
595 latLon.lat = coord;
596 } else {
597 latLon.lon = coord;
598 }
599 }
600
601 /**
602 * Parses the given string as lat/lon.
603 * @param coord String to parse
604 * @return parsed lat/lon
605 * @since 11045
606 */
607 public static LatLon parse(String coord) {
608 final LatLonHolder latLon = new LatLonHolder();
609 final Matcher mXml = P_XML.matcher(coord);
610 if (mXml.matches()) {
611 setLatLonObj(latLon,
612 Double.valueOf(mXml.group(1).replace(',', '.')), 0.0, 0.0, "N",
613 Double.valueOf(mXml.group(2).replace(',', '.')), 0.0, 0.0, "E");
614 } else {
615 final Matcher m = P.matcher(coord);
616
617 final StringBuilder sb = new StringBuilder();
618 final List<Object> list = new ArrayList<>();
619
620 while (m.find()) {
621 if (m.group(1) != null) {
622 sb.append('R'); // floating point number
623 list.add(Double.valueOf(m.group(1).replace(',', '.')));
624 } else if (m.group(2) != null) {
625 sb.append('Z'); // integer number
626 list.add(Double.valueOf(m.group(2)));
627 } else if (m.group(3) != null) {
628 sb.append('o'); // degree sign
629 } else if (m.group(4) != null) {
630 sb.append('\''); // seconds sign
631 } else if (m.group(5) != null) {
632 sb.append('"'); // minutes sign
633 } else if (m.group(6) != null) {
634 sb.append(','); // separator
635 } else if (m.group(7) != null) {
636 sb.append('x'); // cardinal direction
637 String c = m.group(7).toUpperCase(Locale.ENGLISH);
638 if ("N".equalsIgnoreCase(c) || "S".equalsIgnoreCase(c) || "E".equalsIgnoreCase(c) || "W".equalsIgnoreCase(c)) {
639 list.add(c);
640 } else {
641 list.add(c.replace(N_TR, 'N').replace(S_TR, 'S')
642 .replace(E_TR, 'E').replace(W_TR, 'W'));
643 }
644 } else if (m.group(8) != null) {
645 throw new IllegalArgumentException("invalid token: " + m.group(8));
646 }
647 }
648
649 final String pattern = sb.toString();
650
651 final Object[] params = list.toArray();
652
653 if (pattern.matches("Ro?,?Ro?")) {
654 setLatLonObj(latLon,
655 params[0], 0.0, 0.0, "N",
656 params[1], 0.0, 0.0, "E");
657 } else if (pattern.matches("xRo?,?xRo?")) {
658 setLatLonObj(latLon,
659 params[1], 0.0, 0.0, params[0],
660 params[3], 0.0, 0.0, params[2]);
661 } else if (pattern.matches("Ro?x,?Ro?x")) {
662 setLatLonObj(latLon,
663 params[0], 0.0, 0.0, params[1],
664 params[2], 0.0, 0.0, params[3]);
665 } else if (pattern.matches("Zo[RZ]'?,?Zo[RZ]'?|Z[RZ],?Z[RZ]")) {
666 setLatLonObj(latLon,
667 params[0], params[1], 0.0, "N",
668 params[2], params[3], 0.0, "E");
669 } else if (pattern.matches("xZo[RZ]'?,?xZo[RZ]'?|xZo?[RZ],?xZo?[RZ]")) {
670 setLatLonObj(latLon,
671 params[1], params[2], 0.0, params[0],
672 params[4], params[5], 0.0, params[3]);
673 } else if (pattern.matches("Zo[RZ]'?x,?Zo[RZ]'?x|Zo?[RZ]x,?Zo?[RZ]x")) {
674 setLatLonObj(latLon,
675 params[0], params[1], 0.0, params[2],
676 params[3], params[4], 0.0, params[5]);
677 } else if (pattern.matches("ZoZ'[RZ]\"?x,?ZoZ'[RZ]\"?x|ZZ[RZ]x,?ZZ[RZ]x")) {
678 setLatLonObj(latLon,
679 params[0], params[1], params[2], params[3],
680 params[4], params[5], params[6], params[7]);
681 } else if (pattern.matches("xZoZ'[RZ]\"?,?xZoZ'[RZ]\"?|xZZ[RZ],?xZZ[RZ]")) {
682 setLatLonObj(latLon,
683 params[1], params[2], params[3], params[0],
684 params[5], params[6], params[7], params[4]);
685 } else if (pattern.matches("ZZ[RZ],?ZZ[RZ]")) {
686 setLatLonObj(latLon,
687 params[0], params[1], params[2], "N",
688 params[3], params[4], params[5], "E");
689 } else {
690 throw new IllegalArgumentException("invalid format: " + pattern);
691 }
692 }
693
694 return new LatLon(latLon.lat, latLon.lon);
695 }
696}
Note: See TracBrowser for help on using the repository browser.