001    /*
002     * Zmanim Java API
003     * Copyright (C) 2004-2007 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 - 2007
029     * @version 1.1
030     */
031    public class GeoLocation {
032            private double latitude;
033    
034            private double longitude;
035    
036            private String locationName;
037    
038            private TimeZone timeZone;
039    
040            private double elevation;
041    
042            /**
043             * Method to get the elevation in Meters.
044             * 
045             * @return Returns the elevation in Meters.
046             */
047            public double getElevation() {
048                    return elevation;
049            }
050    
051            /**
052             * Method to set the elevation in Meters <b>above </b> sea level.
053             * 
054             * @param elevation
055             *            The elevation to set in Meters. An IllegalArgumentException
056             *            will be thrown if the value is a negative.
057             */
058            public void setElevation(double elevation) {
059                    if (elevation < 0) {
060                            throw new IllegalArgumentException("Elevation can not be negative");
061    
062                    }
063                    this.elevation = elevation;
064            }
065    
066            /**
067             * GeoLocation constructor with parameters for all required fields.
068             * 
069             * @param name
070             *            The location name for display use such as "Lakewood,
071             *            NJ"
072             * @param latitude
073             *            the latitude in a double format such as 40.095965 for Lakewood,
074             *            NJ
075             * @param longitude
076             *            double the longitude in a double format such as -74.222130 for
077             *            Lakewood, NJ. <br/> <b>Note: </b> For longitudes east of the
078             *            <a href="http://en.wikipedia.org/wiki/Prime_Meridian">Prime
079             *            Meridian </a> (Greenwich), a negative value should be used.
080             * @param timeZone
081             *            the <code>TimeZone</code> for the location.
082             */
083            public GeoLocation(String name, double latitude, double longitude,
084                            TimeZone timeZone) {
085                    this(name, latitude, longitude, 0, timeZone);
086                    this.setLocationName(name);
087                    this.setLatitude(latitude);
088                    this.setLongitude(longitude);
089                    this.setTimeZone(timeZone);
090            }
091    
092            /**
093             * GeoLocation constructor with parameters for all required fields.
094             * 
095             * @param name
096             *            The location name for display use such as "Lakewood,
097             *            NJ"
098             * @param latitude
099             *            the latitude in a double format such as 40.095965 for Lakewood,
100             *            NJ
101             * @param longitude
102             *            double the longitude in a double format such as -74.222130 for
103             *            Lakewood, NJ. <br/> <b>Note: </b> For longitudes east of the
104             *            <a href="http://en.wikipedia.org/wiki/Prime_Meridian">Prime
105             *            Meridian </a> (Greenwich), a negative value should be used.
106             * @param elevation
107             *            the elevation above sea level in Meters. Elevation is not used
108             *            in most algorithms used for calculating sunrise and set.
109             * @param timeZone
110             *            the <code>TimeZone</code> for the location.
111             */
112            public GeoLocation(String name, double latitude, double longitude,
113                            double elevation, TimeZone timeZone) {
114                    setLocationName(name);
115                    setLatitude(latitude);
116                    setLongitude(longitude);
117                    setElevation(elevation);
118                    setTimeZone(timeZone);
119            }
120    
121            /**
122             * Default GeoLocation constructor will set location to the Prime Meridian
123             * at Greenwich, England and a TimeZone of GMT. The longitude will be set to 0 and
124             * the latitude will be 51.4772 to match the location of the <a
125             * href="http://www.rog.nmm.ac.uk">Royal Observatory, Greenwich </a>. No
126             * daylight savings time will be used.
127             */
128            public GeoLocation() {
129                    setLocationName("Greenwich, England");
130                    setLongitude(0); // should not be needed
131                    setLatitude(51.4772);
132                    setTimeZone(TimeZone.getTimeZone("GMT"));
133            }
134    
135            /**
136             * Method to set the latitude in a double format.
137             * 
138             * @param latitude
139             *            The degrees of latitude to set in a double format between -90°
140             *            and 90°. An IllegalArgumentException will be thrown if the
141             *            value exceeds the limit. For example 40.095965 would be used for
142             *            Lakewood, NJ.
143             */
144            public void setLatitude(double latitude) {
145                    if (latitude > 90 || latitude < -90) {
146                            throw new IllegalArgumentException(
147                                            "Latitude must be between -90 and  90");
148                    }
149                    this.latitude = latitude;
150            }
151    
152            /**
153             * Method to set the latitude in degrees, minutes and seconds.
154             * 
155             * @param degrees
156             *            The degrees of latitude to set between -90 and 90. An
157             *            IllegalArgumentException will be thrown if the value exceeds
158             *            the limit. For example 40 would be used for Lakewood, NJ.
159             * @param minutes
160             * @param seconds
161             * @param direction
162             *            N for north and S for south. An IllegalArgumentException will
163             *            be thrown if the value is not S or N.
164             */
165            public void setLatitude(int degrees, int minutes, double seconds,
166                            String direction) {
167                    double tempLat = degrees + ((minutes + (seconds / 60.0)) / 60.0);
168                    if (tempLat > 90 || tempLat < 0) {
169                            throw new IllegalArgumentException(
170                                            "Latitude must be between 0 and  90. Use direction of S instead of negative.");
171                    }
172                    if (direction.equals("S")) {
173                            tempLat *= -1;
174                    } else if (!direction.equals("N")) {
175                            throw new IllegalArgumentException(
176                                            "Latitude direction must be N or S");
177                    }
178                    this.latitude = tempLat;
179            }
180    
181            /**
182             * @return Returns the latitude.
183             */
184            public double getLatitude() {
185                    return latitude;
186            }
187    
188            /**
189             * Method to set the longitude in a double format.
190             * 
191             * @param longitude
192             *            The degrees of longitude to set in a double format between
193             *            -180° and 180°. An IllegalArgumentException will be thrown if
194             *            the value exceeds the limit. For example -74.222130 would be
195             *            used for Lakewood, NJ. Note: for longitudes east of the <a
196             *            href="http://en.wikipedia.org/wiki/Prime_Meridian">Prime
197             *            Meridian </a> (Greenwich) a negative value should be used.
198             */
199            public void setLongitude(double longitude) {
200                    if (longitude > 180 || longitude < -180) {
201                            throw new IllegalArgumentException(
202                                            "Longitude must be between -180 and  180");
203                    }
204                    this.longitude = longitude;
205            }
206    
207            /**
208             * Method to set the longitude in degrees, minutes and seconds.
209             * 
210             * @param degrees
211             *            The degrees of longitude to set between -180 and 180. An
212             *            IllegalArgumentException will be thrown if the value exceeds
213             *            the limit. For example -74 would be used for Lakewood, NJ.
214             *            Note: for longitudes east of the <a
215             *            href="http://en.wikipedia.org/wiki/Prime_Meridian">Prime
216             *            Meridian </a> (Greenwich) a negative value should be used.
217             * @param minutes
218             * @param seconds
219             * @param direction
220             *            E for east of the Prime Meridian or W for west of it. An
221             *            IllegalArgumentException will be thrown if the value is not E
222             *            or W.
223             */
224            public void setLongitude(int degrees, int minutes, double seconds,
225                            String direction) {
226                    double longTemp = degrees + ((minutes + (seconds / 60.0)) / 60.0);
227                    if (longTemp > 180 || longitude < 0) {
228                            throw new IllegalArgumentException(
229                                            "Longitude must be between 0 and  180. Use the ");
230                    }
231                    if (direction.equals("W")) {
232                            longTemp *= -1;
233                    } else if (!direction.equals("E")) {
234                            throw new IllegalArgumentException(
235                                            "Longitude direction must be E or W");
236                    }
237                    this.longitude = longTemp;
238            }
239    
240            /**
241             * @return Returns the longitude.
242             */
243            public double getLongitude() {
244                    return longitude;
245            }
246    
247            /**
248             * @return Returns the location name.
249             */
250            public String getLocationName() {
251                    return locationName;
252            }
253    
254            /**
255             * @param name
256             *            The setter method for the display name.
257             */
258            public void setLocationName(String name) {
259                    this.locationName = name;
260            }
261    
262            /**
263             * @return Returns the timeZone.
264             */
265            public TimeZone getTimeZone() {
266                    return timeZone;
267            }
268    
269            /**
270             * @param timeZone
271             *            The timeZone to set.
272             */
273            public void setTimeZone(TimeZone timeZone) {
274                    this.timeZone = timeZone;
275            }
276    
277            /**
278             * A method that returns an XML formatted <code>String</code> representing
279             * the serialized <code>Object</code>. Very similar to the toString
280             * method but the return value is in an xml format. The format currently
281             * used (subject to change) is:
282             * 
283             * <pre>
284             *   <GeoLocation>
285             *       <LocationName>Lakewood, NJ</LocationName>
286             *       <Latitude>-74.222130°</Latitude>
287             *       <Longitude>-74.222130°</Longitude>
288             *       <Elevation>0 Meters</Elevation>
289             *       <TimezoneName>America/New_York</TimezoneName>
290             *       <TimeZoneDisplayName>Eastern Standard Time</TimeZoneDisplayName>
291             *       <TimezoneGMTOffset>-5</TimezoneGMTOffset>
292             *       <TimezoneDSTOffset>1</TimezoneDSTOffset>
293             *   </GeoLocation>
294             * </pre>
295             * 
296             * @return The XML formatted <code>String</code>.
297             */
298            public String toXML() {
299                    StringBuffer sb = new StringBuffer();
300                    sb.append("<GeoLocation>\n");
301                    sb.append("\t<LocationName>").append(getLocationName()).append(
302                                    "</LocationName>\n");
303                    sb.append("\t<Latitude>").append(getLatitude()).append("°").append(
304                                    "</Latitude>\n");
305                    sb.append("\t<Longitude>").append(getLongitude()).append("°").append(
306                                    "</Longitude>\n");
307                    sb.append("\t<Elevation>").append(getElevation()).append(" Meters")
308                                    .append("</Elevation>\n");
309                    sb.append("\t<TimezoneName>").append(getTimeZone().getID()).append(
310                                    "</TimezoneName>\n");
311                    /*
312                     * sb.append("\t<TimeZoneDisplayName>").append(
313                     * getTimeZone().getDisplayName()).append( "</TimeZoneDisplayName>\n");
314                     */
315                    sb.append("\t<TimezoneGMTOffset>").append(
316                                    getTimeZone().getRawOffset() / 3600000).append(
317                                    "</TimezoneGMTOffset>\n");
318                    sb.append("\t<TimezoneDSTOffset>").append(
319                                    getTimeZone().getDSTSavings() / 3600000).append(
320                                    "</TimezoneDSTOffset>\n");
321                    sb.append("</GeoLocation>");
322                    return sb.toString();
323            }
324    
325            /**
326             * @see java.lang.Object#equals(Object)
327             */
328            public boolean equals(Object object) {
329                    if (this == object)
330                            return true;
331                    if (!(object instanceof GeoLocation))
332                            return false;
333                    GeoLocation geo = (GeoLocation) object;
334                    return Double.doubleToLongBits(latitude) == Double
335                                    .doubleToLongBits(geo.latitude)
336                                    && Double.doubleToLongBits(longitude) == Double
337                                                    .doubleToLongBits(geo.longitude)
338                                    && elevation == geo.elevation
339                                    && (locationName == null ? geo.locationName == null
340                                                    : locationName.equals(geo.locationName))
341                                    && (timeZone == null ? geo.timeZone == null : timeZone
342                                                    .equals(geo.timeZone));
343            }
344    
345            /**
346             * @see java.lang.Object#hashCode()
347             */
348            public int hashCode() {
349    
350                    int result = 17;
351                    long latLong = Double.doubleToLongBits(latitude);
352                    long lonLong = Double.doubleToLongBits(longitude);
353                    long elevLong = Double.doubleToLongBits(elevation);
354                    int latInt = (int) (latLong ^ (latLong >>> 32));
355                    int lonInt = (int) (lonLong ^ (lonLong >>> 32));
356                    int elevInt = (int) (elevLong ^ (elevLong >>> 32));
357                    result = 37 * result + getClass().hashCode();
358                    result += 37 * result + latInt;
359                    result += 37 * result + lonInt;
360                    result += 37 * result + elevInt;
361                    result += 37 * result
362                                    + (locationName == null ? 0 : locationName.hashCode());
363                    result += 37 * result + (timeZone == null ? 0 : timeZone.hashCode());
364                    return result;
365            }
366    
367            /**
368             * @see java.lang.Object#toString()
369             */
370            public String toString() {
371                    StringBuffer sb = new StringBuffer();
372                    sb.append("\nLocation Name:\t\t\t").append(getLocationName());
373                    sb.append("\nLatitude:\t\t\t").append(getLatitude()).append("°");
374                    sb.append("\nLongitude:\t\t\t").append(getLongitude()).append("°");
375                    sb.append("\nElevation:\t\t\t").append(getElevation())
376                                    .append(" Meters");
377                    sb.append("\nTimezone Name:\t\t\t").append(getTimeZone().getID());
378                    /*
379                     * sb.append("\nTimezone Display Name:\t\t").append(
380                     * getTimeZone().getDisplayName());
381                     */
382                    sb.append("\nTimezone GMT Offset:\t\t").append(
383                                    getTimeZone().getRawOffset() / 3600000);
384                    sb.append("\nTimezone DST Offset:\t\t").append(
385                                    getTimeZone().getDSTSavings() / 3600000);
386                    return sb.toString();
387            }
388    }