1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package triptracker.core;
21
22 import static java.lang.Math.*;
23 import java.util.Date;
24 import java.util.Iterator;
25 import java.util.List;
26
27 /***
28 * Represents a coordinate in a latitude/longitude coordinate system on a
29 * given time and date. Data in a Coordinate object is immutable.
30 * <p>
31 * Latitude is numbered from 0 degrees to 90 degrees north and south. 0 degrees
32 * is the equator, 90 degrees north is the North Pole and 90 degrees south is
33 * the South Pole. The <code>Coordinate</code> class numbers latitudes from -90
34 * degrees to 90 degrees where -90 degrees is the South Pole and 90 degrees is
35 * the North Pole.
36 * <p>
37 * Longitude is numbered from 0 degrees to 180 degrees east and west. 0 degrees
38 * is at Greenwich, England and they continue 180 degrees east and 180 degrees
39 * west where they meet at the International Date Line in the Pacific Ocean. The
40 * <code>Coordinate</code> class numbers longitude from -180 degrees to 180
41 * degrees where negative degrees are west and positive degrees are east of
42 * Greenwich, England at 0 degrees.
43 * <p>
44 * To precisely locate points on the earth's surface, degrees longitude and
45 * latitude have been divided into minutes (') and seconds ("). There are 60
46 * minutes in each degree and each minute is divided into 60 seconds. Seconds can
47 * be further divided into tenths, hundredths, or even thousandths.
48 */
49 public class Coordinate {
50 /***
51 * Earth radius.
52 * See <a href="http://www.movable-type.co.uk/scripts/GIS-FAQ-5.1.html">
53 * http://www.movable-type.co.uk/scripts/GIS-FAQ-5.1.html</a>
54 * section 5.1a or
55 * <a href="http://www.movable-type.co.uk/scripts/LatLong.html">
56 * http://www.movable-type.co.uk/scripts/LatLong.html</a>.
57 */
58 public static final double EARTH_RADIUS = 6366.7070194937075;
59
60
61 /***
62 * See <a href="http://www.movable-type.co.uk/scripts/LatLongVincenty.html">
63 * http://www.movable-type.co.uk/scripts/LatLongVincenty.html</a>
64 */
65 public static final double WGS84_A = 6378137;
66 public static final double WGS84_B = 6356752.3142;
67 public static final double WGS84_F = 1 / 298.257223563;
68
69 private int coordId;
70 private final double ypos;
71 private final double xpos;
72 private final Date date;
73
74 /***
75 * Default constructor. Creates a coordinate with x=0 and y=0.
76 */
77 public Coordinate() {
78 this(-1, 0, 0, null);
79 }
80
81 /***
82 * Creates a coordinate with the given latitude and longitude. The values of
83 * latitude and longitude can be retrieved by calling {@link #getY()} and
84 * {@link #getX()} respectively.
85 *
86 * @param latitude latitude from 90 degrees (north) to -90 degrees (south)
87 * @param longitude longitude from 180 degrees (east) to -180 degrees (west)
88 * @throws IllegalArgumentException if latitude or longitude is out of
89 * range.
90 */
91 public Coordinate(final double latitude, final double longitude) {
92 this(-1, latitude, longitude, null);
93 }
94
95 /***
96 * Creates a coordinate with the given latitude, longitude and date. The
97 * values of latitude and longitude can be retrieved by calling
98 * {@link #getY()} and {@link #getX()} respectively.
99 *
100 * @param latitude latitude from 90 degrees (north) to -90 degrees (south)
101 * @param longitude longitude from 180 degrees (east) to -180 degrees (west)
102 * @param date time associated with coordinate
103 * @throws IllegalArgumentException if latitude or longitude is out of
104 * range.
105 */
106 public Coordinate(final double latitude, final double longitude,
107 final Date date) {
108 this(-1, latitude, longitude, date);
109 }
110
111 public Coordinate(final int coordId, final double latitude,
112 final double longitude) {
113 this(coordId, latitude, longitude, null);
114 }
115
116 public Coordinate(final int coordId, final double latitude,
117 final double longitude, final Date date) {
118
119 if (latitude > 90 || latitude < -90) {
120 throw new IllegalArgumentException(
121 "latitude not in range -90 to 90");
122 } else if (longitude > 180 || longitude < -180) {
123 throw new IllegalArgumentException(
124 "longitude not in range -180 to 180");
125 }
126
127 this.coordId = coordId;
128 this.ypos = latitude;
129 this.xpos = longitude;
130 this.date = date;
131 }
132
133
134 /***
135 * Returns the latitude position of the coordinate.
136 *
137 * @return latitude position of the coordinate
138 */
139 public double getY() {
140 return ypos;
141
142
143 }
144
145 /***
146 * Returns the longitude position of the coordinate.
147 *
148 * @return longitude position of the coordinate
149 */
150 public double getX() {
151
152
153 return xpos;
154 }
155
156 /***
157 * Returns the time and date associated with the coordinate.
158 *
159 * @return time and date associated with coordinate
160 */
161 public String getDateString() {
162 if (date == null) {
163 return "";
164 } else {
165 return Utils.dateToString(date);
166 }
167 }
168
169
170 /***
171 * Returns the string representation of a latitude/longitude coordinate.
172 * Example: 38?53'23.83"N 77?00'27.76"W
173 *
174 * @return string representation of a latitude/longitude coordinate
175 * @see java.lang.Object#toString()
176 */
177 @Override
178 public String toString() {
179 StringBuilder str = new StringBuilder();
180
181
182 str.append(ypos);
183 if (ypos < 0) {
184 str.append("S");
185 } else {
186 str.append("N");
187 }
188
189
190 str.append(" " + xpos);
191 if (xpos < 0) {
192 str.append("W");
193 } else {
194 str.append("E");
195 }
196
197
198 str.append(", " + date.toString());
199
200 return str.toString();
201 }
202
203 /***
204 * Returns a string representation of the latitude
205 *
206 * @return string representation of latitude
207 */
208 public String getStringLat() {
209 StringBuilder str = new StringBuilder();
210
211
212 str.append(Sexagesimal.toString(ypos));
213 if (ypos < 0)
214 str.append(" S");
215 else
216 str.append(" N");
217
218 return str.toString();
219 }
220
221 /***
222 * Returns a string representation of the longitude
223 *
224 * @return string representation of longitude
225 */
226 public String getStringLon() {
227 StringBuilder str = new StringBuilder();
228
229
230 str.append(Sexagesimal.toString(xpos));
231 if (xpos < 0) {
232 str.append(" W");
233 } else {
234 str.append(" E");
235 }
236
237 return str.toString();
238 }
239
240 /***
241 * Converts degrees in sexagesimal format to decimal format.
242 *
243 * @param sex sexagesimal formatted angle
244 * @return degrees in decimal format
245 */
246 public static double sexToDec(final Sexagesimal sex) {
247 return sexToDec(sex.getDeg(), sex.getMin(), sex.getSec());
248 }
249
250 /***
251 * Converts degrees in sexagesimal format to decimal format.
252 *
253 * @param degrees whole degrees
254 * @param minutes whole minutes
255 * @param seconds whole or fractional seconds
256 * @return degrees in decimal format
257 */
258 public static double sexToDec(final int degrees, final int minutes,
259 final double seconds) {
260 return (degrees + ((double)minutes / 60) + (seconds / 3600));
261 }
262
263 /***
264 * Converts degrees in decimal format to NMEA sexagesimal format.
265 *
266 * @param degrees decimal degrees
267 * @return degrees in NMEAs sexagesimal format with degrees and minutes in
268 * front of the decimal separator and seconds (in 0-99) after in full
269 * decimal format after.
270 */
271 public static double decToNmea(double degrees) {
272 int deg = (int)degrees;
273 degrees = (degrees - deg) * 60;
274
275 int min = (int)degrees;
276 degrees = degrees - min;
277
278 return ((deg * 100) + min + degrees);
279 }
280
281 /***
282 * Converts degrees in decimal format to NMEA sexagesimal format. This is
283 * the old bugged conversion.
284 *
285 * @deprecated Replaced by {@link #decToNmea(double)} to correct a
286 * calculation error.
287 * @param degrees decimal degrees
288 * @return degrees in NMEAs sexagesimal format with degrees and minutes in
289 * front of the decimal separator and seconds (in 0-99) after in full
290 * decimal format after.
291 */
292 @Deprecated
293 public static double decOldToNmea(double degrees) {
294 int deg = (int)degrees;
295 degrees = (degrees - deg) * 60;
296
297 int min = (int)degrees;
298 degrees = degrees - min;
299
300 double sec = degrees * 60;
301
302 return Utils.round((deg * 100) + min + (sec / 100), 4);
303 }
304
305 /***
306 * Converts degrees in NMEA sexagesimal format to decimal format.
307 *
308 * @param degrees in NMEA sexagesimal format with degrees and minutes in
309 * front of the decimal separator and seconds after
310 * @return degrees in decimal format
311 */
312 public static double nmeaToDec(double degrees) {
313
314 degrees = degrees / 100;
315 int deg = (int)degrees;
316 degrees = degrees - deg;
317
318 degrees = degrees * 100;
319 int min = (int)degrees;
320 degrees = degrees - min;
321
322 double sec = degrees * 60;
323
324 return sexToDec(deg, min, sec);
325 }
326
327 /***
328 * Converts degrees in NMEA sexagesimal format to decimal format.
329 *
330 * @deprecated Replaced by {@link #nmeaToDec(double)} to correct a
331 * calculation error.
332 * @param degrees in NMEA sexagesimal format with degrees and minutes in
333 * front of the decimal separator and seconds after
334 * @return degrees in decimal format
335 */
336 @Deprecated
337 public static double nmeaOldToDec(double degrees) {
338
339 degrees = degrees / 100;
340 int deg = (int)degrees;
341 degrees = degrees - deg;
342
343 degrees = degrees * 100;
344 int min = (int)degrees;
345 degrees = degrees - min;
346
347 double sec = degrees * 100;
348
349 return sexToDec(deg, min, sec);
350 }
351
352 /***
353 * Returns the Haversine distance from this to another coordinate.
354 *
355 * @param coord destination coordinate
356 * @return Haversine distance
357 * @see #haversineDist(double, double, double, double)
358 */
359 public double haversineDist(final Coordinate coord) {
360 return haversineDist(this, coord);
361 }
362
363 /***
364 * Returns the Haversine distance between two points.
365 *
366 * @param coord1 first coordinate
367 * @param coord2 secound coordinate
368 * @return Haversine distance between two points
369 * @see #haversineDist(double, double, double, double)
370 */
371 public static double haversineDist(final Coordinate coord1,
372 final Coordinate coord2) {
373 return haversineDist(coord1.getY(), coord1.getX(),
374 coord2.getY(), coord2.getX());
375 }
376
377 /***
378 * Returns the Haversine distance between two points. See
379 * <a href="http://en.wikipedia.org/wiki/Haversine_formula">Wikipedia</a> or
380 * <a href="http://www.movable-type.co.uk/scripts/GIS-FAQ-5.1.html">
381 * GIS-FAQ</a> for more in-depth information on the formula.
382 *
383 * @param lat1 latitude of first coordinate
384 * @param lon1 longitude of first coordinate
385 * @param lat2 latitude of second coordinate
386 * @param lon2 longitude of second coordinate
387 * @return Haversine distance between the two coordinates
388 */
389 public static double haversineDist(final double lat1, final double lon1,
390 final double lat2, final double lon2) {
391 final double la1 = toRadians(lat1);
392 final double la2 = toRadians(lat2);
393 final double lo1 = toRadians(lon1);
394 final double lo2 = toRadians(lon2);
395
396 final double dLat = la2 - la1;
397 final double dLong = lo2 - lo1;
398
399 final double a = sin(dLat / 2) * sin(dLat / 2)
400 + cos(la1) * cos(la2) * sin(dLong / 2) * sin(dLong / 2);
401
402 return (2 * atan2(sqrt(a), sqrt(1 - a)) * EARTH_RADIUS);
403 }
404
405 /***
406 * Returns the sequential Haverside distance between the coordinates. The
407 * method takes the distance between each consecutive coordinate and adds it
408 * together. Because of the sequencing of calculations, the order of the
409 * coordinates in the list determines the total distance.
410 *
411 * @param coords list of coordinates used for distance calculation
412 * @return total distance between all coordinates
413 * @see #haversineDist(double, double, double, double)
414 */
415 public static double haversineDist(final List<Coordinate> coords) {
416 double dist = 0;
417
418 Iterator<Coordinate> iter = coords.iterator();
419 Coordinate coord, lastCoord;
420
421 if (iter.hasNext()) {
422 coord = iter.next();
423 } else {
424 return 0;
425 }
426
427 while (iter.hasNext()) {
428 lastCoord = coord;
429 coord = iter.next();
430 dist += lastCoord.haversineDist(coord);
431
432
433 }
434
435 return dist;
436 }
437
438 /***
439 * Returns the distance between two points according to the law of cosines.
440 * Use of this method of distance calculation is HIGHLY DISCOURAGED as it is
441 * unreliable for small distances because the inverse cosine is
442 * ill-conditioned.
443 *
444 * @param coord1 first coordinate
445 * @param coord2 secound coordinate
446 * @return cosine distance between two points
447 */
448 public static double cosineDist(final Coordinate coord1,
449 final Coordinate coord2) {
450 final double la1 = toRadians(coord1.getY());
451 final double la2 = toRadians(coord2.getY());
452 final double lo1 = toRadians(coord1.getX());
453 final double lo2 = toRadians(coord2.getX());
454
455 return (acos(sin(la1) * sin(la2)
456 + cos(la1) * cos(la2) * cos(lo2 - lo1)) * EARTH_RADIUS);
457 }
458
459 /***
460 * Converts from lat/lon to a UTMpoint containting northing and easting plus
461 * zone with zone letter
462 *
463 * @param ellipsoidId ellipsoid to use (i.e. WGS84 = 22)
464 * @param lat points latitude
465 * @param lon points longitude
466 * @return UTMPoint with northing/easting + zone and zone letter
467 */
468 public static UTMPoint llToUTM(final int ellipsoidId, final double lat,
469 final double lon) {
470 final Ellipsoid ellipsoid = ReferenceEllipsoids
471 .getEllipsoid(ellipsoidId);
472
473 final double const1 = 0.017453292519943295;
474 final double const2 = 0.99960000000000004;
475
476
477 final double radius = ellipsoid.getRadius();
478 final double eccsq = ellipsoid.getEccentricity();
479
480 final double eccsq2 = eccsq * eccsq;
481 final double eccsq3 = eccsq2 * eccsq;
482
483 final double d12 = (lon + 180) - ((int)((lon + 180) / 360) * 360) - 180;
484 final double d13 = lat * const1;
485
486 final int zoneNumber = decideZoneNum(d12, lat);
487
488 final double d5 = ((zoneNumber - 1) * 6 - 180) + 3;
489
490 final double d14 = d12 * const1;
491 final double d15 = d5 * const1;
492
493 final double d6 = eccsq / (1 - eccsq);
494 final double d7 = radius / sqrt(1 - eccsq * sin(d13) * sin(d13));
495 final double d8 = tan(d13) * tan(d13);
496 final double d9 = d6 * cos(d13) * cos(d13);
497 final double d10 = cos(d13) * (d14 - d15);
498 final double d11 = radius
499 * ((((1 - eccsq / 4 - (3 * eccsq2) / 64 - (5 * eccsq3) / 256)
500 * d13 - ((3 * eccsq) / 8 + (3 * eccsq2) / 32
501 + (45 * eccsq3) / 1024) * sin(2 * d13))
502 + ((15 * eccsq2) / 256 + (45 * eccsq3) / 1024) * sin(4 * d13))
503 - ((35 * eccsq3) / 3072) * sin(6 * d13));
504 final double easting = (const2 * d7 * (d10
505 + (((1 - d8) + d9) * pow(d10, 3)) / 6
506 + ((((5 - 18 * d8) + d8 * d8 + 72 * d9) - 58 * d6)
507 * pow(d10, 5)) / 120) + 500000);
508 double northing = (const2 * (d11 + d7 * tan(d13) * ((d10 * d10) / 2
509 + (((5 - d8) + 9 * d9 + 4 * d9 * d9) * pow(d10, 4)) / 24
510 + ((((3 * d8) + d8 * d8 + 600 * d9) - 330 * d6)
511 * pow(d10, 6)) / 720)));
512
513 if (lat < 0) {
514 northing += 1E+007;
515 }
516 return new UTMPoint(northing, easting, zoneNumber,
517 getUTMZoneLetter(lat));
518 }
519
520 /***
521 * Finds the given zone's letter (most of Norway is 32<b>V</b>)
522 *
523 * @param latitude decide zone letter from this value
524 * @return zone letter.
525 */
526 private static char getUTMZoneLetter(final double latitude) {
527 char zoneLetter = 'Z';
528 if (84 >= latitude && latitude >= 72) {
529 zoneLetter = 'X';
530 } else if (72 > latitude && latitude >= 64) {
531 zoneLetter = 'W';
532 } else if (64 > latitude && latitude >= 56) {
533 zoneLetter = 'V';
534 } else if (56 > latitude && latitude >= 48) {
535 zoneLetter = 'U';
536 } else if (48 > latitude && latitude >= 40) {
537 zoneLetter = 'T';
538 } else if (40 > latitude && latitude >= 32) {
539 zoneLetter = 'S';
540 } else if (32 > latitude && latitude >= 24) {
541 zoneLetter = 'R';
542 } else if (24 > latitude && latitude >= 16) {
543 zoneLetter = 'Q';
544 } else if (16 > latitude && latitude >= 8) {
545 zoneLetter = 'P';
546 } else if (8 > latitude && latitude >= 0) {
547 zoneLetter = 'N';
548 } else if (0 > latitude && latitude >= -8) {
549 zoneLetter = 'M';
550 } else if (-8 > latitude && latitude >= -16) {
551 zoneLetter = 'L';
552 } else if (-16 > latitude && latitude >= -24) {
553 zoneLetter = 'K';
554 } else if (-24 > latitude && latitude >= -32) {
555 zoneLetter = 'J';
556 } else if (-32 > latitude && latitude >= -40) {
557 zoneLetter = 'H';
558 } else if (-40 > latitude && latitude >= -48) {
559 zoneLetter = 'G';
560 } else if (-48 > latitude && latitude >= -56) {
561 zoneLetter = 'F';
562 } else if (-56 > latitude && latitude >= -64) {
563 zoneLetter = 'E';
564 } else if (-64 > latitude && latitude >= -72) {
565 zoneLetter = 'D';
566 } else if (-72 > latitude && latitude >= -80) {
567 zoneLetter = 'C';
568 }
569
570 return zoneLetter;
571 }
572
573
574 public static Coordinate utmToLl(final int i, final UTMPoint utmpoint) {
575 return utmToLl(i, utmpoint.getNorthing(), utmpoint.getEasting(),
576 utmpoint.getZoneNumber(), utmpoint.getZoneLetter());
577 }
578
579 public static Coordinate utmToLl(final int ellipsoidId,
580 final double northing, final double easting, final int zoneNum,
581 final char zoneLetter) {
582 final double const1 = 57.295779513082323;
583 final double const2 = 0.99960000000000004;
584
585 if (zoneNum < 0 || zoneNum > 60) {
586 return null;
587 }
588
589 Ellipsoid ellipsoid = ReferenceEllipsoids.getEllipsoid(ellipsoidId);
590
591 final double radius = ellipsoid.getRadius();
592 final double eccsq = ellipsoid.getEccentricity();
593 final double d4 = (1 - sqrt(1 - eccsq)) / (1 + sqrt(1 - eccsq));
594
595 final double d4pow3 = pow(d4, 3);
596
597 double east = easting - 500000;
598 double north = northing;
599
600
601 if (zoneLetter >= 'N') {
602
603 } else {
604
605 north -= 10000000;
606 }
607
608 final double d11 = ((zoneNum - 1) * 6 - 180) + 3;
609 final double d3 = eccsq / (1 - eccsq);
610 final double d10 = north / const2;
611 final double d12 = d10 / (radius * (1 - eccsq / 4 - (3 * pow(eccsq, 2))
612 / 64 - (5 * pow(eccsq, 3)) / 256));
613 final double d14 = d12 + ((3 * d4) / 2 - (27 * d4pow3) / 32)
614 * sin(2 * d12) + ((21 * d4 * d4) / 16
615 - (55 * d4 * d4 * d4 * d4) / 32)
616 * sin(4 * d12) + ((151 * d4pow3) / 96) * sin(6 * d12);
617
618 final double d5 = radius / sqrt(1 - eccsq * sin(d14) * sin(d14));
619 final double d6 = tan(d14) * tan(d14);
620 final double d7 = d3 * cos(d14) * cos(d14);
621 final double d8 = (radius * (1 - eccsq)) / pow(1 - eccsq * sin(d14)
622 * sin(d14), 1.5);
623 final double d9 = east / (d5 * const2);
624
625 double d17 = d14 - ((d5 * tan(d14)) / d8) * (((d9 * d9) / 2
626 - (((5 + 3 * d6 + 10 * d7) - 4 * d7 * d7 - 9 * d3)
627 * pow(d9, 4)) / 24)
628 + (((61 + 90 * d6 + 298 * d7 + 45 * d6 * d6)
629 - 252 * d3 - 3 * d7 * d7) * pow(d9, 6)) / 720);
630 d17 *= const1;
631
632 double d18 = ((d9 - ((1 + 2 * d6 + d7) * pow(d9, 3)) / 6)
633 + (((((5 - 2 * d7) + 28 * d6) - 3 * d7 * d7)
634 + 8 * d3 + 24 * d6 * d6) * pow(d9, 5)) / 120) / cos(d14);
635 d18 = d11 + d18 * const1;
636
637 System.out.println("(TMP) lat " + d17 + ", lon: " + d18);
638
639
640
641
642 return new Coordinate(d17, d18);
643 }
644
645 /***
646 * Decides a UTM Zone Number for a given latitude and result.
647 *
648 * @param result result
649 * @param latitude latitude
650 * @return the zonenumber
651 */
652 public static int decideZoneNum(final double result,
653 final double latitude) {
654
655
656 int zoneNumber = (int)((result + 180) / 6) + 1;
657
658 if (latitude >= 56 && latitude < 64 && result >= 3 && result < 12) {
659 zoneNumber = 32;
660 }
661
662 if (latitude >= 72 && latitude < 84) {
663 if (result >= 0 && result < 9) {
664 zoneNumber = 31;
665 } else if (result >= 9 && result < 21) {
666 zoneNumber = 33;
667 } else if (result >= 21 && result < 33) {
668 zoneNumber = 35;
669 } else if (result >= 33 && result < 42) {
670 zoneNumber = 37;
671 }
672 }
673 return zoneNumber;
674 }
675
676 /*** Returns id of coordinate */
677 public int getCoordId() {
678 return coordId;
679 }
680
681 /*** Returns date of coordinate */
682 public Date getDate() {
683 return date;
684 }
685 }
686