001: /*
002: *
003: *
004: * Copyright 1990-2007 Sun Microsystems, Inc. All Rights Reserved.
005: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
006: *
007: * This program is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU General Public License version
009: * 2 only, as published by the Free Software Foundation.
010: *
011: * This program is distributed in the hope that it will be useful, but
012: * WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * General Public License version 2 for more details (a copy is
015: * included at /legal/license.txt).
016: *
017: * You should have received a copy of the GNU General Public License
018: * version 2 along with this work; if not, write to the Free Software
019: * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
020: * 02110-1301 USA
021: *
022: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
023: * Clara, CA 95054 or visit www.sun.com if you need additional
024: * information or have any questions.
025: */
026: package javax.microedition.location;
027:
028: import com.sun.j2me.location.*;
029:
030: /**
031: * This class is defined by the JSR-179 specification
032: * <em>Location API for J2ME for J2ME™.</em>
033: */
034: // JAVADOC COMMENT ELIDED
035: public class Coordinates {
036: // JAVADOC COMMENT ELIDED
037: public final static int DD_MM_SS = 1;
038: // JAVADOC COMMENT ELIDED
039: public final static int DD_MM = 2;
040: // JAVADOC COMMENT ELIDED
041: private double latitude;
042: // JAVADOC COMMENT ELIDED
043: private double longitude;
044: // JAVADOC COMMENT ELIDED
045: private float altitude;
046:
047: // JAVADOC COMMENT ELIDED
048: static final double EARTH_RADIUS = 6378137D;
049: // JAVADOC COMMENT ELIDED
050: static final double FLATTENING = 298.257223563D;
051: // JAVADOC COMMENT ELIDED
052: static final double DEG2RAD = 0.01745329252D;
053: // JAVADOC COMMENT ELIDED
054: private float azimuth;
055: // JAVADOC COMMENT ELIDED
056: private float distance;
057:
058: // JAVADOC COMMENT ELIDED
059: public Coordinates(double latitude, double longitude, float altitude) {
060: setLatitude(latitude);
061: setLongitude(longitude);
062: this .altitude = altitude;
063: }
064:
065: // JAVADOC COMMENT ELIDED
066: public double getLatitude() {
067: return latitude;
068: }
069:
070: // JAVADOC COMMENT ELIDED
071: public double getLongitude() {
072: return longitude;
073: }
074:
075: // JAVADOC COMMENT ELIDED
076: public float getAltitude() {
077: return altitude;
078: }
079:
080: // JAVADOC COMMENT ELIDED
081: public void setAltitude(float altitude) {
082: this .altitude = altitude;
083: }
084:
085: // JAVADOC COMMENT ELIDED
086: public void setLatitude(double latitude) {
087: Util.checkRange(latitude, -90, 90,
088: "Latitude out of range [-90.0, 90]: ");
089: this .latitude = latitude;
090: }
091:
092: // JAVADOC COMMENT ELIDED
093: public void setLongitude(double longitude) {
094: Util.checkRange(longitude, -180, 180,
095: "Longitude out of range [-180.0, 180): ");
096: if (longitude == 180) {
097: throw new IllegalArgumentException(
098: "Longitude out of range [-180.0, 180): "
099: + longitude);
100: }
101: this .longitude = longitude;
102: }
103:
104: // JAVADOC COMMENT ELIDED
105: public static double convert(String coordinate) {
106: if (coordinate == null) {
107: throw new NullPointerException("Null string specified");
108: }
109: // tokenize the coordianates to 2 or 3 elements
110: if (coordinate.startsWith("0")
111: && (!coordinate.startsWith("0:"))) {
112: throw new IllegalArgumentException(
113: "A coordinate cannot start with a 0 with two digits");
114: }
115: double[] coordinates = new double[] { Double.NaN, Double.NaN, 0 };
116: int next = -1;
117: int current = 0;
118: do {
119: if (current > 2) {
120: throw new IllegalArgumentException(
121: "Invalid coordinate format");
122: }
123: int position = next + 1;
124: next = coordinate.indexOf(':', position);
125: String currentText;
126: if (next > -1) {
127: currentText = coordinate.substring(position, next);
128: try {
129: coordinates[current] = Double
130: .parseDouble(currentText);
131: } catch (NumberFormatException e) {
132: throw new IllegalArgumentException(
133: "Invalid coordinate format: "
134: + e.getMessage());
135: }
136:
137: // only the last coordinate may be a fracture
138: if ((long) coordinates[current] != coordinates[current]) {
139: throw new IllegalArgumentException(
140: "Only the last coordinate may be a fracture: "
141: + coordinate);
142: }
143: } else {
144: currentText = coordinate.substring(position, coordinate
145: .length());
146: try {
147: coordinates[current] = Double
148: .parseDouble(currentText);
149: } catch (NumberFormatException e) {
150: throw new IllegalArgumentException(
151: "Invalid coordinate format: "
152: + e.getMessage());
153: }
154: }
155: if (currentText.startsWith("+")) {
156: throw new IllegalArgumentException(
157: "Coordinate should not use 'plus' sign :"
158: + currentText);
159: }
160: if (current > 0) {
161: int pos = currentText.indexOf('.');
162: if (pos > -1) {
163: if (pos != 2 || currentText.length() < 4) {
164: throw new IllegalArgumentException(
165: "Invalid coordinate format");
166: }
167: if (current != 2) {
168: if (currentText.length() - pos > 6) {
169: throw new IllegalArgumentException(
170: "Invalid coordinate format");
171: }
172: } else {
173: if (currentText.length() - pos > 4) {
174: throw new IllegalArgumentException(
175: "Invalid coordinate format");
176: }
177: }
178: } else {
179: if (currentText.length() != 2) {
180: throw new IllegalArgumentException(
181: "Invalid coordinate format");
182: }
183: }
184: }
185: if (currentText.endsWith(".")) {
186: throw new IllegalArgumentException(
187: "Invalid coordinate format");
188:
189: }
190: current++;
191: } while (next > -1);
192:
193: // special case for 180 when the degrees is -180 and the
194: // minutes, seconds and decimal fractions are 0
195: if (coordinates[0] != -180) {
196: Util.checkRange(coordinates[0], -179, 179,
197: "Degrees out of range [-179.0, 179]: ");
198: Util.checkRange(coordinates[1], 0, 60,
199: "Minutes out of range [0, 59]: ");
200: Util.checkRange(coordinates[2], 0, 60,
201: "Seconds out of range [0, 59]: ");
202: if (coordinates[1] == 60) {
203: throw new IllegalArgumentException(
204: "Minutes out of range [0, 59]: 60");
205: }
206: if (coordinates[2] == 60) {
207: throw new IllegalArgumentException(
208: "Seconds out of range [0, 59]: 60");
209: }
210: if (Double.isNaN(coordinates[1])) {
211: throw new IllegalArgumentException(
212: "Invalid coordinate format");
213: }
214: } else {
215: if (coordinates[1] != 0 || coordinates[2] != 0) {
216: throw new IllegalArgumentException(
217: "Invalid coordinate format");
218: }
219: }
220:
221: // convert the integer array to a numeric representation:
222: double value = coordinates[0];
223: if (!coordinate.startsWith("-")) {
224: value += coordinates[1] / 60 + coordinates[2] / 3600;
225: } else {
226: value -= coordinates[1] / 60 + coordinates[2] / 3600;
227: }
228: return value;
229: }
230:
231: /**
232: * Rounds a number to nearest whole value.
233: *
234: * @param value input data
235: * @return rounded value
236: */
237: private static double round(double value) {
238: long top = (long) value;
239: long bottom = (long) ((value - top) * 100000);
240: if (Math.abs(bottom % 10) >= 5) {
241: if (value > 0) {
242: bottom = bottom / 10 + 1;
243: } else {
244: bottom = bottom / 10 - 1;
245: }
246: } else {
247: bottom = bottom / 10;
248: }
249: value = top;
250: value += ((double) bottom) / 10000.0;
251: return value;
252: }
253:
254: // JAVADOC COMMENT ELIDED
255: public static String convert(double coordinate, int outputType) {
256: if (coordinate == 180 || coordinate == Double.NaN) {
257: throw new IllegalArgumentException(
258: "Coordinate out of range");
259: }
260: Util.checkRange(coordinate, -180, 180,
261: "Coordinate out of range [-180.0, 180): ");
262: StringBuffer buffer = new StringBuffer();
263: if (coordinate < 0) {
264: buffer.append("-");
265: }
266: coordinate = Math.abs(coordinate);
267: int deg = (int) coordinate;
268: buffer.append(deg);
269: buffer.append(":");
270: double dMin = (coordinate - deg) * 60D;
271: if (outputType == DD_MM_SS) {
272: int min1 = (int) dMin;
273: if (min1 == 60)
274: min1 = 59;
275: if (min1 < 10) {
276: buffer.append("0");
277: }
278: buffer.append(min1);
279: buffer.append(":");
280: double dSec = (dMin - min1) * 60D;
281: double sec1 = (double) (int) Math
282: .floor(1000D * dSec + 0.5D) / 1000D;
283: if (sec1 >= 60)
284: sec1 = 59.999;
285: if (sec1 < 10) {
286: buffer.append("0");
287: }
288: buffer.append(sec1);
289: } else {
290: if (outputType != DD_MM) {
291: throw new IllegalArgumentException(
292: "outputType must be either DD_MM or DD_MM_SS, "
293: + "instead we got: " + outputType);
294: }
295: double min2 = (double) (int) Math
296: .floor(100000D * dMin + 0.5D) / 100000D;
297: if (min2 >= 60)
298: min2 = 59.99999;
299: if (min2 < 10) {
300: buffer.append("0");
301: }
302: buffer.append(min2);
303: }
304: return buffer.toString();
305: }
306:
307: // JAVADOC COMMENT ELIDED
308: public float azimuthTo(Coordinates to) {
309: if (to == null) {
310: throw new NullPointerException("Null coordinates specified");
311: }
312: computeAzimuthAndDistance(latitude, longitude, to.latitude,
313: to.longitude);
314: return azimuth;
315: }
316:
317: // JAVADOC COMMENT ELIDED
318: public float distance(Coordinates to) {
319: if (to == null) {
320: throw new NullPointerException("Null coordinates specified");
321: }
322: computeAzimuthAndDistance(latitude, longitude, to.latitude,
323: to.longitude);
324: return distance;
325: }
326:
327: // JAVADOC COMMENT ELIDED
328: private void computeAzimuthAndDistance(double lat1, double long1,
329: double lat2, double long2) {
330: if (lat1 == lat2 && long1 == long2) {
331: azimuth = 0;
332: distance = 0;
333: return;
334: }
335: double c = 0.0;
336: double d = 0.0;
337: double e = 0.0;
338: double y = 0.0;
339: double sa = 0.0;
340: double sx = 0.0;
341: double sy = 0.0;
342: double cx = 0.0;
343: double cy = 0.0;
344: double cz = 0.0;
345: double c2a = 0.0;
346:
347: double f = 1.0D / FLATTENING; // Flattening factor
348:
349: // Initial values
350: double eps = 0.5E-13; // Tolerence
351: double glon1 = long1 * DEG2RAD;
352: double glat1 = lat1 * DEG2RAD;
353: double glon2 = long2 * DEG2RAD;
354: double glat2 = lat2 * DEG2RAD;
355:
356: double r = 1.0D - f;
357: double tu1 = r * Math.sin(glat1) / Math.cos(glat1);
358: double tu2 = r * Math.sin(glat2) / Math.cos(glat2);
359: double cu1 = 1 / Math.sqrt(1 + tu1 * tu1);
360: double su1 = cu1 * tu1;
361: double cu2 = 1 / Math.sqrt(1 + tu2 * tu2);
362: double s = cu1 * cu2;
363: double baz = s * tu2;
364: double faz = baz * tu1;
365: double x = glon2 - glon1;
366:
367: // Iterate
368: do {
369: sx = Math.sin(x);
370: cx = Math.cos(x);
371: tu1 = cu2 * sx;
372: tu2 = baz - su1 * cu2 * cx;
373: sy = Math.sqrt(tu1 * tu1 + tu2 * tu2);
374: cy = s * cx + faz;
375: y = atan2(sy, cy);
376: sa = s * sx / sy;
377: c2a = -sa * sa + 1;
378: cz = faz + faz;
379: if (c2a > 0) {
380: cz = -cz / c2a + cy;
381: }
382: e = cz * cz * 2 - 1;
383: c = ((-3 * c2a + 4) * f + 4) * c2a * f / 16;
384: d = x;
385: x = ((e * cy * c + cz) * sy * c + y) * sa;
386: x = (1 - c) * x * f + glon2 - glon1;
387: } while (Math.abs(d - x) > eps);
388:
389: // Finish up
390: faz = atan2(tu1, tu2);
391: azimuth = (float) (faz / DEG2RAD);
392: if (azimuth < 0) {
393: azimuth = azimuth + 360;
394: }
395: if (lat1 == 90D) {
396: azimuth = 180F;
397: } else if (lat1 == -90D) {
398: azimuth = 0.0F;
399: }
400: x = Math.sqrt((1 / r / r - 1) * c2a + 1) + 1;
401: x = (x - 2) / x;
402: c = 1 - x;
403: c = (x * x / 4 + 1) / c;
404: d = (0.375 * x * x - 1) * x;
405: x = e * cy;
406: s = 1 - 2 * e;
407: distance = (float) (((((sy * sy * 4 - 3) * s * cz * d / 6 - x)
408: * d / 4 + cz)
409: * sy * d + y)
410: * c * EARTH_RADIUS * r);
411: }
412:
413: // JAVADOC COMMENT ELIDED
414: private native double atan2(double x, double y);
415:
416: }
|