MGRS is a Javascript library providing Military Grid Reference System functionality, a geocoordinate standard used by NATO militaries for locating points on Earth.
26 |
27 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/docs/stylesheets/github-light.css:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2014 GitHub Inc.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 |
16 | */
17 |
18 | .pl-c /* comment */ {
19 | color: #969896;
20 | }
21 |
22 | .pl-c1 /* constant, markup.raw, meta.diff.header, meta.module-reference, meta.property-name, support, support.constant, support.variable, variable.other.constant */,
23 | .pl-s .pl-v /* string variable */ {
24 | color: #0086b3;
25 | }
26 |
27 | .pl-e /* entity */,
28 | .pl-en /* entity.name */ {
29 | color: #795da3;
30 | }
31 |
32 | .pl-s .pl-s1 /* string source */,
33 | .pl-smi /* storage.modifier.import, storage.modifier.package, storage.type.java, variable.other, variable.parameter.function */ {
34 | color: #333;
35 | }
36 |
37 | .pl-ent /* entity.name.tag */ {
38 | color: #63a35c;
39 | }
40 |
41 | .pl-k /* keyword, storage, storage.type */ {
42 | color: #a71d5d;
43 | }
44 |
45 | .pl-pds /* punctuation.definition.string, string.regexp.character-class */,
46 | .pl-s /* string */,
47 | .pl-s .pl-pse .pl-s1 /* string punctuation.section.embedded source */,
48 | .pl-sr /* string.regexp */,
49 | .pl-sr .pl-cce /* string.regexp constant.character.escape */,
50 | .pl-sr .pl-sra /* string.regexp string.regexp.arbitrary-repitition */,
51 | .pl-sr .pl-sre /* string.regexp source.ruby.embedded */ {
52 | color: #183691;
53 | }
54 |
55 | .pl-v /* variable */ {
56 | color: #ed6a43;
57 | }
58 |
59 | .pl-id /* invalid.deprecated */ {
60 | color: #b52a1d;
61 | }
62 |
63 | .pl-ii /* invalid.illegal */ {
64 | background-color: #b52a1d;
65 | color: #f8f8f8;
66 | }
67 |
68 | .pl-sr .pl-cce /* string.regexp constant.character.escape */ {
69 | color: #63a35c;
70 | font-weight: bold;
71 | }
72 |
73 | .pl-ml /* markup.list */ {
74 | color: #693a17;
75 | }
76 |
77 | .pl-mh /* markup.heading */,
78 | .pl-mh .pl-en /* markup.heading entity.name */,
79 | .pl-ms /* meta.separator */ {
80 | color: #1d3e81;
81 | font-weight: bold;
82 | }
83 |
84 | .pl-mq /* markup.quote */ {
85 | color: #008080;
86 | }
87 |
88 | .pl-mi /* markup.italic */ {
89 | color: #333;
90 | font-style: italic;
91 | }
92 |
93 | .pl-mb /* markup.bold */ {
94 | color: #333;
95 | font-weight: bold;
96 | }
97 |
98 | .pl-md /* markup.deleted, meta.diff.header.from-file */ {
99 | background-color: #ffecec;
100 | color: #bd2c00;
101 | }
102 |
103 | .pl-mi1 /* markup.inserted, meta.diff.header.to-file */ {
104 | background-color: #eaffea;
105 | color: #55a532;
106 | }
107 |
108 | .pl-mdr /* meta.diff.range */ {
109 | color: #795da3;
110 | font-weight: bold;
111 | }
112 |
113 | .pl-mo /* meta.output */ {
114 | color: #1d3e81;
115 | }
116 |
117 |
--------------------------------------------------------------------------------
/docs/stylesheets/normalize.css:
--------------------------------------------------------------------------------
1 | /*! normalize.css v3.0.2 | MIT License | git.io/normalize */
2 |
3 | /**
4 | * 1. Set default font family to sans-serif.
5 | * 2. Prevent iOS text size adjust after orientation change, without disabling
6 | * user zoom.
7 | */
8 |
9 | html {
10 | font-family: sans-serif; /* 1 */
11 | -ms-text-size-adjust: 100%; /* 2 */
12 | -webkit-text-size-adjust: 100%; /* 2 */
13 | }
14 |
15 | /**
16 | * Remove default margin.
17 | */
18 |
19 | body {
20 | margin: 0;
21 | }
22 |
23 | /* HTML5 display definitions
24 | ========================================================================== */
25 |
26 | /**
27 | * Correct `block` display not defined for any HTML5 element in IE 8/9.
28 | * Correct `block` display not defined for `details` or `summary` in IE 10/11
29 | * and Firefox.
30 | * Correct `block` display not defined for `main` in IE 11.
31 | */
32 |
33 | article,
34 | aside,
35 | details,
36 | figcaption,
37 | figure,
38 | footer,
39 | header,
40 | hgroup,
41 | main,
42 | menu,
43 | nav,
44 | section,
45 | summary {
46 | display: block;
47 | }
48 |
49 | /**
50 | * 1. Correct `inline-block` display not defined in IE 8/9.
51 | * 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera.
52 | */
53 |
54 | audio,
55 | canvas,
56 | progress,
57 | video {
58 | display: inline-block; /* 1 */
59 | vertical-align: baseline; /* 2 */
60 | }
61 |
62 | /**
63 | * Prevent modern browsers from displaying `audio` without controls.
64 | * Remove excess height in iOS 5 devices.
65 | */
66 |
67 | audio:not([controls]) {
68 | display: none;
69 | height: 0;
70 | }
71 |
72 | /**
73 | * Address `[hidden]` styling not present in IE 8/9/10.
74 | * Hide the `template` element in IE 8/9/11, Safari, and Firefox < 22.
75 | */
76 |
77 | [hidden],
78 | template {
79 | display: none;
80 | }
81 |
82 | /* Links
83 | ========================================================================== */
84 |
85 | /**
86 | * Remove the gray background color from active links in IE 10.
87 | */
88 |
89 | a {
90 | background-color: transparent;
91 | }
92 |
93 | /**
94 | * Improve readability when focused and also mouse hovered in all browsers.
95 | */
96 |
97 | a:active,
98 | a:hover {
99 | outline: 0;
100 | }
101 |
102 | /* Text-level semantics
103 | ========================================================================== */
104 |
105 | /**
106 | * Address styling not present in IE 8/9/10/11, Safari, and Chrome.
107 | */
108 |
109 | abbr[title] {
110 | border-bottom: 1px dotted;
111 | }
112 |
113 | /**
114 | * Address style set to `bolder` in Firefox 4+, Safari, and Chrome.
115 | */
116 |
117 | b,
118 | strong {
119 | font-weight: bold;
120 | }
121 |
122 | /**
123 | * Address styling not present in Safari and Chrome.
124 | */
125 |
126 | dfn {
127 | font-style: italic;
128 | }
129 |
130 | /**
131 | * Address variable `h1` font-size and margin within `section` and `article`
132 | * contexts in Firefox 4+, Safari, and Chrome.
133 | */
134 |
135 | h1 {
136 | font-size: 2em;
137 | margin: 0.67em 0;
138 | }
139 |
140 | /**
141 | * Address styling not present in IE 8/9.
142 | */
143 |
144 | mark {
145 | background: #ff0;
146 | color: #000;
147 | }
148 |
149 | /**
150 | * Address inconsistent and variable font size in all browsers.
151 | */
152 |
153 | small {
154 | font-size: 80%;
155 | }
156 |
157 | /**
158 | * Prevent `sub` and `sup` affecting `line-height` in all browsers.
159 | */
160 |
161 | sub,
162 | sup {
163 | font-size: 75%;
164 | line-height: 0;
165 | position: relative;
166 | vertical-align: baseline;
167 | }
168 |
169 | sup {
170 | top: -0.5em;
171 | }
172 |
173 | sub {
174 | bottom: -0.25em;
175 | }
176 |
177 | /* Embedded content
178 | ========================================================================== */
179 |
180 | /**
181 | * Remove border when inside `a` element in IE 8/9/10.
182 | */
183 |
184 | img {
185 | border: 0;
186 | }
187 |
188 | /**
189 | * Correct overflow not hidden in IE 9/10/11.
190 | */
191 |
192 | svg:not(:root) {
193 | overflow: hidden;
194 | }
195 |
196 | /* Grouping content
197 | ========================================================================== */
198 |
199 | /**
200 | * Address margin not present in IE 8/9 and Safari.
201 | */
202 |
203 | figure {
204 | margin: 1em 40px;
205 | }
206 |
207 | /**
208 | * Address differences between Firefox and other browsers.
209 | */
210 |
211 | hr {
212 | box-sizing: content-box;
213 | height: 0;
214 | }
215 |
216 | /**
217 | * Contain overflow in all browsers.
218 | */
219 |
220 | pre {
221 | overflow: auto;
222 | }
223 |
224 | /**
225 | * Address odd `em`-unit font size rendering in all browsers.
226 | */
227 |
228 | code,
229 | kbd,
230 | pre,
231 | samp {
232 | font-family: monospace, monospace;
233 | font-size: 1em;
234 | }
235 |
236 | /* Forms
237 | ========================================================================== */
238 |
239 | /**
240 | * Known limitation: by default, Chrome and Safari on OS X allow very limited
241 | * styling of `select`, unless a `border` property is set.
242 | */
243 |
244 | /**
245 | * 1. Correct color not being inherited.
246 | * Known issue: affects color of disabled elements.
247 | * 2. Correct font properties not being inherited.
248 | * 3. Address margins set differently in Firefox 4+, Safari, and Chrome.
249 | */
250 |
251 | button,
252 | input,
253 | optgroup,
254 | select,
255 | textarea {
256 | color: inherit; /* 1 */
257 | font: inherit; /* 2 */
258 | margin: 0; /* 3 */
259 | }
260 |
261 | /**
262 | * Address `overflow` set to `hidden` in IE 8/9/10/11.
263 | */
264 |
265 | button {
266 | overflow: visible;
267 | }
268 |
269 | /**
270 | * Address inconsistent `text-transform` inheritance for `button` and `select`.
271 | * All other form control elements do not inherit `text-transform` values.
272 | * Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera.
273 | * Correct `select` style inheritance in Firefox.
274 | */
275 |
276 | button,
277 | select {
278 | text-transform: none;
279 | }
280 |
281 | /**
282 | * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio`
283 | * and `video` controls.
284 | * 2. Correct inability to style clickable `input` types in iOS.
285 | * 3. Improve usability and consistency of cursor style between image-type
286 | * `input` and others.
287 | */
288 |
289 | button,
290 | html input[type="button"], /* 1 */
291 | input[type="reset"],
292 | input[type="submit"] {
293 | -webkit-appearance: button; /* 2 */
294 | cursor: pointer; /* 3 */
295 | }
296 |
297 | /**
298 | * Re-set default cursor for disabled elements.
299 | */
300 |
301 | button[disabled],
302 | html input[disabled] {
303 | cursor: default;
304 | }
305 |
306 | /**
307 | * Remove inner padding and border in Firefox 4+.
308 | */
309 |
310 | button::-moz-focus-inner,
311 | input::-moz-focus-inner {
312 | border: 0;
313 | padding: 0;
314 | }
315 |
316 | /**
317 | * Address Firefox 4+ setting `line-height` on `input` using `!important` in
318 | * the UA stylesheet.
319 | */
320 |
321 | input {
322 | line-height: normal;
323 | }
324 |
325 | /**
326 | * It's recommended that you don't attempt to style these elements.
327 | * Firefox's implementation doesn't respect box-sizing, padding, or width.
328 | *
329 | * 1. Address box sizing set to `content-box` in IE 8/9/10.
330 | * 2. Remove excess padding in IE 8/9/10.
331 | */
332 |
333 | input[type="checkbox"],
334 | input[type="radio"] {
335 | box-sizing: border-box; /* 1 */
336 | padding: 0; /* 2 */
337 | }
338 |
339 | /**
340 | * Fix the cursor style for Chrome's increment/decrement buttons. For certain
341 | * `font-size` values of the `input`, it causes the cursor style of the
342 | * decrement button to change from `default` to `text`.
343 | */
344 |
345 | input[type="number"]::-webkit-inner-spin-button,
346 | input[type="number"]::-webkit-outer-spin-button {
347 | height: auto;
348 | }
349 |
350 | /**
351 | * 1. Address `appearance` set to `searchfield` in Safari and Chrome.
352 | * 2. Address `box-sizing` set to `border-box` in Safari and Chrome
353 | * (include `-moz` to future-proof).
354 | */
355 |
356 | input[type="search"] {
357 | -webkit-appearance: textfield; /* 1 */ /* 2 */
358 | box-sizing: content-box;
359 | }
360 |
361 | /**
362 | * Remove inner padding and search cancel button in Safari and Chrome on OS X.
363 | * Safari (but not Chrome) clips the cancel button when the search input has
364 | * padding (and `textfield` appearance).
365 | */
366 |
367 | input[type="search"]::-webkit-search-cancel-button,
368 | input[type="search"]::-webkit-search-decoration {
369 | -webkit-appearance: none;
370 | }
371 |
372 | /**
373 | * Define consistent border, margin, and padding.
374 | */
375 |
376 | fieldset {
377 | border: 1px solid #c0c0c0;
378 | margin: 0 2px;
379 | padding: 0.35em 0.625em 0.75em;
380 | }
381 |
382 | /**
383 | * 1. Correct `color` not being inherited in IE 8/9/10/11.
384 | * 2. Remove padding so people aren't caught out if they zero out fieldsets.
385 | */
386 |
387 | legend {
388 | border: 0; /* 1 */
389 | padding: 0; /* 2 */
390 | }
391 |
392 | /**
393 | * Remove default vertical scrollbar in IE 8/9/10/11.
394 | */
395 |
396 | textarea {
397 | overflow: auto;
398 | }
399 |
400 | /**
401 | * Don't inherit the `font-weight` (applied by a rule above).
402 | * NOTE: the default cannot safely be changed in Chrome and Safari on OS X.
403 | */
404 |
405 | optgroup {
406 | font-weight: bold;
407 | }
408 |
409 | /* Tables
410 | ========================================================================== */
411 |
412 | /**
413 | * Remove most spacing between table cells.
414 | */
415 |
416 | table {
417 | border-collapse: collapse;
418 | border-spacing: 0;
419 | }
420 |
421 | td,
422 | th {
423 | padding: 0;
424 | }
425 |
--------------------------------------------------------------------------------
/docs/stylesheets/stylesheet.css:
--------------------------------------------------------------------------------
1 | * {
2 | box-sizing: border-box; }
3 |
4 | body {
5 | padding: 0;
6 | margin: 0;
7 | font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
8 | font-size: 16px;
9 | line-height: 1.5;
10 | color: #606c71; }
11 |
12 | a {
13 | color: #1e6bb8;
14 | text-decoration: none; }
15 | a:hover {
16 | text-decoration: underline; }
17 |
18 | .btn {
19 | display: inline-block;
20 | margin-bottom: 1rem;
21 | color: rgba(255, 255, 255, 0.7);
22 | background-color: rgba(255, 255, 255, 0.08);
23 | border-color: rgba(255, 255, 255, 0.2);
24 | border-style: solid;
25 | border-width: 1px;
26 | border-radius: 0.3rem;
27 | transition: color 0.2s, background-color 0.2s, border-color 0.2s; }
28 | .btn + .btn {
29 | margin-left: 1rem; }
30 |
31 | .btn:hover {
32 | color: rgba(255, 255, 255, 0.8);
33 | text-decoration: none;
34 | background-color: rgba(255, 255, 255, 0.2);
35 | border-color: rgba(255, 255, 255, 0.3); }
36 |
37 | @media screen and (min-width: 64em) {
38 | .btn {
39 | padding: 0.75rem 1rem; } }
40 |
41 | @media screen and (min-width: 42em) and (max-width: 64em) {
42 | .btn {
43 | padding: 0.6rem 0.9rem;
44 | font-size: 0.9rem; } }
45 |
46 | @media screen and (max-width: 42em) {
47 | .btn {
48 | display: block;
49 | width: 100%;
50 | padding: 0.75rem;
51 | font-size: 0.9rem; }
52 | .btn + .btn {
53 | margin-top: 1rem;
54 | margin-left: 0; } }
55 |
56 | .page-header {
57 | color: #fff;
58 | text-align: center;
59 | background-color: #159957;
60 | background-image: linear-gradient(120deg, #155799, #159957); }
61 |
62 | @media screen and (min-width: 64em) {
63 | .page-header {
64 | padding: 5rem 6rem; } }
65 |
66 | @media screen and (min-width: 42em) and (max-width: 64em) {
67 | .page-header {
68 | padding: 3rem 4rem; } }
69 |
70 | @media screen and (max-width: 42em) {
71 | .page-header {
72 | padding: 2rem 1rem; } }
73 |
74 | .project-name {
75 | margin-top: 0;
76 | margin-bottom: 0.1rem; }
77 |
78 | @media screen and (min-width: 64em) {
79 | .project-name {
80 | font-size: 3.25rem; } }
81 |
82 | @media screen and (min-width: 42em) and (max-width: 64em) {
83 | .project-name {
84 | font-size: 2.25rem; } }
85 |
86 | @media screen and (max-width: 42em) {
87 | .project-name {
88 | font-size: 1.75rem; } }
89 |
90 | .project-tagline {
91 | margin-bottom: 2rem;
92 | font-weight: normal;
93 | opacity: 0.7; }
94 |
95 | @media screen and (min-width: 64em) {
96 | .project-tagline {
97 | font-size: 1.25rem; } }
98 |
99 | @media screen and (min-width: 42em) and (max-width: 64em) {
100 | .project-tagline {
101 | font-size: 1.15rem; } }
102 |
103 | @media screen and (max-width: 42em) {
104 | .project-tagline {
105 | font-size: 1rem; } }
106 |
107 | .main-content :first-child {
108 | margin-top: 0; }
109 | .main-content img {
110 | max-width: 100%; }
111 | .main-content h1, .main-content h2, .main-content h3, .main-content h4, .main-content h5, .main-content h6 {
112 | margin-top: 2rem;
113 | margin-bottom: 1rem;
114 | font-weight: normal;
115 | color: #159957; }
116 | .main-content p {
117 | margin-bottom: 1em; }
118 | .main-content code {
119 | padding: 2px 4px;
120 | font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace;
121 | font-size: 0.9rem;
122 | color: #383e41;
123 | background-color: #f3f6fa;
124 | border-radius: 0.3rem; }
125 | .main-content pre {
126 | padding: 0.8rem;
127 | margin-top: 0;
128 | margin-bottom: 1rem;
129 | font: 1rem Consolas, "Liberation Mono", Menlo, Courier, monospace;
130 | color: #567482;
131 | word-wrap: normal;
132 | background-color: #f3f6fa;
133 | border: solid 1px #dce6f0;
134 | border-radius: 0.3rem; }
135 | .main-content pre > code {
136 | padding: 0;
137 | margin: 0;
138 | font-size: 0.9rem;
139 | color: #567482;
140 | word-break: normal;
141 | white-space: pre;
142 | background: transparent;
143 | border: 0; }
144 | .main-content .highlight {
145 | margin-bottom: 1rem; }
146 | .main-content .highlight pre {
147 | margin-bottom: 0;
148 | word-break: normal; }
149 | .main-content .highlight pre, .main-content pre {
150 | padding: 0.8rem;
151 | overflow: auto;
152 | font-size: 0.9rem;
153 | line-height: 1.45;
154 | border-radius: 0.3rem; }
155 | .main-content pre code, .main-content pre tt {
156 | display: inline;
157 | max-width: initial;
158 | padding: 0;
159 | margin: 0;
160 | overflow: initial;
161 | line-height: inherit;
162 | word-wrap: normal;
163 | background-color: transparent;
164 | border: 0; }
165 | .main-content pre code:before, .main-content pre code:after, .main-content pre tt:before, .main-content pre tt:after {
166 | content: normal; }
167 | .main-content ul, .main-content ol {
168 | margin-top: 0; }
169 | .main-content blockquote {
170 | padding: 0 1rem;
171 | margin-left: 0;
172 | color: #819198;
173 | border-left: 0.3rem solid #dce6f0; }
174 | .main-content blockquote > :first-child {
175 | margin-top: 0; }
176 | .main-content blockquote > :last-child {
177 | margin-bottom: 0; }
178 | .main-content table {
179 | display: block;
180 | width: 100%;
181 | overflow: auto;
182 | word-break: normal;
183 | word-break: keep-all; }
184 | .main-content table th {
185 | font-weight: bold; }
186 | .main-content table th, .main-content table td {
187 | padding: 0.5rem 1rem;
188 | border: 1px solid #e9ebec; }
189 | .main-content dl {
190 | padding: 0; }
191 | .main-content dl dt {
192 | padding: 0;
193 | margin-top: 1rem;
194 | font-size: 1rem;
195 | font-weight: bold; }
196 | .main-content dl dd {
197 | padding: 0;
198 | margin-bottom: 1rem; }
199 | .main-content hr {
200 | height: 2px;
201 | padding: 0;
202 | margin: 1rem 0;
203 | background-color: #eff0f1;
204 | border: 0; }
205 |
206 | @media screen and (min-width: 64em) {
207 | .main-content {
208 | max-width: 64rem;
209 | padding: 2rem 6rem;
210 | margin: 0 auto;
211 | font-size: 1.1rem; } }
212 |
213 | @media screen and (min-width: 42em) and (max-width: 64em) {
214 | .main-content {
215 | padding: 2rem 4rem;
216 | font-size: 1.1rem; } }
217 |
218 | @media screen and (max-width: 42em) {
219 | .main-content {
220 | padding: 2rem 1rem;
221 | font-size: 1rem; } }
222 |
223 | .site-footer {
224 | padding-top: 2rem;
225 | margin-top: 2rem;
226 | border-top: solid 1px #eff0f1; }
227 |
228 | .site-footer-owner {
229 | display: block;
230 | font-weight: bold; }
231 |
232 | .site-footer-credits {
233 | color: #819198; }
234 |
235 | @media screen and (min-width: 64em) {
236 | .site-footer {
237 | font-size: 1rem; } }
238 |
239 | @media screen and (min-width: 42em) and (max-width: 64em) {
240 | .site-footer {
241 | font-size: 1rem; } }
242 |
243 | @media screen and (max-width: 42em) {
244 | .site-footer {
245 | font-size: 0.9rem; } }
246 |
--------------------------------------------------------------------------------
/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * This allows for imports from @ngageoint/mgrs-js
3 | * @since 1.0.0
4 | */
5 | export * from './lib/MGRS';
6 | export * from './lib/MGRSConstants';
7 | export * from './lib/MGRSUtils';
8 |
9 | export * from './lib/features/GridLine';
10 |
11 | export * from './lib/grid/Grid';
12 | export * from './lib/grid/GridLabel';
13 | export * from './lib/grid/GridLabeler';
14 | export * from './lib/grid/GridType';
15 | export * from './lib/grid/GridTypeUtils';
16 | export * from './lib/grid/Grids';
17 | export * from './lib/grid/MGRSLabeler';
18 | export * from './lib/grid/ZoomGrids';
19 |
20 | export * from './lib/gzd/BandLetterRange';
21 | export * from './lib/gzd/GZDLabeler';
22 | export * from './lib/gzd/GridRange';
23 | export * from './lib/gzd/GridZone';
24 | export * from './lib/gzd/GridZones';
25 | export * from './lib/gzd/LatitudeBand';
26 | export * from './lib/gzd/LongitudinalStrip';
27 | export * from './lib/gzd/ZoneNumberRange';
28 |
29 | export * from './lib/property/MGRSProperties';
30 |
31 | export * from './lib/utm/UTM';
--------------------------------------------------------------------------------
/lib/MGRS.ts:
--------------------------------------------------------------------------------
1 | import { Hemisphere, Line, Point } from '@ngageoint/grid-js';
2 | import { GridType } from './grid/GridType';
3 | import * as sprintf from 'sprintf-js';
4 | import { GridTypeUtils } from './grid/GridTypeUtils';
5 | import { GridZone } from './gzd/GridZone';
6 | import { UTM } from './utm/UTM';
7 | import { GridZones } from './gzd/GridZones';
8 | import { MGRSUtils } from './MGRSUtils';
9 |
10 | /**
11 | * Military Grid Reference System Coordinate
12 | */
13 | export class MGRS {
14 | /**
15 | * 100km grid square column (‘e’) letters repeat every third zone
16 | */
17 | private static readonly columnLetters = ['ABCDEFGH', 'JKLMNPQR', 'STUVWXYZ'];
18 |
19 | /**
20 | * 100km grid square row (‘n’) letters repeat every other zone
21 | */
22 | private static readonly rowLetters = ['ABCDEFGHJKLMNPQRSTUV', 'FGHJKLMNPQRSTUVABCDE'];
23 |
24 | /**
25 | * MGRS string pattern
26 | */
27 | private static readonly mgrsPattern = new RegExp(
28 | '^(\\d{1,2})([C-HJ-NP-X])(?:([A-HJ-NP-Z][A-HJ-NP-V])((\\d{2}){0,5}))?$',
29 | 'i',
30 | );
31 |
32 | /**
33 | * MGRS invalid string pattern (Svalbard)
34 | */
35 | private static readonly mgrsInvalidPattern = new RegExp('^3[246]X.*$', 'i');
36 |
37 | /**
38 | * Zone number
39 | */
40 | private readonly zone: number;
41 |
42 | /**
43 | * Band letter
44 | */
45 | private readonly band: string;
46 |
47 | /**
48 | * Column letter
49 | */
50 | private readonly column: string;
51 |
52 | /**
53 | * Row letter
54 | */
55 | private readonly row: string;
56 |
57 | /**
58 | * Easting
59 | */
60 | private readonly easting: number;
61 |
62 | /**
63 | * Northing
64 | */
65 | private readonly northing: number;
66 |
67 | /**
68 | * Create
69 | *
70 | * @param zone
71 | * zone number
72 | * @param band
73 | * band letter
74 | * @param easting
75 | * easting
76 | * @param northing
77 | * northing
78 | * @return MGRS
79 | */
80 | public static create(
81 | zone: number,
82 | band: string,
83 | easting: number,
84 | northing: number,
85 | column?: string,
86 | row?: string,
87 | ): MGRS {
88 | if (!column) {
89 | column = this.getColumnLetter(zone, easting);
90 | }
91 | if (!row) {
92 | row = this.getRowLetter(zone, northing);
93 | }
94 |
95 | return new MGRS(zone, band, column, row, easting, northing);
96 | }
97 |
98 | /**
99 | * Constructor
100 | *
101 | * @param zone
102 | * zone number
103 | * @param band
104 | * band letter
105 | * @param column
106 | * column letter
107 | * @param row
108 | * row letter
109 | * @param easting
110 | * easting
111 | * @param northing
112 | * northing
113 | */
114 | constructor(zone: number, band: string, column: string, row: string, easting: number, northing: number) {
115 | this.zone = zone;
116 | this.band = band;
117 | this.column = column;
118 | this.row = row;
119 | this.easting = easting;
120 | this.northing = northing;
121 | }
122 |
123 | /**
124 | * Get the zone number
125 | *
126 | * @return zone number
127 | */
128 | public getZone(): number {
129 | return this.zone;
130 | }
131 |
132 | /**
133 | * Get the band letter
134 | *
135 | * @return band letter
136 | */
137 | public getBand(): string {
138 | return this.band;
139 | }
140 |
141 | /**
142 | * Get the column letter
143 | *
144 | * @return column letter
145 | */
146 | public getColumn(): string {
147 | return this.column;
148 | }
149 |
150 | /**
151 | * Get the row letter
152 | *
153 | * @return row letter
154 | */
155 | public getRow(): string {
156 | return this.row;
157 | }
158 |
159 | /**
160 | * Get the easting
161 | *
162 | * @return easting
163 | */
164 | public getEasting(): number {
165 | return this.easting;
166 | }
167 |
168 | /**
169 | * Get the northing
170 | *
171 | * @return northing
172 | */
173 | public getNorthing(): number {
174 | return this.northing;
175 | }
176 |
177 | /**
178 | * Get the hemisphere
179 | *
180 | * @return hemisphere
181 | */
182 | public getHemisphere(): Hemisphere {
183 | return MGRSUtils.getHemisphere(this.band);
184 | }
185 |
186 | /**
187 | * Get the MGRS coordinate with specified grid precision
188 | *
189 | * @param type
190 | * grid type precision
191 | * @return MGRS coordinate
192 | */
193 | public coordinate(type?: GridType): string {
194 | let mgrs = '';
195 |
196 | if (type === null || type === undefined) {
197 | type = GridType.METER;
198 | }
199 |
200 | mgrs += this.zone;
201 | mgrs += this.band;
202 |
203 | if (type.valueOf() !== GridType.GZD.valueOf()) {
204 | mgrs += this.column;
205 | mgrs += this.row;
206 |
207 | if (type !== GridType.HUNDRED_KILOMETER) {
208 | mgrs += this.getEastingAndNorthing(type);
209 | }
210 | }
211 |
212 | return mgrs.toString();
213 | }
214 |
215 | /**
216 | * Get the easting and northing concatenated value in the grid type
217 | * precision
218 | *
219 | * @param type
220 | * grid type precision
221 | * @return easting and northing value
222 | */
223 | public getEastingAndNorthing(type: GridType): string {
224 | const accuracy = 5 - ~~Math.log10(type);
225 |
226 | // TODO apply locale
227 | const easting = sprintf.sprintf('%05d', this.easting);
228 | const northing = sprintf.sprintf('%05d', this.northing);
229 |
230 | return easting.substring(0, accuracy) + northing.substring(0, accuracy);
231 | }
232 |
233 | /**
234 | * Get the MGRS coordinate with the accuracy number of digits in the easting
235 | * and northing values. Accuracy must be inclusively between 0
236 | * ({@link GridType#HUNDRED_KILOMETER}) and 5 ({@link GridType#METER}).
237 | *
238 | * @param accuracy
239 | * accuracy digits between 0 (inclusive) and 5 (inclusive)
240 | * @return MGRS coordinate
241 | */
242 | public coordinateFromAccuracy(accuracy: number): string {
243 | return this.coordinate(GridTypeUtils.withAccuracy(accuracy));
244 | }
245 |
246 | /**
247 | * Get the MGRS coordinate grid precision
248 | *
249 | * @return grid type precision
250 | */
251 | public precision(): GridType {
252 | return GridTypeUtils.withAccuracy(this.accuracy());
253 | }
254 |
255 | /**
256 | * Get the MGRS coordinate accuracy number of digits
257 | *
258 | * @return accuracy digits
259 | */
260 | public accuracy(): number {
261 | let accuracy = 5;
262 |
263 | for (let accuracyLevel = 10; accuracyLevel <= 100000; accuracyLevel *= 10) {
264 | if (this.easting % accuracyLevel !== 0 || this.northing % accuracyLevel !== 0) {
265 | break;
266 | }
267 | accuracy--;
268 | }
269 |
270 | return accuracy;
271 | }
272 |
273 | /**
274 | * Get the two letter column and row 100k designator
275 | *
276 | * @return the two letter column and row 100k designator
277 | */
278 | public getColumnRowId(): string {
279 | return this.column + this.row;
280 | }
281 |
282 | /**
283 | * Get the GZD grid zone
284 | *
285 | * @return GZD grid zone
286 | */
287 | public getGridZone(): GridZone {
288 | return GridZones.getGridZoneFromMGRS(this);
289 | }
290 |
291 | /**
292 | * Convert to a point
293 | *
294 | * @return point
295 | */
296 | public toPoint(): Point {
297 | return this.toUTM().toPoint();
298 | }
299 |
300 | /**
301 | * Convert to UTM coordinate
302 | *
303 | * @return UTM
304 | */
305 | public toUTM(): UTM {
306 | const easting = this.getUTMEasting();
307 | const northing = this.getUTMNorthing();
308 | const hemisphere = this.getHemisphere();
309 |
310 | return UTM.create(this.zone, hemisphere, easting, northing);
311 | }
312 |
313 | /**
314 | * Get the UTM easting
315 | *
316 | * @return UTM easting
317 | */
318 | public getUTMEasting(): number {
319 | // get easting specified by e100k
320 | const columnLetters = MGRS.getColumnLetters(this.zone);
321 | const columnIndex = columnLetters.indexOf(this.column) + 1;
322 | // index+1 since A (index 0) -> 1*100e3, B (index 1) -> 2*100e3, etc.
323 | const e100kNum = columnIndex * 100000.0; // e100k in meters
324 |
325 | return e100kNum + this.easting;
326 | }
327 |
328 | /**
329 | * Get the UTM northing
330 | *
331 | * @return UTM northing
332 | */
333 | public getUTMNorthing(): number {
334 | // get northing specified by n100k
335 | const rowLetters = MGRS.getRowLetters(this.zone);
336 | const rowIndex = rowLetters.indexOf(this.row);
337 | const n100kNum = rowIndex * 100000.0; // n100k in meters
338 |
339 | // get latitude of (bottom of) band
340 | const latBand = GridZones.getSouthLatitude(this.band);
341 |
342 | // northing of bottom of band, extended to include entirety of
343 | // bottommost 100km square
344 | // (100km square boundaries are aligned with 100km UTM northing
345 | // intervals)
346 |
347 | const latBandNorthing = UTM.from(Point.degrees(0, latBand)).getNorthing();
348 | const nBand = Math.floor(latBandNorthing / 100000) * 100000;
349 |
350 | // 100km grid square row letters repeat every 2,000km north; add enough
351 | // 2,000km blocks to get
352 | // into required band
353 | let n2M = 0; // northing of 2,000km block
354 | while (n2M + n100kNum + this.northing < nBand) {
355 | n2M += 2000000;
356 | }
357 |
358 | return n2M + n100kNum + this.northing;
359 | }
360 |
361 | /**
362 | * {@inheritDoc}
363 | */
364 | public toString(): string {
365 | return this.coordinate(GridType.METER);
366 | }
367 |
368 | /**
369 | * Return whether the given string is valid MGRS string
370 | *
371 | * @param mgrs
372 | * potential MGRS string
373 | * @return true if MGRS string is valid, false otherwise
374 | */
375 | public static isMGRS(mgrs: string): boolean {
376 | mgrs = MGRS.removeSpaces(mgrs);
377 | return MGRS.mgrsPattern.test(mgrs) && !MGRS.mgrsInvalidPattern.test(mgrs);
378 | }
379 |
380 | /**
381 | * Removed spaces from the value
382 | *
383 | * @param value
384 | * value string
385 | * @return value without spaces
386 | */
387 | private static removeSpaces(value: string): string {
388 | return value.replace(/\s/g, '');
389 | }
390 |
391 | /**
392 | * Encodes a point as a MGRS string
393 | *
394 | * @param point
395 | * point
396 | * @return MGRS
397 | */
398 | public static from(point: Point): MGRS {
399 | point = point.toDegrees();
400 |
401 | const utm = UTM.from(point);
402 |
403 | const bandLetter = GridZones.getBandLetterFromLatitude(point.getLatitude());
404 |
405 | const columnLetter = MGRS.getColumnLetterFromUTM(utm);
406 |
407 | const rowLetter = MGRS.getRowLetterFromUTM(utm);
408 |
409 | // truncate easting/northing to within 100km grid square
410 | const easting = ~~(utm.getEasting() % 100000);
411 | const northing = ~~(utm.getNorthing() % 100000);
412 |
413 | return MGRS.create(utm.getZone(), bandLetter, easting, northing, columnLetter, rowLetter);
414 | }
415 |
416 | /**
417 | * Parse a MGRS string
418 | *
419 | * @param mgrs
420 | * MGRS string
421 | * @return MGRS
422 | * @throws ParseException
423 | * upon failure to parse the MGRS string
424 | */
425 | public static parse(mgrs: string): MGRS {
426 | if (!MGRS.mgrsPattern.test(MGRS.removeSpaces(mgrs))) {
427 | throw new Error('Invalid MGRS: ' + mgrs);
428 | }
429 |
430 | const matches = MGRS.removeSpaces(mgrs).match(MGRS.mgrsPattern);
431 |
432 | const zone = Number.parseInt(matches![1], 10);
433 | const band = matches![2].toUpperCase().charAt(0);
434 |
435 | const gridZone = GridZones.getGridZone(zone, band);
436 | if (gridZone == null) {
437 | throw new Error('Invalid MGRS: ' + mgrs);
438 | }
439 |
440 | let mgrsValue: MGRS | undefined;
441 |
442 | let columnRow = matches![3];
443 | if (columnRow) {
444 | columnRow = columnRow.toUpperCase();
445 | const column = columnRow.charAt(0);
446 | const row = columnRow.charAt(1);
447 |
448 | // parse easting & northing
449 | let easting = 0;
450 | let northing = 0;
451 | const location = matches![4];
452 | if (location.length > 0) {
453 | const precision = location.length / 2;
454 | const multiplier = Math.pow(10.0, 5 - precision);
455 | easting = +location.substring(0, precision) * multiplier;
456 | northing = +location.substring(precision) * multiplier;
457 | }
458 |
459 | mgrsValue = MGRS.create(zone, band, easting, northing, column, row);
460 |
461 | if (location.length === 0) {
462 | const point = mgrsValue.toPoint().toDegrees();
463 | const gridBounds = gridZone.getBounds();
464 | const gridSouthwest = gridBounds.getSouthwest().toDegrees();
465 |
466 | const westBounds = point.getLongitude() < gridSouthwest.getLongitude();
467 | const southBounds = point.getLatitude() < gridSouthwest.getLatitude();
468 |
469 | if (westBounds || southBounds) {
470 | if (westBounds && southBounds) {
471 | const northeast = MGRS.create(
472 | zone,
473 | band,
474 | GridType.HUNDRED_KILOMETER,
475 | GridType.HUNDRED_KILOMETER,
476 | column,
477 | row,
478 | ).toPoint();
479 | if (gridBounds.containsPoint(northeast)) {
480 | mgrsValue = MGRS.from(Point.degrees(gridSouthwest.getLongitude(), gridSouthwest.getLatitude()));
481 | }
482 | } else if (westBounds) {
483 | const east = MGRS.create(zone, band, GridType.HUNDRED_KILOMETER, northing, column, row).toPoint();
484 | if (gridBounds.containsPoint(east)) {
485 | const intersection = MGRS.getWesternBoundsPoint(gridZone, point, east);
486 | mgrsValue = MGRS.from(intersection);
487 | }
488 | } else if (southBounds) {
489 | const north = MGRS.create(zone, band, easting, GridType.HUNDRED_KILOMETER, column, row).toPoint();
490 | if (gridBounds.containsPoint(north)) {
491 | const intersection = MGRS.getSouthernBoundsPoint(gridZone, point, north);
492 | mgrsValue = MGRS.from(intersection);
493 | }
494 | }
495 | }
496 | }
497 | } else {
498 | mgrsValue = MGRS.from(gridZone.getBounds().getSouthwest());
499 | }
500 |
501 | return mgrsValue;
502 | }
503 |
504 | /**
505 | * Get the point on the western grid zone bounds point between the western
506 | * and eastern points
507 | *
508 | * @param gridZone
509 | * grid zone
510 | * @param west
511 | * western point
512 | * @param east
513 | * eastern point
514 | * @return western grid bounds point
515 | */
516 | private static getWesternBoundsPoint(gridZone: GridZone, west: Point, east: Point): Point {
517 | const eastUTM = UTM.from(east);
518 | const northing = eastUTM.getNorthing();
519 |
520 | const zoneNumber = gridZone.getNumber();
521 | const hemisphere = gridZone.getHemisphere();
522 |
523 | const line = Line.line(west, east);
524 | const boundsLine = gridZone.getBounds().getWestLine();
525 |
526 | const intersection = line.intersection(boundsLine);
527 |
528 | // Intersection easting
529 | const intersectionUTM = UTM.from(intersection!, zoneNumber, hemisphere);
530 | const intersectionEasting = intersectionUTM.getEasting();
531 |
532 | // One meter precision just inside the bounds
533 | const boundsEasting = Math.ceil(intersectionEasting);
534 |
535 | // Higher precision point just inside of the bounds
536 | const boundsPoint = UTM.point(zoneNumber, hemisphere, boundsEasting, northing);
537 |
538 | boundsPoint.setLongitude(boundsLine.getPoint1().getLongitude());
539 |
540 | return boundsPoint;
541 | }
542 |
543 | /**
544 | * Get the point on the southern grid zone bounds point between the southern
545 | * and northern points
546 | *
547 | * @param gridZone
548 | * grid zone
549 | * @param south
550 | * southern point
551 | * @param north
552 | * northern point
553 | * @return southern grid bounds point
554 | */
555 | private static getSouthernBoundsPoint(gridZone: GridZone, south: Point, north: Point): Point {
556 | const northUTM = UTM.from(north);
557 | const easting = northUTM.getEasting();
558 |
559 | const zoneNumber = gridZone.getNumber();
560 | const hemisphere = gridZone.getHemisphere();
561 |
562 | const line = Line.line(south, north);
563 | const boundsLine = gridZone.getBounds().getSouthLine();
564 |
565 | const intersection = line.intersection(boundsLine);
566 |
567 | // Intersection northing
568 | const intersectionUTM = UTM.from(intersection!, zoneNumber, hemisphere);
569 | const intersectionNorthing = intersectionUTM.getNorthing();
570 |
571 | // One meter precision just inside the bounds
572 | const boundsNorthing = Math.ceil(intersectionNorthing);
573 |
574 | // Higher precision point just inside of the bounds
575 | const boundsPoint = UTM.point(zoneNumber, hemisphere, easting, boundsNorthing);
576 |
577 | boundsPoint.setLatitude(boundsLine.getPoint1().getLatitude());
578 |
579 | return boundsPoint;
580 | }
581 |
582 | /**
583 | * Parse the MGRS string for the precision
584 | *
585 | * @param mgrs
586 | * MGRS string
587 | * @return grid type precision
588 | * @throws ParseException
589 | * upon failure to parse the MGRS string
590 | */
591 | public static precision(mgrs: string): GridType {
592 | if (!MGRS.mgrsPattern.test(MGRS.removeSpaces(mgrs))) {
593 | throw new Error('Invalid MGRS: ' + mgrs);
594 | }
595 |
596 | const matches = MGRS.removeSpaces(mgrs).match(MGRS.mgrsPattern);
597 |
598 | let precision: GridType | undefined;
599 |
600 | if (matches![3]) {
601 | const location = matches![4];
602 | if (location.length > 0) {
603 | precision = GridTypeUtils.withAccuracy(location.length / 2);
604 | } else {
605 | precision = GridType.HUNDRED_KILOMETER;
606 | }
607 | } else {
608 | precision = GridType.GZD;
609 | }
610 |
611 | return precision;
612 | }
613 |
614 | /**
615 | * Get the MGRS coordinate accuracy number of digits
616 | *
617 | * @param mgrs
618 | * MGRS string
619 | * @return accuracy digits
620 | * @throws ParseException
621 | * upon failure to parse the MGRS string
622 | */
623 | public static accuracy(mgrs: string): number {
624 | return GridTypeUtils.getAccuracy(MGRS.precision(mgrs));
625 | }
626 |
627 | /**
628 | * Get the two letter column and row 100k designator for a given UTM
629 | * easting, northing and zone number value
630 | *
631 | * @param easting
632 | * easting
633 | * @param northing
634 | * northing
635 | * @param zoneNumber
636 | * zone number
637 | * @return the two letter column and row 100k designator
638 | */
639 | public static getColumnRowId(easting: number, northing: number, zoneNumber: number): string {
640 | const columnLetter = MGRS.getColumnLetter(zoneNumber, easting);
641 |
642 | const rowLetter = MGRS.getRowLetter(zoneNumber, northing);
643 |
644 | return columnLetter + rowLetter;
645 | }
646 |
647 | /**
648 | * Get the column letter from the UTM
649 | *
650 | * @param utm
651 | * UTM
652 | * @return column letter
653 | */
654 | public static getColumnLetterFromUTM(utm: UTM): string {
655 | return MGRS.getColumnLetter(utm.getZone(), utm.getEasting());
656 | }
657 |
658 | /**
659 | * Get the column letter from the zone number and easting
660 | *
661 | * @param zoneNumber
662 | * zone number
663 | * @param easting
664 | * easting
665 | * @return column letter
666 | */
667 | public static getColumnLetter(zoneNumber: number, easting: number): string {
668 | // columns in zone 1 are A-H, zone 2 J-R, zone 3 S-Z, then repeating
669 | // every 3rd zone
670 | const column = ~~Math.floor(easting / 100000);
671 | const columnLetters = MGRS.getColumnLetters(zoneNumber);
672 | return columnLetters.charAt(column - 1);
673 | }
674 |
675 | /**
676 | * Get the row letter from the UTM
677 | *
678 | * @param utm
679 | * UTM
680 | * @return row letter
681 | */
682 | public static getRowLetterFromUTM(utm: UTM): string {
683 | return MGRS.getRowLetter(utm.getZone(), utm.getNorthing());
684 | }
685 |
686 | /**
687 | * Get the row letter from the zone number and northing
688 | *
689 | * @param zoneNumber
690 | * zone number
691 | * @param northing
692 | * northing
693 | * @return row letter
694 | */
695 | public static getRowLetter(zoneNumber: number, northing: number): string {
696 | // rows in even zones are A-V, in odd zones are F-E
697 | const row = ~~Math.floor(northing / 100000) % 20;
698 | const rowLetters = MGRS.getRowLetters(zoneNumber);
699 | return rowLetters.charAt(row);
700 | }
701 |
702 | /**
703 | * Get the column letters for the zone number
704 | *
705 | * @param zoneNumber
706 | * zone number
707 | * @return column letters
708 | */
709 | private static getColumnLetters(zoneNumber: number): string {
710 | return MGRS.columnLetters[(zoneNumber - 1) % 3];
711 | }
712 |
713 | /**
714 | * Get the row letters for the zone number
715 | *
716 | * @param zoneNumber
717 | * zone number
718 | * @return row letters
719 | */
720 | private static getRowLetters(zoneNumber: number): string {
721 | return MGRS.rowLetters[(zoneNumber - 1) % 2];
722 | }
723 | }
724 |
--------------------------------------------------------------------------------
/lib/MGRSConstants.ts:
--------------------------------------------------------------------------------
1 | import { GridConstants } from '@ngageoint/grid-js';
2 |
3 | /**
4 | * Military Grid Reference System Constants
5 | */
6 | export class MGRSConstants {
7 | /**
8 | * Minimum longitude
9 | */
10 | public static readonly MIN_LON = GridConstants.MIN_LON;
11 |
12 | /**
13 | * Maximum longitude
14 | */
15 | public static readonly MAX_LON = GridConstants.MAX_LON;
16 |
17 | /**
18 | * Minimum latitude
19 | */
20 | public static readonly MIN_LAT = -80.0;
21 |
22 | /**
23 | * Maximum latitude
24 | */
25 | public static readonly MAX_LAT = 84.0;
26 |
27 | /**
28 | * Minimum grid zone number
29 | */
30 | public static readonly MIN_ZONE_NUMBER = 1;
31 |
32 | /**
33 | * Maximum grid zone number
34 | */
35 | public static readonly MAX_ZONE_NUMBER = 60;
36 |
37 | /**
38 | * Grid zone width
39 | */
40 | public static readonly ZONE_WIDTH = 6.0;
41 |
42 | /**
43 | * Minimum grid band letter
44 | */
45 | public static readonly MIN_BAND_LETTER = 'C';
46 |
47 | /**
48 | * Maximum grid band letter
49 | */
50 | public static readonly MAX_BAND_LETTER = 'X';
51 |
52 | /**
53 | * Number of bands
54 | */
55 | public static readonly NUM_BANDS = 20;
56 |
57 | /**
58 | * Grid band height for all by but the {@link #MAX_BAND_LETTER}
59 | */
60 | public static readonly BAND_HEIGHT = 8.0;
61 |
62 | /**
63 | * Grid band height for the {@link #MAX_BAND_LETTER}
64 | */
65 | public static readonly MAX_BAND_HEIGHT = 12.0;
66 |
67 | /**
68 | * Last southern hemisphere band letter
69 | */
70 | public static readonly BAND_LETTER_SOUTH = 'M';
71 |
72 | /**
73 | * First northern hemisphere band letter
74 | */
75 | public static readonly BAND_LETTER_NORTH = 'N';
76 |
77 | /**
78 | * Min zone number in Svalbard grid zones
79 | */
80 | public static readonly MIN_SVALBARD_ZONE_NUMBER = 31;
81 |
82 | /**
83 | * Max zone number in Svalbard grid zones
84 | */
85 | public static readonly MAX_SVALBARD_ZONE_NUMBER = 37;
86 |
87 | /**
88 | * Band letter in Svalbard grid zones
89 | */
90 | public static readonly SVALBARD_BAND_LETTER = MGRSConstants.MAX_BAND_LETTER;
91 |
92 | /**
93 | * Min zone number in Norway grid zones
94 | */
95 | public static readonly MIN_NORWAY_ZONE_NUMBER = 31;
96 |
97 | /**
98 | * Max zone number in Norway grid zones
99 | */
100 | public static readonly MAX_NORWAY_ZONE_NUMBER = 32;
101 |
102 | /**
103 | * Band letter in Norway grid zones
104 | */
105 | public static readonly NORWAY_BAND_LETTER = 'V';
106 | }
107 |
--------------------------------------------------------------------------------
/lib/MGRSUtils.ts:
--------------------------------------------------------------------------------
1 | import { GridUtils, Hemisphere } from '@ngageoint/grid-js';
2 | import { MGRSConstants } from './MGRSConstants';
3 |
4 | /**
5 | * Military Grid Reference System utilities
6 | */
7 | export class MGRSUtils {
8 | /**
9 | * Validate the zone number
10 | *
11 | * @param zoneNumber
12 | * zone number
13 | */
14 | public static validateZoneNumber(zoneNumber: number): void {
15 | if (zoneNumber < MGRSConstants.MIN_ZONE_NUMBER || zoneNumber > MGRSConstants.MAX_ZONE_NUMBER) {
16 | throw new Error(
17 | 'Illegal zone number (expected ' +
18 | MGRSConstants.MIN_ZONE_NUMBER +
19 | ' - ' +
20 | MGRSConstants.MAX_ZONE_NUMBER +
21 | '): ' +
22 | zoneNumber,
23 | );
24 | }
25 | }
26 |
27 | /**
28 | * Validate the band letter
29 | *
30 | * @param letter
31 | * band letter
32 | */
33 | public static validateBandLetter(letter: string): void {
34 | if (
35 | letter.charCodeAt(0) < MGRSConstants.MIN_BAND_LETTER.charCodeAt(0) ||
36 | letter.charCodeAt(0) > MGRSConstants.MAX_BAND_LETTER.charCodeAt(0) ||
37 | GridUtils.isOmittedBandLetter(letter)
38 | ) {
39 | throw new Error('Illegal band letter (CDEFGHJKLMNPQRSTUVWX): ' + letter);
40 | }
41 | }
42 |
43 | /**
44 | * Get the next band letter
45 | *
46 | * @param letter
47 | * band letter
48 | * @return next band letter, 'Y' ({@link MGRSConstants#MAX_BAND_LETTER} + 1)
49 | * if no next bands
50 | */
51 | public static nextBandLetter(letter: string): string {
52 | MGRSUtils.validateBandLetter(letter);
53 |
54 | let charLetter = letter.charCodeAt(0);
55 |
56 | charLetter++;
57 | if (GridUtils.isOmittedBandLetter(String.fromCharCode(charLetter))) {
58 | charLetter++;
59 | }
60 | return String.fromCharCode(charLetter);
61 | }
62 |
63 | /**
64 | * Get the previous band letter
65 | *
66 | * @param letter
67 | * band letter
68 | * @return previous band letter, 'B' ({@link MGRSConstants#MIN_BAND_LETTER}
69 | * - 1) if no previous bands
70 | */
71 | public static previousBandLetter(letter: string): string {
72 | MGRSUtils.validateBandLetter(letter);
73 |
74 | let charLetter = letter.charCodeAt(0);
75 | charLetter--;
76 | if (GridUtils.isOmittedBandLetter(String.fromCharCode(charLetter))) {
77 | charLetter--;
78 | }
79 | return String.fromCharCode(charLetter);
80 | }
81 |
82 | /**
83 | * Get the label name
84 | *
85 | * @param zoneNumber
86 | * zone number
87 | * @param bandLetter
88 | * band letter
89 | * @return name
90 | */
91 | public static getLabelName(zoneNumber: number, bandLetter: string): string {
92 | return zoneNumber + bandLetter;
93 | }
94 |
95 | /**
96 | * Get the hemisphere from the band letter
97 | *
98 | * @param bandLetter
99 | * band letter
100 | * @return hemisphere
101 | */
102 | public static getHemisphere(bandLetter: string): Hemisphere {
103 | return bandLetter < MGRSConstants.BAND_LETTER_NORTH ? Hemisphere.SOUTH : Hemisphere.NORTH;
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/lib/features/GridLine.ts:
--------------------------------------------------------------------------------
1 | import { Line, Point } from '@ngageoint/grid-js';
2 | import { GridType } from '../grid/GridType';
3 |
4 | /**
5 | * Line between two points
6 | */
7 | export class GridLine extends Line {
8 | /**
9 | * Grid type the line represents if any
10 | */
11 | private gridType?: GridType;
12 |
13 | /**
14 | * Create a line
15 | *
16 | * @param point1
17 | * first point
18 | * @param point2
19 | * second point
20 | * @param gridType
21 | * line grid type (optional)
22 | * @return line
23 | */
24 | public static lineFromPoints(point1: Point, point2: Point, gridType?: GridType): GridLine {
25 | const gridLine = new GridLine(Line.line(point1, point2));
26 | gridLine.gridType = gridType;
27 | return gridLine;
28 | }
29 |
30 | /**
31 | * Create a line
32 | *
33 | * @param line
34 | * line to copy
35 | * @param gridType
36 | * line grid type (optional)
37 | * @return line
38 | */
39 | public static lineFromLine(line: Line, gridType?: GridType): GridLine {
40 | const gridLine = new GridLine(line);
41 | gridLine.gridType = gridType;
42 | return gridLine;
43 | }
44 |
45 | /**
46 | * Copy a line
47 | *
48 | * @param line
49 | * line to copy
50 | * @return line
51 | */
52 | public static lineFromGridLine(line: GridLine): GridLine {
53 | return GridLine.lineFromLine(line, line.gridType);
54 | }
55 |
56 | /**
57 | * Constructor
58 | *
59 | * @param line
60 | * line to copy
61 | */
62 | constructor(line: Line) {
63 | super(line);
64 | }
65 |
66 | /**
67 | * Get the line grid type
68 | *
69 | * @return grid type
70 | */
71 | public getGridType(): GridType | undefined {
72 | return this.gridType;
73 | }
74 |
75 | /**
76 | * Check if the line has a grid type
77 | *
78 | * @return true if has grid type
79 | */
80 | public hasGridType(): boolean {
81 | if (this.gridType) {
82 | return true;
83 | }
84 | return false;
85 | }
86 |
87 | /**
88 | * Set the line grid type
89 | *
90 | * @param gridType
91 | * grid type
92 | */
93 | public setGridType(gridType: GridType): void {
94 | this.gridType = gridType;
95 | }
96 |
97 | /**
98 | * Copy the line
99 | *
100 | * @return line copy
101 | */
102 | public copy(): GridLine {
103 | return GridLine.lineFromGridLine(this);
104 | }
105 |
106 | /**
107 | * {@inheritDoc}
108 | */
109 | public equals(obj: any): boolean {
110 | if (this === obj) return true;
111 | if (!super.equals(obj)) return false;
112 | if (typeof this !== typeof obj) return false;
113 | const other = obj as GridLine;
114 | if (this.gridType !== other.gridType) return false;
115 | return true;
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/lib/grid/Grid.ts:
--------------------------------------------------------------------------------
1 | import { Color } from '@ngageoint/color-js';
2 | import { BaseGrid, Bounds, GridStyle, GridTile, PropertyConstants } from '@ngageoint/grid-js';
3 | import { IComparable } from 'tstl';
4 | import { GridLine } from '../features/GridLine';
5 | import { GridZone } from '../gzd/GridZone';
6 | import { MGRSProperties } from '../property/MGRSProperties';
7 | import { GridLabel } from './GridLabel';
8 | import { GridLabeler } from './GridLabeler';
9 | import { GridType } from './GridType';
10 | import { GridTypeUtils } from './GridTypeUtils';
11 |
12 | /**
13 | * Grid
14 | */
15 | export class Grid extends BaseGrid implements IComparable {
16 | /**
17 | * Default line width
18 | */
19 | public static readonly DEFAULT_WIDTH = MGRSProperties.getInstance().getDoubleProperty(
20 | true,
21 | PropertyConstants.GRID.toString(),
22 | PropertyConstants.WIDTH.toString(),
23 | );
24 |
25 | /**
26 | * Grid type
27 | */
28 | private readonly type: GridType;
29 |
30 | /**
31 | * Grid line styles
32 | */
33 | private styles = new Map();
34 |
35 | /**
36 | * Constructor
37 | *
38 | * @param type
39 | * grid type
40 | */
41 | constructor(type: GridType) {
42 | super();
43 | this.type = type;
44 | }
45 |
46 | /**
47 | * Get the grid type
48 | *
49 | * @return grid type
50 | */
51 | public getType(): GridType {
52 | return this.type;
53 | }
54 |
55 | /**
56 | * Is the provided grid type
57 | *
58 | * @param type
59 | * grid type
60 | * @return true if the type
61 | */
62 | public isType(type: GridType): boolean {
63 | return this.type === type;
64 | }
65 |
66 | /**
67 | * Get the precision in meters
68 | *
69 | * @return precision meters
70 | */
71 | public getPrecision(): number {
72 | return this.type;
73 | }
74 |
75 | /**
76 | * Get the grid type precision line style for the grid type
77 | *
78 | * @param gridType
79 | * grid type
80 | * @return grid type line style
81 | */
82 | public getStyle(gridType?: GridType): GridStyle {
83 | let style: GridStyle;
84 | if (gridType !== null && gridType !== undefined) {
85 | if (gridType === this.type) {
86 | style = super.getStyle();
87 | } else {
88 | style = this.styles.get(gridType)!;
89 | }
90 | } else {
91 | style = super.getStyle();
92 | }
93 |
94 | return style;
95 | }
96 |
97 | /**
98 | * Get the grid type line style for the grid type or create it
99 | *
100 | * @param gridType
101 | * grid type
102 | * @return grid type line style
103 | */
104 | private getOrCreateStyle(gridType: GridType): GridStyle {
105 | let style = this.getStyle(gridType);
106 | if (!style) {
107 | style = new GridStyle(undefined, 0);
108 | this.setStyle(style, gridType);
109 | }
110 | return style!;
111 | }
112 |
113 | /**
114 | * Set the grid type precision line style
115 | *
116 | * @param gridType
117 | * grid type
118 | * @param style
119 | * grid line style
120 | */
121 | public setStyle(style?: GridStyle, gridType?: GridType): void {
122 | if (gridType === null || gridType === undefined) {
123 | gridType = this.type;
124 | }
125 |
126 | if (gridType < this.getPrecision()) {
127 | throw new Error(
128 | 'Grid can not define a style for a higher precision grid type. Type: ' +
129 | this.type +
130 | ', Style Type: ' +
131 | gridType,
132 | );
133 | }
134 | if (gridType === this.type) {
135 | super.setStyle(style);
136 | } else {
137 | this.styles.set(gridType, style ? style : new GridStyle(undefined, 0));
138 | }
139 | }
140 |
141 | /**
142 | * Clear the propagated grid type precision styles
143 | */
144 | public clearPrecisionStyles(): void {
145 | this.styles.clear();
146 | }
147 |
148 | /**
149 | * Get the grid type precision line color
150 | *
151 | * @param gridType
152 | * grid type
153 | * @return grid type line color
154 | */
155 | public getColor(gridType?: GridType): Color | undefined {
156 | let color: Color | undefined;
157 | if (gridType !== null && gridType !== undefined) {
158 | const style = this.getStyle(gridType);
159 | if (style) {
160 | color = style.getColor();
161 | }
162 | }
163 | if (!color) {
164 | color = super.getColor();
165 | }
166 |
167 | return color;
168 | }
169 |
170 | /**
171 | * Set the grid type precision line color
172 | *
173 | * @param gridType
174 | * grid type
175 | * @param color
176 | * grid line color
177 | */
178 | public setColor(color?: Color, gridType?: GridType): void {
179 | if (gridType === null || gridType === undefined) {
180 | gridType = this.type;
181 | }
182 | this.getOrCreateStyle(gridType).setColor(color);
183 | }
184 |
185 | /**
186 | * Get the grid type precision line width
187 | *
188 | * @param gridType
189 | * grid type
190 | * @return grid type line width
191 | */
192 | public getWidth(gridType?: GridType): number {
193 | let width = 0;
194 | const style = this.getStyle(gridType);
195 | if (style) {
196 | width = style.getWidth();
197 | }
198 | if (width === 0) {
199 | width = super.getWidth();
200 | }
201 | return width;
202 | }
203 |
204 | /**
205 | * Set the grid type precision line width
206 | *
207 | * @param gridType
208 | * grid type
209 | * @param width
210 | * grid line width
211 | */
212 | public setWidth(width: number, gridType?: GridType): void {
213 | if (gridType === null || gridType === undefined) {
214 | gridType = this.type;
215 | }
216 | this.getOrCreateStyle(gridType).setWidth(width);
217 | }
218 |
219 | /**
220 | * Get the grid labeler
221 | *
222 | * @return grid labeler
223 | */
224 | public getLabeler(): GridLabeler {
225 | return super.getLabeler() as GridLabeler;
226 | }
227 |
228 | /**
229 | * Set the grid labeler
230 | *
231 | * @param labeler
232 | * grid labeler
233 | */
234 | public setLabeler(labeler: GridLabeler): void {
235 | super.setLabeler(labeler);
236 | }
237 |
238 | /**
239 | * Get the lines for the tile and zone
240 | *
241 | * @param tile
242 | * tile
243 | * @param zone
244 | * grid zone
245 | * @return lines
246 | */
247 | public getLinesFromGridTile(tile: GridTile, zone: GridZone): GridLine[] | undefined {
248 | return this.getLines(tile.getZoom(), zone, tile.getBounds());
249 | }
250 |
251 | /**
252 | * Get the lines for the zoom, tile bounds, and zone
253 | *
254 | * @param zoom
255 | * zoom level
256 | * @param tileBounds
257 | * tile bounds
258 | * @param zone
259 | * grid zone
260 | * @return lines
261 | */
262 | public getLines(zoom: number, zone: GridZone, tileBounds?: Bounds): GridLine[] | undefined {
263 | let lines: GridLine[] | undefined;
264 | if (tileBounds && this.isLinesWithin(zoom)) {
265 | lines = this.getLinesFromBounds(tileBounds, zone);
266 | }
267 | return lines;
268 | }
269 |
270 | /**
271 | * Get the lines for the tile bounds and zone
272 | *
273 | * @param tileBounds
274 | * tile bounds
275 | * @param zone
276 | * grid zone
277 | * @return lines
278 | */
279 | public getLinesFromBounds(tileBounds: Bounds, zone: GridZone): GridLine[] | undefined {
280 | return zone.getLines(tileBounds, this.type);
281 | }
282 |
283 | /**
284 | * Get the labels for the tile and zone
285 | *
286 | * @param tile
287 | * tile
288 | * @param zone
289 | * grid zone
290 | * @return labels
291 | */
292 | public getLabelsFromGridTile(tile: GridTile, zone: GridZone): GridLabel[] | undefined {
293 | return this.getLabels(tile.getZoom(), zone, tile.getBounds());
294 | }
295 |
296 | /**
297 | * Get the labels for the zoom, tile bounds, and zone
298 | *
299 | * @param zoom
300 | * zoom level
301 | * @param tileBounds
302 | * tile bounds
303 | * @param zone
304 | * grid zone
305 | * @return labels
306 | */
307 | public getLabels(zoom: number, zone: GridZone, tileBounds?: Bounds): GridLabel[] | undefined {
308 | let labels: GridLabel[] | undefined;
309 | if (this.isLabelerWithin(zoom)) {
310 | labels = this.getLabeler().getLabels(this.type, zone, tileBounds);
311 | }
312 | return labels;
313 | }
314 |
315 | /**
316 | * {@inheritDoc}
317 | */
318 | public compareTo(other: Grid): number {
319 | return this.getPrecisionCompare() - other.getPrecisionCompare();
320 | }
321 |
322 | /**
323 | * Get the precision in meters
324 | *
325 | * @return precision meters
326 | */
327 | public getPrecisionCompare(): number {
328 | let precision = this.getPrecision();
329 | if (precision <= GridType.GZD) {
330 | precision = Number.MAX_SAFE_INTEGER;
331 | }
332 | return precision;
333 | }
334 |
335 | public hashCode(): number {
336 | const prime = 31;
337 | let result = 1;
338 | result = prime * result + (!this.type ? 0 : GridTypeUtils.hashCode(this.type));
339 | return result;
340 | }
341 |
342 | /**
343 | * {@inheritDoc}
344 | */
345 | public equals(obj: any): boolean {
346 | if (this === obj) return true;
347 | if (!obj) return false;
348 | if (typeof this !== typeof obj) return false;
349 | const other = obj as Grid;
350 | if (this.type !== other.type) return false;
351 | return true;
352 | }
353 |
354 | public less(other: Grid): boolean {
355 | return this.hashCode() < other.hashCode();
356 | }
357 | }
358 |
--------------------------------------------------------------------------------
/lib/grid/GridLabel.ts:
--------------------------------------------------------------------------------
1 | import { Bounds, Label, Point } from '@ngageoint/grid-js';
2 | import { MGRS } from '../MGRS';
3 | import { GridType } from './GridType';
4 |
5 | /**
6 | * MGRS Grid Label
7 | */
8 | export class GridLabel extends Label {
9 | /**
10 | * Grid type
11 | */
12 | private gridType: GridType;
13 |
14 | /**
15 | * MGRS coordinate
16 | */
17 | private coordinate: MGRS;
18 |
19 | /**
20 | * Constructor
21 | *
22 | * @param name
23 | * name
24 | * @param center
25 | * center point
26 | * @param bounds
27 | * bounds
28 | * @param gridType
29 | * grid type
30 | * @param coordinate
31 | * MGRS coordinate
32 | */
33 | constructor(name: string, center: Point, bounds: Bounds, gridType: GridType, coordinate: MGRS) {
34 | super(name, center, bounds);
35 | this.gridType = gridType;
36 | this.coordinate = coordinate;
37 | }
38 |
39 | /**
40 | * Get the grid type
41 | *
42 | * @return grid type
43 | */
44 | public getGridType(): GridType {
45 | return this.gridType;
46 | }
47 |
48 | /**
49 | * Set the grid type
50 | *
51 | * @param gridType
52 | * grid type
53 | */
54 | public setGridType(gridType: GridType): void {
55 | this.gridType = gridType;
56 | }
57 |
58 | /**
59 | * Get the MGRS coordinate
60 | *
61 | * @return MGRS coordinate
62 | */
63 | public getCoordinate(): MGRS {
64 | return this.coordinate;
65 | }
66 |
67 | /**
68 | * Set the MGRS coordinate
69 | *
70 | * @param coordinate
71 | * MGRS coordinate
72 | */
73 | public setCoordinate(coordinate: MGRS): void {
74 | this.coordinate = coordinate;
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/lib/grid/GridLabeler.ts:
--------------------------------------------------------------------------------
1 | import { Color } from '@ngageoint/color-js';
2 | import { Bounds, Labeler, PropertyConstants } from '@ngageoint/grid-js';
3 | import { GridZone } from '../gzd/GridZone';
4 | import { MGRSProperties } from '../property/MGRSProperties';
5 | import { GridLabel } from './GridLabel';
6 | import { GridType } from './GridType';
7 |
8 | /**
9 | * Grid Labeler
10 | */
11 | export abstract class GridLabeler extends Labeler {
12 | /**
13 | * Default text size
14 | */
15 | public static readonly DEFAULT_TEXT_SIZE = MGRSProperties.getInstance().getDoubleProperty(
16 | true,
17 | PropertyConstants.LABELER.toString(),
18 | PropertyConstants.TEXT_SIZE.toString(),
19 | );
20 |
21 | /**
22 | * Default buffer size
23 | */
24 | public static readonly DEFAULT_BUFFER = MGRSProperties.getInstance().getDoubleProperty(
25 | true,
26 | PropertyConstants.LABELER.toString(),
27 | PropertyConstants.BUFFER.toString(),
28 | );
29 |
30 | constructor(
31 | enabled: boolean,
32 | minZoom = 0,
33 | maxZoom: number | undefined,
34 | color = Color.black(),
35 | textSize = GridLabeler.DEFAULT_TEXT_SIZE,
36 | buffer = GridLabeler.DEFAULT_BUFFER,
37 | ) {
38 | super(enabled, minZoom, maxZoom, color, textSize!, buffer!);
39 | }
40 |
41 | /**
42 | * Get labels for the bounds
43 | *
44 | * @param gridType
45 | * grid type
46 | * @param zone
47 | * grid zone
48 | * * @param tileBounds
49 | * tile bounds
50 | * @return labels
51 | */
52 | public abstract getLabels(gridType: GridType, zone: GridZone, tileBounds?: Bounds): GridLabel[] | undefined;
53 | }
54 |
--------------------------------------------------------------------------------
/lib/grid/GridType.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Grid type enumeration
3 | */
4 | export enum GridType {
5 | /**
6 | * Grid Zone Designator
7 | */
8 | GZD = 0,
9 |
10 | /**
11 | * Hundred Kilometer
12 | */
13 | HUNDRED_KILOMETER = 100000,
14 |
15 | /**
16 | * Ten Kilometer
17 | */
18 | TEN_KILOMETER = 10000,
19 |
20 | /**
21 | * Kilometer
22 | */
23 | KILOMETER = 1000,
24 |
25 | /**
26 | * Hundred Meter
27 | */
28 | HUNDRED_METER = 100,
29 |
30 | /**
31 | * Ten Meter
32 | */
33 | TEN_METER = 10,
34 |
35 | /**
36 | * Meter
37 | */
38 | METER = 1,
39 | }
40 |
--------------------------------------------------------------------------------
/lib/grid/GridTypeUtils.ts:
--------------------------------------------------------------------------------
1 | import { GridType } from './GridType';
2 |
3 | export class GridTypeUtils {
4 | /**
5 | * Get the Grid Type accuracy number of digits in the easting and northing
6 | * values
7 | *
8 | * @return accuracy digits
9 | */
10 | public static getAccuracy(gridType: GridType): number {
11 | return Math.max(GridTypeUtils.ordinal(gridType) - 1, 0);
12 | }
13 |
14 | /**
15 | * Get the Grid Type with the accuracy number of digits in the easting and
16 | * northing values. Accuracy must be inclusively between 0
17 | * ({@link GridType#HUNDRED_KILOMETER}) and 5 ({@link GridType#METER}).
18 | *
19 | * @param accuracy
20 | * accuracy digits between 0 (inclusive) and 5 (inclusive)
21 | * @return grid type
22 | */
23 | public static withAccuracy(accuracy: number): GridType {
24 | if (accuracy < 0 || accuracy > 5) {
25 | throw new Error('Grid Type accuracy digits must be >= 0 and <= 5. accuracy digits: ' + accuracy);
26 | }
27 | return GridTypeUtils.values()[accuracy + 1];
28 | }
29 |
30 | /**
31 | * Get the precision of the value in meters based upon trailing 0's
32 | *
33 | * @param value
34 | * value in meters
35 | * @return precision grid type
36 | */
37 | public static getPrecision(value: number): GridType {
38 | let precision: GridType;
39 | if (value % GridType.HUNDRED_KILOMETER === 0) {
40 | precision = GridType.HUNDRED_KILOMETER;
41 | } else if (value % GridType.TEN_KILOMETER === 0) {
42 | precision = GridType.TEN_KILOMETER;
43 | } else if (value % GridType.KILOMETER === 0) {
44 | precision = GridType.KILOMETER;
45 | } else if (value % GridType.HUNDRED_METER === 0) {
46 | precision = GridType.HUNDRED_METER;
47 | } else if (value % GridType.TEN_METER === 0) {
48 | precision = GridType.TEN_METER;
49 | } else {
50 | precision = GridType.METER;
51 | }
52 | return precision;
53 | }
54 |
55 | public static values(): GridType[] {
56 | const gridTypes: GridType[] = [];
57 | const values = Object.keys(GridType).map((key: any) => GridType[key]);
58 | for (const type of values) {
59 | if (Number.isInteger(type)) {
60 | gridTypes.push(type as unknown as number);
61 | }
62 | }
63 | return gridTypes;
64 | }
65 |
66 | public static ordinal(type: GridType): number {
67 | const types: string[] = Object.keys(GridType);
68 |
69 | let ordinal = 0;
70 | for (let i = 0; i < types.length; i++) {
71 | if (isNaN(Number(types[i]))) {
72 | if (types[i] === GridType[type]) {
73 | break;
74 | }
75 | ordinal++;
76 | }
77 | }
78 |
79 | return ordinal;
80 | }
81 |
82 | public static hashCode(type: GridType): number {
83 | let h: number = 0;
84 | const str = type.toString();
85 | for (let i = 0; i < str.length; i++) {
86 | h = 31 * h + str.charCodeAt(i);
87 | }
88 | return h & 0xffffffff;
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/lib/grid/Grids.ts:
--------------------------------------------------------------------------------
1 | import { Color } from '@ngageoint/color-js';
2 | import { BaseGrids, GridStyle, PropertyConstants } from '@ngageoint/grid-js';
3 | import { GZDLabeler } from '../gzd/GZDLabeler';
4 | import { MGRSProperties } from '../property/MGRSProperties';
5 | import { Grid } from './Grid';
6 | import { GridLabeler } from './GridLabeler';
7 | import { GridType } from './GridType';
8 | import { GridTypeUtils } from './GridTypeUtils';
9 | import { MGRSLabeler } from './MGRSLabeler';
10 | import { ZoomGrids } from './ZoomGrids';
11 |
12 | /**
13 | * Grids
14 | */
15 | export class Grids extends BaseGrids {
16 | /**
17 | * Grids
18 | */
19 | private gridMap = new Map();
20 |
21 | /**
22 | * Create with grids to enable
23 | *
24 | * @param types
25 | * grid types to enable
26 | * @return grids
27 | */
28 | public static create(types?: GridType[]): Grids {
29 | return new Grids(types);
30 | }
31 |
32 | /**
33 | * Create only Grid Zone Designator grids
34 | *
35 | * @return grids
36 | */
37 | public static createGZD(): Grids {
38 | return Grids.create([GridType.GZD]);
39 | }
40 |
41 | /**
42 | * Constructor
43 | *
44 | * @param types
45 | * grid types to enable
46 | */
47 | constructor(types?: GridType[]) {
48 | super(MGRSProperties.getInstance());
49 |
50 | if (types && types.length > 0) {
51 | this.createGrids(false);
52 | for (const type of types) {
53 | const grid = this.getGrid(type);
54 | if (grid) {
55 | grid.setEnabled(true);
56 | }
57 | }
58 | } else {
59 | this.createGrids();
60 | }
61 |
62 | this.createZoomGrids();
63 | }
64 |
65 | /**
66 | * {@inheritDoc}
67 | */
68 | public getDefaultWidth(): number {
69 | return Grid.DEFAULT_WIDTH!;
70 | }
71 |
72 | /**
73 | * {@inheritDoc}
74 | */
75 | public grids(): Grid[] {
76 | const result: Grid[] = [];
77 |
78 | for (const grid of this.gridMap.values()) {
79 | result.push(grid);
80 | }
81 |
82 | return result;
83 | }
84 |
85 | /**
86 | * Create a new grid, override to create a specialized grid
87 | *
88 | * @param type
89 | * grid type
90 | * @return grid
91 | */
92 | protected newGrid(type: GridType): Grid {
93 | return new Grid(type);
94 | }
95 |
96 | /**
97 | * {@inheritDoc}
98 | */
99 | protected newZoomGrids(zoom: number): ZoomGrids {
100 | return new ZoomGrids(zoom);
101 | }
102 |
103 | /**
104 | * Create the grids
105 | *
106 | * @param enabled
107 | * enable created grids
108 | */
109 | private createGrids(enabled?: boolean): void {
110 | const propagate = this.properties.getBooleanProperty(false, PropertyConstants.GRIDS, PropertyConstants.PROPAGATE);
111 | let styles: Map | undefined;
112 | if (propagate !== undefined && propagate !== null && propagate) {
113 | styles = new Map();
114 | }
115 |
116 | this.createGrid(GridType.GZD, new GZDLabeler(true), styles, enabled);
117 | this.createGrid(GridType.HUNDRED_KILOMETER, new MGRSLabeler(true), styles, enabled);
118 | this.createGrid(GridType.TEN_KILOMETER, new MGRSLabeler(true), styles, enabled);
119 | this.createGrid(GridType.KILOMETER, new MGRSLabeler(true), styles, enabled);
120 | this.createGrid(GridType.HUNDRED_METER, new MGRSLabeler(true), styles, enabled);
121 | this.createGrid(GridType.TEN_METER, new MGRSLabeler(true), styles, enabled);
122 | this.createGrid(GridType.METER, new MGRSLabeler(true), styles, enabled);
123 | }
124 |
125 | /**
126 | * Create the grid
127 | *
128 | * @param type
129 | * grid type
130 | * @param styles
131 | * propagate grid styles
132 | * @param enabled
133 | * enable created grids
134 | * @param labeler
135 | * grid labeler
136 | */
137 | private createGrid(type: GridType, labeler: GridLabeler, styles?: Map, enabled?: boolean): void {
138 | const grid = this.newGrid(type);
139 |
140 | const gridKey = GridType[type].toLowerCase();
141 |
142 | this.loadGrid(grid, gridKey, labeler, enabled);
143 |
144 | if (styles) {
145 | styles.set(type, GridStyle.style(grid.getColor(), grid.getWidth()));
146 | }
147 |
148 | this.loadGridStyles(grid, gridKey, styles);
149 |
150 | this.gridMap.set(type, grid);
151 | }
152 |
153 | /**
154 | * Load grid styles within a higher precision grid
155 | *
156 | * @param grid
157 | * grid
158 | * @param gridKey
159 | * grid key
160 | * @param styles
161 | * propagate grid styles
162 | */
163 | private loadGridStyles(grid: Grid, gridKey: string, styles?: Map): void {
164 | const precision = grid.getPrecision();
165 | if (precision < GridType.HUNDRED_KILOMETER) {
166 | this.loadGridStyle(grid, gridKey, GridType.HUNDRED_KILOMETER, styles);
167 | }
168 | if (precision < GridType.TEN_KILOMETER) {
169 | this.loadGridStyle(grid, gridKey, GridType.TEN_KILOMETER, styles);
170 | }
171 | if (precision < GridType.KILOMETER) {
172 | this.loadGridStyle(grid, gridKey, GridType.KILOMETER, styles);
173 | }
174 | if (precision < GridType.HUNDRED_METER) {
175 | this.loadGridStyle(grid, gridKey, GridType.HUNDRED_METER, styles);
176 | }
177 | if (precision < GridType.TEN_METER) {
178 | this.loadGridStyle(grid, gridKey, GridType.TEN_METER, styles);
179 | }
180 | }
181 |
182 | /**
183 | * Load a grid style within a higher precision grid
184 | *
185 | * @param grid
186 | * grid
187 | *
188 | * @param gridKey
189 | * grid key
190 | * @param gridType
191 | * style grid type
192 | * @param styles
193 | * propagate grid styles
194 | */
195 | private loadGridStyle(grid: Grid, gridKey: string, gridType: GridType, styles?: Map): void {
196 | const gridKey2 = GridType[gridType].toLowerCase();
197 |
198 | let color = this.loadGridStyleColor(gridKey, gridKey2);
199 | let width = this.loadGridStyleWidth(gridKey, gridKey2);
200 |
201 | if ((!color || width === undefined || width === null) && styles) {
202 | const style = styles.get(gridType);
203 | if (style) {
204 | if (!color) {
205 | const styleColor = style.getColor();
206 | if (styleColor) {
207 | color = styleColor.copy();
208 | }
209 | }
210 | if (width === null || width === undefined) {
211 | width = style.getWidth();
212 | }
213 | }
214 | }
215 |
216 | if (color || (width !== null && width !== undefined)) {
217 | const style = this.getGridStyle(grid, color, width);
218 | grid.setStyle(style, gridType);
219 |
220 | if (styles) {
221 | styles.set(gridType, style);
222 | }
223 | }
224 | }
225 |
226 | /**
227 | * Get the grid
228 | *
229 | * @param type
230 | * grid type
231 | * @return grid
232 | */
233 | public getGrid(type: GridType): Grid | undefined {
234 | return this.gridMap.get(type);
235 | }
236 |
237 | /**
238 | * Get the grid precision for the zoom level
239 | *
240 | * @param zoom
241 | * zoom level
242 | * @return grid type precision
243 | */
244 | public getPrecision(zoom: number): GridType | undefined {
245 | const grids = this.getGrids(zoom);
246 | let precision: GridType | undefined;
247 | if (grids) {
248 | precision = grids.getPrecision();
249 | }
250 | return precision;
251 | }
252 |
253 | /**
254 | * Set the active grid types
255 | *
256 | * @param types
257 | * grid types
258 | */
259 | public setGridTypes(types: GridType[]): void {
260 | const disableTypes = new Set(Object.keys(GridType));
261 |
262 | for (const gridType of types) {
263 | this.enableByType(gridType);
264 | disableTypes.delete(GridType[gridType]);
265 | }
266 | const enums: GridType[] = [];
267 | for (const disableType of disableTypes) {
268 | enums.push(GridType[disableType as keyof typeof GridType]);
269 | }
270 | this.disableTypes(enums);
271 | }
272 |
273 | /**
274 | * Set the active grids
275 | *
276 | * @param grids
277 | * grids
278 | */
279 | public setGrids(grids: Grid[]): void {
280 | const disableTypes = new Set(Object.keys(GridType));
281 | for (const grid of grids) {
282 | this.enable(grid);
283 | disableTypes.delete(GridType[grid.getType()]);
284 | }
285 | const enums: GridType[] = [];
286 | for (const disableType of disableTypes) {
287 | enums.push(GridType[disableType as keyof typeof GridType]);
288 | }
289 | this.disableTypes(enums);
290 | }
291 |
292 | /**
293 | * Enable grid types
294 | *
295 | * @param types
296 | * grid types
297 | */
298 | public enableTypes(types: GridType[]): void {
299 | for (const type of types) {
300 | this.enableByType(type);
301 | }
302 | }
303 |
304 | /**
305 | * Disable grid types
306 | *
307 | * @param types
308 | * grid types
309 | */
310 | public disableTypes(types: GridType[]): void {
311 | for (const type of types) {
312 | this.disableByType(type);
313 | }
314 | }
315 |
316 | /**
317 | * Is the grid type enabled
318 | *
319 | * @param type
320 | * grid type
321 | * @return true if enabled
322 | */
323 | public isEnabled(type: GridType): boolean {
324 | return this.getGrid(type)!.isEnabled();
325 | }
326 |
327 | /**
328 | * Enable the grid type
329 | *
330 | * @param type
331 | * grid type
332 | */
333 | public enableByType(type: GridType): void {
334 | this.enable(this.getGrid(type)!);
335 | }
336 |
337 | /**
338 | * Disable the grid type
339 | *
340 | * @param type
341 | * grid type
342 | */
343 | public disableByType(type: GridType): void {
344 | this.disable(this.getGrid(type)!);
345 | }
346 |
347 | /**
348 | * Set the grid minimum zoom
349 | *
350 | * @param type
351 | * grid type
352 | * @param minZoom
353 | * minimum zoom
354 | */
355 | public setMinZoomByType(type: GridType, minZoom: number): void {
356 | super.setMinZoom(this.getGrid(type)!, minZoom);
357 | }
358 |
359 | /**
360 | * Set the grid maximum zoom
361 | *
362 | * @param type
363 | * grid type
364 | * @param maxZoom
365 | * maximum zoom
366 | */
367 | public setMaxZoomByType(type: GridType, maxZoom: number): void {
368 | super.setMaxZoom(this.getGrid(type)!, maxZoom);
369 | }
370 |
371 | /**
372 | * Set the grid zoom range
373 | *
374 | * @param type
375 | * grid type
376 | * @param minZoom
377 | * minimum zoom
378 | * @param maxZoom
379 | * maximum zoom
380 | */
381 | public setZoomRangeByType(type: GridType, minZoom: number, maxZoom: number): void {
382 | super.setZoomRange(this.getGrid(type)!, minZoom, maxZoom);
383 | }
384 |
385 | /**
386 | * Set the grid minimum level override for drawing grid lines
387 | *
388 | * @param type
389 | * grid type
390 | * @param minZoom
391 | * minimum zoom level or null to remove
392 | */
393 | public setLinesMinZoom(type: GridType, minZoom: number): void {
394 | this.getGrid(type)!.setLinesMinZoom(minZoom);
395 | }
396 |
397 | /**
398 | * Set the grid maximum level override for drawing grid lines
399 | *
400 | * @param type
401 | * grid type
402 | * @param maxZoom
403 | * maximum zoom level or null to remove
404 | */
405 | public setLinesMaxZoom(type: GridType, maxZoom?: number): void {
406 | this.getGrid(type)!.setLinesMaxZoom(maxZoom);
407 | }
408 |
409 | /**
410 | * Set all grid line colors
411 | *
412 | * @param color
413 | * grid line color
414 | */
415 | public setAllColors(color: Color): void {
416 | this.setColor(GridTypeUtils.values(), color);
417 | }
418 |
419 | /**
420 | * Set all grid line widths
421 | *
422 | * @param width
423 | * grid line width
424 | */
425 | public setAllWidths(width: number): void {
426 | this.setWidth(GridTypeUtils.values(), width);
427 | }
428 |
429 | /**
430 | * Delete propagated styles for the grid types
431 | *
432 | * @param types
433 | * grid types
434 | */
435 | public deletePropagatedStyles(types?: GridType[]): void {
436 | if (!types) {
437 | types = GridTypeUtils.values();
438 | }
439 | for (const type of types!) {
440 | this.getGrid(type)!.clearPrecisionStyles();
441 | }
442 | }
443 |
444 | /**
445 | * Set the grid type precision line color for the grid types
446 | *
447 | * @param types
448 | * grid types
449 | * @param precisionTypes
450 | * precision grid types
451 | * @param color
452 | * grid line color
453 | */
454 | public setColor(types: GridType[], color: Color, precisionTypes?: GridType[]): void {
455 | if (precisionTypes) {
456 | for (const precisionType of precisionTypes) {
457 | for (const type of types) {
458 | this.getGrid(type)!.setColor(color, precisionType);
459 | }
460 | }
461 | } else {
462 | for (const type of types) {
463 | this.getGrid(type)!.setColor(color);
464 | }
465 | }
466 | }
467 |
468 | /**
469 | * Set the grid type precision line width for the grid types
470 | *
471 | * @param types
472 | * grid types
473 | * @param precisionType
474 | * precision grid type
475 | * @param width
476 | * grid line width
477 | */
478 | public setWidth(types: GridType[], width: number, precisionTypes?: GridType[]): void {
479 | if (precisionTypes) {
480 | for (const precisionType of precisionTypes) {
481 | for (const type of types) {
482 | this.getGrid(type)!.setWidth(width, precisionType);
483 | }
484 | }
485 | } else {
486 | for (const type of types) {
487 | this.getGrid(type)!.setWidth(width);
488 | }
489 | }
490 | }
491 |
492 | /**
493 | * Get the labeler for the grid type
494 | *
495 | * @param type
496 | * grid type
497 | * @return labeler or null
498 | */
499 | public getLabeler(type: GridType): GridLabeler {
500 | return this.getGrid(type)!.getLabeler();
501 | }
502 |
503 | /**
504 | * Has a labeler for the grid type
505 | *
506 | * @param type
507 | * grid type
508 | * @return true if has labeler
509 | */
510 | public hasLabeler(type: GridType): boolean {
511 | return this.getGrid(type)!.hasLabeler();
512 | }
513 |
514 | /**
515 | * Set the labeler for the grid type
516 | *
517 | * @param type
518 | * grid type
519 | * @param labeler
520 | * labeler
521 | */
522 | public setLabeler(type: GridType, labeler: GridLabeler): void {
523 | this.getGrid(type)!.setLabeler(labeler);
524 | }
525 |
526 | /**
527 | * Disable all grid labelers
528 | */
529 | public disableAllLabelers(): void {
530 | this.disableLabelers(GridTypeUtils.values());
531 | }
532 |
533 | /**
534 | * Enable the labelers for the grid types
535 | *
536 | * @param types
537 | * grid types
538 | */
539 | public enableLabelers(types: GridType[]): void {
540 | for (const type of types) {
541 | this.enableLabeler(type);
542 | }
543 | }
544 |
545 | /**
546 | * Disable the labelers for the grid types
547 | *
548 | * @param types
549 | * grid types
550 | */
551 | public disableLabelers(types: GridType[]): void {
552 | for (const type of types) {
553 | this.disableLabeler(type);
554 | }
555 | }
556 |
557 | /**
558 | * Is a labeler enabled for the grid type
559 | *
560 | * @param type
561 | * grid type
562 | * @return true if labeler enabled
563 | */
564 | public isLabelerEnabled(type: GridType): boolean {
565 | const labeler = this.getLabeler(type);
566 | return labeler !== null && labeler.isEnabled();
567 | }
568 |
569 | /**
570 | * Enable the grid type labeler
571 | *
572 | * @param type
573 | * grid type
574 | */
575 | public enableLabeler(type: GridType): void {
576 | this.getRequiredLabeler(type).setEnabled(true);
577 | }
578 |
579 | /**
580 | * Disable the grid type labeler
581 | *
582 | * @param type
583 | * grid type
584 | */
585 | public disableLabeler(type: GridType): void {
586 | const labeler = this.getLabeler(type);
587 | if (labeler) {
588 | labeler.setEnabled(false);
589 | }
590 | }
591 |
592 | /**
593 | * Get the labeler for the grid type
594 | *
595 | * @param type
596 | * grid type
597 | * @return labeler or null
598 | */
599 | private getRequiredLabeler(type: GridType): GridLabeler {
600 | const labeler = this.getLabeler(type);
601 | if (!labeler) {
602 | throw new Error('Grid type does not have a labeler: ' + type);
603 | }
604 | return labeler;
605 | }
606 |
607 | /**
608 | * Set the grid minimum zoom
609 | *
610 | * @param type
611 | * grid type
612 | * @param minZoom
613 | * minimum zoom
614 | */
615 | public setLabelMinZoom(type: GridType, minZoom: number): void {
616 | const labeler = this.getRequiredLabeler(type);
617 | labeler.setMinZoom(minZoom);
618 | const maxZoom = labeler.getMaxZoom();
619 | if (maxZoom !== null && maxZoom !== undefined && maxZoom < minZoom) {
620 | labeler.setMaxZoom(minZoom);
621 | }
622 | }
623 |
624 | /**
625 | * Set the grid maximum zoom
626 | *
627 | * @param type
628 | * grid type
629 | * @param maxZoom
630 | * maximum zoom
631 | */
632 | public setLabelMaxZoom(type: GridType, maxZoom: number): void {
633 | const labeler = this.getRequiredLabeler(type);
634 | labeler.setMaxZoom(maxZoom);
635 | if (maxZoom !== null && maxZoom !== undefined && labeler.getMinZoom() > maxZoom) {
636 | labeler.setMinZoom(maxZoom);
637 | }
638 | }
639 |
640 | /**
641 | * Set the grid zoom range
642 | *
643 | * @param type
644 | * grid type
645 | * @param minZoom
646 | * minimum zoom
647 | * @param maxZoom
648 | * maximum zoom
649 | */
650 | public setLabelZoomRange(type: GridType, minZoom: number, maxZoom: number): void {
651 | const labeler = this.getRequiredLabeler(type);
652 | if (maxZoom !== null && maxZoom !== undefined && maxZoom < minZoom) {
653 | throw new Error("Min zoom '" + minZoom + "' can not be larger than max zoom '" + maxZoom + "'");
654 | }
655 | labeler.setMinZoom(minZoom);
656 | labeler.setMaxZoom(maxZoom);
657 | }
658 |
659 | /**
660 | * Set the label grid edge buffer for the grid types
661 | *
662 | * @param buffer
663 | * label buffer (greater than or equal to 0.0 and less than 0.5)
664 | * @param types
665 | * grid types
666 | */
667 | public setLabelBuffer(buffer: number, types: GridType[]): void {
668 | for (const type of types) {
669 | this.getRequiredLabeler(type).setBuffer(buffer);
670 | }
671 | }
672 |
673 | /**
674 | * Get the label grid edge buffer
675 | *
676 | * @param type
677 | * grid type
678 | * @return label buffer (greater than or equal to 0.0 and less than 0.5)
679 | */
680 | public getLabelBuffer(type: GridType): number {
681 | return this.getGrid(type)!.getLabelBuffer();
682 | }
683 |
684 | /**
685 | * Set all label colors
686 | *
687 | * @param color
688 | * label color
689 | */
690 | public setAllLabelColors(color: Color): void {
691 | for (const grid of this.gridMap.values()) {
692 | if (grid.hasLabeler()) {
693 | this.setLabelColor(color, [grid.getType()]);
694 | }
695 | }
696 | }
697 |
698 | /**
699 | * Set the label color for the grid types
700 | *
701 | * @param color
702 | * label color
703 | * @param types
704 | * grid types
705 | */
706 | public setLabelColor(color: Color, types: GridType[]): void {
707 | for (const type of types) {
708 | this.getRequiredLabeler(type).setColor(color);
709 | }
710 | }
711 |
712 | /**
713 | * Set all label text sizes
714 | *
715 | * @param textSize
716 | * label text size
717 | */
718 | public setAllLabelTextSizes(textSize: number): void {
719 | for (const grid of this.gridMap.values()) {
720 | if (grid.hasLabeler()) {
721 | this.setLabelTextSize(textSize, [grid.getType()]);
722 | }
723 | }
724 | }
725 |
726 | /**
727 | * Set the label text size for the grid types
728 | *
729 | * @param textSize
730 | * label text size
731 | * @param types
732 | * grid types
733 | */
734 | public setLabelTextSize(textSize: number, types: GridType[]): void {
735 | for (const type of types) {
736 | this.getRequiredLabeler(type).setTextSize(textSize);
737 | }
738 | }
739 | }
740 |
--------------------------------------------------------------------------------
/lib/grid/MGRSLabeler.ts:
--------------------------------------------------------------------------------
1 | import { Color } from '@ngageoint/color-js';
2 | import { Bounds } from '@ngageoint/grid-js';
3 | import { GridZone } from '../gzd/GridZone';
4 | import { MGRS } from '../MGRS';
5 | import { UTM } from '../utm/UTM';
6 | import { GridLabel } from './GridLabel';
7 | import { GridLabeler } from './GridLabeler';
8 | import { GridType } from './GridType';
9 |
10 | /**
11 | * MGRS grid labeler
12 | */
13 | export class MGRSLabeler extends GridLabeler {
14 | constructor(enabled: boolean, minZoom = 0, maxZoom?: number, color?: Color, textSize?: number, buffer?: number) {
15 | super(enabled, minZoom, maxZoom, color, textSize, buffer);
16 | }
17 |
18 | /**
19 | * {@inheritDoc}
20 | */
21 | public getLabels(gridType: GridType, zone: GridZone, tileBounds?: Bounds): GridLabel[] | undefined {
22 | let labels: GridLabel[] | undefined;
23 |
24 | if (tileBounds) {
25 | const drawBounds = zone.getDrawBounds(tileBounds, gridType);
26 |
27 | if (drawBounds) {
28 | labels = [];
29 |
30 | const precision = gridType;
31 |
32 | for (
33 | let easting = drawBounds.getMinLongitude();
34 | easting <= drawBounds.getMaxLongitude();
35 | easting += precision
36 | ) {
37 | for (
38 | let northing = drawBounds.getMinLatitude();
39 | northing <= drawBounds.getMaxLatitude();
40 | northing += precision
41 | ) {
42 | const label = this.getLabel(gridType, zone, easting, northing);
43 | if (label) {
44 | labels.push(label);
45 | }
46 | }
47 | }
48 | }
49 | }
50 |
51 | return labels;
52 | }
53 |
54 | /**
55 | * Get the grid zone label
56 | *
57 | * @param gridType
58 | * grid type
59 | * @param easting
60 | * easting
61 | * @param northing
62 | * northing
63 | * @return labels
64 | */
65 | private getLabel(gridType: GridType, zone: GridZone, easting: number, northing: number): GridLabel | undefined {
66 | let label: GridLabel | undefined;
67 |
68 | const precision = gridType;
69 | const bounds = zone.getBounds();
70 | const zoneNumber = zone.getNumber();
71 | const hemisphere = zone.getHemisphere();
72 |
73 | const northwest = UTM.point(zoneNumber, hemisphere, easting, northing + precision);
74 | const southwest = UTM.point(zoneNumber, hemisphere, easting, northing);
75 | const southeast = UTM.point(zoneNumber, hemisphere, easting + precision, northing);
76 | const northeast = UTM.point(zoneNumber, hemisphere, easting + precision, northing + precision);
77 |
78 | let minLatitude = Math.max(southwest.getLatitude(), southeast.getLatitude());
79 | minLatitude = Math.max(minLatitude, bounds.getMinLatitude());
80 | let maxLatitude = Math.min(northwest.getLatitude(), northeast.getLatitude());
81 | maxLatitude = Math.min(maxLatitude, bounds.getMaxLatitude());
82 |
83 | let minLongitude = Math.max(southwest.getLongitude(), northwest.getLongitude());
84 | minLongitude = Math.max(minLongitude, bounds.getMinLongitude());
85 | let maxLongitude = Math.min(southeast.getLongitude(), northeast.getLongitude());
86 | maxLongitude = Math.min(maxLongitude, bounds.getMaxLongitude());
87 |
88 | if (minLongitude <= maxLongitude && minLatitude <= maxLatitude) {
89 | const labelBounds = Bounds.degrees(minLongitude, minLatitude, maxLongitude, maxLatitude);
90 | const center = labelBounds.getCentroid();
91 |
92 | const mgrs = MGRS.from(center);
93 | let id: string;
94 | if (gridType === GridType.HUNDRED_KILOMETER) {
95 | id = mgrs.getColumnRowId();
96 | } else {
97 | id = mgrs.getEastingAndNorthing(gridType);
98 | }
99 |
100 | label = new GridLabel(id, center, labelBounds, gridType, mgrs);
101 | }
102 |
103 | return label;
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/lib/grid/ZoomGrids.ts:
--------------------------------------------------------------------------------
1 | import { BaseZoomGrids } from '@ngageoint/grid-js';
2 | import { Grid } from './Grid';
3 | import { GridType } from './GridType';
4 |
5 | /**
6 | * Zoom Level Matching Grids
7 | */
8 | export class ZoomGrids extends BaseZoomGrids {
9 | /**
10 | * Constructor
11 | *
12 | * @param zoom
13 | * zoom level
14 | */
15 | constructor(zoom: number) {
16 | super(zoom);
17 | }
18 |
19 | /**
20 | * Get the grid type precision
21 | *
22 | * @return grid type precision
23 | */
24 | public getPrecision(): GridType | undefined {
25 | let type: GridType | undefined;
26 | if (super.hasGrids()) {
27 | type = this.grids.begin().value.getType();
28 | }
29 | return type;
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/lib/gzd/BandLetterRange.ts:
--------------------------------------------------------------------------------
1 | import { MGRSConstants } from '../MGRSConstants';
2 | import { MGRSUtils } from '../MGRSUtils';
3 | import { GridZones } from './GridZones';
4 |
5 | /**
6 | * Band Letter Range
7 | */
8 | export class BandLetterRange implements IterableIterator {
9 | /**
10 | * Southern band letter
11 | */
12 | private south: string;
13 |
14 | /**
15 | * Northern band letter
16 | */
17 | private north: string;
18 |
19 | private letter: string;
20 |
21 | /**
22 | * Constructor
23 | *
24 | * @param south
25 | * southern band letter
26 | * @param north
27 | * northern band letter
28 | */
29 | constructor(south = MGRSConstants.MIN_BAND_LETTER, north = MGRSConstants.MAX_BAND_LETTER) {
30 | this.south = south;
31 | this.north = north;
32 |
33 | this.letter = south;
34 | }
35 |
36 | /**
37 | * Get the southern band letter
38 | *
39 | * @return southern band letter
40 | */
41 | public getSouth(): string {
42 | return this.south;
43 | }
44 |
45 | /**
46 | * Set the southern band letter
47 | *
48 | * @param south
49 | * southern band letter
50 | */
51 | public setSouth(south: string): void {
52 | this.south = south;
53 | }
54 |
55 | /**
56 | * Get the northern band letter
57 | *
58 | * @return northern band letter
59 | */
60 | public getNorth(): string {
61 | return this.north;
62 | }
63 |
64 | /**
65 | * Set the northern band letter
66 | *
67 | * @param north
68 | * northern band letter
69 | */
70 | public setNorth(north: string): void {
71 | this.north = north;
72 | }
73 |
74 | /**
75 | * Get the southern latitude
76 | *
77 | * @return latitude
78 | */
79 | public getSouthLatitude(): number {
80 | return GridZones.getLatitudeBand(this.south).getSouth();
81 | }
82 |
83 | /**
84 | * Get the northern latitude
85 | *
86 | * @return latitude
87 | */
88 | public getNorthLatitude(): number {
89 | return GridZones.getLatitudeBand(this.north).getNorth();
90 | }
91 |
92 | public next(): IteratorResult {
93 | if (this.letter <= this.north) {
94 | const currentLetter = this.letter;
95 | this.letter = MGRSUtils.nextBandLetter(this.letter);
96 | return {
97 | done: false,
98 | value: currentLetter,
99 | };
100 | } else {
101 | return {
102 | done: true,
103 | value: null,
104 | };
105 | }
106 | }
107 |
108 | public reset(): void {
109 | this.letter = this.south;
110 | }
111 |
112 | [Symbol.iterator](): IterableIterator {
113 | return this;
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/lib/gzd/GZDLabeler.ts:
--------------------------------------------------------------------------------
1 | import { Color } from '@ngageoint/color-js';
2 | import { GridLabel } from '../grid/GridLabel';
3 | import { GridLabeler } from '../grid/GridLabeler';
4 | import { GridType } from '../grid/GridType';
5 | import { MGRS } from '../MGRS';
6 | import { GridZone } from './GridZone';
7 |
8 | /**
9 | * Grid Zone Designator labeler
10 | */
11 | export class GZDLabeler extends GridLabeler {
12 | constructor(enabled: boolean, minZoom = 0, maxZoom?: number, color?: Color, textSize?: number, buffer?: number) {
13 | super(enabled, minZoom, maxZoom, color, textSize, buffer);
14 | }
15 |
16 | /**
17 | * {@inheritDoc}
18 | */
19 | public getLabels(gridType: GridType, zone: GridZone): GridLabel[] {
20 | const labels: GridLabel[] = [];
21 | const bounds = zone.getBounds();
22 | const center = bounds.getCentroid();
23 | labels.push(new GridLabel(zone.getName(), center, bounds, gridType, MGRS.from(center)));
24 | return labels;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/lib/gzd/GridRange.ts:
--------------------------------------------------------------------------------
1 | import { Bounds } from '@ngageoint/grid-js';
2 | import { MGRSUtils } from '../MGRSUtils';
3 | import { BandLetterRange } from './BandLetterRange';
4 | import { GridZone } from './GridZone';
5 | import { GridZones } from './GridZones';
6 | import { ZoneNumberRange } from './ZoneNumberRange';
7 |
8 | /**
9 | * Grid Range
10 | */
11 | export class GridRange implements IterableIterator {
12 | /**
13 | * Zone Number Range
14 | */
15 | private zoneNumberRange: ZoneNumberRange;
16 |
17 | /**
18 | * Band Letter Range
19 | */
20 | private bandLetterRange: BandLetterRange;
21 |
22 | /**
23 | * Minimum zone number
24 | */
25 | private readonly minZoneNumber: number;
26 |
27 | /**
28 | * Maximum zone number
29 | */
30 | private readonly maxZoneNumber: number;
31 |
32 | /**
33 | * Minimum band letter
34 | */
35 | private readonly minBandLetter: string;
36 |
37 | /**
38 | * Minimum band letter
39 | */
40 | private readonly maxBandLetter: string;
41 |
42 | /**
43 | * Zone number
44 | */
45 | private zoneNumber: number;
46 |
47 | /**
48 | * Band letter
49 | */
50 | private bandLetter: string;
51 |
52 | /**
53 | * Grid zone
54 | */
55 | private gridZone?: GridZone;
56 |
57 | /**
58 | * Additional special case grid zones
59 | */
60 | private additional: GridZone[] = [];
61 |
62 | /**
63 | * Constructor
64 | *
65 | * @param zoneNumberRange
66 | * zone number range
67 | * @param bandLetterRange
68 | * band letter range
69 | */
70 | constructor(zoneNumberRange = new ZoneNumberRange(), bandLetterRange = new BandLetterRange()) {
71 | this.zoneNumberRange = zoneNumberRange;
72 | this.bandLetterRange = bandLetterRange;
73 |
74 | this.minZoneNumber = zoneNumberRange.getWest();
75 | this.maxZoneNumber = zoneNumberRange.getEast();
76 | this.minBandLetter = bandLetterRange.getSouth();
77 | this.maxBandLetter = bandLetterRange.getNorth();
78 | this.zoneNumber = this.minZoneNumber;
79 | this.bandLetter = this.minBandLetter;
80 | }
81 |
82 | /**
83 | * Get the zone number range
84 | *
85 | * @return zone number range
86 | */
87 | public getZoneNumberRange(): ZoneNumberRange {
88 | return this.zoneNumberRange;
89 | }
90 |
91 | /**
92 | * Set the zone number range
93 | *
94 | * @param zoneNumberRange
95 | * zone number range
96 | */
97 | public setZoneNumberRange(zoneNumberRange: ZoneNumberRange): void {
98 | this.zoneNumberRange = zoneNumberRange;
99 | }
100 |
101 | /**
102 | * Get the band letter range
103 | *
104 | * @return band letter range
105 | */
106 | public getBandLetterRange(): BandLetterRange {
107 | return this.bandLetterRange;
108 | }
109 |
110 | /**
111 | * Set the band letter range
112 | *
113 | * @param bandLetterRange
114 | * band letter range
115 | */
116 | public setBandLetterRange(bandLetterRange: BandLetterRange): void {
117 | this.bandLetterRange = bandLetterRange;
118 | }
119 |
120 | /**
121 | * Get the grid range bounds
122 | *
123 | * @return bounds
124 | */
125 | public getBounds(): Bounds {
126 | const west = this.zoneNumberRange.getWestLongitude();
127 | const south = this.bandLetterRange.getSouthLatitude();
128 | const east = this.zoneNumberRange.getEastLongitude();
129 | const north = this.bandLetterRange.getNorthLatitude();
130 |
131 | return Bounds.degrees(west, south, east, north);
132 | }
133 |
134 | public next(): IteratorResult {
135 | while (!this.gridZone && this.zoneNumber <= this.maxZoneNumber) {
136 | this.gridZone = GridZones.getGridZone(this.zoneNumber, this.bandLetter);
137 |
138 | // Handle special case grid gaps (Svalbard)
139 | if (!this.gridZone) {
140 | // Retrieve the western grid if on the left edge
141 | if (this.zoneNumber === this.minZoneNumber) {
142 | this.additional.push(GridZones.getGridZone(this.zoneNumber - 1, this.bandLetter));
143 | }
144 |
145 | // Expand to the eastern grid if on the right edge
146 | if (this.zoneNumber === this.maxZoneNumber) {
147 | this.additional.push(GridZones.getGridZone(this.zoneNumber + 1, this.bandLetter));
148 | }
149 | } else {
150 | // Handle special case grid zone expansions (Norway)
151 | const expand = this.gridZone.getStripExpand();
152 | if (expand !== 0) {
153 | if (expand > 0) {
154 | for (let expandZone = this.zoneNumber + expand; expandZone > this.zoneNumber; expandZone--) {
155 | if (expandZone > this.maxZoneNumber) {
156 | this.additional.push(GridZones.getGridZone(expandZone, this.bandLetter));
157 | } else {
158 | break;
159 | }
160 | }
161 | } else {
162 | for (let expandZone = this.zoneNumber + expand; expandZone < this.zoneNumber; expandZone++) {
163 | if (expandZone < this.minZoneNumber) {
164 | this.additional.push(GridZones.getGridZone(expandZone, this.bandLetter));
165 | } else {
166 | break;
167 | }
168 | }
169 | }
170 | }
171 | }
172 |
173 | this.bandLetter = MGRSUtils.nextBandLetter(this.bandLetter);
174 | if (this.bandLetter > this.maxBandLetter) {
175 | this.zoneNumber++;
176 | this.bandLetter = this.minBandLetter;
177 | }
178 | }
179 |
180 | if (!this.gridZone && this.additional.length > 0) {
181 | this.gridZone = this.additional.shift();
182 | }
183 |
184 | if (this.gridZone) {
185 | const result = this.gridZone;
186 | this.gridZone = undefined;
187 | return {
188 | done: false,
189 | value: result,
190 | };
191 | } else
192 | return {
193 | done: true,
194 | value: null,
195 | };
196 | }
197 |
198 | public reset(): void {
199 | this.zoneNumber = this.minZoneNumber;
200 | this.bandLetter = this.minBandLetter;
201 | }
202 |
203 | [Symbol.iterator](): IterableIterator {
204 | return this;
205 | }
206 | }
207 |
--------------------------------------------------------------------------------
/lib/gzd/GridZone.ts:
--------------------------------------------------------------------------------
1 | import { Bounds, Hemisphere, Line, Point } from '@ngageoint/grid-js';
2 | import { GridLine } from '../features/GridLine';
3 | import { GridType } from '../grid/GridType';
4 | import { GridTypeUtils } from '../grid/GridTypeUtils';
5 | import { MGRSUtils } from '../MGRSUtils';
6 | import { UTM } from '../utm/UTM';
7 | import { LatitudeBand } from './LatitudeBand';
8 | import { LongitudinalStrip } from './LongitudinalStrip';
9 |
10 | /**
11 | * Grid Zone
12 | */
13 | export class GridZone {
14 | /**
15 | * Longitudinal strip
16 | */
17 | private strip: LongitudinalStrip;
18 |
19 | /**
20 | * Latitude band
21 | */
22 | private band: LatitudeBand;
23 |
24 | /**
25 | * Bounds
26 | */
27 | private bounds: Bounds;
28 |
29 | /**
30 | * Constructor
31 | *
32 | * @param strip
33 | * longitudinal strip
34 | * @param band
35 | * latitude band
36 | */
37 | constructor(strip: LongitudinalStrip, band: LatitudeBand) {
38 | this.strip = strip;
39 | this.band = band;
40 | this.bounds = Bounds.degrees(strip.getWest(), band.getSouth(), strip.getEast(), band.getNorth());
41 | }
42 |
43 | /**
44 | * Get the longitudinal strip
45 | *
46 | * @return longitudinal strip
47 | */
48 | public getStrip(): LongitudinalStrip {
49 | return this.strip;
50 | }
51 |
52 | /**
53 | * Get the latitude band
54 | *
55 | * @return latitude band
56 | */
57 | public getBand(): LatitudeBand {
58 | return this.band;
59 | }
60 |
61 | /**
62 | * Get the zone number
63 | *
64 | * @return zone number
65 | */
66 | public getNumber(): number {
67 | return this.strip.getNumber();
68 | }
69 |
70 | /**
71 | * Get the band letter
72 | *
73 | * @return band letter
74 | */
75 | public getLetter(): string {
76 | return this.band.getLetter();
77 | }
78 |
79 | /**
80 | * Get the hemisphere
81 | *
82 | * @return hemisphere
83 | */
84 | public getHemisphere(): Hemisphere {
85 | return this.band.getHemisphere();
86 | }
87 |
88 | /**
89 | * Get the bounds
90 | *
91 | * @return bounds
92 | */
93 | public getBounds(): Bounds {
94 | return this.bounds;
95 | }
96 |
97 | /**
98 | * Get the label name
99 | *
100 | * @return name
101 | */
102 | public getName(): string {
103 | return MGRSUtils.getLabelName(this.getNumber(), this.getLetter());
104 | }
105 |
106 | /**
107 | * Is the provided bounds within the zone bounds
108 | *
109 | * @param bounds
110 | * bounds
111 | * @return true if within bounds
112 | */
113 | public isWithin(bounds: Bounds): boolean {
114 | if (this.bounds.getUnit()) {
115 | bounds = bounds.toUnit(this.bounds.getUnit()!);
116 | }
117 | return (
118 | this.bounds.getSouth() <= bounds.getNorth() &&
119 | this.bounds.getNorth() >= bounds.getSouth() &&
120 | this.bounds.getWest() <= bounds.getEast() &&
121 | this.bounds.getEast() >= bounds.getWest()
122 | );
123 | }
124 |
125 | /**
126 | * Get the longitudinal strip expansion, number of additional neighbors to
127 | * iterate over in combination with this strip
128 | *
129 | * @return longitudinal strip neighbor iteration expansion
130 | */
131 | public getStripExpand(): number {
132 | return this.strip.getExpand();
133 | }
134 |
135 | /**
136 | * Get the grid zone lines
137 | *
138 | * @param gridType
139 | * grid type
140 | * @return lines
141 | */
142 | public getLinesFromGridType(gridType: GridType): GridLine[] | undefined {
143 | return this.getLines(this.bounds, gridType);
144 | }
145 |
146 | /**
147 | * Get the grid zone lines
148 | *
149 | * @param tileBounds
150 | * tile bounds
151 | * @param gridType
152 | * grid type
153 | * @return lines
154 | */
155 | public getLines(tileBounds: Bounds, gridType: GridType): GridLine[] | undefined {
156 | let lines: GridLine[] | undefined;
157 |
158 | if (gridType === GridType.GZD) {
159 | // if precision is 0, draw the zone bounds
160 | lines = [];
161 | for (const line of this.bounds.getLines()) {
162 | lines.push(GridLine.lineFromLine(line, GridType.GZD));
163 | }
164 | } else {
165 | const drawBounds = this.getDrawBounds(tileBounds, gridType);
166 |
167 | if (drawBounds) {
168 | lines = [];
169 |
170 | const precision = gridType;
171 | const zoneNumber = this.getNumber();
172 | const hemisphere = this.getHemisphere();
173 | const minLon = this.bounds.getMinLongitude();
174 | const maxLon = this.bounds.getMaxLongitude();
175 |
176 | for (let easting = drawBounds.getMinLongitude(); easting < drawBounds.getMaxLongitude(); easting += precision) {
177 | const eastingPrecision = GridTypeUtils.getPrecision(easting);
178 |
179 | for (
180 | let northing = drawBounds.getMinLatitude();
181 | northing < drawBounds.getMaxLatitude();
182 | northing += precision
183 | ) {
184 | const northingPrecision = GridTypeUtils.getPrecision(northing);
185 |
186 | let southwest = UTM.point(zoneNumber, hemisphere, easting, northing);
187 | const northwest = UTM.point(zoneNumber, hemisphere, easting, northing + precision);
188 | let southeast = UTM.point(zoneNumber, hemisphere, easting + precision, northing);
189 |
190 | // For points outside the tile grid longitude bounds,
191 | // get a bound just outside the bounds
192 | if (precision > 1) {
193 | if (southwest.getLongitude() < minLon) {
194 | southwest = this.getWestBoundsPoint(easting, northing, southwest, southeast);
195 | } else if (southeast.getLongitude() > maxLon) {
196 | southeast = this.getEastBoundsPoint(easting, northing, southwest, southeast);
197 | }
198 | }
199 |
200 | // Vertical line
201 | lines.push(GridLine.lineFromPoints(southwest, northwest, eastingPrecision));
202 |
203 | // Horizontal line
204 | lines.push(GridLine.lineFromPoints(southwest, southeast, northingPrecision));
205 | }
206 | }
207 | }
208 | }
209 |
210 | return lines;
211 | }
212 |
213 | /**
214 | * Get a point west of the horizontal bounds at one meter precision
215 | *
216 | * @param easting
217 | * easting value
218 | * @param northing
219 | * northing value
220 | * @param west
221 | * west point
222 | * @param east
223 | * east point
224 | * @return higher precision point
225 | */
226 | private getWestBoundsPoint(easting: number, northing: number, west: Point, east: Point): Point {
227 | return this.getBoundsPoint(easting, northing, west, east, false);
228 | }
229 |
230 | /**
231 | * Get a point east of the horizontal bounds at one meter precision
232 | *
233 | * @param easting
234 | * easting value
235 | * @param northing
236 | * northing value
237 | * @param west
238 | * west point
239 | * @param east
240 | * east point
241 | * @return higher precision point
242 | */
243 | private getEastBoundsPoint(easting: number, northing: number, west: Point, east: Point): Point {
244 | return this.getBoundsPoint(easting, northing, west, east, true);
245 | }
246 |
247 | /**
248 | * Get a point outside of the horizontal bounds at one meter precision
249 | *
250 | * @param easting
251 | * easting value
252 | * @param northing
253 | * northing value
254 | * @param west
255 | * west point
256 | * @param east
257 | * east point
258 | * @param eastern
259 | * true if east of the eastern bounds, false if west of the
260 | * western bounds
261 | * @return higher precision point
262 | */
263 | private getBoundsPoint(easting: number, northing: number, west: Point, east: Point, eastern: boolean): Point {
264 | const line = Line.line(west, east);
265 |
266 | let boundsLine: Line;
267 | if (eastern) {
268 | boundsLine = this.bounds.getEastLine();
269 | } else {
270 | boundsLine = this.bounds.getWestLine();
271 | }
272 |
273 | const zoneNumber = this.getNumber();
274 | const hemisphere = this.getHemisphere();
275 |
276 | // Intersection between the horizontal line and vertical bounds line
277 | const intersection = line.intersection(boundsLine);
278 |
279 | let boundsEasting = easting;
280 | if (intersection) {
281 | // Intersection easting
282 | const intersectionUTM = UTM.from(intersection, zoneNumber, hemisphere);
283 | const intersectionEasting = intersectionUTM.getEasting();
284 | boundsEasting = intersectionEasting - easting;
285 | }
286 |
287 | // One meter precision just outside the bounds
288 | if (eastern) {
289 | boundsEasting = Math.ceil(boundsEasting);
290 | } else {
291 | boundsEasting = Math.floor(boundsEasting);
292 | }
293 |
294 | // Higher precision point just outside of the bounds
295 | const boundsPoint = UTM.point(zoneNumber, hemisphere, easting + boundsEasting, northing);
296 |
297 | return boundsPoint;
298 | }
299 |
300 | /**
301 | * Get the draw bounds of easting and northing in meters
302 | *
303 | * @param tileBounds
304 | * tile bounds
305 | * @param gridType
306 | * grid type
307 | * @return draw bounds or null
308 | */
309 | public getDrawBounds(tileBounds: Bounds, gridType: GridType): Bounds | undefined {
310 | let drawBounds: Bounds | undefined;
311 |
312 | tileBounds = tileBounds.toDegrees().overlap(this.bounds) as Bounds;
313 |
314 | if (tileBounds && !tileBounds.isEmpty()) {
315 | const zoneNumber = this.getNumber();
316 | const hemisphere = this.getHemisphere();
317 |
318 | const upperLeftUTM = UTM.from(tileBounds.getNorthwest(), zoneNumber, hemisphere);
319 | const lowerLeftUTM = UTM.from(tileBounds.getSouthwest(), zoneNumber, hemisphere);
320 | const lowerRightUTM = UTM.from(tileBounds.getSoutheast(), zoneNumber, hemisphere);
321 | const upperRightUTM = UTM.from(tileBounds.getNortheast(), zoneNumber, hemisphere);
322 |
323 | const precision = gridType;
324 | const leftEasting =
325 | Math.floor(Math.min(upperLeftUTM.getEasting(), lowerLeftUTM.getEasting()) / precision) * precision;
326 | const lowerNorthing =
327 | Math.floor(Math.min(lowerLeftUTM.getNorthing(), lowerRightUTM.getNorthing()) / precision) * precision;
328 | const rightEasting =
329 | Math.ceil(Math.max(lowerRightUTM.getEasting(), upperRightUTM.getEasting()) / precision) * precision;
330 | const upperNorthing =
331 | Math.ceil(Math.max(upperRightUTM.getNorthing(), upperLeftUTM.getNorthing()) / precision) * precision;
332 |
333 | drawBounds = Bounds.meters(leftEasting, lowerNorthing, rightEasting, upperNorthing);
334 | }
335 |
336 | return drawBounds;
337 | }
338 | }
339 |
--------------------------------------------------------------------------------
/lib/gzd/GridZones.ts:
--------------------------------------------------------------------------------
1 | import { Bounds, Point } from '@ngageoint/grid-js';
2 | import { MGRS } from '../MGRS';
3 | import { MGRSConstants } from '../MGRSConstants';
4 | import { MGRSUtils } from '../MGRSUtils';
5 | import { BandLetterRange } from './BandLetterRange';
6 | import { GridRange } from './GridRange';
7 | import { GridZone } from './GridZone';
8 | import { LatitudeBand } from './LatitudeBand';
9 | import { LongitudinalStrip } from './LongitudinalStrip';
10 | import { ZoneNumberRange } from './ZoneNumberRange';
11 |
12 | /**
13 | * Grid Zones, Longitudinal Strips, and Latitude Bands
14 | */
15 | export class GridZones {
16 | /**
17 | * Longitudinal Strips
18 | */
19 | public static readonly strips = new Map();
20 |
21 | /**
22 | * Latitude Bands
23 | */
24 | public static readonly bands = new Map();
25 |
26 | /**
27 | * Grid Zones
28 | */
29 | public static readonly gridZones = new Map>();
30 |
31 | static {
32 | // Create longitudinal strips
33 | const numberRange = new ZoneNumberRange();
34 | for (const zoneNumber of numberRange) {
35 | const longitude = MGRSConstants.MIN_LON + (zoneNumber - 1) * MGRSConstants.ZONE_WIDTH;
36 | const strip = new LongitudinalStrip(zoneNumber, longitude, longitude + MGRSConstants.ZONE_WIDTH);
37 | this.strips.set(strip.getNumber(), strip);
38 | }
39 |
40 | // Create latitude bands
41 | let latitude = MGRSConstants.MIN_LAT;
42 | const letterRange = new BandLetterRange();
43 | for (const bandLetter of letterRange) {
44 | const min = latitude;
45 | if (bandLetter === MGRSConstants.MAX_BAND_LETTER) {
46 | latitude += MGRSConstants.MAX_BAND_HEIGHT;
47 | } else {
48 | latitude += MGRSConstants.BAND_HEIGHT;
49 | }
50 | this.bands.set(bandLetter, new LatitudeBand(bandLetter, min, latitude));
51 | }
52 |
53 | // Create grid zones
54 | for (const strip of GridZones.strips.values()) {
55 | const zoneNumber = strip.getNumber();
56 |
57 | const stripGridZones = new Map();
58 | for (const band of GridZones.bands.values()) {
59 | const bandLetter = band.getLetter();
60 |
61 | let gridZoneStrip = strip;
62 |
63 | if (this.isSvalbard(zoneNumber, bandLetter)) {
64 | gridZoneStrip = this.getSvalbardStrip(strip)!;
65 | } else if (this.isNorway(zoneNumber, bandLetter)) {
66 | gridZoneStrip = this.getNorwayStrip(strip);
67 | }
68 |
69 | if (gridZoneStrip) {
70 | stripGridZones.set(bandLetter, new GridZone(gridZoneStrip, band));
71 | }
72 | }
73 | this.gridZones.set(zoneNumber, stripGridZones);
74 | }
75 | }
76 |
77 | /**
78 | * Get the longitudinal strip by zone number
79 | *
80 | * @param zoneNumber
81 | * zone number
82 | * @return longitudinal strip
83 | */
84 | public static getLongitudinalStrip(zoneNumber: number): LongitudinalStrip {
85 | MGRSUtils.validateZoneNumber(zoneNumber);
86 | return this.strips.get(zoneNumber)!;
87 | }
88 |
89 | /**
90 | * Get the west longitude in degrees of the zone number
91 | *
92 | * @param zoneNumber
93 | * zone number
94 | * @return longitude in degrees
95 | */
96 | public static getWestLongitude(zoneNumber: number): number {
97 | return this.getLongitudinalStrip(zoneNumber).getWest();
98 | }
99 |
100 | /**
101 | * Get the east longitude in degrees of the zone number
102 | *
103 | * @param zoneNumber
104 | * zone number
105 | * @return longitude in degrees
106 | */
107 | public static getEastLongitude(zoneNumber: number): number {
108 | return this.getLongitudinalStrip(zoneNumber).getEast();
109 | }
110 |
111 | /**
112 | * Get the latitude band by band letter
113 | *
114 | * @param bandLetter
115 | * band letter
116 | * @return latitude band
117 | */
118 | public static getLatitudeBand(bandLetter: string): LatitudeBand {
119 | MGRSUtils.validateBandLetter(bandLetter);
120 | return this.bands.get(bandLetter)!;
121 | }
122 |
123 | /**
124 | * Get the south latitude in degrees of the band letter
125 | *
126 | * @param bandLetter
127 | * band letter
128 | * @return latitude in degrees
129 | */
130 | public static getSouthLatitude(bandLetter: string): number {
131 | return this.getLatitudeBand(bandLetter).getSouth();
132 | }
133 |
134 | /**
135 | * Get the north latitude in degrees of the band letter
136 | *
137 | * @param bandLetter
138 | * band letter
139 | * @return latitude in degrees
140 | */
141 | public static getNorthLatitude(bandLetter: string): number {
142 | return this.getLatitudeBand(bandLetter).getNorth();
143 | }
144 |
145 | /**
146 | * Get the zones within the bounds
147 | *
148 | * @param bounds
149 | * bounds
150 | * @return grid zones
151 | */
152 | public static getZones(bounds: Bounds): GridZone[] {
153 | const zones: GridZone[] = [];
154 |
155 | const gridRange = this.getGridRange(bounds);
156 | for (const zone of gridRange) {
157 | zones.push(zone);
158 | }
159 |
160 | return zones;
161 | }
162 |
163 | /**
164 | * Get the grid zone by zone number and band letter
165 | *
166 | * @param zoneNumber
167 | * zone number
168 | * @param bandLetter
169 | * band letter
170 | * @return grid zone
171 | */
172 | public static getGridZone(zoneNumber: number, bandLetter: string): GridZone {
173 | MGRSUtils.validateZoneNumber(zoneNumber);
174 | MGRSUtils.validateBandLetter(bandLetter);
175 | return this.gridZones.get(zoneNumber)!.get(bandLetter)!;
176 | }
177 |
178 | /**
179 | * Get the grid zone by MGRS
180 | *
181 | * @param mgrs
182 | * mgrs coordinate
183 | * @return grid zone
184 | */
185 | public static getGridZoneFromMGRS(mgrs: MGRS): GridZone {
186 | return this.getGridZone(mgrs.getZone(), mgrs.getBand());
187 | }
188 |
189 | /**
190 | * Get a grid range from the bounds
191 | *
192 | * @param bounds
193 | * bounds
194 | * @return grid range
195 | */
196 | public static getGridRange(bounds: Bounds): GridRange {
197 | bounds = bounds.toDegrees();
198 | const zoneNumberRange = this.getZoneNumberRangeFromBounds(bounds);
199 | const bandLetterRange = this.getBandLetterRangeFromBounds(bounds);
200 | return new GridRange(zoneNumberRange, bandLetterRange);
201 | }
202 |
203 | /**
204 | * Get a zone number range between the western and eastern bounds
205 | *
206 | * @param bounds
207 | * bounds
208 | * @return zone number range
209 | */
210 | public static getZoneNumberRangeFromBounds(bounds: Bounds): ZoneNumberRange {
211 | bounds = bounds.toDegrees();
212 | return this.getZoneNumberRange(bounds.getWest(), bounds.getEast());
213 | }
214 |
215 | /**
216 | * Get a zone number range between the western and eastern longitudes
217 | *
218 | * @param west
219 | * western longitude in degrees
220 | * @param east
221 | * eastern longitude in degrees
222 | * @return zone number range
223 | */
224 | public static getZoneNumberRange(west: number, east: number): ZoneNumberRange {
225 | const westZone = this.getZoneNumberFromLongitude(west, false);
226 | const eastZone = this.getZoneNumberFromLongitude(east, true);
227 | return new ZoneNumberRange(westZone, eastZone);
228 | }
229 |
230 | /**
231 | * Get the zone number of the point
232 | *
233 | * @param point
234 | * point
235 | * @return zone number
236 | */
237 | public static getZoneNumberFromPoint(point: Point): number {
238 | point = point.toDegrees();
239 | return this.getZoneNumber(point.getLongitude(), point.getLatitude());
240 | }
241 |
242 | /**
243 | * Get the zone number of the longitude and latitude
244 | *
245 | * @param longitude
246 | * longitude
247 | * @param latitude
248 | * latitude
249 | * @return zone number
250 | */
251 | public static getZoneNumber(longitude: number, latitude: number): number {
252 | let zoneNumber = this.getZoneNumberFromLongitude(longitude);
253 | const svalbardZone = this.isSvalbardZone(zoneNumber);
254 | const norwayZone = this.isNorwayZone(zoneNumber);
255 | if (svalbardZone || norwayZone) {
256 | const bandLetter = this.getBandLetterFromLatitude(latitude);
257 | if (svalbardZone && this.isSvalbardLetter(bandLetter)) {
258 | zoneNumber = this.getSvalbardZone(longitude);
259 | } else if (norwayZone && this.isNorwayLetter(bandLetter)) {
260 | zoneNumber = this.getNorwayZone(longitude);
261 | }
262 | }
263 | return zoneNumber;
264 | }
265 |
266 | /**
267 | * Get the zone number of the longitude (degrees between
268 | * {@link MGRSConstants#MIN_LON} and {@link MGRSConstants#MAX_LON})
269 | *
270 | * @param longitude
271 | * longitude in degrees
272 | * @param eastern
273 | * true for eastern number on edges, false for western
274 | * @return zone number
275 | */
276 | public static getZoneNumberFromLongitude(longitude: number, eastern = true): number {
277 | // Normalize the longitude if needed
278 | if (longitude < MGRSConstants.MIN_LON || longitude > MGRSConstants.MAX_LON) {
279 | longitude = ((longitude - MGRSConstants.MIN_LON) % (2 * MGRSConstants.MAX_LON)) + MGRSConstants.MIN_LON;
280 | }
281 |
282 | // Determine the zone
283 | const zoneValue = (longitude - MGRSConstants.MIN_LON) / MGRSConstants.ZONE_WIDTH;
284 | let zoneNumber = 1 + ~~zoneValue;
285 |
286 | // Handle western edge cases and 180.0
287 | if (!eastern) {
288 | if (zoneNumber > 1 && zoneValue % 1.0 === 0.0) {
289 | zoneNumber--;
290 | }
291 | } else if (zoneNumber > MGRSConstants.MAX_ZONE_NUMBER) {
292 | zoneNumber--;
293 | }
294 |
295 | return zoneNumber;
296 | }
297 |
298 | /**
299 | * Get a band letter range between the southern and northern bounds
300 | *
301 | * @param bounds
302 | * bounds
303 | * @return band letter range
304 | */
305 | public static getBandLetterRangeFromBounds(bounds: Bounds): BandLetterRange {
306 | bounds = bounds.toDegrees();
307 | return this.getBandLetterRange(bounds.getSouth(), bounds.getNorth());
308 | }
309 |
310 | /**
311 | * Get a band letter range between the southern and northern latitudes in
312 | * degrees
313 | *
314 | * @param south
315 | * southern latitude in degrees
316 | * @param north
317 | * northern latitude in degrees
318 | * @return band letter range
319 | */
320 | public static getBandLetterRange(south: number, north: number): BandLetterRange {
321 | const southLetter = this.getBandLetterFromLatitude(south, false);
322 | const northLetter = this.getBandLetterFromLatitude(north, true);
323 | return new BandLetterRange(southLetter, northLetter);
324 | }
325 |
326 | /**
327 | * Get the band letter of the latitude (degrees between
328 | * {@link MGRSConstants#MIN_LAT} and {@link MGRSConstants#MAX_LAT})
329 | *
330 | * @param latitude
331 | * latitude in degrees
332 | * @param northern
333 | * true for northern band on edges, false for southern
334 | * @return band letter
335 | */
336 | public static getBandLetterFromLatitude(latitude: number, northern = true): string {
337 | // Bound the latitude if needed
338 | if (latitude < MGRSConstants.MIN_LAT) {
339 | latitude = MGRSConstants.MIN_LAT;
340 | } else if (latitude > MGRSConstants.MAX_LAT) {
341 | latitude = MGRSConstants.MAX_LAT;
342 | }
343 |
344 | const bandValue = (latitude - MGRSConstants.MIN_LAT) / MGRSConstants.BAND_HEIGHT;
345 | let bands = ~~bandValue;
346 |
347 | // Handle 80.0 to 84.0 and southern edge cases
348 | if (bands >= MGRSConstants.NUM_BANDS) {
349 | bands--;
350 | } else if (!northern && bands > 0 && bandValue % 1.0 === 0.0) {
351 | bands--;
352 | }
353 |
354 | // Handle skipped 'I' and 'O' letters
355 | if (bands > 10) {
356 | bands += 2;
357 | } else if (bands > 5) {
358 | bands++;
359 | }
360 |
361 | let letter = MGRSConstants.MIN_BAND_LETTER.codePointAt(0);
362 | if (letter) {
363 | letter += bands;
364 | }
365 | return String.fromCharCode(letter!);
366 | }
367 |
368 | /**
369 | * Is the zone number and band letter a Svalbard GZD (31X - 37X)
370 | *
371 | * @param zoneNumber
372 | * zone number
373 | * @param bandLetter
374 | * band letter
375 | * @return true if a Svalbard GZD
376 | */
377 | public static isSvalbard(zoneNumber: number, bandLetter: string): boolean {
378 | return this.isSvalbardLetter(bandLetter) && this.isSvalbardZone(zoneNumber);
379 | }
380 |
381 | /**
382 | * Is the band letter a Svalbard GZD (X)
383 | *
384 | * @param bandLetter
385 | * band letter
386 | * @return true if a Svalbard GZD
387 | */
388 | public static isSvalbardLetter(bandLetter: string): boolean {
389 | return bandLetter === MGRSConstants.SVALBARD_BAND_LETTER;
390 | }
391 |
392 | /**
393 | * Is the zone number a Svalbard GZD (31 - 37)
394 | *
395 | * @param zoneNumber
396 | * zone number
397 | * @return true if a Svalbard GZD
398 | */
399 | public static isSvalbardZone(zoneNumber: number): boolean {
400 | return zoneNumber >= MGRSConstants.MIN_SVALBARD_ZONE_NUMBER && zoneNumber <= MGRSConstants.MAX_SVALBARD_ZONE_NUMBER;
401 | }
402 |
403 | /**
404 | * Get the Svalbard longitudinal strip from the strip
405 | *
406 | * @param strip
407 | * longitudinal strip
408 | * @return Svalbard strip or null for empty strips
409 | */
410 | private static getSvalbardStrip(strip: LongitudinalStrip): LongitudinalStrip | undefined {
411 | let svalbardStrip: LongitudinalStrip | undefined;
412 |
413 | const stripNumber = strip.getNumber();
414 | if (stripNumber % 2 === 1) {
415 | let west = strip.getWest();
416 | let east = strip.getEast();
417 | const halfWidth = (east - west) / 2.0;
418 | if (stripNumber > 31) {
419 | west -= halfWidth;
420 | }
421 | if (stripNumber < 37) {
422 | east += halfWidth;
423 | }
424 | svalbardStrip = new LongitudinalStrip(stripNumber, west, east);
425 | }
426 |
427 | return svalbardStrip;
428 | }
429 |
430 | /**
431 | * Get the Svalbard zone number from the longitude
432 | *
433 | * @param longitude
434 | * longitude
435 | * @return zone number
436 | */
437 | private static getSvalbardZone(longitude: number): number {
438 | const minimumLongitude = this.getWestLongitude(MGRSConstants.MIN_SVALBARD_ZONE_NUMBER);
439 | const zoneValue =
440 | MGRSConstants.MIN_SVALBARD_ZONE_NUMBER + (longitude - minimumLongitude) / MGRSConstants.ZONE_WIDTH;
441 | let zone = ~~Math.round(zoneValue);
442 | if (zone % 2 === 0) {
443 | zone--;
444 | }
445 | return zone;
446 | }
447 |
448 | /**
449 | * Is the zone number and band letter a Norway GZD (31V or 32V)
450 | *
451 | * @param zoneNumber
452 | * zone number
453 | * @param bandLetter
454 | * band letter
455 | * @return true if a Norway GZD
456 | */
457 | private static isNorway(zoneNumber: number, bandLetter: string): boolean {
458 | return this.isNorwayLetter(bandLetter) && this.isNorwayZone(zoneNumber);
459 | }
460 |
461 | /**
462 | * Is the band letter a Norway GZD (V)
463 | *
464 | * @param bandLetter
465 | * band letter
466 | * @return true if a Norway GZD band letter
467 | */
468 | private static isNorwayLetter(bandLetter: string): boolean {
469 | return bandLetter === MGRSConstants.NORWAY_BAND_LETTER;
470 | }
471 |
472 | /**
473 | * Is the zone number a Norway GZD (31 or 32)
474 | *
475 | * @param zoneNumber
476 | * zone number
477 | * @return true if a Norway GZD zone number
478 | */
479 | private static isNorwayZone(zoneNumber: number): boolean {
480 | return zoneNumber >= MGRSConstants.MIN_NORWAY_ZONE_NUMBER && zoneNumber <= MGRSConstants.MAX_NORWAY_ZONE_NUMBER;
481 | }
482 |
483 | /**
484 | * Get the Norway longitudinal strip from the strip
485 | *
486 | * @param strip
487 | * longitudinal strip
488 | * @return Norway strip
489 | */
490 | private static getNorwayStrip(strip: LongitudinalStrip): LongitudinalStrip {
491 | const stripNumber = strip.getNumber();
492 | let west = strip.getWest();
493 | let east = strip.getEast();
494 | const halfWidth = (east - west) / 2.0;
495 |
496 | let expand = 0;
497 | if (stripNumber === MGRSConstants.MIN_NORWAY_ZONE_NUMBER) {
498 | east -= halfWidth;
499 | expand++;
500 | } else if (stripNumber === MGRSConstants.MAX_NORWAY_ZONE_NUMBER) {
501 | west -= halfWidth;
502 | }
503 |
504 | return new LongitudinalStrip(stripNumber, west, east, expand);
505 | }
506 |
507 | /**
508 | * Get the Norway zone number from the longitude
509 | *
510 | * @param longitude
511 | * longitude
512 | * @return zone number
513 | */
514 | private static getNorwayZone(longitude: number): number {
515 | const minimumLongitude = this.getWestLongitude(MGRSConstants.MIN_NORWAY_ZONE_NUMBER);
516 | let zone = MGRSConstants.MIN_NORWAY_ZONE_NUMBER;
517 | if (longitude >= minimumLongitude + MGRSConstants.ZONE_WIDTH / 2.0) {
518 | zone++;
519 | }
520 | return zone;
521 | }
522 | }
523 |
--------------------------------------------------------------------------------
/lib/gzd/LatitudeBand.ts:
--------------------------------------------------------------------------------
1 | import { Hemisphere } from '@ngageoint/grid-js';
2 | import { MGRSUtils } from '../MGRSUtils';
3 |
4 | /**
5 | * Latitude (horizontal) band
6 | */
7 | export class LatitudeBand {
8 | /**
9 | * Band letter
10 | */
11 | private letter: string;
12 |
13 | /**
14 | * Southern latitude
15 | */
16 | private south: number;
17 |
18 | /**
19 | * Northern latitude
20 | */
21 | private north: number;
22 |
23 | /**
24 | * Hemisphere
25 | */
26 | private hemisphere: Hemisphere;
27 |
28 | /**
29 | * Constructor
30 | *
31 | * @param letter
32 | * band letter
33 | * @param south
34 | * southern latitude
35 | * @param north
36 | * northern latitude
37 | */
38 | constructor(letter: string, south: number, north: number) {
39 | this.letter = letter;
40 | this.hemisphere = MGRSUtils.getHemisphere(letter);
41 | this.south = south;
42 | this.north = north;
43 | }
44 |
45 | /**
46 | * Get the band letter
47 | *
48 | * @return band letter
49 | */
50 | public getLetter(): string {
51 | return this.letter;
52 | }
53 |
54 | /**
55 | * Set the band letter
56 | *
57 | * @param letter
58 | * band letter
59 | */
60 | public setLetter(letter: string): void {
61 | this.letter = letter;
62 | this.hemisphere = MGRSUtils.getHemisphere(letter);
63 | }
64 |
65 | /**
66 | * Get the southern latitude
67 | *
68 | * @return southern latitude
69 | */
70 | public getSouth(): number {
71 | return this.south;
72 | }
73 |
74 | /**
75 | * Set the southern latitude
76 | *
77 | * @param south
78 | * southern latitude
79 | */
80 | public setSouth(south: number): void {
81 | this.south = south;
82 | }
83 |
84 | /**
85 | * Get the northern latitude
86 | *
87 | * @return northern latitude
88 | */
89 | public getNorth(): number {
90 | return this.north;
91 | }
92 |
93 | /**
94 | * Set the northern latitude
95 | *
96 | * @param north
97 | * northern latitude
98 | */
99 | public setNorth(north: number): void {
100 | this.north = north;
101 | }
102 |
103 | /**
104 | * Get the hemisphere
105 | *
106 | * @return hemisphere
107 | */
108 | public getHemisphere(): Hemisphere {
109 | return this.hemisphere;
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/lib/gzd/LongitudinalStrip.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Longitudinal (vertical) strip
3 | */
4 | export class LongitudinalStrip {
5 | /**
6 | * Zone number
7 | */
8 | private zoneNumber: number;
9 |
10 | /**
11 | * Western longitude
12 | */
13 | private west: number;
14 |
15 | /**
16 | * Eastern longitude
17 | */
18 | private east: number;
19 |
20 | /**
21 | * Expansion for range iterations over neighboring strips
22 | */
23 | private expand: number;
24 |
25 | /**
26 | * Constructor
27 | *
28 | * @param zoneNumber
29 | * zone number
30 | * @param west
31 | * western longitude
32 | * @param east
33 | * eastern longitude
34 | * @param expand
35 | * expansion for range iterations over neighboring strips
36 | */
37 | constructor(zoneNumber: number, west: number, east: number, expand = 0) {
38 | this.zoneNumber = zoneNumber;
39 | this.west = west;
40 | this.east = east;
41 | this.expand = expand;
42 | }
43 |
44 | /**
45 | * Get the zone number
46 | *
47 | * @return zone number
48 | */
49 | public getNumber(): number {
50 | return this.zoneNumber;
51 | }
52 |
53 | /**
54 | * Set the zone number
55 | *
56 | * @param zoneNumber
57 | * zone number
58 | */
59 | public setNumber(zoneNumber: number): void {
60 | this.zoneNumber = zoneNumber;
61 | }
62 |
63 | /**
64 | * Get the western longitude
65 | *
66 | * @return western longitude
67 | */
68 | public getWest(): number {
69 | return this.west;
70 | }
71 |
72 | /**
73 | * Set the western longitude
74 | *
75 | * @param west
76 | * western longitude
77 | */
78 | public setWest(west: number): void {
79 | this.west = west;
80 | }
81 |
82 | /**
83 | * Get the eastern longitude
84 | *
85 | * @return eastern longitude
86 | */
87 | public getEast(): number {
88 | return this.east;
89 | }
90 |
91 | /**
92 | * Set the eastern longitude
93 | *
94 | * @param east
95 | * eastern longitude
96 | */
97 | public setEast(east: number): void {
98 | this.east = east;
99 | }
100 |
101 | /**
102 | * Get expand, number of additional neighbors to iterate over in combination
103 | * with this strip
104 | *
105 | * @return neighbor iteration expansion
106 | */
107 | public getExpand(): number {
108 | return this.expand;
109 | }
110 |
111 | /**
112 | * Set the expand, number of additional neighbors to iterate over in
113 | * combination with this strip
114 | *
115 | * @param expand
116 | * neighbor iteration expansion
117 | */
118 | public setExpand(expand: number): void {
119 | this.expand = expand;
120 | }
121 | }
122 |
--------------------------------------------------------------------------------
/lib/gzd/ZoneNumberRange.ts:
--------------------------------------------------------------------------------
1 | import { MGRSConstants } from '../MGRSConstants';
2 | import { GridZones } from './GridZones';
3 |
4 | /**
5 | * Zone Number Range
6 | */
7 | export class ZoneNumberRange implements IterableIterator {
8 | /**
9 | * Western zone number
10 | */
11 | private west: number;
12 |
13 | /**
14 | * Eastern zone number
15 | */
16 | private east: number;
17 |
18 | /**
19 | * Zone number
20 | */
21 | private zoneNumber: number;
22 |
23 | /**
24 | * Constructor
25 | *
26 | * @param west
27 | * western zone number
28 | * @param east
29 | * eastern zone number
30 | */
31 | constructor(west = MGRSConstants.MIN_ZONE_NUMBER, east = MGRSConstants.MAX_ZONE_NUMBER) {
32 | this.west = west;
33 | this.east = east;
34 | this.zoneNumber = west;
35 | }
36 |
37 | /**
38 | * Get the western zone number
39 | *
40 | * @return western zone number
41 | */
42 | public getWest(): number {
43 | return this.west;
44 | }
45 |
46 | /**
47 | * Set the western zone number
48 | *
49 | * @param west
50 | * western zone number
51 | */
52 | public setWest(west: number): void {
53 | this.west = west;
54 | }
55 |
56 | /**
57 | * Get the eastern zone number
58 | *
59 | * @return eastern zone number
60 | */
61 | public getEast(): number {
62 | return this.east;
63 | }
64 |
65 | /**
66 | * Set the eastern zone number
67 | *
68 | * @param east
69 | * eastern zone number
70 | */
71 | public setEast(east: number): void {
72 | this.east = east;
73 | }
74 |
75 | /**
76 | * Get the western longitude
77 | *
78 | * @return longitude
79 | */
80 | public getWestLongitude(): number {
81 | return GridZones.getLongitudinalStrip(this.west).getWest();
82 | }
83 |
84 | /**
85 | * Get the eastern longitude
86 | *
87 | * @return longitude
88 | */
89 | public getEastLongitude(): number {
90 | return GridZones.getLongitudinalStrip(this.east).getEast();
91 | }
92 |
93 | public next(): IteratorResult {
94 | if (this.zoneNumber <= this.east) {
95 | const currentZoneNumber = this.zoneNumber;
96 | this.zoneNumber++;
97 | return {
98 | done: false,
99 | value: currentZoneNumber,
100 | };
101 | } else {
102 | return {
103 | done: true,
104 | value: null,
105 | };
106 | }
107 | }
108 |
109 | public reset(): void {
110 | this.zoneNumber = this.west;
111 | }
112 |
113 | [Symbol.iterator](): IterableIterator {
114 | return this;
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/lib/property/MGRSProperties.ts:
--------------------------------------------------------------------------------
1 | import { GridProperties } from '@ngageoint/grid-js';
2 | import * as config from '../../resources/mgrs.json';
3 |
4 | /**
5 | * MGRS property loader
6 | */
7 | export class MGRSProperties extends GridProperties {
8 | /**
9 | * Singleton instance
10 | */
11 | public static instance = new MGRSProperties(config);
12 |
13 | /**
14 | * Get the singleton instance
15 | *
16 | * @return instance
17 | */
18 | public static getInstance(): MGRSProperties {
19 | return MGRSProperties.instance;
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/mgrs.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ngageoint/mgrs-js/a35ce05f839af6ec3d7996648c0b5aeff37bf252/mgrs.jpg
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@ngageoint/mgrs-js",
3 | "version": "1.1.0",
4 | "displayName": "MGRS Javascript",
5 | "description": "MGRS Javascript",
6 | "keywords": [
7 | "NGA",
8 | "MGRS"
9 | ],
10 | "repository": {
11 | "type": "git",
12 | "url": "https://github.com/ngageoint/mgrs-js.git"
13 | },
14 | "author": {
15 | "name": "NGA"
16 | },
17 | "contributors": [
18 | {
19 | "name": "Kevin Gilland",
20 | "email": "kgilland@caci.com"
21 | }
22 | ],
23 | "homepage": "https://www.nga.mil",
24 | "engines": {
25 | "npm": ">= 6.x"
26 | },
27 | "license": "MIT",
28 | "main": "dist/index.js",
29 | "types": "dist/index.d.ts",
30 | "dependencies": {
31 | "@ngageoint/color-js": "^2.1.0",
32 | "@ngageoint/grid-js": "^2.1.0",
33 | "decimal-format": "^3.0.0",
34 | "sprintf-js": "^1.1.2",
35 | "tstl": "^2.5.8"
36 | },
37 | "devDependencies": {
38 | "@tsconfig/recommended": "^1.0.1",
39 | "@types/chai": "^4.3.3",
40 | "@types/mocha": "9.1.1",
41 | "@types/sprintf-js": "^1.1.2",
42 | "chai": "4.3.6",
43 | "coveralls": "3.1.1",
44 | "mocha": "^10.0.0",
45 | "nyc": "^15.1.0",
46 | "prettier": "^2.7.1",
47 | "ts-node": "^10.9.1",
48 | "tslint": "^6.1.3",
49 | "tslint-config-prettier": "^1.18.0",
50 | "typescript": "^4.8.3",
51 | "typedoc": "^0.23.19"
52 | },
53 | "scripts": {
54 | "gh-pages-build": "npm install && npm run typedoc --options typedoc.json",
55 | "typedoc": "rm -rf ./docs/api; typedoc --tsconfig tsconfig.json --out docs/api index.ts",
56 | "clean": "rm -rf ./.test_run; rm -rf ./.nyc_output; rm -rf ./docs/coverage; rm -rf ./dist; npm run clean-test; rm -rf docs/api",
57 | "clean-test": "rm -rf ./test/bundle ./test/node_modules ./test/tmp",
58 | "test:run": "mocha --config .mocharc.js",
59 | "test": "npm run test:run",
60 | "compile": "tsc",
61 | "build": "npm run compile",
62 | "report": "nyc report",
63 | "coverage": "nyc npm run test:run",
64 | "lint:prettier": "prettier --check \"lib/**/*.ts\" \"test/**/*.ts\"",
65 | "lint:tslint": "tslint -p tsconfig.json",
66 | "lint": "npm run lint:tslint && npm run lint:prettier",
67 | "fix:prettier": "prettier --write \"lib/**/*.ts\" \"test/**/*.ts\"",
68 | "prebuild": "npm run clean",
69 | "pretest": "npm run build",
70 | "prepublishOnly": "npm run build && npm run lint"
71 | },
72 | "files": [
73 | "dist",
74 | "resources"
75 | ],
76 | "directories": {
77 | "lib": "./lib"
78 | },
79 | "nyc": {
80 | "temp-dir": "./.test_run",
81 | "report-dir": "./docs/coverage",
82 | "reporter": [
83 | "lcov"
84 | ]
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/resources/mgrs.json:
--------------------------------------------------------------------------------
1 | {
2 | "grids":{
3 | "gzd":{
4 | "enabled":"true",
5 | "min_zoom":"0",
6 | "color":"EF5350",
7 | "width":"2.0",
8 | "labeler":{
9 | "enabled":"true",
10 | "min_zoom":"4",
11 | "color":"EF5350",
12 | "text_size":"24.0",
13 | "buffer":"0.05"
14 | }
15 | },
16 | "hundred_kilometer":{
17 | "enabled":"true",
18 | "min_zoom":"5",
19 | "lines":{
20 | "max_zoom":"8"
21 | },
22 | "color":"3D8C40",
23 | "width":"2.0",
24 | "labeler":{
25 | "enabled":"true",
26 | "min_zoom":"6",
27 | "color":"3D8C40",
28 | "text_size":"24.0",
29 | "buffer":"0.05"
30 | }
31 | },
32 | "ten_kilometer":{
33 | "enabled":"true",
34 | "min_zoom":"9",
35 | "max_zoom":"11",
36 | "color":"888888",
37 | "width":"2.0",
38 | "labeler":{
39 | "enabled":"false",
40 | "min_zoom":"10",
41 | "max_zoom":"11",
42 | "color":"888888",
43 | "text_size":"24.0",
44 | "buffer":"0.05"
45 | }
46 | },
47 | "kilometer":{
48 | "enabled":"true",
49 | "min_zoom":"12",
50 | "max_zoom":"14",
51 | "color":"888888",
52 | "width":"2.0",
53 | "labeler":{
54 | "enabled":"false",
55 | "min_zoom":"13",
56 | "max_zoom":"14",
57 | "color":"888888",
58 | "text_size":"24.0",
59 | "buffer":"0.05"
60 | }
61 | },
62 | "hundred_meter":{
63 | "enabled":"true",
64 | "min_zoom":"15",
65 | "max_zoom":"17",
66 | "color":"888888",
67 | "width":"2.0",
68 | "labeler":{
69 | "enabled":"false",
70 | "min_zoom":"16",
71 | "max_zoom":"17",
72 | "color":"888888",
73 | "text_size":"24.0",
74 | "buffer":"0.05"
75 | }
76 | },
77 | "ten_meter":{
78 | "enabled":"true",
79 | "min_zoom":"18",
80 | "max_zoom":"20",
81 | "color":"888888",
82 | "width":"2.0",
83 | "labeler":{
84 | "enabled":"false",
85 | "min_zoom":"19",
86 | "max_zoom":"20",
87 | "color":"888888",
88 | "text_size":"24.0",
89 | "buffer":"0.05"
90 | }
91 | },
92 | "meter":{
93 | "enabled":"true",
94 | "min_zoom":"21",
95 | "color":"888888",
96 | "width":"2.0"
97 | }
98 | }
99 | }
--------------------------------------------------------------------------------
/test/MGRS.spec.ts:
--------------------------------------------------------------------------------
1 | import { expect } from 'chai';
2 | import { Point } from '@ngageoint/grid-js';
3 | import { MGRS } from '../lib/MGRS';
4 | import { GridType } from '../lib/grid/GridType';
5 | import { UTM } from '../lib/utm/UTM';
6 | import { GridRange } from '../lib/gzd/GridRange';
7 |
8 | /**
9 | * MGRS Test
10 | *
11 | *
12 | */
13 | describe('MGRS Tests', function () {
14 | /**
15 | * Test parsing a MGRS string value
16 | *
17 | * @throws ParseException
18 | * upon failure to parse
19 | */
20 | it('test parse', function () {
21 | let mgrsValue = '33XVG74594359';
22 | let utmValue = '33 N 474590 8643590';
23 |
24 | expect(MGRS.isMGRS(mgrsValue));
25 | let mgrs = MGRS.parse(mgrsValue);
26 | expect(MGRS.precision(mgrsValue)).to.equal(GridType.TEN_METER);
27 | expect(MGRS.accuracy(mgrsValue)).to.equal(4);
28 | expect(mgrs.coordinate(GridType.TEN_METER)).to.equal(mgrsValue);
29 | expect(mgrs.coordinateFromAccuracy(4)).to.equal(mgrsValue);
30 | expect(mgrs.precision()).to.equal(GridType.TEN_METER);
31 | expect(mgrs.accuracy()).to.equal(4);
32 |
33 | let utm = mgrs.toUTM();
34 | expect(utmValue, utm.toString());
35 |
36 | mgrsValue = '33X VG 74596 43594';
37 | utmValue = '33 N 474596 8643594';
38 |
39 | expect(MGRS.isMGRS(mgrsValue)).to.be.true;
40 | mgrs = MGRS.parse(mgrsValue.toLowerCase());
41 | expect(MGRS.precision(mgrsValue)).to.equal(GridType.METER);
42 | expect(MGRS.accuracy(mgrsValue)).to.equal(5);
43 | expect(mgrs.toString()).to.equal(mgrsValue.replace(/\s/g, ''));
44 |
45 | utm = mgrs.toUTM();
46 | expect(utm.toString()).to.equal(utmValue);
47 |
48 | expect(UTM.isUTM(utmValue)).to.be.true;
49 | utm = UTM.parse(utmValue);
50 | expect(utm.toString()).to.equal(utmValue);
51 |
52 | mgrs = utm.toMGRS();
53 | expect(mgrs.toString()).to.equal(mgrsValue.replace(/\s/g, ''));
54 |
55 | utmValue = '33 N 474596.26 8643594.54';
56 |
57 | expect(UTM.isUTM(utmValue)).to.be.true;
58 | utm = UTM.parse(utmValue.toLowerCase());
59 | expect(utm.toString()).to.equal(utmValue);
60 |
61 | mgrs = utm.toMGRS();
62 | expect(mgrs.toString()).to.equal(mgrsValue.replace(/\s/g, ''));
63 |
64 | mgrsValue = '33X';
65 | expect(MGRS.isMGRS(mgrsValue)).to.be.true;
66 | mgrs = MGRS.parse(mgrsValue);
67 | expect(MGRS.precision(mgrsValue)).to.equal(GridType.GZD);
68 | expect(MGRS.accuracy(mgrsValue)).to.equal(0);
69 | expect(mgrs.getZone()).to.equal(33);
70 | expect(mgrs.getBand()).to.equal('X');
71 | expect(mgrs.getColumn()).to.equal('T');
72 | expect(mgrs.getRow()).to.equal('V');
73 | expect(mgrs.getColumnRowId()).to.equal('TV');
74 | expect(mgrs.getEasting()).to.equal(93363);
75 | expect(mgrs.getNorthing()).to.equal(99233);
76 | expect(mgrs.coordinate()).to.equal('33XTV9336399233');
77 | let point = mgrs.toPoint();
78 | expect(point.getLongitude()).to.be.approximately(9.0, 0.0001);
79 | expect(point.getLatitude()).to.be.approximately(72.0, 0.0001);
80 | expect(mgrs.precision()).to.equal(GridType.METER);
81 | expect(mgrs.accuracy()).to.equal(5);
82 |
83 | mgrsValue = '33XVG';
84 | expect(MGRS.isMGRS(mgrsValue)).to.be.true;
85 | mgrs = MGRS.parse(mgrsValue);
86 | expect(MGRS.precision(mgrsValue)).to.equal(GridType.HUNDRED_KILOMETER);
87 | expect(MGRS.accuracy(mgrsValue)).to.equal(0);
88 | expect(mgrs.getZone()).to.equal(33);
89 | expect(mgrs.getBand()).to.equal('X');
90 | expect(mgrs.getColumn()).to.equal('V');
91 | expect(mgrs.getRow()).to.equal('G');
92 | expect(mgrs.getColumnRowId()).to.equal('VG');
93 | expect(mgrs.getEasting()).to.equal(0);
94 | expect(mgrs.getNorthing()).to.equal(0);
95 | expect(mgrs.coordinate(GridType.HUNDRED_KILOMETER)).to.equal(mgrsValue);
96 | expect(mgrs.coordinate()).to.equal('33XVG0000000000');
97 | point = mgrs.toPoint();
98 | expect(point.getLongitude()).to.be.approximately(10.8756458, 0.0001);
99 | expect(point.getLatitude()).to.be.approximately(77.445472, 0.0001);
100 | expect(mgrs.precision()).to.equal(GridType.HUNDRED_KILOMETER);
101 | expect(mgrs.accuracy()).to.equal(0);
102 |
103 | mgrsValue = '33XVG74';
104 | expect(MGRS.isMGRS(mgrsValue)).to.be.true;
105 | mgrs = MGRS.parse(mgrsValue);
106 | expect(MGRS.precision(mgrsValue)).to.equal(GridType.TEN_KILOMETER);
107 | expect(MGRS.accuracy(mgrsValue)).to.equal(1);
108 | expect(mgrs.getZone()).to.equal(33);
109 | expect(mgrs.getBand()).to.equal('X');
110 | expect(mgrs.getColumn()).to.equal('V');
111 | expect(mgrs.getRow()).to.equal('G');
112 | expect(mgrs.getColumnRowId()).to.equal('VG');
113 | expect(mgrs.getEasting()).to.equal(70000);
114 | expect(mgrs.getNorthing()).to.equal(40000);
115 | expect(mgrs.coordinate(GridType.TEN_KILOMETER)).to.equal(mgrsValue);
116 | expect(mgrs.coordinate()).to.equal('33XVG7000040000');
117 | point = mgrs.toPoint();
118 | expect(point.getLongitude()).to.be.approximately(13.7248758, 0.0001);
119 | expect(point.getLatitude()).to.be.approximately(77.8324735, 0.0001);
120 | expect(mgrs.precision()).to.equal(GridType.TEN_KILOMETER);
121 | expect(mgrs.accuracy()).to.equal(1);
122 |
123 | mgrsValue = '33XVG7443';
124 | expect(MGRS.isMGRS(mgrsValue)).to.be.true;
125 | mgrs = MGRS.parse(mgrsValue);
126 | expect(MGRS.precision(mgrsValue)).to.equal(GridType.KILOMETER);
127 | expect(MGRS.accuracy(mgrsValue)).to.equal(2);
128 | expect(mgrs.getZone()).to.equal(33);
129 | expect(mgrs.getBand()).to.equal('X');
130 | expect(mgrs.getColumn()).to.equal('V');
131 | expect(mgrs.getRow()).to.equal('G');
132 | expect(mgrs.getColumnRowId()).to.equal('VG');
133 | expect(mgrs.getEasting()).to.equal(74000);
134 | expect(mgrs.getNorthing()).to.equal(43000);
135 | expect(mgrs.coordinate(GridType.KILOMETER)).to.equal(mgrsValue);
136 | expect(mgrs.coordinate()).to.equal('33XVG7400043000');
137 | point = mgrs.toPoint();
138 | expect(point.getLongitude()).to.be.approximately(13.8924385, 0.0001);
139 | expect(point.getLatitude()).to.be.approximately(77.8600782, 0.0001);
140 | expect(mgrs.precision()).to.equal(GridType.KILOMETER);
141 | expect(mgrs.accuracy()).to.equal(2);
142 |
143 | mgrsValue = '33XVG745435';
144 | expect(MGRS.isMGRS(mgrsValue)).to.be.true;
145 | mgrs = MGRS.parse(mgrsValue);
146 | expect(MGRS.precision(mgrsValue)).to.equal(GridType.HUNDRED_METER);
147 | expect(MGRS.accuracy(mgrsValue)).to.equal(3);
148 | expect(mgrs.getZone()).to.equal(33);
149 | expect(mgrs.getBand()).to.equal('X');
150 | expect(mgrs.getColumn()).to.equal('V');
151 | expect(mgrs.getRow()).to.equal('G');
152 | expect(mgrs.getColumnRowId()).to.equal('VG');
153 | expect(mgrs.getEasting()).to.equal(74500);
154 | expect(mgrs.getNorthing()).to.equal(43500);
155 | expect(mgrs.coordinate(GridType.HUNDRED_METER)).to.equal(mgrsValue);
156 | expect(mgrs.coordinate()).to.equal('33XVG7450043500');
157 | point = mgrs.toPoint();
158 | expect(point.getLongitude()).to.be.approximately(13.9133378, 0.0001);
159 | expect(point.getLatitude()).to.be.approximately(77.8646415, 0.0001);
160 | expect(mgrs.precision()).to.equal(GridType.HUNDRED_METER);
161 | expect(mgrs.accuracy()).to.equal(3);
162 |
163 | mgrsValue = '33XVG74594359';
164 | expect(MGRS.isMGRS(mgrsValue)).to.be.true;
165 | mgrs = MGRS.parse(mgrsValue);
166 | expect(MGRS.precision(mgrsValue)).to.equal(GridType.TEN_METER);
167 | expect(MGRS.accuracy(mgrsValue)).to.equal(4);
168 | expect(mgrs.getZone()).to.equal(33);
169 | expect(mgrs.getBand()).to.equal('X');
170 | expect(mgrs.getColumn()).to.equal('V');
171 | expect(mgrs.getRow()).to.equal('G');
172 | expect(mgrs.getColumnRowId()).to.equal('VG');
173 | expect(mgrs.getEasting()).to.equal(74590);
174 | expect(mgrs.getNorthing()).to.equal(43590);
175 | expect(mgrs.coordinate(GridType.TEN_METER)).to.equal(mgrsValue);
176 | expect(mgrs.coordinate()).to.equal('33XVG7459043590');
177 | point = mgrs.toPoint();
178 | expect(point.getLongitude()).to.be.approximately(13.9171014, 0.0001);
179 | expect(point.getLatitude()).to.be.approximately(77.8654628, 0.0001);
180 | expect(mgrs.precision()).to.equal(GridType.TEN_METER);
181 | expect(mgrs.accuracy()).to.equal(4);
182 |
183 | mgrsValue = '33XVG7459743593';
184 | expect(MGRS.isMGRS(mgrsValue)).to.be.true;
185 | mgrs = MGRS.parse(mgrsValue);
186 | expect(MGRS.precision(mgrsValue)).to.equal(GridType.METER);
187 | expect(MGRS.accuracy(mgrsValue)).to.equal(5);
188 | expect(mgrs.getZone()).to.equal(33);
189 | expect(mgrs.getBand()).to.equal('X');
190 | expect(mgrs.getColumn()).to.equal('V');
191 | expect(mgrs.getRow()).to.equal('G');
192 | expect(mgrs.getColumnRowId()).to.equal('VG');
193 | expect(mgrs.getEasting()).to.equal(74597);
194 | expect(mgrs.getNorthing()).to.equal(43593);
195 | expect(mgrs.coordinate()).to.equal(mgrsValue);
196 | expect(mgrs.coordinate()).to.equal('33XVG7459743593');
197 | point = mgrs.toPoint();
198 | expect(point.getLongitude()).to.be.approximately(13.9173973, 0.0001);
199 | expect(point.getLatitude()).to.be.approximately(77.8654908, 0.0001);
200 | expect(mgrs.precision()).to.equal(GridType.METER);
201 | expect(mgrs.accuracy()).to.equal(5);
202 | });
203 |
204 | /**
205 | * Test parsing a 100k MGRS string value that falls outside grid zone bounds
206 | *
207 | * @throws ParseException
208 | * upon failure to parse
209 | */
210 | it('test parse 100k bounds', function () {
211 | let mgrsValue = '32VJN';
212 | expect(MGRS.isMGRS(mgrsValue)).to.be.true;
213 | let mgrs = MGRS.parse(mgrsValue);
214 | let point = mgrs.toPoint();
215 | expect(point.getLongitude()).to.be.approximately(3.0, 0.0001);
216 | expect(point.getLatitude()).to.be.approximately(60.3007719, 0.0001);
217 | let comparePoint = MGRS.parse(mgrs.coordinate()).toPoint();
218 | expect(point.getLongitude()).to.be.approximately(comparePoint.getLongitude(), 0.0001);
219 | expect(point.getLatitude()).to.be.approximately(comparePoint.getLatitude(), 0.0001);
220 |
221 | mgrsValue = '32VKS';
222 | expect(MGRS.isMGRS(mgrsValue)).to.be.true;
223 | mgrs = MGRS.parse(mgrsValue);
224 | point = mgrs.toPoint();
225 | expect(point.getLongitude()).to.be.approximately(3.0, 0.0001);
226 | expect(point.getLatitude()).to.be.approximately(63.9024981, 0.0001);
227 | comparePoint = MGRS.parse(mgrs.coordinate()).toPoint();
228 | expect(point.getLongitude()).to.be.approximately(comparePoint.getLongitude(), 0.0001);
229 | expect(point.getLatitude()).to.be.approximately(comparePoint.getLatitude(), 0.0001);
230 |
231 | mgrsValue = '32VJR';
232 | expect(MGRS.isMGRS(mgrsValue)).to.be.true;
233 | mgrs = MGRS.parse(mgrsValue);
234 | point = mgrs.toPoint();
235 | expect(point.getLongitude()).to.be.approximately(3.0, 0.0001);
236 | expect(point.getLatitude()).to.be.approximately(63.0020546, 0.0001);
237 | comparePoint = MGRS.parse(mgrs.coordinate()).toPoint();
238 | expect(point.getLongitude()).to.be.approximately(comparePoint.getLongitude(), 0.0001);
239 | expect(point.getLatitude()).to.be.approximately(comparePoint.getLatitude(), 0.0001);
240 |
241 | mgrsValue = '32VJH';
242 | expect(MGRS.isMGRS(mgrsValue)).to.be.true;
243 | mgrs = MGRS.parse(mgrsValue);
244 | point = mgrs.toPoint();
245 | expect(point.getLongitude()).to.be.approximately(3.0, 0.0001);
246 | expect(point.getLatitude()).to.be.approximately(56.0, 0.0001);
247 | comparePoint = MGRS.parse(mgrs.coordinate()).toPoint();
248 | expect(point.getLongitude()).to.be.approximately(comparePoint.getLongitude(), 0.0001);
249 | expect(point.getLatitude()).to.be.approximately(comparePoint.getLatitude(), 0.0001);
250 |
251 | mgrsValue = '38KNU';
252 | expect(MGRS.isMGRS(mgrsValue)).to.be.true;
253 | mgrs = MGRS.parse(mgrsValue);
254 | point = mgrs.toPoint();
255 | expect(point.getLongitude()).to.be.approximately(45.0, 0.0001);
256 | expect(point.getLatitude()).to.be.approximately(-24.0, 0.0001);
257 | comparePoint = MGRS.parse(mgrs.coordinate()).toPoint();
258 | expect(point.getLongitude()).to.be.approximately(comparePoint.getLongitude(), 0.0001);
259 | expect(point.getLatitude()).to.be.approximately(comparePoint.getLatitude(), 0.0001);
260 |
261 | mgrsValue = '38KRU';
262 | expect(MGRS.isMGRS(mgrsValue)).to.be.true;
263 | mgrs = MGRS.parse(mgrsValue);
264 | point = mgrs.toPoint();
265 | expect(point.getLongitude()).to.be.approximately(47.9486444, 0.0001);
266 | expect(point.getLatitude()).to.be.approximately(-24.0, 0.0001);
267 | comparePoint = MGRS.parse(mgrs.coordinate()).toPoint();
268 | expect(point.getLongitude()).to.be.approximately(comparePoint.getLongitude(), 0.0001);
269 | expect(point.getLatitude()).to.be.approximately(comparePoint.getLatitude(), 0.0001);
270 |
271 | mgrsValue = '32VPH';
272 | expect(MGRS.isMGRS(mgrsValue)).to.be.true;
273 | mgrs = MGRS.parse(mgrsValue);
274 | point = mgrs.toPoint();
275 | expect(point.getLongitude()).to.be.approximately(10.6034691, 0.0001);
276 | expect(point.getLatitude()).to.be.approximately(56.0, 0.0001);
277 | comparePoint = MGRS.parse(mgrs.coordinate()).toPoint();
278 | expect(point.getLongitude()).to.be.approximately(comparePoint.getLongitude(), 0.0001);
279 | expect(point.getLatitude()).to.be.approximately(comparePoint.getLatitude(), 0.0001);
280 | });
281 |
282 | /**
283 | * Test parsing a MGRS string value
284 | *
285 | * @throws ParseException
286 | * upon failure to parse
287 | */
288 | it('test coordinate', function () {
289 | let mgrs = '35VPL0115697387';
290 | testCoordinate(29.06757, 63.98863, mgrs);
291 | testCoordinateMeters(3235787.09, 9346877.48, mgrs);
292 |
293 | mgrs = '39PYP7290672069';
294 | testCoordinate(53.51, 12.4, mgrs);
295 | testCoordinateMeters(5956705.95, 1391265.16, mgrs);
296 |
297 | mgrs = '4QFJ1234056781';
298 | testCoordinate(-157.916861, 21.309444, mgrs);
299 | testCoordinateMeters(-17579224.55, 2428814.96, mgrs);
300 |
301 | mgrs = '33PYJ6132198972';
302 | testCoordinate(17.3714337, 8.1258235, mgrs, false);
303 | testCoordinateMeters(1933779.15, 907610.2, mgrs, false);
304 | });
305 |
306 | /**
307 | * Test parsing GZD coordinates
308 | *
309 | * @throws ParseException
310 | * upon failure to parse
311 | */
312 | it('test GDZ parse', function () {
313 | let gridRange = new GridRange();
314 |
315 | for (const zone of gridRange) {
316 | let zoneNumber = zone.getNumber();
317 | let bandLetter = zone.getLetter();
318 |
319 | let gzd = zoneNumber.toString() + bandLetter;
320 | expect(MGRS.isMGRS(gzd)).to.be.true;
321 | let mgrs = MGRS.parse(gzd);
322 | expect(mgrs).to.not.be.null;
323 | expect(mgrs.getZone()).to.be.equal(zoneNumber);
324 | expect(mgrs.getBand()).to.be.equal(bandLetter);
325 |
326 | let point = mgrs.toPoint();
327 | let southwest = zone.getBounds().getSouthwest();
328 |
329 | expect(southwest.getLongitude()).to.be.approximately(point.getLongitude(), 0.0001);
330 | expect(southwest.getLatitude()).to.be.approximately(point.getLatitude(), 0.0001);
331 | }
332 | });
333 |
334 | /**
335 | * Test parsing a Svalbard MGRS string values
336 | *
337 | * @throws ParseException
338 | * upon failure to parse
339 | */
340 | it('test Svalbard parse', function () {
341 | expect(MGRS.isMGRS('31X')).to.be.true;
342 | expect(MGRS.parse('31X')).to.not.be.null;
343 | expect(MGRS.isMGRS('32X')).to.be.false;
344 | try {
345 | expect(MGRS.parse('32X')).to.be.null;
346 | expect.fail('Expected parse exception');
347 | } catch (Error) {}
348 | expect(MGRS.isMGRS('32XMH')).to.be.false;
349 | try {
350 | MGRS.parse('32XMH');
351 | expect.fail('Expected parse exception');
352 | } catch (Error) {}
353 | expect(MGRS.isMGRS('32XMH11')).to.be.false;
354 | try {
355 | MGRS.parse('32XMH11');
356 | expect.fail('Expected parse exception');
357 | } catch (Error) {}
358 | expect(MGRS.isMGRS('32XMH1111')).to.be.false;
359 | try {
360 | MGRS.parse('32XMH1111');
361 | expect.fail('Expected parse exception');
362 | } catch (Error) {}
363 | expect(MGRS.isMGRS('32XMH111111')).to.be.false;
364 | try {
365 | MGRS.parse('32XMH111111');
366 | expect.fail('Expected parse exception');
367 | } catch (Error) {}
368 | expect(MGRS.isMGRS('32XMH11111111')).to.be.false;
369 | try {
370 | MGRS.parse('32XMH11111111');
371 | expect.fail('Expected parse exception');
372 | } catch (Error) {}
373 | expect(MGRS.isMGRS('32XMH111111111')).to.be.false;
374 | try {
375 | MGRS.parse('32XMH111111111');
376 | expect.fail('Expected parse exception');
377 | } catch (Error) {}
378 | expect(MGRS.isMGRS('33X')).to.be.true;
379 | expect(MGRS.parse('33X')).to.not.be.null;
380 | expect(MGRS.isMGRS('34X')).to.be.false;
381 | try {
382 | expect(MGRS.parse('34X')).to.be.null;
383 | expect.fail('Expected parse exception');
384 | } catch (Error) {}
385 | expect(MGRS.isMGRS('35X')).to.be.true;
386 | expect(MGRS.parse('35X')).to.not.be.null;
387 | expect(MGRS.isMGRS('36X')).to.be.false;
388 | try {
389 | expect(MGRS.parse('36X')).to.be.null;
390 | expect.fail('Expected parse exception');
391 | } catch (Error) {}
392 | expect(MGRS.isMGRS('37X')).to.be.true;
393 | expect(MGRS.parse('37X')).to.not.be.null;
394 | });
395 | });
396 |
397 | /**
398 | * Test the WGS84 coordinate with expected MGSR coordinate
399 | *
400 | * @param longitude
401 | * longitude in degrees
402 | * @param latitude
403 | * latitude in degrees
404 | * @param value
405 | * MGRS value
406 | * @param test100k
407 | * set false when falls outside the grid zone
408 | * @throws ParseException
409 | * upon failure to parse
410 | */
411 | function testCoordinate(longitude: number, latitude: number, value: string, test100k = true): void {
412 | const point = Point.point(longitude, latitude);
413 | testCoordinateByPoint(point, value, test100k);
414 | testCoordinateByPoint(point.toMeters(), value, test100k);
415 | }
416 |
417 | /**
418 | * Test the WGS84 coordinate with expected MGSR coordinate
419 | *
420 | * @param longitude
421 | * longitude in degrees
422 | * @param latitude
423 | * latitude in degrees
424 | * @param value
425 | * MGRS value
426 | * @param test100k
427 | * set false when falls outside the grid zone
428 | * @throws ParseException
429 | * upon failure to parse
430 | */
431 | function testCoordinateMeters(longitude: number, latitude: number, value: string, test100k = true): void {
432 | const point = Point.meters(longitude, latitude);
433 | testCoordinateByPoint(point, value, test100k);
434 | testCoordinateByPoint(point.toDegrees(), value, test100k);
435 | }
436 |
437 | /**
438 | * Test the coordinate with expected MGSR coordinate
439 | *
440 | * @param point
441 | * point
442 | * @param value
443 | * MGRS value
444 | * @param test100k
445 | * set false when falls outside the grid zone
446 | * @throws ParseException
447 | * upon failure to parse
448 | */
449 | function testCoordinateByPoint(point: Point, value: string, test100k: boolean): void {
450 | let mgrs = MGRS.from(point);
451 | expect(value, mgrs.toString());
452 | expect(value, mgrs.coordinate());
453 |
454 | let gzd = mgrs.coordinate(GridType.GZD);
455 | expect(gzd).to.equal(accuracyValue(value, -1));
456 | expect(MGRS.isMGRS(gzd)).to.be.true;
457 | let gzdMGRS = MGRS.parse(gzd);
458 | expect(MGRS.precision(gzd)).to.equal(GridType.GZD);
459 | expect(MGRS.accuracy(gzd)).to.equal(0);
460 | expect(gzdMGRS.coordinate(GridType.GZD)).to.equal(gzd);
461 |
462 | let hundredKilometer = mgrs.coordinate(GridType.HUNDRED_KILOMETER);
463 | expect(hundredKilometer).to.equal(accuracyValue(value, 0));
464 | expect(mgrs.coordinateFromAccuracy(0)).to.equal(hundredKilometer);
465 | expect(MGRS.isMGRS(hundredKilometer)).to.be.true;
466 | let hundredKilometerMGRS = MGRS.parse(hundredKilometer);
467 | expect(MGRS.precision(hundredKilometer)).to.equal(GridType.HUNDRED_KILOMETER);
468 | expect(MGRS.accuracy(hundredKilometer)).to.equal(0);
469 | expect(hundredKilometer, hundredKilometerMGRS.coordinate(GridType.HUNDRED_KILOMETER)).to.equal(hundredKilometer);
470 | if (test100k) {
471 | expect(hundredKilometerMGRS.getEasting()).to.equal(0);
472 | expect(hundredKilometerMGRS.getNorthing()).to.equal(0);
473 | expect(hundredKilometerMGRS.precision()).to.equal(GridType.HUNDRED_KILOMETER);
474 | expect(hundredKilometerMGRS.accuracy()).to.equal(0);
475 | }
476 |
477 | let tenKilometer = mgrs.coordinate(GridType.TEN_KILOMETER);
478 | expect(tenKilometer).to.equal(accuracyValue(value, 1));
479 | expect(mgrs.coordinateFromAccuracy(1)).to.equal(tenKilometer);
480 | expect(MGRS.isMGRS(tenKilometer)).to.be.true;
481 | let tenKilometerMGRS = MGRS.parse(tenKilometer);
482 | expect(MGRS.precision(tenKilometer)).to.equal(GridType.TEN_KILOMETER);
483 | expect(MGRS.accuracy(tenKilometer)).to.equal(1);
484 | expect(tenKilometerMGRS.coordinate(GridType.TEN_KILOMETER)).to.equal(tenKilometer);
485 | expect(tenKilometerMGRS.getEasting()).to.equal(getEasting(tenKilometer, 1));
486 | expect(tenKilometerMGRS.getNorthing()).to.equal(getNorthing(tenKilometer, 1));
487 | expect(tenKilometerMGRS.precision()).to.equal(GridType.TEN_KILOMETER);
488 | expect(tenKilometerMGRS.accuracy()).to.equal(1);
489 |
490 | let kilometer = mgrs.coordinate(GridType.KILOMETER);
491 | expect(kilometer).to.equal(accuracyValue(value, 2));
492 | expect(mgrs.coordinateFromAccuracy(2)).to.equal(kilometer);
493 | expect(MGRS.isMGRS(kilometer)).to.be.true;
494 | let kilometerMGRS = MGRS.parse(kilometer);
495 | expect(MGRS.precision(kilometer)).to.equal(GridType.KILOMETER);
496 | expect(MGRS.accuracy(kilometer)).to.equal(2);
497 | expect(kilometerMGRS.coordinate(GridType.KILOMETER)).to.equal(kilometer);
498 | expect(kilometerMGRS.getEasting()).to.equal(getEasting(kilometer, 2));
499 | expect(kilometerMGRS.getNorthing()).to.equal(getNorthing(kilometer, 2));
500 | expect(kilometerMGRS.precision()).to.equal(GridType.KILOMETER);
501 | expect(kilometerMGRS.accuracy()).to.equal(2);
502 |
503 | let hundredMeter = mgrs.coordinate(GridType.HUNDRED_METER);
504 | expect(hundredMeter).to.equal(accuracyValue(value, 3));
505 | expect(mgrs.coordinateFromAccuracy(3)).to.equal(hundredMeter);
506 | expect(MGRS.isMGRS(hundredMeter)).to.be.true;
507 | let hundredMeterMGRS = MGRS.parse(hundredMeter);
508 | expect(MGRS.precision(hundredMeter)).to.equal(GridType.HUNDRED_METER);
509 | expect(MGRS.accuracy(hundredMeter)).to.equal(3);
510 | expect(hundredMeterMGRS.coordinate(GridType.HUNDRED_METER)).to.equal(hundredMeter);
511 | expect(hundredMeterMGRS.getEasting()).to.equal(getEasting(hundredMeter, 3));
512 | expect(hundredMeterMGRS.getNorthing()).to.equal(getNorthing(hundredMeter, 3));
513 | expect(hundredMeterMGRS.precision()).to.equal(GridType.HUNDRED_METER);
514 | expect(hundredMeterMGRS.accuracy()).to.equal(3);
515 |
516 | let tenMeter = mgrs.coordinate(GridType.TEN_METER);
517 | expect(tenMeter).to.equal(accuracyValue(value, 4));
518 | expect(mgrs.coordinateFromAccuracy(4)).to.equal(tenMeter);
519 | expect(MGRS.isMGRS(tenMeter)).to.be.true;
520 | let tenMeterMGRS = MGRS.parse(tenMeter);
521 | expect(MGRS.precision(tenMeter)).to.equal(GridType.TEN_METER);
522 | expect(MGRS.accuracy(tenMeter)).to.equal(4);
523 | expect(tenMeterMGRS.coordinate(GridType.TEN_METER)).to.equal(tenMeter);
524 | expect(tenMeterMGRS.getEasting()).to.equal(getEasting(tenMeter, 4));
525 | expect(tenMeterMGRS.getNorthing()).to.equal(getNorthing(tenMeter, 4));
526 | expect(tenMeterMGRS.precision()).to.equal(GridType.TEN_METER);
527 | expect(tenMeterMGRS.accuracy()).to.equal(4);
528 |
529 | let meter = mgrs.coordinate();
530 | expect(value).to.equal(meter);
531 | expect(meter).to.equal(accuracyValue(value, 5));
532 | expect(mgrs.coordinateFromAccuracy(5)).to.equal(meter);
533 | expect(MGRS.isMGRS(meter)).to.be.true;
534 | let meterMGRS = MGRS.parse(meter);
535 | expect(MGRS.precision(meter)).to.equal(GridType.METER);
536 | expect(MGRS.accuracy(meter)).to.equal(5);
537 | expect(meterMGRS.coordinate()).to.equal(meter);
538 | expect(meterMGRS.getEasting()).to.equal(getEasting(meter, 5));
539 | expect(meterMGRS.getNorthing()).to.equal(getNorthing(meter, 5));
540 | expect(meterMGRS.precision()).to.equal(GridType.METER);
541 | expect(meterMGRS.accuracy()).to.equal(5);
542 | }
543 |
544 | /**
545 | * Get the MGRS value in the accuracy digits
546 | *
547 | * @param value
548 | * MGRS value
549 | * @param accuracy
550 | * accuracy digits (-1 for GZD)
551 | * @return MGRS in accuracy
552 | */
553 | function accuracyValue(value: string, accuracy: number): string {
554 | let gzdLength = value.length % 2 == 1 ? 3 : 2;
555 | let accuracyValue = value.substring(0, gzdLength);
556 |
557 | if (accuracy >= 0) {
558 | accuracyValue += value.substring(gzdLength, gzdLength + 2);
559 |
560 | if (accuracy > 0) {
561 | let eastingNorthing = value.substring(accuracyValue.length);
562 | let currentAccuracy = eastingNorthing.length / 2;
563 | let easting = eastingNorthing.substring(0, currentAccuracy);
564 | let northing = eastingNorthing.substring(currentAccuracy);
565 |
566 | accuracyValue += easting.substring(0, accuracy);
567 | accuracyValue += northing.substring(0, accuracy);
568 | }
569 | }
570 |
571 | return accuracyValue;
572 | }
573 |
574 | /**
575 | * Get the easting of the MGRS value in the accuracy
576 | *
577 | * @param value
578 | * MGRS value
579 | * @param accuracy
580 | * accuracy digits
581 | * @return easting
582 | */
583 | function getEasting(value: string, accuracy: number): number {
584 | return padAccuracy(value.substring(value.length - 2 * accuracy, value.length - accuracy), accuracy);
585 | }
586 |
587 | /**
588 | * Get the northing of the MGRS value in the accuracy
589 | *
590 | * @param value
591 | * MGRS value
592 | * @param accuracy
593 | * accuracy digits
594 | * @return northing
595 | */
596 | function getNorthing(value: string, accuracy: number): number {
597 | return padAccuracy(value.substring(value.length - accuracy), accuracy);
598 | }
599 |
600 | /**
601 | * Pad the value with the accuracy and parse as a long
602 | *
603 | * @param value
604 | * MGRS value
605 | * @param accuracy
606 | * accuracy digits
607 | * @return long value
608 | */
609 | function padAccuracy(value: string, accuracy: number): number {
610 | for (let i = accuracy; i < 5; i++) {
611 | value += '0';
612 | }
613 | return Number(value);
614 | }
615 |
--------------------------------------------------------------------------------
/test/MGRSUtils.spec.ts:
--------------------------------------------------------------------------------
1 | import { Hemisphere } from '@ngageoint/grid-js';
2 | import { expect } from 'chai';
3 | import { MGRSConstants } from '../lib/MGRSConstants';
4 | import { MGRSUtils } from '../lib/MGRSUtils';
5 |
6 | describe('MGRSUtils Tests', function () {
7 | it('test validate zone number', function () {
8 | expect(function () {
9 | MGRSUtils.validateZoneNumber(MGRSConstants.MIN_ZONE_NUMBER - 1);
10 | }).to.throw(Error);
11 | expect(function () {
12 | MGRSUtils.validateZoneNumber(MGRSConstants.MAX_ZONE_NUMBER + 1);
13 | }).to.throw(Error);
14 | });
15 |
16 | it('test validate band letter', function () {
17 | expect(function () {
18 | let min = MGRSConstants.MIN_BAND_LETTER.charCodeAt(0);
19 | min--;
20 | MGRSUtils.validateBandLetter(String.fromCharCode(min));
21 | }).to.throw(Error);
22 | expect(function () {
23 | let max = MGRSConstants.MAX_BAND_LETTER.charCodeAt(0);
24 | max++;
25 | MGRSUtils.validateBandLetter(String.fromCharCode(max));
26 | }).to.throw(Error);
27 | });
28 |
29 | it('test next band letter', function () {
30 | const nextBandLetter = MGRSUtils.nextBandLetter(MGRSConstants.MIN_BAND_LETTER);
31 | expect(nextBandLetter.charCodeAt(0)).is.greaterThan(MGRSConstants.MIN_BAND_LETTER.charCodeAt(0));
32 | });
33 |
34 | it('test previous band letter', function () {
35 | const prevBandLetter = MGRSUtils.previousBandLetter(MGRSConstants.MAX_BAND_LETTER);
36 | expect(prevBandLetter.charCodeAt(0)).is.lessThan(MGRSConstants.MAX_BAND_LETTER.charCodeAt(0));
37 | });
38 |
39 | it('test hemisphere', function () {
40 | expect(MGRSUtils.getHemisphere(MGRSConstants.BAND_LETTER_NORTH).valueOf()).to.equal(Hemisphere.NORTH.valueOf());
41 | });
42 | });
43 |
--------------------------------------------------------------------------------
/test/Readme.spec.ts:
--------------------------------------------------------------------------------
1 | import { GridTile, Point } from '@ngageoint/grid-js';
2 | import { Grids } from '../lib/grid/Grids';
3 | import { GridType } from '../lib/grid/GridType';
4 | import { MGRS } from '../lib/MGRS';
5 | import { UTM } from '../lib/utm/UTM';
6 | import { GridZones } from '../lib/gzd/GridZones';
7 |
8 | /**
9 | * README example tests
10 | *
11 | *
12 | */
13 | describe('Readme Tests', function () {
14 | /**
15 | * Test MGRS coordinates
16 | *
17 | */
18 | it('test coordinates', function () {
19 | const mgrs = MGRS.parse('33XVG74594359');
20 | const point = mgrs.toPoint();
21 | const pointMeters = point.toMeters();
22 | const utm = mgrs.toUTM();
23 | const utmCoordinate = utm.toString();
24 | const point2 = utm.toPoint();
25 |
26 | const mgrs2 = MGRS.parse('33X VG 74596 43594');
27 |
28 | const latitude = 63.98862388;
29 | const longitude = 29.06755082;
30 | const point3 = Point.point(longitude, latitude);
31 | const mgrs3 = MGRS.from(point3);
32 | const mgrsCoordinate = mgrs3.toString();
33 | const mgrsGZD = mgrs3.coordinate(GridType.GZD);
34 | const mgrs100k = mgrs3.coordinate(GridType.HUNDRED_KILOMETER);
35 | const mgrs10k = mgrs3.coordinate(GridType.TEN_KILOMETER);
36 | const mgrs1k = mgrs3.coordinate(GridType.KILOMETER);
37 | const mgrs100m = mgrs3.coordinate(GridType.HUNDRED_METER);
38 | const mgrs10m = mgrs3.coordinate(GridType.TEN_METER);
39 | const mgrs1m = mgrs3.coordinate(GridType.METER);
40 |
41 | const utm2 = UTM.from(point3);
42 | const mgrs4 = utm2.toMGRS();
43 |
44 | const utm3 = UTM.parse('18 N 585628 4511322');
45 | const mgrs5 = utm3.toMGRS();
46 | });
47 |
48 | /**
49 | * Test draw tile template logic
50 | *
51 | * @param tile
52 | * grid tile
53 | */
54 | it('test draw tile', function () {
55 | testDrawTile(GridTile.tile(512, 512, 8, 12, 5));
56 | });
57 | });
58 |
59 | /**
60 | * Test draw tile template logic
61 | *
62 | * @param tile
63 | * grid tile
64 | */
65 | function testDrawTile(tile: GridTile): void {
66 | // GridTile tile = ...;
67 |
68 | const grids = Grids.create();
69 |
70 | const zoomGrids = grids.getGrids(tile.getZoom());
71 | if (zoomGrids && zoomGrids.hasGrids()) {
72 | const gridRange = GridZones.getGridRange(tile.getBounds()!);
73 |
74 | for (const grid of zoomGrids) {
75 | // draw this grid for each zone
76 | for (const zone of gridRange) {
77 | const lines = grid.getLinesFromGridTile(tile, zone);
78 | if (lines) {
79 | const pixelRange = zone.getBounds().getPixelRangeFromTile(tile);
80 | for (const line of lines) {
81 | const pixel1 = line.getPoint1().getPixelFromTile(tile);
82 | const pixel2 = line.getPoint2().getPixelFromTile(tile);
83 | // Draw line
84 | }
85 | }
86 |
87 | const labels = grid.getLabelsFromGridTile(tile, zone);
88 | if (labels) {
89 | for (const label of labels) {
90 | const pixelRange = label.getBounds()?.getPixelRangeFromTile(tile);
91 | const centerPixel = label.getCenter()?.getPixelFromTile(tile);
92 | // Draw label
93 | }
94 | }
95 | }
96 | }
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/test/features/GridLine.spec.ts:
--------------------------------------------------------------------------------
1 | import { Line, Point } from '@ngageoint/grid-js';
2 | import { expect } from 'chai';
3 | import { GridLine } from '../../lib/features/GridLine';
4 | import { GridType } from '../../lib/grid/GridType';
5 |
6 | describe('GridLine Tests', function () {
7 | it('test copy', function () {
8 | const point1 = Point.point(0, 0);
9 | const point2 = Point.point(1, 1);
10 | const gridLine = new GridLine(GridLine.line(point1, point2));
11 | gridLine.setGridType(GridType.KILOMETER);
12 |
13 | const gridLineCopy = gridLine.copy();
14 | expect(gridLineCopy.getGridType()).to.equal(gridLine.getGridType());
15 | expect(gridLineCopy.numPoints()).to.equal(gridLineCopy.numPoints());
16 | expect(gridLineCopy.equals(gridLine)).to.be.true;
17 | });
18 |
19 | it('test create from line', function () {
20 | const point1 = Point.point(0, 0);
21 | const point2 = Point.point(1, 1);
22 | const gridType = GridType.KILOMETER;
23 | const gridLine = GridLine.lineFromPoints(point1, point2, GridType.KILOMETER);
24 | expect(gridLine.numPoints()).to.equal(2);
25 | expect(gridLine.getGridType()).to.equal(gridType);
26 | });
27 | });
28 |
--------------------------------------------------------------------------------
/test/grid/Grid.spec.ts:
--------------------------------------------------------------------------------
1 | import { GridStyle } from '@ngageoint/grid-js';
2 | import { expect } from 'chai';
3 | import { Grid } from '../../lib/grid/Grid';
4 | import { GridType } from '../../lib/grid/GridType';
5 |
6 | describe('Grid Tests', function () {
7 | it('test precision', function () {
8 | const grid = new Grid(GridType.METER);
9 | expect(grid.getPrecision()).to.equal(GridType.METER);
10 | });
11 |
12 | it('test style', function () {
13 | const grid = new Grid(GridType.KILOMETER);
14 |
15 | expect(function () {
16 | grid.setStyle(new GridStyle(undefined, 0), GridType.METER);
17 | }).to.throw(Error);
18 | });
19 |
20 | it('test compare', function () {
21 | const grid = new Grid(GridType.METER);
22 | let grid2 = new Grid(GridType.METER);
23 | expect(grid.equals(grid2)).to.be.true;
24 |
25 | grid2 = new Grid(GridType.KILOMETER);
26 | expect(grid.less(grid2)).to.be.true;
27 |
28 | const grid3 = new Grid(GridType.TEN_KILOMETER);
29 | expect(grid2.less(grid3)).to.be.true;
30 |
31 | expect(grid3.less(grid)).to.be.false;
32 | });
33 |
34 | it('test type', function () {
35 | const grid = new Grid(GridType.GZD);
36 | expect(grid.isType(GridType.GZD)).to.be.true;
37 | expect(grid.isType(GridType.METER)).to.be.false;
38 | });
39 | });
40 |
--------------------------------------------------------------------------------
/test/grid/GridType.spec.ts:
--------------------------------------------------------------------------------
1 | import { expect } from 'chai';
2 | import { GridType } from '../../lib/grid/GridType';
3 | import { GridTypeUtils } from '../../lib/grid/GridTypeUtils';
4 |
5 | /**
6 | * Grid Type Test
7 | *
8 | *
9 | */
10 | describe('GridType Tests', function () {
11 | /**
12 | * Test precisions
13 | */
14 | it('test precisions', function () {
15 | expect(GridType.GZD).to.equal(0);
16 | expect(GridType.HUNDRED_KILOMETER).to.equal(100000);
17 | expect(GridType.TEN_KILOMETER).to.equal(10000);
18 | expect(GridType.KILOMETER).to.equal(1000);
19 | expect(GridType.HUNDRED_METER).to.equal(100);
20 | expect(GridType.TEN_METER).to.equal(10);
21 | expect(GridType.METER).to.equal(1);
22 | });
23 |
24 | /**
25 | * Test digit accuracies
26 | */
27 | it('test accuracies', function () {
28 | expect(GridTypeUtils.getAccuracy(GridType.GZD)).to.equal(0);
29 |
30 | expect(GridTypeUtils.withAccuracy(0)).to.equal(GridType.HUNDRED_KILOMETER);
31 | expect(GridTypeUtils.getAccuracy(GridType.HUNDRED_KILOMETER)).to.equal(0);
32 |
33 | expect(GridTypeUtils.withAccuracy(1)).to.equal(GridType.TEN_KILOMETER);
34 | expect(GridTypeUtils.getAccuracy(GridType.TEN_KILOMETER)).to.equal(1);
35 |
36 | expect(GridTypeUtils.withAccuracy(2)).to.equal(GridType.KILOMETER);
37 | expect(GridTypeUtils.getAccuracy(GridType.KILOMETER)).to.equal(2);
38 |
39 | expect(GridTypeUtils.withAccuracy(3)).to.equal(GridType.HUNDRED_METER);
40 | expect(GridTypeUtils.getAccuracy(GridType.HUNDRED_METER)).to.equal(3);
41 |
42 | expect(GridTypeUtils.withAccuracy(4)).to.equal(GridType.TEN_METER);
43 | expect(GridTypeUtils.getAccuracy(GridType.TEN_METER)).to.equal(4);
44 |
45 | expect(GridTypeUtils.withAccuracy(5)).to.equal(GridType.METER);
46 | expect(GridTypeUtils.getAccuracy(GridType.METER)).to.equal(5);
47 | });
48 | });
49 |
--------------------------------------------------------------------------------
/test/grid/Grids.spec.ts:
--------------------------------------------------------------------------------
1 | import { expect } from 'chai';
2 | import { Grids } from '../../lib/grid/Grids';
3 | import { GridType } from '../../lib/grid/GridType';
4 |
5 | describe('Grids Tests', function () {
6 | it('test construction', function () {
7 | const defaultGrids = new Grids();
8 | for (const grid of defaultGrids.grids()) {
9 | expect(grid.isEnabled()).to.equal(true);
10 | }
11 |
12 | const enabledGrids = new Grids([GridType.KILOMETER]);
13 | for (const grid of enabledGrids.grids()) {
14 | if (grid.getType().valueOf() === GridType.KILOMETER.valueOf()) {
15 | expect(grid.isEnabled()).to.equal(true);
16 | } else {
17 | expect(grid.isEnabled()).to.equal(false);
18 | }
19 | }
20 | });
21 | });
22 |
--------------------------------------------------------------------------------
/test/grid/ZoomGrids.spec.ts:
--------------------------------------------------------------------------------
1 | import { expect } from 'chai';
2 | import { ZoomGrids } from '../../lib/grid/ZoomGrids';
3 | import { GridType } from '../../lib/grid/GridType';
4 | import { Grid } from '../../lib/grid/Grid';
5 |
6 | describe('ZoomGrids Tests', function () {
7 | it('test precision', function () {
8 | const zoomGrids = new ZoomGrids(5);
9 | expect(zoomGrids.getPrecision()).to.be.undefined;
10 |
11 | zoomGrids.addGrid(new Grid(GridType.HUNDRED_KILOMETER));
12 | zoomGrids.addGrid(new Grid(GridType.TEN_KILOMETER));
13 | zoomGrids.addGrid(new Grid(GridType.METER));
14 |
15 | expect(zoomGrids.getPrecision()?.valueOf()).to.equal(GridType.METER.valueOf());
16 | });
17 | });
18 |
--------------------------------------------------------------------------------
/test/gzd/BandLetterRange.spec.ts:
--------------------------------------------------------------------------------
1 | import { expect } from 'chai';
2 | import { GridZones } from '../../lib/gzd/GridZones';
3 | import { BandLetterRange } from '../../lib/gzd/BandLetterRange';
4 |
5 | const BAND_LETTERS = 'CDEFGHJKLMNPQRSTUVWXX';
6 |
7 | /**
8 | * Test the full range
9 | */
10 | describe('BandLetterRange Tests', function () {
11 | /**
12 | * Test the full range
13 | */
14 | it('test full range', function () {
15 | const bandRange = new BandLetterRange();
16 | for (const bandLetter of bandRange) {
17 | expect((BAND_LETTERS.indexOf(bandLetter) - 10) * 8).to.be.approximately(
18 | GridZones.getSouthLatitude(bandLetter),
19 | 0.0,
20 | );
21 | }
22 | });
23 | });
24 |
--------------------------------------------------------------------------------
/test/gzd/GridRange.spec.ts:
--------------------------------------------------------------------------------
1 | import { expect } from 'chai';
2 | import { GridRange } from '../../lib/gzd/GridRange';
3 | import { MGRS } from '../../lib/MGRS';
4 |
5 | describe('GridRange Tests', function () {
6 | it('test iterator', function () {
7 | let gridRange = new GridRange();
8 |
9 | for (const zone of gridRange) {
10 | let zoneNumber = zone.getNumber();
11 | let bandLetter = zone.getLetter();
12 |
13 | let gzd = zoneNumber.toString() + bandLetter;
14 | expect(MGRS.isMGRS(gzd)).to.be.true;
15 | let mgrs = MGRS.parse(gzd);
16 | expect(mgrs).to.not.be.null;
17 | expect(mgrs.getZone()).to.be.equal(zoneNumber);
18 | expect(mgrs.getBand()).to.be.equal(bandLetter);
19 |
20 | let point = mgrs.toPoint();
21 | let southwest = zone.getBounds().getSouthwest();
22 |
23 | expect(southwest.getLongitude()).to.be.approximately(point.getLongitude(), 0.0001);
24 | expect(southwest.getLatitude()).to.be.approximately(point.getLatitude(), 0.0001);
25 | }
26 | });
27 | });
28 |
--------------------------------------------------------------------------------
/test/gzd/GridZones.spec.ts:
--------------------------------------------------------------------------------
1 | import { expect } from 'chai';
2 | import { GridZones } from '../../lib/gzd/GridZones';
3 | import { MGRSConstants } from '../../lib/MGRSConstants';
4 | import { MGRSUtils } from '../../lib/MGRSUtils';
5 |
6 | const BAND_LETTERS = 'CDEFGHJKLMNPQRSTUVWXX';
7 |
8 | describe('GridZones Tests', function () {
9 | /**
10 | * Test zone numbers
11 | */
12 | it('test zone numbers', function () {
13 | let zoneNumber = MGRSConstants.MIN_ZONE_NUMBER;
14 | for (
15 | let longitude = MGRSConstants.MIN_LON;
16 | longitude <= MGRSConstants.MAX_LON;
17 | longitude += MGRSConstants.ZONE_WIDTH
18 | ) {
19 | const west = longitude > MGRSConstants.MIN_LON && longitude < MGRSConstants.MAX_LON ? zoneNumber - 1 : zoneNumber;
20 | const east = zoneNumber;
21 |
22 | if (longitude < MGRSConstants.MAX_LON) {
23 | expect(~~Math.floor(longitude / 6 + 31)).to.equal(east);
24 | }
25 |
26 | expect(GridZones.getZoneNumberFromLongitude(longitude, false)).to.equal(west);
27 | expect(GridZones.getZoneNumberFromLongitude(longitude, true)).to.equal(east);
28 | expect(GridZones.getZoneNumberFromLongitude(longitude)).to.equal(east);
29 |
30 | if (zoneNumber < MGRSConstants.MAX_ZONE_NUMBER) {
31 | zoneNumber++;
32 | }
33 | }
34 | });
35 |
36 | /**
37 | * Test band letters
38 | */
39 | it('test band letters', function () {
40 | let bandLetter = MGRSConstants.MIN_BAND_LETTER;
41 | for (
42 | let latitude = MGRSConstants.MIN_LAT;
43 | latitude <= MGRSConstants.MAX_LAT;
44 | latitude += latitude < 80.0 ? MGRSConstants.BAND_HEIGHT : 4.0
45 | ) {
46 | let south =
47 | latitude > MGRSConstants.MIN_LAT && latitude < 80.0 ? MGRSUtils.previousBandLetter(bandLetter) : bandLetter;
48 | let north = bandLetter;
49 |
50 | expect(BAND_LETTERS.charAt(~~Math.floor(latitude / 8 + 10))).to.equal(north);
51 |
52 | expect(GridZones.getBandLetterFromLatitude(latitude, false)).to.equal(south);
53 | expect(GridZones.getBandLetterFromLatitude(latitude, true)).to.equal(north);
54 | expect(GridZones.getBandLetterFromLatitude(latitude)).to.equal(north);
55 |
56 | if (bandLetter < MGRSConstants.MAX_BAND_LETTER) {
57 | bandLetter = MGRSUtils.nextBandLetter(bandLetter);
58 | }
59 | }
60 | });
61 | });
62 |
--------------------------------------------------------------------------------
/test/gzd/ZoneNumberRange.spec.ts:
--------------------------------------------------------------------------------
1 | import { expect } from 'chai';
2 | import { ZoneNumberRange } from '../../lib/gzd/ZoneNumberRange';
3 | import { MGRSConstants } from '../../lib/MGRSConstants';
4 |
5 | describe('ZoneNumberRange Tests', function () {
6 | it('test iterator', function () {
7 | const range = new ZoneNumberRange();
8 |
9 | let count = 0;
10 | for (const zoneNumber of range) {
11 | count++;
12 | }
13 | expect(count).to.equal(MGRSConstants.MAX_ZONE_NUMBER);
14 | });
15 | });
16 |
--------------------------------------------------------------------------------
/test/utm/UTM.spec.ts:
--------------------------------------------------------------------------------
1 | import { Hemisphere, Point } from '@ngageoint/grid-js';
2 | import { expect } from 'chai';
3 | import { UTM } from '../../lib/utm/UTM';
4 |
5 | describe('UTM Tests', function () {
6 | it('test to point', function () {
7 | const utm = UTM.create(0, Hemisphere.NORTH, 0, 0);
8 | const point = utm.toPoint();
9 | expect(point).to.not.be.undefined;
10 | });
11 |
12 | it('test from point', function () {
13 | const utm = UTM.create(0, Hemisphere.NORTH, 0, 0);
14 | const point = utm.toPoint();
15 | const from = UTM.from(point, utm.getZone(), utm.getHemisphere());
16 | expect(utm.toString() === from.toString()).to.be.true;
17 | });
18 |
19 | it('test is UTM', function () {
20 | const utm = UTM.create(0, Hemisphere.NORTH, 0, 0);
21 |
22 | expect(UTM.isUTM(utm.toString())).to.be.true;
23 | expect(UTM.isUTM('not utm')).to.be.false;
24 | });
25 |
26 | it('test parse', function () {
27 | const utm = UTM.create(0, Hemisphere.NORTH, 0, 0);
28 | const parsed = UTM.parse(utm.toString());
29 |
30 | expect(utm.toString() === parsed.toString()).to.be.true;
31 | });
32 | });
33 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@tsconfig/recommended/tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "./dist", /* Redirect output structure to the directory. */
5 | "declaration": true, /* Generates corresponding '.d.ts' file. */
6 | "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
7 | "sourceMap": true, /* Generates corresponding '.map' file. */
8 | "resolveJsonModule": true,
9 | },
10 | "exclude": [
11 | "**/*.d.ts",
12 | "node_modules",
13 | "test"
14 | ],
15 | "include": [
16 | "lib",
17 | "index.ts"
18 | ]
19 | }
--------------------------------------------------------------------------------
/tslint.json:
--------------------------------------------------------------------------------
1 |
2 |
3 | {
4 | "extends": ["tslint:recommended", "tslint-config-prettier"],
5 | "linterOptions": {
6 | "exclude": [
7 | "test/**/*.ts"
8 | ]
9 | },
10 | "rules": {
11 | "no-bitwise": false,
12 | "prefer-for-of": false
13 | }
14 | }
--------------------------------------------------------------------------------
/typedoc.json:
--------------------------------------------------------------------------------
1 | {
2 | "out": "./docs/api",
3 | "tsconfig": "tsconfig.json",
4 | "exclude": "test/**/*",
5 | "excludePrivate": true,
6 | "includeVersion": true
7 | }
--------------------------------------------------------------------------------