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