geo_calc 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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'