001: /*
002: * <copyright>
003: *
004: * Copyright 2001-2004 BBNT Solutions, LLC
005: * under sponsorship of the Defense Advanced Research Projects
006: * Agency (DARPA).
007: *
008: * You can redistribute this software and/or modify it under the
009: * terms of the Cougaar Open Source License as published on the
010: * Cougaar Open Source Website (www.cougaar.org).
011: *
012: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
013: * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
014: * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
015: * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
016: * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
017: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
018: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
019: * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
020: * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
021: * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
022: * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
023: *
024: * </copyright>
025: */
026: package org.cougaar.tools.csmart.util;
027:
028: import org.cougaar.planning.ldm.measure.Latitude;
029: import org.cougaar.planning.ldm.measure.Longitude;
030: import org.cougaar.planning.ldm.plan.Location;
031:
032: import java.io.DataInputStream;
033: import java.io.DataOutputStream;
034: import java.io.IOException;
035: import java.io.Serializable;
036:
037: /**
038: * Courtesy of <a href="http://openmap.bbn.com">Openmap</a><br>
039: * Written by Don Dietrick, BBNT
040: * <br>
041: * Encapsulates latitude and longitude coordinates in decimal degrees.
042: * Normalizes the internal representation of latitude and longitude.
043: * <p>
044: * <strong>Normalized Latitude:</strong><br>
045: * -90° <= φ <= 90°
046: * <p>
047: * <strong>Normalized Longitude:</strong><br>
048: * -180° <= λ <= 180°<br>
049: *
050: * Also supports conversion to/from Cougaar core <code>Latitude</code> and <code>Longitude</code>
051: *
052: * @see Location
053: */
054: public class LatLonPoint implements Location, Cloneable, Serializable {
055: // SOUTH_POLE <= phi <= NORTH_POLE
056: // -DATELINE <= lambda <= DATELINE
057: public static final float NORTH_POLE = 90.0f;
058: public static final float SOUTH_POLE = -NORTH_POLE;
059: public static final float DATELINE = 180.0f;
060: public static final float LON_RANGE = 360.0f;
061: // initialize to something sane
062: protected float lat_ = 0.0f;
063: protected float lon_ = 0.0f;
064:
065: /**
066: * Construct a default LatLonPoint.
067: */
068: public LatLonPoint() {
069: }
070:
071: /**
072: * Construct a LatLonPoint from raw float lat/lon in decimal degrees.
073: *
074: * @param lat latitude in decimal degrees
075: * @param lon longitude in decimal degrees
076: */
077: public LatLonPoint(float lat, float lon) {
078: lat_ = normalize_latitude(lat);
079: lon_ = wrap_longitude(lon);
080: radlat_ = EarthConstants.degToRad(lat_);
081: radlon_ = EarthConstants.degToRad(lon_);
082: }
083:
084: /**
085: * Creates a new <code>LatLonPoint</code> from the core measures
086: *
087: * @param lat a <code>Latitude</code>
088: * @param lon a <code>Longitude</code>
089: */
090: public LatLonPoint(Latitude lat, Longitude lon) {
091: lat_ = normalize_latitude((float) lat.getDegrees());
092: lon_ = wrap_longitude((float) lon.getDegrees());
093: radlat_ = EarthConstants.degToRad(lat_);
094: radlon_ = EarthConstants.degToRad(lon_);
095: }
096:
097: /**
098: * Construct a LatLonPoint from raw float lat/lon in radians.
099: *
100: * @param lat latitude in radians
101: * @param lon longitude in radians
102: * @param isRadian placeholder indicates radians
103: */
104: public LatLonPoint(float lat, float lon, boolean isRadian) {
105: radlat_ = lat;
106: radlon_ = lon;
107: lat_ = normalize_latitude(EarthConstants.radToDeg(radlat_));
108: lon_ = wrap_longitude(EarthConstants.radToDeg(radlon_));
109: }
110:
111: /**
112: * Copy construct a LatLonPoint.
113: *
114: * @param pt LatLonPoint
115: */
116: public LatLonPoint(LatLonPoint pt) {
117: lat_ = pt.lat_;
118: lon_ = pt.lon_;
119: radlat_ = pt.radlat_;
120: radlon_ = pt.radlon_;
121: }
122:
123: /**
124: * Construct a LatLonPoint from raw double lat/lon.
125: *
126: * @param lat latitude in decimal degrees
127: * @param lon longitude in decimal degrees
128: */
129: public LatLonPoint(double lat, double lon) {
130: this ((float) lat, (float) lon);
131: }
132:
133: /* uncomment to see how many are being used and thrown away...
134: protected void finalize() {
135: Debug.output("finalized " + this);
136: }
137: */
138:
139: /**
140: * Returns a string representation of the object.
141: * @return String representation
142: */
143: public String toString() {
144: return "[Lat.=" + lat_ + ",Lon.=" + lon_ + "]";
145: }
146:
147: /**
148: * Clone the LatLonPoint.
149: * @return clone
150: */
151: public Object clone() {
152: try {
153: return super .clone();
154: } catch (CloneNotSupportedException e) {
155: // FIXME!!!! - something instead of assert...
156: //Assert.assert(false, "LatLonPoint: internal error!");
157: return null; // statement not reached
158: }
159: }
160:
161: /**
162: * Set latitude.
163: * @param lat latitude in decimal degrees
164: */
165: public void setLatitude(float lat) {
166: lat_ = normalize_latitude(lat);
167: radlat_ = EarthConstants.degToRad(lat_);
168: }
169:
170: /**
171: * Set latitude from core Latitude object.
172: * @param lat <code>Latitidue</code>
173: */
174: public void setLatitude(Latitude lat) {
175: lat_ = normalize_latitude((float) lat.getDegrees());
176: radlat_ = EarthConstants.degToRad(lat_);
177: }
178:
179: /**
180: * Set longitude.
181: * @param lon longitude in decimal degrees
182: */
183: public void setLongitude(float lon) {
184: lon_ = wrap_longitude(lon);
185: radlon_ = EarthConstants.degToRad(lon_);
186: }
187:
188: public void setLongitude(Longitude lon) {
189: lon_ = wrap_longitude((float) lon.getDegrees());
190: radlon_ = EarthConstants.degToRad(lon_);
191: }
192:
193: /**
194: * Set latitude and longitude.
195: * @param lat latitude in decimal degrees
196: * @param lon longitude in decimal degrees
197: */
198: public void setLatLon(float lat, float lon) {
199: lat_ = normalize_latitude(lat);
200: lon_ = wrap_longitude(lon);
201: radlat_ = EarthConstants.degToRad(lat_);
202: radlon_ = EarthConstants.degToRad(lon_);
203: }
204:
205: /**
206: * Set latitude and longitude.
207: * @param lat latitude in radians
208: * @param lon longitude in radians
209: * @param isRadian placeholder indicates radians
210: */
211: public void setLatLon(float lat, float lon, boolean isRadian) {
212: radlat_ = lat;
213: radlon_ = lon;
214: lat_ = normalize_latitude(EarthConstants.radToDeg(radlat_));
215: lon_ = wrap_longitude(EarthConstants.radToDeg(radlon_));
216: }
217:
218: /**
219: * Set LatLonPoint.
220: * @param llpt LatLonPoint
221: */
222: public void setLatLon(LatLonPoint llpt) {
223: lat_ = llpt.lat_;
224: lon_ = llpt.lon_;
225: radlat_ = EarthConstants.degToRad(lat_);
226: radlon_ = EarthConstants.degToRad(lon_);
227: }
228:
229: /**
230: * Get normalized latitude.
231: * @return float latitude in decimal degrees
232: * (-90° <= φ <= 90°)
233: */
234: public float getLatitude() {
235: return lat_;
236: }
237:
238: /**
239: * Get normalized Latitude.
240: */
241: public Latitude getLatitudeObject() {
242: return Latitude.newLatitude(lat_);
243: }
244:
245: /**
246: * Get wrapped longitude.
247: * @return float longitude in decimal degrees
248: * (-180° <= λ <= 180°)
249: */
250: public float getLongitude() {
251: return lon_;
252: }
253:
254: /**
255: * Get wrapped longitude.
256: * @return Longitude
257: */
258: public Longitude getLongitudeObject() {
259: return Longitude.newLongitude(lon_);
260: }
261:
262: /**
263: * Determines whether two LatLonPoints are equal.
264: * @param obj Object
265: * @return Whether the two points are equal up to a tolerance of
266: * 10<sup>-5</sup> degrees in latitude and longitude.
267: */
268: public boolean equals(Object obj) {
269: final float TOLERANCE = 0.00001f;
270: if (obj instanceof LatLonPoint) {
271: LatLonPoint pt = (LatLonPoint) obj;
272: return (EarthConstants.approximately_equal(lat_, pt.lat_,
273: TOLERANCE) && EarthConstants.approximately_equal(
274: lon_, pt.lon_, TOLERANCE));
275: }
276: return false;
277: }
278:
279: /**
280: * Hash the lat/lon value.
281: * <p>
282: * @return int hash value
283: */
284: public int hashCode() {
285: return EarthConstants.hashLatLon(lat_, lon_);
286: }
287:
288: /**
289: * Write object.
290: * @param s DataOutputStream
291: */
292: public void write(DataOutputStream s) throws IOException {
293: // Write my information
294: s.writeFloat(lat_);
295: s.writeFloat(lon_);
296: }
297:
298: /**
299: * Read object.
300: * @param s DataInputStream
301: */
302: public void read(DataInputStream s) throws IOException {
303: // HMMM. do we really need to be safe here?
304: lat_ = normalize_latitude(s.readFloat());
305: lon_ = wrap_longitude(s.readFloat());
306: radlat_ = EarthConstants.degToRad(lat_);
307: radlon_ = EarthConstants.degToRad(lon_);
308: }
309:
310: /**
311: * Sets latitude to something sane.
312: * @param lat latitude in decimal degrees
313: * @return float normalized latitude in decimal degrees
314: * (-90° <= φ <= 90°)
315: */
316: public static final float normalize_latitude(float lat) {
317: if (lat > NORTH_POLE) {
318: lat = NORTH_POLE;
319: }
320: if (lat < SOUTH_POLE) {
321: lat = SOUTH_POLE;
322: }
323: return lat;
324: }
325:
326: /**
327: * Sets longitude to something sane.
328: * @param lon longitude in decimal degrees
329: * @return float wrapped longitude in decimal degrees
330: * (-180° <= λ <= 180°)
331: */
332: public static final float wrap_longitude(float lon) {
333: if ((lon < -DATELINE) || (lon > DATELINE)) {
334: //System.out.print("LatLonPoint: wrapping longitude " + lon);
335: lon += DATELINE;
336: lon = lon % LON_RANGE;
337: lon = (lon < 0) ? DATELINE + lon : -DATELINE + lon;
338: //Debug.output(" to " + lon);
339: }
340: return lon;
341: }
342:
343: /**
344: * Check if latitude is bogus.
345: * Latitude is invalid if lat > 90° or if lat < -90°.
346: * @param lat latitude in decimal degrees
347: * @return boolean true if latitude is invalid
348: */
349: public static boolean isInvalidLatitude(float lat) {
350: return ((lat > NORTH_POLE) || (lat < SOUTH_POLE));
351: }
352:
353: /**
354: * Check if longitude is bogus.
355: * Longitude is invalid if lon > 180° or if lon < -180°.
356: * @param lon longitude in decimal degrees
357: * @return boolean true if longitude is invalid
358: */
359: public static boolean isInvalidLongitude(float lon) {
360: return ((lon < -DATELINE) || (lon > DATELINE));
361: }
362:
363: /**
364: * Calculate the <code>radlat_</code> and <code>radlon_</code>
365: * instance variables upon deserialization.
366: * Also, check <code>lat_</code> and <code>lon_</code> for safety;
367: * someone may have tampered with the stream.
368: * @param stream Stream to read <code>lat_</code> and <code>lon_</code> from.
369: */
370: private void readObject(java.io.ObjectInputStream stream)
371: throws IOException, ClassNotFoundException {
372: stream.defaultReadObject();
373: lat_ = normalize_latitude(lat_);
374: lon_ = wrap_longitude(lon_);
375: radlat_ = EarthConstants.degToRad(lat_);
376: radlon_ = EarthConstants.degToRad(lon_);
377: }
378:
379: /**
380: * Used by the projection code for read-only quick access.
381: * This is meant for quick backdoor access by the projection library.
382: * Modify at your own risk!
383: * @see #lat_
384: */
385: public transient float radlat_ = 0.0f;
386: /**
387: * Used by the projection code for read-only quick access.
388: * This is meant for quick backdoor access by the projection library.
389: * Modify at your own risk!
390: * @see #lon_
391: */
392: public transient float radlon_ = 0.0f;
393: } // LatLonPoint.java
|