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