forked from chrisveness/geodesy
-
Notifications
You must be signed in to change notification settings - Fork 0
/
mgrs.js
257 lines (206 loc) · 9.7 KB
/
mgrs.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
/* MGRS / UTM Conversion Functions (c) Chris Veness 2014-2016 */
/* MIT Licence */
/* www.movable-type.co.uk/scripts/latlong-utm-mgrs.html */
/* www.movable-type.co.uk/scripts/geodesy/docs/module-mgrs.html */
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
'use strict';
if (typeof module!='undefined' && module.exports) var Utm = require('./utm.js'); // ≡ import Utm from 'utm.js'
if (typeof module!='undefined' && module.exports) var LatLon = require('./latlon-ellipsoidal.js'); // ≡ import LatLon from 'latlon-ellipsoidal.js'
/**
* Convert between Universal Transverse Mercator (UTM) coordinates and Military Grid Reference
* System (MGRS/NATO) grid references.
*
* @module mgrs
* @requires utm
* @requires latlon-ellipsoidal
*/
/* qv www.fgdc.gov/standards/projects/FGDC-standards-projects/usng/fgdc_std_011_2001_usng.pdf p10 */
/*
* Latitude bands C..X 8° each, covering 80°S to 84°N
*/
Mgrs.latBands = 'CDEFGHJKLMNPQRSTUVWXX'; // X is repeated for 80-84°N
/*
* 100km grid square column (‘e’) letters repeat every third zone
*/
Mgrs.e100kLetters = [ 'ABCDEFGH', 'JKLMNPQR', 'STUVWXYZ' ];
/*
* 100km grid square row (‘n’) letters repeat every other zone
*/
Mgrs.n100kLetters = [ 'ABCDEFGHJKLMNPQRSTUV', 'FGHJKLMNPQRSTUVABCDE' ];
/**
* Creates an Mgrs grid reference object.
*
* @constructor
* @param {number} zone - 6° longitudinal zone (1..60 covering 180°W..180°E).
* @param {string} band - 8° latitudinal band (C..X covering 80°S..84°N).
* @param {string} e100k - First letter (E) of 100km grid square.
* @param {string} n100k - Second letter (N) of 100km grid square.
* @param {number} easting - Easting in metres within 100km grid square.
* @param {number} northing - Northing in metres within 100km grid square.
* @param {LatLon.datum} [datum=WGS84] - Datum UTM coordinate is based on.
* @throws {Error} Invalid MGRS grid reference.
*
* @example
* var mgrsRef = new Mgrs(31, 'U', 'D', 'Q', 48251, 11932); // 31U DQ 48251 11932
*/
function Mgrs(zone, band, e100k, n100k, easting, northing, datum) {
// allow instantiation without 'new'
if (!(this instanceof Mgrs)) return new Mgrs(zone, band, e100k, n100k, easting, northing, datum);
if (datum === undefined) datum = LatLon.datum.WGS84; // default if not supplied
if (!(1<=zone && zone<=60)) throw new Error('Invalid MGRS grid reference (zone ‘'+zone+'’)');
if (band.length != 1) throw new Error('Invalid MGRS grid reference (band ‘'+band+'’)');
if (Mgrs.latBands.indexOf(band) == -1) throw new Error('Invalid MGRS grid reference (band ‘'+band+'’)');
if (e100k.length!=1) throw new Error('Invalid MGRS grid reference (e100k ‘'+e100k+'’)');
if (n100k.length!=1) throw new Error('Invalid MGRS grid reference (n100k ‘'+n100k+'’)');
this.zone = Number(zone);
this.band = band;
this.e100k = e100k;
this.n100k = n100k;
this.easting = Number(easting);
this.northing = Number(northing);
this.datum = datum;
}
/**
* Converts UTM coordinate to MGRS reference.
*
* @returns {Mgrs}
* @throws {Error} Invalid UTM coordinate.
*
* @example
* var utmCoord = new Utm(31, 'N', 448251, 5411932);
* var mgrsRef = utmCoord.toMgrs(); // 31U DQ 48251 11932
*/
Utm.prototype.toMgrs = function() {
if (isNaN(this.zone + this.easting + this.northing)) throw new Error('Invalid UTM coordinate ‘'+this.toString()+'’');
// MGRS zone is same as UTM zone
var zone = this.zone;
// convert UTM to lat/long to get latitude to determine band
var latlong = this.toLatLonE();
// grid zones are 8° tall, 0°N is 10th band
var band = Mgrs.latBands.charAt(Math.floor(latlong.lat/8+10)); // latitude band
// columns in zone 1 are A-H, zone 2 J-R, zone 3 S-Z, then repeating every 3rd zone
var col = Math.floor(this.easting / 100e3);
var e100k = Mgrs.e100kLetters[(zone-1)%3].charAt(col-1); // col-1 since 1*100e3 -> A (index 0), 2*100e3 -> B (index 1), etc.
// rows in even zones are A-V, in odd zones are F-E
var row = Math.floor(this.northing / 100e3) % 20;
var n100k = Mgrs.n100kLetters[(zone-1)%2].charAt(row);
// truncate easting/northing to within 100km grid square
var easting = this.easting % 100e3;
var northing = this.northing % 100e3;
// round to nm precision
easting = Number(easting.toFixed(6));
northing = Number(northing.toFixed(6));
return new Mgrs(zone, band, e100k, n100k, easting, northing);
};
/**
* Converts MGRS grid reference to UTM coordinate.
*
* @returns {Utm}
*
* @example
* var utmCoord = Mgrs.parse('31U DQ 448251 11932').toUtm(); // 31 N 448251 5411932
*/
Mgrs.prototype.toUtm = function() {
var zone = this.zone;
var band = this.band;
var e100k = this.e100k;
var n100k = this.n100k;
var easting = this.easting;
var northing = this.northing;
var hemisphere = band>='N' ? 'N' : 'S';
// get easting specified by e100k
var col = Mgrs.e100kLetters[(zone-1)%3].indexOf(e100k) + 1; // index+1 since A (index 0) -> 1*100e3, B (index 1) -> 2*100e3, etc.
var e100kNum = col * 100e3; // e100k in metres
// get northing specified by n100k
var row = Mgrs.n100kLetters[(zone-1)%2].indexOf(n100k);
var n100kNum = row * 100e3; // n100k in metres
// get latitude of (bottom of) band
var latBand = (Mgrs.latBands.indexOf(band)-10)*8;
// northing of bottom of band, extended to include entirety of bottommost 100km square
// (100km square boundaries are aligned with 100km UTM northing intervals)
var nBand = Math.floor(new LatLon(latBand, 0).toUtm().northing/100e3)*100e3;
// 100km grid square row letters repeat every 2,000km north; add enough 2,000km blocks to get
// into required band
var n2M = 0; // northing of 2,000km block
while (n2M + n100kNum + northing < nBand) n2M += 2000e3;
return new Utm(zone, hemisphere, e100kNum+easting, n2M+n100kNum+northing, this.datum);
};
/**
* Parses string representation of MGRS grid reference.
*
* An MGRS grid reference comprises (space-separated)
* - grid zone designator (GZD)
* - 100km grid square letter-pair
* - easting
* - northing.
*
* @param {string} mgrsGridRef - String representation of MGRS grid reference.
* @returns {Mgrs} Mgrs grid reference object.
* @throws {Error} Invalid MGRS grid reference.
*
* @example
* var mgrsRef = Mgrs.parse('31U DQ 48251 11932');
* var mgrsRef = Mgrs.parse('31UDQ4825111932');
* // mgrsRef: { zone:31, band:'U', e100k:'D', n100k:'Q', easting:48251, northing:11932 }
*/
Mgrs.parse = function(mgrsGridRef) {
mgrsGridRef = mgrsGridRef.trim();
// check for military-style grid reference with no separators
if (!mgrsGridRef.match(/\s/)) {
var en = mgrsGridRef.slice(5); // get easting/northing following zone/band/100ksq
en = en.slice(0, en.length/2)+' '+en.slice(-en.length/2); // separate easting/northing
mgrsGridRef = mgrsGridRef.slice(0, 3)+' '+mgrsGridRef.slice(3, 5)+' '+en; // insert spaces
}
// match separate elements (separated by whitespace)
mgrsGridRef = mgrsGridRef.match(/\S+/g);
if (mgrsGridRef==null || mgrsGridRef.length!=4) throw new Error('Invalid MGRS grid reference ‘'+mgrsGridRef+'’');
// split gzd into zone/band
var gzd = mgrsGridRef[0];
var zone = gzd.slice(0, 2);
var band = gzd.slice(2, 3);
// split 100km letter-pair into e/n
var en100k = mgrsGridRef[1];
var e100k = en100k.slice(0, 1);
var n100k = en100k.slice(1, 2);
var e = mgrsGridRef[2], n = mgrsGridRef[3];
// standardise to 10-digit refs - ie metres) (but only if < 10-digit refs, to allow decimals)
e = e.length>=5 ? e : (e+'00000').slice(0, 5);
n = n.length>=5 ? n : (n+'00000').slice(0, 5);
return new Mgrs(zone, band, e100k, n100k, e, n);
};
/**
* Returns a string representation of an MGRS grid reference.
*
* To distinguish from civilian UTM coordinate representations, no space is included within the
* zone/band grid zone designator.
*
* Components are separated by spaces: for a military-style unseparated string, use
* Mgrs.toString().replace(/ /g, '');
*
* Note that MGRS grid references get truncated, not rounded (unlike UTM coordinates).
*
* @param {number} [digits=10] - Precision of returned grid reference (eg 4 = km, 10 = m).
* @returns {string} This grid reference in standard format.
* @throws {Error} Invalid precision.
*
* @example
* var mgrsStr = new Mgrs(31, 'U', 'D', 'Q', 48251, 11932).toString(); // '31U DQ 48251 11932'
*/
Mgrs.prototype.toString = function(digits) {
digits = (digits === undefined) ? 10 : Number(digits);
if ([ 2,4,6,8,10 ].indexOf(digits) == -1) throw new Error('Invalid precision ‘'+digits+'’');
var zone = ('00'+this.zone).slice(-2); // ensure leading zero
var band = this.band;
var e100k = this.e100k;
var n100k = this.n100k;
// truncate to required precision
var eRounded = Math.floor(this.easting/Math.pow(10, 5-digits/2));
var nRounded = Math.floor(this.northing/Math.pow(10, 5-digits/2));
// ensure leading zeros
var easting = ('00000'+eRounded).slice(-digits/2);
var northing = ('00000'+nRounded).slice(-digits/2);
return zone+band + ' ' + e100k+n100k + ' ' + easting + ' ' + northing;
};
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
if (typeof module != 'undefined' && module.exports) module.exports = Mgrs; // ≡ export default Mgrs