geo_calc 0.5.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,551 @@
1
+ /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
2
+ /* Latitude/longitude spherical geodesy formulae & scripts (c) Chris Veness 2002-2010 */
3
+ /* - www.movable-type.co.uk/scripts/latlong.html */
4
+ /* */
5
+ /* Sample usage: */
6
+ /* var p1 = new LatLon(51.5136, -0.0983); */
7
+ /* var p2 = new LatLon(51.4778, -0.0015); */
8
+ /* var dist = p1.distanceTo(p2); // in km */
9
+ /* var brng = p1.bearingTo(p2); // in degrees clockwise from north */
10
+ /* ... etc */
11
+ /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
12
+
13
+ /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
14
+ /* Note that minimal error checking is performed in this example code! */
15
+ /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
16
+
17
+
18
+ /**
19
+ * Creates a point on the earth's surface at the supplied latitude / longitude
20
+ *
21
+ * @constructor
22
+ * @param {Number} lat: latitude in numeric degrees
23
+ * @param {Number} lon: longitude in numeric degrees
24
+ * @param {Number} [rad=6371]: radius of earth if different value is required from standard 6,371km
25
+ */
26
+ function LatLon(lat, lon, rad) {
27
+ if (typeof(rad) == 'undefined') rad = 6371; // earth's mean radius in km
28
+ // only accept numbers or valid numeric strings
29
+ this._lat = typeof(lat)=='number' ? lat : typeof(lat)=='string' && lat.trim()!='' ? +lat : NaN;
30
+ this._lon = typeof(lat)=='number' ? lon : typeof(lon)=='string' && lon.trim()!='' ? +lon : NaN;
31
+ this._radius = typeof(rad)=='number' ? rad : typeof(rad)=='string' && trim(lon)!='' ? +rad : NaN;
32
+ }
33
+
34
+
35
+ /**
36
+ * Returns the distance from this point to the supplied point, in km
37
+ * (using Haversine formula)
38
+ *
39
+ * from: Haversine formula - R. W. Sinnott, "Virtues of the Haversine",
40
+ * Sky and Telescope, vol 68, no 2, 1984
41
+ *
42
+ * @param {LatLon} point: Latitude/longitude of destination point
43
+ * @param {Number} [precision=4]: no of significant digits to use for returned value
44
+ * @returns {Number} Distance in km between this point and destination point
45
+ */
46
+ LatLon.prototype.distanceTo = function(point, precision) {
47
+ // default 4 sig figs reflects typical 0.3% accuracy of spherical model
48
+ if (typeof precision == 'undefined') precision = 4;
49
+
50
+ var R = this._radius;
51
+ var lat1 = this._lat.toRad(), lon1 = this._lon.toRad();
52
+ var lat2 = point._lat.toRad(), lon2 = point._lon.toRad();
53
+ var dLat = lat2 - lat1;
54
+ var dLon = lon2 - lon1;
55
+
56
+ var a = Math.sin(dLat/2) * Math.sin(dLat/2) +
57
+ Math.cos(lat1) * Math.cos(lat2) *
58
+ Math.sin(dLon/2) * Math.sin(dLon/2);
59
+ var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
60
+ var d = R * c;
61
+ return d.toPrecisionFixed(precision);
62
+ }
63
+
64
+
65
+ /**
66
+ * Returns the (initial) bearing from this point to the supplied point, in degrees
67
+ * see http://williams.best.vwh.net/avform.htm#Crs
68
+ *
69
+ * @param {LatLon} point: Latitude/longitude of destination point
70
+ * @returns {Number} Initial bearing in degrees from North
71
+ */
72
+ LatLon.prototype.bearingTo = function(point) {
73
+ var lat1 = this._lat.toRad(), lat2 = point._lat.toRad();
74
+ var dLon = (point._lon-this._lon).toRad();
75
+
76
+ var y = Math.sin(dLon) * Math.cos(lat2);
77
+ var x = Math.cos(lat1)*Math.sin(lat2) -
78
+ Math.sin(lat1)*Math.cos(lat2)*Math.cos(dLon);
79
+ var brng = Math.atan2(y, x);
80
+
81
+ return (brng.toDeg()+360) % 360;
82
+ }
83
+
84
+
85
+ /**
86
+ * Returns final bearing arriving at supplied destination point from this point; the final bearing
87
+ * will differ from the initial bearing by varying degrees according to distance and latitude
88
+ *
89
+ * @param {LatLon} point: Latitude/longitude of destination point
90
+ * @returns {Number} Final bearing in degrees from North
91
+ */
92
+ LatLon.prototype.finalBearingTo = function(point) {
93
+ // get initial bearing from supplied point back to this point...
94
+ var lat1 = point._lat.toRad(), lat2 = this._lat.toRad();
95
+ var dLon = (this._lon-point._lon).toRad();
96
+
97
+ var y = Math.sin(dLon) * Math.cos(lat2);
98
+ var x = Math.cos(lat1)*Math.sin(lat2) -
99
+ Math.sin(lat1)*Math.cos(lat2)*Math.cos(dLon);
100
+ var brng = Math.atan2(y, x);
101
+
102
+ // ... & reverse it by adding 180°
103
+ return (brng.toDeg()+180) % 360;
104
+ }
105
+
106
+
107
+ /**
108
+ * Returns the midpoint between this point and the supplied point.
109
+ * see http://mathforum.org/library/drmath/view/51822.html for derivation
110
+ *
111
+ * @param {LatLon} point: Latitude/longitude of destination point
112
+ * @returns {LatLon} Midpoint between this point and the supplied point
113
+ */
114
+ LatLon.prototype.midpointTo = function(point) {
115
+ lat1 = this._lat.toRad(), lon1 = this._lon.toRad();
116
+ lat2 = point._lat.toRad();
117
+ var dLon = (point._lon-this._lon).toRad();
118
+
119
+ var Bx = Math.cos(lat2) * Math.cos(dLon);
120
+ var By = Math.cos(lat2) * Math.sin(dLon);
121
+
122
+ lat3 = Math.atan2(Math.sin(lat1)+Math.sin(lat2),
123
+ Math.sqrt( (Math.cos(lat1)+Bx)*(Math.cos(lat1)+Bx) + By*By) );
124
+ lon3 = lon1 + Math.atan2(By, Math.cos(lat1) + Bx);
125
+
126
+ return new LatLon(lat3.toDeg(), lon3.toDeg());
127
+ }
128
+
129
+
130
+ /**
131
+ * Returns the destination point from this point having travelled the given distance (in km) on the
132
+ * given initial bearing (bearing may vary before destination is reached)
133
+ *
134
+ * see http://williams.best.vwh.net/avform.htm#LL
135
+ *
136
+ * @param {Number} brng: Initial bearing in degrees
137
+ * @param {Number} dist: Distance in km
138
+ * @returns {LatLon} Destination point
139
+ */
140
+ LatLon.prototype.destinationPoint = function(brng, dist) {
141
+ dist = typeof(dist)=='number' ? dist : typeof(dist)=='string' && dist.trim()!='' ? +dist : NaN;
142
+ dist = dist/this._radius; // convert dist to angular distance in radians
143
+ brng = brng.toRad(); //
144
+ var lat1 = this._lat.toRad(), lon1 = this._lon.toRad();
145
+
146
+ var lat2 = Math.asin( Math.sin(lat1)*Math.cos(dist) +
147
+ Math.cos(lat1)*Math.sin(dist)*Math.cos(brng) );
148
+ var lon2 = lon1 + Math.atan2(Math.sin(brng)*Math.sin(dist)*Math.cos(lat1),
149
+ Math.cos(dist)-Math.sin(lat1)*Math.sin(lat2));
150
+ lon2 = (lon2+3*Math.PI)%(2*Math.PI) - Math.PI; // normalise to -180...+180
151
+
152
+ return new LatLon(lat2.toDeg(), lon2.toDeg());
153
+ }
154
+
155
+
156
+ /**
157
+ * Returns the point of intersection of two paths defined by point and bearing
158
+ *
159
+ * see http://williams.best.vwh.net/avform.htm#Intersection
160
+ *
161
+ * @param {LatLon} p1: First point
162
+ * @param {Number} brng1: Initial bearing from first point
163
+ * @param {LatLon} p2: Second point
164
+ * @param {Number} brng2: Initial bearing from second point
165
+ * @returns {LatLon} Destination point (null if no unique intersection defined)
166
+ */
167
+ LatLon.intersection = function(p1, brng1, p2, brng2) {
168
+ brng1 = typeof brng1 == 'number' ? brng1 : typeof brng1 == 'string' && trim(brng1)!='' ? +brng1 : NaN;
169
+ brng2 = typeof brng2 == 'number' ? brng2 : typeof brng2 == 'string' && trim(brng2)!='' ? +brng2 : NaN;
170
+ lat1 = p1._lat.toRad(), lon1 = p1._lon.toRad();
171
+ lat2 = p2._lat.toRad(), lon2 = p2._lon.toRad();
172
+ brng13 = brng1.toRad(), brng23 = brng2.toRad();
173
+ dLat = lat2-lat1, dLon = lon2-lon1;
174
+
175
+ dist12 = 2*Math.asin( Math.sqrt( Math.sin(dLat/2)*Math.sin(dLat/2) +
176
+ Math.cos(lat1)*Math.cos(lat2)*Math.sin(dLon/2)*Math.sin(dLon/2) ) );
177
+ if (dist12 == 0) return null;
178
+
179
+ // initial/final bearings between points
180
+ brngA = Math.acos( ( Math.sin(lat2) - Math.sin(lat1)*Math.cos(dist12) ) /
181
+ ( Math.sin(dist12)*Math.cos(lat1) ) );
182
+ if (isNaN(brngA)) brngA = 0; // protect against rounding
183
+ brngB = Math.acos( ( Math.sin(lat1) - Math.sin(lat2)*Math.cos(dist12) ) /
184
+ ( Math.sin(dist12)*Math.cos(lat2) ) );
185
+
186
+ if (Math.sin(lon2-lon1) > 0) {
187
+ brng12 = brngA;
188
+ brng21 = 2*Math.PI - brngB;
189
+ } else {
190
+ brng12 = 2*Math.PI - brngA;
191
+ brng21 = brngB;
192
+ }
193
+
194
+ alpha1 = (brng13 - brng12 + Math.PI) % (2*Math.PI) - Math.PI; // angle 2-1-3
195
+ alpha2 = (brng21 - brng23 + Math.PI) % (2*Math.PI) - Math.PI; // angle 1-2-3
196
+
197
+ if (Math.sin(alpha1)==0 && Math.sin(alpha2)==0) return null; // infinite intersections
198
+ if (Math.sin(alpha1)*Math.sin(alpha2) < 0) return null; // ambiguous intersection
199
+
200
+ //alpha1 = Math.abs(alpha1);
201
+ //alpha2 = Math.abs(alpha2);
202
+ // ... Ed Williams takes abs of alpha1/alpha2, but seems to break calculation?
203
+
204
+ alpha3 = Math.acos( -Math.cos(alpha1)*Math.cos(alpha2) +
205
+ Math.sin(alpha1)*Math.sin(alpha2)*Math.cos(dist12) );
206
+ dist13 = Math.atan2( Math.sin(dist12)*Math.sin(alpha1)*Math.sin(alpha2),
207
+ Math.cos(alpha2)+Math.cos(alpha1)*Math.cos(alpha3) )
208
+ lat3 = Math.asin( Math.sin(lat1)*Math.cos(dist13) +
209
+ Math.cos(lat1)*Math.sin(dist13)*Math.cos(brng13) );
210
+ dLon13 = Math.atan2( Math.sin(brng13)*Math.sin(dist13)*Math.cos(lat1),
211
+ Math.cos(dist13)-Math.sin(lat1)*Math.sin(lat3) );
212
+ lon3 = lon1+dLon13;
213
+ lon3 = (lon3+Math.PI) % (2*Math.PI) - Math.PI; // normalise to -180..180º
214
+
215
+ return new LatLon(lat3.toDeg(), lon3.toDeg());
216
+ }
217
+
218
+
219
+ /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
220
+
221
+ /**
222
+ * Returns the distance from this point to the supplied point, in km, travelling along a rhumb line
223
+ *
224
+ * see http://williams.best.vwh.net/avform.htm#Rhumb
225
+ *
226
+ * @param {LatLon} point: Latitude/longitude of destination point
227
+ * @returns {Number} Distance in km between this point and destination point
228
+ */
229
+ LatLon.prototype.rhumbDistanceTo = function(point) {
230
+ var R = this._radius;
231
+ var lat1 = this._lat.toRad(), lat2 = point._lat.toRad();
232
+ var dLat = (point._lat-this._lat).toRad();
233
+ var dLon = Math.abs(point._lon-this._lon).toRad();
234
+
235
+ var dPhi = Math.log(Math.tan(lat2/2+Math.PI/4)/Math.tan(lat1/2+Math.PI/4));
236
+ var q = (!isNaN(dLat/dPhi)) ? dLat/dPhi : Math.cos(lat1); // E-W line gives dPhi=0
237
+ // if dLon over 180° take shorter rhumb across 180° meridian:
238
+ if (dLon > Math.PI) dLon = 2*Math.PI - dLon;
239
+ var dist = Math.sqrt(dLat*dLat + q*q*dLon*dLon) * R;
240
+
241
+ return dist.toPrecisionFixed(4); // 4 sig figs reflects typical 0.3% accuracy of spherical model
242
+ }
243
+
244
+ /**
245
+ * Returns the bearing from this point to the supplied point along a rhumb line, in degrees
246
+ *
247
+ * @param {LatLon} point: Latitude/longitude of destination point
248
+ * @returns {Number} Bearing in degrees from North
249
+ */
250
+ LatLon.prototype.rhumbBearingTo = function(point) {
251
+ var lat1 = this._lat.toRad(), lat2 = point._lat.toRad();
252
+ var dLon = (point._lon-this._lon).toRad();
253
+
254
+ var dPhi = Math.log(Math.tan(lat2/2+Math.PI/4)/Math.tan(lat1/2+Math.PI/4));
255
+ if (Math.abs(dLon) > Math.PI) dLon = dLon>0 ? -(2*Math.PI-dLon) : (2*Math.PI+dLon);
256
+ var brng = Math.atan2(dLon, dPhi);
257
+
258
+ return (brng.toDeg()+360) % 360;
259
+ }
260
+
261
+ /**
262
+ * Returns the destination point from this point having travelled the given distance (in km) on the
263
+ * given bearing along a rhumb line
264
+ *
265
+ * @param {Number} brng: Bearing in degrees from North
266
+ * @param {Number} dist: Distance in km
267
+ * @returns {LatLon} Destination point
268
+ */
269
+ LatLon.prototype.rhumbDestinationPoint = function(brng, dist) {
270
+ var R = this._radius;
271
+ var d = parseFloat(dist)/R; // d = angular distance covered on earth's surface
272
+ var lat1 = this._lat.toRad(), lon1 = this._lon.toRad();
273
+ brng = brng.toRad();
274
+
275
+ var lat2 = lat1 + d*Math.cos(brng);
276
+ var dLat = lat2-lat1;
277
+ var dPhi = Math.log(Math.tan(lat2/2+Math.PI/4)/Math.tan(lat1/2+Math.PI/4));
278
+ var q = (!isNaN(dLat/dPhi)) ? dLat/dPhi : Math.cos(lat1); // E-W line gives dPhi=0
279
+ var dLon = d*Math.sin(brng)/q;
280
+ // check for some daft bugger going past the pole
281
+ if (Math.abs(lat2) > Math.PI/2) lat2 = lat2>0 ? Math.PI-lat2 : -(Math.PI-lat2);
282
+ lon2 = (lon1+dLon+3*Math.PI)%(2*Math.PI) - Math.PI;
283
+
284
+ return new LatLon(lat2.toDeg(), lon2.toDeg());
285
+ }
286
+
287
+ /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
288
+
289
+
290
+ /**
291
+ * Returns the latitude of this point; signed numeric degrees if no format, otherwise format & dp
292
+ * as per Geo.toLat()
293
+ *
294
+ * @param {String} [format]: Return value as 'd', 'dm', 'dms'
295
+ * @param {Number} [dp=0|2|4]: No of decimal places to display
296
+ * @returns {Number|String} Numeric degrees if no format specified, otherwise deg/min/sec
297
+ *
298
+ * @requires Geo
299
+ */
300
+ LatLon.prototype.lat = function(format, dp) {
301
+ if (typeof format == 'undefined') return this._lat;
302
+
303
+ return Geo.toLat(this._lat, format, dp);
304
+ }
305
+
306
+ /**
307
+ * Returns the longitude of this point; signed numeric degrees if no format, otherwise format & dp
308
+ * as per Geo.toLon()
309
+ *
310
+ * @param {String} [format]: Return value as 'd', 'dm', 'dms'
311
+ * @param {Number} [dp=0|2|4]: No of decimal places to display
312
+ * @returns {Number|String} Numeric degrees if no format specified, otherwise deg/min/sec
313
+ *
314
+ * @requires Geo
315
+ */
316
+ LatLon.prototype.lon = function(format, dp) {
317
+ if (typeof format == 'undefined') return this._lon;
318
+
319
+ return Geo.toLon(this._lon, format, dp);
320
+ }
321
+
322
+ /**
323
+ * Returns a string representation of this point; format and dp as per lat()/lon()
324
+ *
325
+ * @param {String} [format]: Return value as 'd', 'dm', 'dms'
326
+ * @param {Number} [dp=0|2|4]: No of decimal places to display
327
+ * @returns {String} Comma-separated latitude/longitude
328
+ *
329
+ * @requires Geo
330
+ */
331
+ LatLon.prototype.toString = function(format, dp) {
332
+ if (typeof format == 'undefined') format = 'dms';
333
+
334
+ if (isNaN(this._lat) || isNaN(this._lon)) return '-,-';
335
+
336
+ return Geo.toLat(this._lat, format, dp) + ', ' + Geo.toLon(this._lon, format, dp);
337
+ }
338
+
339
+ /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
340
+
341
+ // ---- extend Number object with methods for converting degrees/radians
342
+
343
+ /** Converts numeric degrees to radians */
344
+ if (typeof(Number.prototype.toRad) === "undefined") {
345
+ Number.prototype.toRad = function() {
346
+ return this * Math.PI / 180;
347
+ }
348
+ }
349
+
350
+ /** Converts radians to numeric (signed) degrees */
351
+ if (typeof(Number.prototype.toDeg) === "undefined") {
352
+ Number.prototype.toDeg = function() {
353
+ return this * 180 / Math.PI;
354
+ }
355
+ }
356
+
357
+ /**
358
+ * Formats the significant digits of a number, using only fixed-point notation (no exponential)
359
+ *
360
+ * @param {Number} precision: Number of significant digits to appear in the returned string
361
+ * @returns {String} A string representation of number which contains precision significant digits
362
+ */
363
+ if (typeof(Number.prototype.toPrecisionFixed) === "undefined") {
364
+ Number.prototype.toPrecisionFixed = function(precision) {
365
+ if (isNaN(this)) return 'NaN';
366
+ var numb = this < 0 ? -this : this; // can't take log of -ve number...
367
+ var sign = this < 0 ? '-' : '';
368
+
369
+ if (numb == 0) { n = '0.'; while (precision--) n += '0'; return n }; // can't take log of zero
370
+
371
+ var scale = Math.ceil(Math.log(numb)*Math.LOG10E); // no of digits before decimal
372
+ var n = String(Math.round(numb * Math.pow(10, precision-scale)));
373
+ if (scale > 0) { // add trailing zeros & insert decimal as required
374
+ l = scale - n.length;
375
+ while (l-- > 0) n = n + '0';
376
+ if (scale < n.length) n = n.slice(0,scale) + '.' + n.slice(scale);
377
+ } else { // prefix decimal and leading zeros if required
378
+ while (scale++ < 0) n = '0' + n;
379
+ n = '0.' + n;
380
+ }
381
+ return sign + n;
382
+ }
383
+ }
384
+
385
+ /** Trims whitespace from string (q.v. blog.stevenlevithan.com/archives/faster-trim-javascript) */
386
+ if (typeof(String.prototype.trim) === "undefined") {
387
+ String.prototype.trim = function() {
388
+ return String(this).replace(/^\s\s*/, '').replace(/\s\s*$/, '');
389
+ }
390
+ }
391
+
392
+
393
+ /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
394
+ /* Geodesy representation conversion functions (c) Chris Veness 2002-2010 */
395
+ /* - www.movable-type.co.uk/scripts/latlong.html */
396
+ /* */
397
+ /* Sample usage: */
398
+ /* var lat = Geo.parseDMS('51° 28′ 40.12″ N'); */
399
+ /* var lon = Geo.parseDMS('000° 00′ 05.31″ W'); */
400
+ /* var p1 = new LatLon(lat, lon); */
401
+ /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
402
+
403
+ var Geo = {}; // Geo namespace, representing static class
404
+
405
+ /**
406
+ * Parses string representing degrees/minutes/seconds into numeric degrees
407
+ *
408
+ * This is very flexible on formats, allowing signed decimal degrees, or deg-min-sec optionally
409
+ * suffixed by compass direction (NSEW). A variety of separators are accepted (eg 3º 37' 09"W)
410
+ * or fixed-width format without separators (eg 0033709W). Seconds and minutes may be omitted.
411
+ * (Note minimal validation is done).
412
+ *
413
+ * @param {String|Number} dmsStr: Degrees or deg/min/sec in variety of formats
414
+ * @returns {Number} Degrees as decimal number
415
+ * @throws {TypeError} dmsStr is an object, perhaps DOM object without .value?
416
+ */
417
+ Geo.parseDMS = function(dmsStr) {
418
+ if (typeof deg == 'object') throw new TypeError('Geo.parseDMS - dmsStr is [DOM?] object');
419
+
420
+ // check for signed decimal degrees without NSEW, if so return it directly
421
+ if (typeof dmsStr === 'number' && isFinite(dmsStr)) return Number(dmsStr);
422
+
423
+ // strip off any sign or compass dir'n & split out separate d/m/s
424
+ var dms = String(dmsStr).trim().replace(/^-/,'').replace(/[NSEW]$/i,'').split(/[^0-9.,]+/);
425
+ if (dms[dms.length-1]=='') dms.splice(dms.length-1); // from trailing symbol
426
+
427
+ if (dms == '') return NaN;
428
+
429
+ // and convert to decimal degrees...
430
+ switch (dms.length) {
431
+ case 3: // interpret 3-part result as d/m/s
432
+ var deg = dms[0]/1 + dms[1]/60 + dms[2]/3600;
433
+ break;
434
+ case 2: // interpret 2-part result as d/m
435
+ var deg = dms[0]/1 + dms[1]/60;
436
+ break;
437
+ case 1: // just d (possibly decimal) or non-separated dddmmss
438
+ var deg = dms[0];
439
+ // check for fixed-width unseparated format eg 0033709W
440
+ if (/[NS]/i.test(dmsStr)) deg = '0' + deg; // - normalise N/S to 3-digit degrees
441
+ if (/[0-9]{7}/.test(deg)) deg = deg.slice(0,3)/1 + deg.slice(3,5)/60 + deg.slice(5)/3600;
442
+ break;
443
+ default:
444
+ return NaN;
445
+ }
446
+ if (/^-|[WS]$/i.test(dmsStr.trim())) deg = -deg; // take '-', west and south as -ve
447
+ return Number(deg);
448
+ }
449
+
450
+ /**
451
+ * Convert decimal degrees to deg/min/sec format
452
+ * - degree, prime, double-prime symbols are added, but sign is discarded, though no compass
453
+ * direction is added
454
+ *
455
+ * @private
456
+ * @param {Number} deg: Degrees
457
+ * @param {String} [format=dms]: Return value as 'd', 'dm', 'dms'
458
+ * @param {Number} [dp=0|2|4]: No of decimal places to use - default 0 for dms, 2 for dm, 4 for d
459
+ * @returns {String} deg formatted as deg/min/secs according to specified format
460
+ * @throws {TypeError} deg is an object, perhaps DOM object without .value?
461
+ */
462
+ Geo.toDMS = function(deg, format, dp) {
463
+ if (typeof deg == 'object') throw new TypeError('Geo.toDMS - deg is [DOM?] object');
464
+ if (isNaN(deg)) return 'NaN'; // give up here if we can't make a number from deg
465
+
466
+ // default values
467
+ if (typeof format == 'undefined') format = 'dms';
468
+ if (typeof dp == 'undefined') {
469
+ switch (format) {
470
+ case 'd': dp = 4; break;
471
+ case 'dm': dp = 2; break;
472
+ case 'dms': dp = 0; break;
473
+ default: format = 'dms'; dp = 0; // be forgiving on invalid format
474
+ }
475
+ }
476
+
477
+ deg = Math.abs(deg); // (unsigned result ready for appending compass dir'n)
478
+
479
+ switch (format) {
480
+ case 'd':
481
+ d = deg.toFixed(dp); // round degrees
482
+ if (d<100) d = '0' + d; // pad with leading zeros
483
+ if (d<10) d = '0' + d;
484
+ dms = d + '\u00B0'; // add º symbol
485
+ break;
486
+ case 'dm':
487
+ var min = (deg*60).toFixed(dp); // convert degrees to minutes & round
488
+ var d = Math.floor(min / 60); // get component deg/min
489
+ var m = (min % 60).toFixed(dp); // pad with trailing zeros
490
+ if (d<100) d = '0' + d; // pad with leading zeros
491
+ if (d<10) d = '0' + d;
492
+ if (m<10) m = '0' + m;
493
+ dms = d + '\u00B0' + m + '\u2032'; // add º, ' symbols
494
+ break;
495
+ case 'dms':
496
+ var sec = (deg*3600).toFixed(dp); // convert degrees to seconds & round
497
+ var d = Math.floor(sec / 3600); // get component deg/min/sec
498
+ var m = Math.floor(sec/60) % 60;
499
+ var s = (sec % 60).toFixed(dp); // pad with trailing zeros
500
+ if (d<100) d = '0' + d; // pad with leading zeros
501
+ if (d<10) d = '0' + d;
502
+ if (m<10) m = '0' + m;
503
+ if (s<10) s = '0' + s;
504
+ dms = d + '\u00B0' + m + '\u2032' + s + '\u2033'; // add º, ', " symbols
505
+ break;
506
+ }
507
+
508
+ return dms;
509
+ }
510
+
511
+ /**
512
+ * Convert numeric degrees to deg/min/sec latitude (suffixed with N/S)
513
+ *
514
+ * @param {Number} deg: Degrees
515
+ * @param {String} [format=dms]: Return value as 'd', 'dm', 'dms'
516
+ * @param {Number} [dp=0|2|4]: No of decimal places to use - default 0 for dms, 2 for dm, 4 for d
517
+ * @returns {String} Deg/min/seconds
518
+ */
519
+ Geo.toLat = function(deg, format, dp) {
520
+ var lat = Geo.toDMS(deg, format, dp);
521
+ return lat=='' ? '' : lat.slice(1) + (deg<0 ? 'S' : 'N'); // knock off initial '0' for lat!
522
+ }
523
+
524
+ /**
525
+ * Convert numeric degrees to deg/min/sec longitude (suffixed with E/W)
526
+ *
527
+ * @param {Number} deg: Degrees
528
+ * @param {String} [format=dms]: Return value as 'd', 'dm', 'dms'
529
+ * @param {Number} [dp=0|2|4]: No of decimal places to use - default 0 for dms, 2 for dm, 4 for d
530
+ * @returns {String} Deg/min/seconds
531
+ */
532
+ Geo.toLon = function(deg, format, dp) {
533
+ var lon = Geo.toDMS(deg, format, dp);
534
+ return lon=='' ? '' : lon + (deg<0 ? 'W' : 'E');
535
+ }
536
+
537
+ /**
538
+ * Convert numeric degrees to deg/min/sec as a bearing (0º..360º)
539
+ *
540
+ * @param {Number} deg: Degrees
541
+ * @param {String} [format=dms]: Return value as 'd', 'dm', 'dms'
542
+ * @param {Number} [dp=0|2|4]: No of decimal places to use - default 0 for dms, 2 for dm, 4 for d
543
+ * @returns {String} Deg/min/seconds
544
+ */
545
+ Geo.toBrng = function(deg, format, dp) {
546
+ deg = (Number(deg)+360) % 360; // normalise -ve values to 180º..360º
547
+ var brng = Geo.toDMS(deg, format, dp);
548
+ return brng.replace('360', '0'); // just in case rounding took us up to 360º!
549
+ }
550
+
551
+ /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
data/lib/geo_calc.rb ADDED
@@ -0,0 +1 @@
1
+ require 'geo_calc/geo_point'