001 /* 002 * Zmanim Java API 003 * Copyright (C) 2004-2011 Eliyahu Hershfeld 004 * 005 * This program is free software; you can redistribute it and/or modify it under the terms of the 006 * GNU General Public License as published by the Free Software Foundation; either version 2 of the 007 * License, or (at your option) any later version. 008 * 009 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without 010 * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 011 * General Public License for more details. 012 * 013 * You should have received a copy of the GNU General Public License along with this program; if 014 * not, write to the Free Software Foundation, Inc. 59 Temple Place - Suite 330, Boston, MA 015 * 02111-1307, USA or connect to: http://www.fsf.org/copyleft/gpl.html 016 */ 017 package net.sourceforge.zmanim.util; 018 019 import java.util.TimeZone; 020 021 /** 022 * A class that contains location information such as latitude and longitude 023 * required for astronomical calculations. The elevation field is not used by 024 * most calculation engines and would be ignored if set. Check the documentation 025 * for specific implementations of the {@link AstronomicalCalculator} to see if 026 * elevation is calculated as part o the algorithm. 027 * 028 * @author © Eliyahu Hershfeld 2004 - 2011 029 * @version 1.1 030 */ 031 public class GeoLocation implements Cloneable { 032 private double latitude; 033 private double longitude; 034 private String locationName; 035 private TimeZone timeZone; 036 private double elevation; 037 private static final int DISTANCE = 0; 038 private static final int INITIAL_BEARING = 1; 039 private static final int FINAL_BEARING = 2; 040 041 /** constant for milliseconds in a minute (60,000) */ 042 private static final long MINUTE_MILLIS = 60 * 1000; 043 044 /** constant for milliseconds in an hour (3,600,000) */ 045 private static final long HOUR_MILLIS = MINUTE_MILLIS * 60; 046 047 /** 048 * Method to get the elevation in Meters. 049 * 050 * @return Returns the elevation in Meters. 051 */ 052 public double getElevation() { 053 return this.elevation; 054 } 055 056 /** 057 * Method to set the elevation in Meters <b>above </b> sea level. 058 * 059 * @param elevation 060 * The elevation to set in Meters. An IllegalArgumentException 061 * will be thrown if the value is a negative. 062 */ 063 public void setElevation(double elevation) { 064 if (elevation < 0) { 065 throw new IllegalArgumentException("Elevation cannot be negative"); 066 } 067 this.elevation = elevation; 068 } 069 070 /** 071 * GeoLocation constructor with parameters for all required fields. 072 * 073 * @param name 074 * The location name for display use such as "Lakewood, 075 * NJ" 076 * @param latitude 077 * the latitude in a double format such as 40.095965 for 078 * Lakewood, NJ <br/> <b>Note: </b> For latitudes south of the 079 * equator, a negative value should be used. 080 * @param longitude 081 * double the longitude in a double format such as -74.222130 for 082 * Lakewood, NJ. <br/> <b>Note: </b> For longitudes east of the 083 * <a href="http://en.wikipedia.org/wiki/Prime_Meridian">Prime 084 * Meridian </a> (Greenwich), a negative value should be used. 085 * @param timeZone 086 * the <code>TimeZone</code> for the location. 087 */ 088 public GeoLocation(String name, double latitude, double longitude, 089 TimeZone timeZone) { 090 this(name, latitude, longitude, 0, timeZone); 091 } 092 093 /** 094 * GeoLocation constructor with parameters for all required fields. 095 * 096 * @param name 097 * The location name for display use such as "Lakewood, 098 * NJ" 099 * @param latitude 100 * the latitude in a double format such as 40.095965 for 101 * Lakewood, NJ <br/> <b>Note: </b> For latitudes south of the 102 * equator, a negative value should be used. 103 * @param longitude 104 * double the longitude in a double format such as -74.222130 for 105 * Lakewood, NJ. <br/> <b>Note: </b> For longitudes east of the 106 * <a href="http://en.wikipedia.org/wiki/Prime_Meridian">Prime 107 * Meridian </a> (Greenwich), a negative value should be used. 108 * @param elevation 109 * the elevation above sea level in Meters. Elevation is not used 110 * in most algorithms used for calculating sunrise and set. 111 * @param timeZone 112 * the <code>TimeZone</code> for the location. 113 */ 114 public GeoLocation(String name, double latitude, double longitude, 115 double elevation, TimeZone timeZone) { 116 setLocationName(name); 117 setLatitude(latitude); 118 setLongitude(longitude); 119 setElevation(elevation); 120 setTimeZone(timeZone); 121 } 122 123 /** 124 * Default GeoLocation constructor will set location to the Prime Meridian 125 * at Greenwich, England and a TimeZone of GMT. The longitude will be set to 126 * 0 and the latitude will be 51.4772 to match the location of the <a 127 * href="http://www.rog.nmm.ac.uk">Royal Observatory, Greenwich </a>. No 128 * daylight savings time will be used. 129 */ 130 public GeoLocation() { 131 setLocationName("Greenwich, England"); 132 setLongitude(0); // added for clarity 133 setLatitude(51.4772); 134 setTimeZone(TimeZone.getTimeZone("GMT")); 135 } 136 137 /** 138 * Method to set the latitude. 139 * 140 * @param latitude 141 * The degrees of latitude to set. The values should be between 142 * -90° and 90°. An IllegalArgumentException will be 143 * thrown if the value exceeds the limit. For example 40.095965 144 * would be used for Lakewood, NJ. <b>Note: </b> For latitudes south of the 145 * equator, a negative value should be used. 146 */ 147 public void setLatitude(double latitude) { 148 if (latitude > 90 || latitude < -90) { 149 throw new IllegalArgumentException( 150 "Latitude must be between -90 and 90"); 151 } 152 this.latitude = latitude; 153 } 154 155 /** 156 * Method to set the latitude in degrees, minutes and seconds. 157 * 158 * @param degrees 159 * The degrees of latitude to set between -90 and 90. An 160 * IllegalArgumentException will be thrown if the value exceeds 161 * the limit. For example 40 would be used for Lakewood, NJ. 162 * @param minutes <a href="http://en.wikipedia.org/wiki/Minute_of_arc#Cartography">minutes of arc</a> 163 * @param seconds <a href="http://en.wikipedia.org/wiki/Minute_of_arc#Cartography">seconds of arc</a> 164 * @param direction 165 * N for north and S for south. An IllegalArgumentException will 166 * be thrown if the value is not S or N. 167 */ 168 public void setLatitude(int degrees, int minutes, double seconds, 169 String direction) { 170 double tempLat = degrees + ((minutes + (seconds / 60.0)) / 60.0); 171 if (tempLat > 90 || tempLat < 0) { 172 throw new IllegalArgumentException( 173 "Latitude must be between 0 and 90. Use direction of S instead of negative."); 174 } 175 if (direction.equals("S")) { 176 tempLat *= -1; 177 } else if (!direction.equals("N")) { 178 throw new IllegalArgumentException( 179 "Latitude direction must be N or S"); 180 } 181 this.latitude = tempLat; 182 } 183 184 /** 185 * @return Returns the latitude. 186 */ 187 public double getLatitude() { 188 return this.latitude; 189 } 190 191 /** 192 * Method to set the longitude in a double format. 193 * 194 * @param longitude 195 * The degrees of longitude to set in a double format between 196 * -180° and 180°. An IllegalArgumentException will be 197 * thrown if the value exceeds the limit. For example -74.2094 198 * would be used for Lakewood, NJ. Note: for longitudes east of 199 * the <a 200 * href="http://en.wikipedia.org/wiki/Prime_Meridian">Prime 201 * Meridian</a> (Greenwich) a negative value should be used. 202 */ 203 public void setLongitude(double longitude) { 204 if (longitude > 180 || longitude < -180) { 205 throw new IllegalArgumentException( 206 "Longitude must be between -180 and 180"); 207 } 208 this.longitude = longitude; 209 } 210 211 /** 212 * Method to set the longitude in degrees, minutes and seconds. 213 * 214 * @param degrees 215 * The degrees of longitude to set between -180 and 180. An 216 * IllegalArgumentException will be thrown if the value exceeds 217 * the limit. For example -74 would be used for Lakewood, NJ. 218 * Note: for longitudes east of the <a 219 * href="http://en.wikipedia.org/wiki/Prime_Meridian">Prime 220 * Meridian </a> (Greenwich) a negative value should be used. 221 * @param minutes <a href="http://en.wikipedia.org/wiki/Minute_of_arc#Cartography">minutes of arc</a> 222 * @param seconds <a href="http://en.wikipedia.org/wiki/Minute_of_arc#Cartography">seconds of arc</a> 223 * @param direction 224 * E for east of the Prime Meridian or W for west of it. An 225 * IllegalArgumentException will be thrown if the value is not E 226 * or W. 227 */ 228 public void setLongitude(int degrees, int minutes, double seconds, 229 String direction) { 230 double longTemp = degrees + ((minutes + (seconds / 60.0)) / 60.0); 231 if (longTemp > 180 || this.longitude < 0) { 232 throw new IllegalArgumentException( 233 "Longitude must be between 0 and 180. Use the "); 234 } 235 if (direction.equals("W")) { 236 longTemp *= -1; 237 } else if (!direction.equals("E")) { 238 throw new IllegalArgumentException( 239 "Longitude direction must be E or W"); 240 } 241 this.longitude = longTemp; 242 } 243 244 /** 245 * @return Returns the longitude. 246 */ 247 public double getLongitude() { 248 return this.longitude; 249 } 250 251 /** 252 * @return Returns the location name. 253 */ 254 public String getLocationName() { 255 return this.locationName; 256 } 257 258 /** 259 * @param name 260 * The setter method for the display name. 261 */ 262 public void setLocationName(String name) { 263 this.locationName = name; 264 } 265 266 /** 267 * @return Returns the timeZone. 268 */ 269 public TimeZone getTimeZone() { 270 return this.timeZone; 271 } 272 273 /** 274 * Method to set the TimeZone. If this is ever set after the GeoLocation is 275 * set in the {@link net.sourceforge.zmanim.AstronomicalCalendar}, it is 276 * critical that 277 * {@link net.sourceforge.zmanim.AstronomicalCalendar#getCalendar()}.{@link java.util.Calendar#setTimeZone(TimeZone) setTimeZone(TimeZone)} 278 * be called in order for the AstronomicalCalendar to output times in the 279 * expected offset. This situation will arise if the AstronomicalCalendar is 280 * ever {@link net.sourceforge.zmanim.AstronomicalCalendar#clone() cloned}. 281 * 282 * @param timeZone 283 * The timeZone to set. 284 */ 285 public void setTimeZone(TimeZone timeZone) { 286 this.timeZone = timeZone; 287 } 288 289 /** 290 * A method that will return the location's local mean time offset in 291 * milliseconds from local standard time. The globe is split into 360°, 292 * with 15° per hour of the day. For a local that is at a longitude that 293 * is evenly divisible by 15 (longitude % 15 == 0), at solar 294 * {@link net.sourceforge.zmanim.AstronomicalCalendar#getSunTransit() noon} 295 * (with adjustment for the <a 296 * href="http://en.wikipedia.org/wiki/Equation_of_time">equation of time</a>) 297 * the sun should be directly overhead, so a user who is 1° west of this 298 * will have noon at 4 minutes after standard time noon, and conversely, a 299 * user who is 1° east of the 15° longitude will have noon at 11:56 300 * AM. The offset returned does not account for the <a 301 * href="http://en.wikipedia.org/wiki/Daylight_saving_time">Daylight saving 302 * time</a> offset since this class is unaware of dates. 303 * 304 * @return the offset in milliseconds not accounting for Daylight saving 305 * time. A positive value will be returned East of the timezone 306 * line, and a negative value West of it. 307 * @since 1.1 308 */ 309 public long getLocalMeanTimeOffset() { 310 return (long) (getLongitude() * 4 * MINUTE_MILLIS - getTimeZone().getRawOffset()); 311 } 312 313 /** 314 * Calculate the initial <a 315 * href="http://en.wikipedia.org/wiki/Great_circle">geodesic</a> bearing 316 * between this Object and a second Object passed to this method using <a 317 * href="http://en.wikipedia.org/wiki/Thaddeus_Vincenty">Thaddeus Vincenty's</a> 318 * inverse formula See T Vincenty, "<a 319 * href="http://www.ngs.noaa.gov/PUBS_LIB/inverse.pdf">Direct and Inverse 320 * Solutions of Geodesics on the Ellipsoid with application of nested 321 * equations</a>", Survey Review, vol XXII no 176, 1975 322 * 323 * @param location 324 * the destination location 325 */ 326 public double getGeodesicInitialBearing(GeoLocation location) { 327 return vincentyFormula(location, INITIAL_BEARING); 328 } 329 330 /** 331 * Calculate the final <a 332 * href="http://en.wikipedia.org/wiki/Great_circle">geodesic</a> bearing 333 * between this Object and a second Object passed to this method using <a 334 * href="http://en.wikipedia.org/wiki/Thaddeus_Vincenty">Thaddeus Vincenty's</a> 335 * inverse formula See T Vincenty, "<a 336 * href="http://www.ngs.noaa.gov/PUBS_LIB/inverse.pdf">Direct and Inverse 337 * Solutions of Geodesics on the Ellipsoid with application of nested 338 * equations</a>", Survey Review, vol XXII no 176, 1975 339 * 340 * @param location 341 * the destination location 342 */ 343 public double getGeodesicFinalBearing(GeoLocation location) { 344 return vincentyFormula(location, FINAL_BEARING); 345 } 346 347 /** 348 * Calculate <a 349 * href="http://en.wikipedia.org/wiki/Great-circle_distance">geodesic 350 * distance</a> in Meters between this Object and a second Object passed to 351 * this method using <a 352 * href="http://en.wikipedia.org/wiki/Thaddeus_Vincenty">Thaddeus Vincenty's</a> 353 * inverse formula See T Vincenty, "<a 354 * href="http://www.ngs.noaa.gov/PUBS_LIB/inverse.pdf">Direct and Inverse 355 * Solutions of Geodesics on the Ellipsoid with application of nested 356 * equations</a>", Survey Review, vol XXII no 176, 1975 357 * 358 * @param location 359 * the destination location 360 */ 361 public double getGeodesicDistance(GeoLocation location) { 362 return vincentyFormula(location, DISTANCE); 363 } 364 365 /** 366 * Calculate <a 367 * href="http://en.wikipedia.org/wiki/Great-circle_distance">geodesic 368 * distance</a> in Meters between this Object and a second Object passed to 369 * this method using <a 370 * href="http://en.wikipedia.org/wiki/Thaddeus_Vincenty">Thaddeus Vincenty's</a> 371 * inverse formula See T Vincenty, "<a 372 * href="http://www.ngs.noaa.gov/PUBS_LIB/inverse.pdf">Direct and Inverse 373 * Solutions of Geodesics on the Ellipsoid with application of nested 374 * equations</a>", Survey Review, vol XXII no 176, 1975 375 * 376 * @param location 377 * the destination location 378 * @param formula 379 * This formula calculates initial bearing ({@link #INITIAL_BEARING}), 380 * final bearing ({@link #FINAL_BEARING}) and distance ({@link #DISTANCE}). 381 */ 382 private double vincentyFormula(GeoLocation location, int formula) { 383 double a = 6378137; 384 double b = 6356752.3142; 385 double f = 1 / 298.257223563; // WGS-84 ellipsiod 386 double L = Math.toRadians(location.getLongitude() - getLongitude()); 387 double U1 = Math 388 .atan((1 - f) * Math.tan(Math.toRadians(getLatitude()))); 389 double U2 = Math.atan((1 - f) 390 * Math.tan(Math.toRadians(location.getLatitude()))); 391 double sinU1 = Math.sin(U1), cosU1 = Math.cos(U1); 392 double sinU2 = Math.sin(U2), cosU2 = Math.cos(U2); 393 394 double lambda = L; 395 double lambdaP = 2 * Math.PI; 396 double iterLimit = 20; 397 double sinLambda = 0; 398 double cosLambda = 0; 399 double sinSigma = 0; 400 double cosSigma = 0; 401 double sigma = 0; 402 double sinAlpha = 0; 403 double cosSqAlpha = 0; 404 double cos2SigmaM = 0; 405 double C; 406 while (Math.abs(lambda - lambdaP) > 1e-12 && --iterLimit > 0) { 407 sinLambda = Math.sin(lambda); 408 cosLambda = Math.cos(lambda); 409 sinSigma = Math.sqrt((cosU2 * sinLambda) * (cosU2 * sinLambda) 410 + (cosU1 * sinU2 - sinU1 * cosU2 * cosLambda) 411 * (cosU1 * sinU2 - sinU1 * cosU2 * cosLambda)); 412 if (sinSigma == 0) 413 return 0; // co-incident points 414 cosSigma = sinU1 * sinU2 + cosU1 * cosU2 * cosLambda; 415 sigma = Math.atan2(sinSigma, cosSigma); 416 sinAlpha = cosU1 * cosU2 * sinLambda / sinSigma; 417 cosSqAlpha = 1 - sinAlpha * sinAlpha; 418 cos2SigmaM = cosSigma - 2 * sinU1 * sinU2 / cosSqAlpha; 419 if (Double.isNaN(cos2SigmaM)) 420 cos2SigmaM = 0; // equatorial line: cosSqAlpha=0 (§6) 421 C = f / 16 * cosSqAlpha * (4 + f * (4 - 3 * cosSqAlpha)); 422 lambdaP = lambda; 423 lambda = L 424 + (1 - C) 425 * f 426 * sinAlpha 427 * (sigma + C 428 * sinSigma 429 * (cos2SigmaM + C * cosSigma 430 * (-1 + 2 * cos2SigmaM * cos2SigmaM))); 431 } 432 if (iterLimit == 0) 433 return Double.NaN; // formula failed to converge 434 435 double uSq = cosSqAlpha * (a * a - b * b) / (b * b); 436 double A = 1 + uSq / 16384 437 * (4096 + uSq * (-768 + uSq * (320 - 175 * uSq))); 438 double B = uSq / 1024 * (256 + uSq * (-128 + uSq * (74 - 47 * uSq))); 439 double deltaSigma = B 440 * sinSigma 441 * (cos2SigmaM + B 442 / 4 443 * (cosSigma * (-1 + 2 * cos2SigmaM * cos2SigmaM) - B 444 / 6 * cos2SigmaM 445 * (-3 + 4 * sinSigma * sinSigma) 446 * (-3 + 4 * cos2SigmaM * cos2SigmaM))); 447 double distance = b * A * (sigma - deltaSigma); 448 449 // initial bearing 450 double fwdAz = Math.toDegrees(Math.atan2(cosU2 * sinLambda, cosU1 451 * sinU2 - sinU1 * cosU2 * cosLambda)); 452 // final bearing 453 double revAz = Math.toDegrees(Math.atan2(cosU1 * sinLambda, -sinU1 454 * cosU2 + cosU1 * sinU2 * cosLambda)); 455 if (formula == DISTANCE) { 456 return distance; 457 } else if (formula == INITIAL_BEARING) { 458 return fwdAz; 459 } else if (formula == FINAL_BEARING) { 460 return revAz; 461 } else { // should never happpen 462 return Double.NaN; 463 } 464 } 465 466 /** 467 * Returns the <a href="http://en.wikipedia.org/wiki/Rhumb_line">rhumb line</a> 468 * bearing from the current location to the GeoLocation passed in. 469 * 470 * @param location 471 * destination location 472 * @return the bearing in degrees 473 */ 474 public double getRhumbLineBearing(GeoLocation location) { 475 double dLon = Math.toRadians(location.getLongitude() - getLongitude()); 476 double dPhi = Math.log(Math.tan(Math.toRadians(location.getLatitude()) 477 / 2 + Math.PI / 4) 478 / Math.tan(Math.toRadians(getLatitude()) / 2 + Math.PI / 4)); 479 if (Math.abs(dLon) > Math.PI) 480 dLon = dLon > 0 ? -(2 * Math.PI - dLon) : (2 * Math.PI + dLon); 481 return Math.toDegrees(Math.atan2(dLon, dPhi)); 482 } 483 484 /** 485 * Returns the <a href="http://en.wikipedia.org/wiki/Rhumb_line">rhumb line</a> 486 * distance from the current location to the GeoLocation passed in. 487 * 488 * @param location 489 * the destination location 490 * @return the distance in Meters 491 */ 492 public double getRhumbLineDistance(GeoLocation location) { 493 double R = 6371; // earth's mean radius in km 494 double dLat = Math.toRadians(location.getLatitude() - getLatitude()); 495 double dLon = Math.toRadians(Math.abs(location.getLongitude() 496 - getLongitude())); 497 double dPhi = Math.log(Math.tan(Math.toRadians(location.getLongitude()) 498 / 2 + Math.PI / 4) 499 / Math.tan(Math.toRadians(getLatitude()) / 2 + Math.PI / 4)); 500 double q = (Math.abs(dLat) > 1e-10) ? dLat / dPhi : Math.cos(Math 501 .toRadians(getLatitude())); 502 // if dLon over 180° take shorter rhumb across 180° meridian: 503 if (dLon > Math.PI) 504 dLon = 2 * Math.PI - dLon; 505 double d = Math.sqrt(dLat * dLat + q * q * dLon * dLon); 506 return d * R; 507 } 508 509 /** 510 * A method that returns an XML formatted <code>String</code> representing 511 * the serialized <code>Object</code>. Very similar to the toString 512 * method but the return value is in an xml format. The format currently 513 * used (subject to change) is: 514 * 515 * <pre> 516 * <GeoLocation> 517 * <LocationName>Lakewood, NJ</LocationName> 518 * <Latitude>40.0828&deg</Latitude> 519 * <Longitude>-74.2094&deg</Longitude> 520 * <Elevation>0 Meters</Elevation> 521 * <TimezoneName>America/New_York</TimezoneName> 522 * <TimeZoneDisplayName>Eastern Standard Time</TimeZoneDisplayName> 523 * <TimezoneGMTOffset>-5</TimezoneGMTOffset> 524 * <TimezoneDSTOffset>1</TimezoneDSTOffset> 525 * </GeoLocation> 526 * </pre> 527 * 528 * @return The XML formatted <code>String</code>. 529 */ 530 public String toXML() { 531 StringBuffer sb = new StringBuffer(); 532 sb.append("<GeoLocation>\n"); 533 sb.append("\t<LocationName>").append(getLocationName()).append( 534 "</LocationName>\n"); 535 sb.append("\t<Latitude>").append(getLatitude()).append("°").append( 536 "</Latitude>\n"); 537 sb.append("\t<Longitude>").append(getLongitude()).append("°") 538 .append("</Longitude>\n"); 539 sb.append("\t<Elevation>").append(getElevation()).append(" Meters") 540 .append("</Elevation>\n"); 541 sb.append("\t<TimezoneName>").append(getTimeZone().getID()).append( 542 "</TimezoneName>\n"); 543 sb.append("\t<TimeZoneDisplayName>").append( 544 getTimeZone().getDisplayName()).append( 545 "</TimeZoneDisplayName>\n"); 546 sb.append("\t<TimezoneGMTOffset>").append( 547 getTimeZone().getRawOffset() / HOUR_MILLIS).append( 548 "</TimezoneGMTOffset>\n"); 549 sb.append("\t<TimezoneDSTOffset>").append( 550 getTimeZone().getDSTSavings() / HOUR_MILLIS).append( 551 "</TimezoneDSTOffset>\n"); 552 sb.append("</GeoLocation>"); 553 return sb.toString(); 554 } 555 556 /** 557 * @see java.lang.Object#equals(Object) 558 */ 559 public boolean equals(Object object) { 560 if (this == object) 561 return true; 562 if (!(object instanceof GeoLocation)) 563 return false; 564 GeoLocation geo = (GeoLocation) object; 565 return Double.doubleToLongBits(this.latitude) == Double 566 .doubleToLongBits(geo.latitude) 567 && Double.doubleToLongBits(this.longitude) == Double 568 .doubleToLongBits(geo.longitude) 569 && this.elevation == geo.elevation 570 && (this.locationName == null ? geo.locationName == null 571 : this.locationName.equals(geo.locationName)) 572 && (this.timeZone == null ? geo.timeZone == null : this.timeZone 573 .equals(geo.timeZone)); 574 } 575 576 /** 577 * @see java.lang.Object#hashCode() 578 */ 579 public int hashCode() { 580 581 int result = 17; 582 long latLong = Double.doubleToLongBits(this.latitude); 583 long lonLong = Double.doubleToLongBits(this.longitude); 584 long elevLong = Double.doubleToLongBits(this.elevation); 585 int latInt = (int) (latLong ^ (latLong >>> 32)); 586 int lonInt = (int) (lonLong ^ (lonLong >>> 32)); 587 int elevInt = (int) (elevLong ^ (elevLong >>> 32)); 588 result = 37 * result + getClass().hashCode(); 589 result += 37 * result + latInt; 590 result += 37 * result + lonInt; 591 result += 37 * result + elevInt; 592 result += 37 * result 593 + (this.locationName == null ? 0 : this.locationName.hashCode()); 594 result += 37 * result + (this.timeZone == null ? 0 : this.timeZone.hashCode()); 595 return result; 596 } 597 598 /** 599 * @see java.lang.Object#toString() 600 */ 601 public String toString() { 602 StringBuffer sb = new StringBuffer(); 603 sb.append("\nLocation Name:\t\t\t").append(getLocationName()); 604 sb.append("\nLatitude:\t\t\t").append(getLatitude()).append("°"); 605 sb.append("\nLongitude:\t\t\t").append(getLongitude()).append("°"); 606 sb.append("\nElevation:\t\t\t").append(getElevation()) 607 .append(" Meters"); 608 sb.append("\nTimezone Name:\t\t\t").append(getTimeZone().getID()); 609 /* 610 * sb.append("\nTimezone Display Name:\t\t").append( 611 * getTimeZone().getDisplayName()); 612 */ 613 sb.append("\nTimezone GMT Offset:\t\t").append( 614 getTimeZone().getRawOffset() / HOUR_MILLIS); 615 sb.append("\nTimezone DST Offset:\t\t").append( 616 getTimeZone().getDSTSavings() / HOUR_MILLIS); 617 return sb.toString(); 618 } 619 620 /** 621 * An implementation of the {@link java.lang.Object#clone()} method that 622 * creates a <a 623 * href="http://en.wikipedia.org/wiki/Object_copy#Deep_copy">deep copy</a> 624 * of the object. <br/><b>Note:</b> If the {@link java.util.TimeZone} in 625 * the clone will be changed from the original, it is critical that 626 * {@link net.sourceforge.zmanim.AstronomicalCalendar#getCalendar()}.{@link java.util.Calendar#setTimeZone(TimeZone) setTimeZone(TimeZone)} 627 * is called after cloning in order for the AstronomicalCalendar to output 628 * times in the expected offset. 629 * 630 * @see java.lang.Object#clone() 631 * @since 1.1 632 */ 633 public Object clone() { 634 GeoLocation clone = null; 635 try { 636 clone = (GeoLocation) super.clone(); 637 } catch (CloneNotSupportedException cnse) { 638 System.out 639 .print("Required by the compiler. Should never be reached since we implement clone()"); 640 } 641 clone.timeZone = (TimeZone) getTimeZone().clone(); 642 clone.locationName = (String) getLocationName(); 643 return clone; 644 } 645 }