@accelint/geo 0.6.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/README.md +98 -1
  3. package/catalog-info.yaml +1 -1
  4. package/dist/cartesian.js +1 -1
  5. package/dist/coordinates/coordinate.d.ts +1 -1
  6. package/dist/coordinates/coordinate.js +1 -1
  7. package/dist/coordinates/latlon/decimal-degrees/formatter.d.ts +36 -3
  8. package/dist/coordinates/latlon/decimal-degrees/formatter.js +34 -5
  9. package/dist/coordinates/latlon/decimal-degrees/formatter.js.map +1 -1
  10. package/dist/coordinates/latlon/decimal-degrees/parser.d.ts +1 -1
  11. package/dist/coordinates/latlon/decimal-degrees/parser.js +1 -1
  12. package/dist/coordinates/latlon/decimal-degrees/system.d.ts +1 -1
  13. package/dist/coordinates/latlon/decimal-degrees/system.js +6 -4
  14. package/dist/coordinates/latlon/decimal-degrees/system.js.map +1 -1
  15. package/dist/coordinates/latlon/degrees-decimal-minutes/formatter.d.ts +43 -3
  16. package/dist/coordinates/latlon/degrees-decimal-minutes/formatter.js +61 -6
  17. package/dist/coordinates/latlon/degrees-decimal-minutes/formatter.js.map +1 -1
  18. package/dist/coordinates/latlon/degrees-decimal-minutes/parser.d.ts +1 -1
  19. package/dist/coordinates/latlon/degrees-decimal-minutes/parser.js +1 -1
  20. package/dist/coordinates/latlon/degrees-decimal-minutes/system.d.ts +1 -1
  21. package/dist/coordinates/latlon/degrees-decimal-minutes/system.js +8 -7
  22. package/dist/coordinates/latlon/degrees-decimal-minutes/system.js.map +1 -1
  23. package/dist/coordinates/latlon/degrees-minutes-seconds/formatter.d.ts +46 -3
  24. package/dist/coordinates/latlon/degrees-minutes-seconds/formatter.js +67 -8
  25. package/dist/coordinates/latlon/degrees-minutes-seconds/formatter.js.map +1 -1
  26. package/dist/coordinates/latlon/degrees-minutes-seconds/parser.d.ts +1 -1
  27. package/dist/coordinates/latlon/degrees-minutes-seconds/parser.js +1 -1
  28. package/dist/coordinates/latlon/degrees-minutes-seconds/system.d.ts +1 -1
  29. package/dist/coordinates/latlon/degrees-minutes-seconds/system.js +8 -7
  30. package/dist/coordinates/latlon/degrees-minutes-seconds/system.js.map +1 -1
  31. package/dist/coordinates/latlon/internal/coordinate-system.d.ts +1 -1
  32. package/dist/coordinates/latlon/internal/coordinate-system.js +1 -1
  33. package/dist/coordinates/latlon/internal/create-cache.d.ts +1 -1
  34. package/dist/coordinates/latlon/internal/create-cache.js +1 -1
  35. package/dist/coordinates/latlon/internal/exhaustive-errors.js +1 -1
  36. package/dist/coordinates/latlon/internal/format.d.ts +50 -5
  37. package/dist/coordinates/latlon/internal/format.js +40 -10
  38. package/dist/coordinates/latlon/internal/format.js.map +1 -1
  39. package/dist/coordinates/latlon/internal/in-range.js +1 -1
  40. package/dist/coordinates/latlon/internal/index.d.ts +4 -3
  41. package/dist/coordinates/latlon/internal/index.js +4 -3
  42. package/dist/coordinates/latlon/internal/index.js.map +1 -1
  43. package/dist/coordinates/latlon/internal/lexer.js +3 -2
  44. package/dist/coordinates/latlon/internal/lexer.js.map +1 -1
  45. package/dist/coordinates/latlon/internal/normalize.js +1 -1
  46. package/dist/coordinates/latlon/internal/ordinal.d.ts +39 -9
  47. package/dist/coordinates/latlon/internal/ordinal.js +32 -13
  48. package/dist/coordinates/latlon/internal/ordinal.js.map +1 -1
  49. package/dist/coordinates/latlon/internal/parse-format.d.ts +1 -1
  50. package/dist/coordinates/latlon/internal/parse-format.js +1 -1
  51. package/dist/coordinates/latlon/internal/parse.d.ts +1 -1
  52. package/dist/coordinates/latlon/internal/parse.js +1 -1
  53. package/dist/coordinates/latlon/internal/pipes/check-ambiguous.d.ts +1 -1
  54. package/dist/coordinates/latlon/internal/pipes/check-ambiguous.js +1 -1
  55. package/dist/coordinates/latlon/internal/pipes/check-numbers.d.ts +1 -1
  56. package/dist/coordinates/latlon/internal/pipes/check-numbers.js +1 -1
  57. package/dist/coordinates/latlon/internal/pipes/fix-bearings.d.ts +1 -1
  58. package/dist/coordinates/latlon/internal/pipes/fix-bearings.js +1 -1
  59. package/dist/coordinates/latlon/internal/pipes/fix-dividers.d.ts +1 -1
  60. package/dist/coordinates/latlon/internal/pipes/fix-dividers.js +1 -1
  61. package/dist/coordinates/latlon/internal/pipes/genome.d.ts +1 -1
  62. package/dist/coordinates/latlon/internal/pipes/genome.js +1 -1
  63. package/dist/coordinates/latlon/internal/pipes/index.d.ts +1 -1
  64. package/dist/coordinates/latlon/internal/pipes/index.js +1 -1
  65. package/dist/coordinates/latlon/internal/pipes/simpler.d.ts +1 -1
  66. package/dist/coordinates/latlon/internal/pipes/simpler.js +1 -1
  67. package/dist/coordinates/latlon/internal/plain-decimal.d.ts +35 -0
  68. package/dist/coordinates/latlon/internal/plain-decimal.js +59 -0
  69. package/dist/coordinates/latlon/internal/plain-decimal.js.map +1 -0
  70. package/dist/coordinates/latlon/internal/validate.d.ts +28 -1
  71. package/dist/coordinates/latlon/internal/validate.js +31 -2
  72. package/dist/coordinates/latlon/internal/validate.js.map +1 -1
  73. package/dist/coordinates/latlon/internal/violation.js +1 -1
  74. package/dist/coordinates/mgrs/parser.d.ts +1 -1
  75. package/dist/coordinates/mgrs/parser.js +1 -1
  76. package/dist/coordinates/mgrs/parts.d.ts +86 -0
  77. package/dist/coordinates/mgrs/parts.js +94 -0
  78. package/dist/coordinates/mgrs/parts.js.map +1 -0
  79. package/dist/coordinates/mgrs/system.d.ts +1 -1
  80. package/dist/coordinates/mgrs/system.js +5 -3
  81. package/dist/coordinates/mgrs/system.js.map +1 -1
  82. package/dist/coordinates/utm/parser.d.ts +1 -1
  83. package/dist/coordinates/utm/parser.js +1 -1
  84. package/dist/coordinates/utm/parts.d.ts +134 -0
  85. package/dist/coordinates/utm/parts.js +142 -0
  86. package/dist/coordinates/utm/parts.js.map +1 -0
  87. package/dist/coordinates/utm/system.d.ts +1 -1
  88. package/dist/coordinates/utm/system.js +5 -4
  89. package/dist/coordinates/utm/system.js.map +1 -1
  90. package/dist/index.d.ts +11 -8
  91. package/dist/index.js +11 -8
  92. package/dist/patterning.js +1 -1
  93. package/package.json +4 -1
@@ -1,5 +1,5 @@
1
1
  /*
2
- * Copyright 2025 Hypergiant Galactic Systems Inc. All rights reserved.
2
+ * Copyright 2026 Hypergiant Galactic Systems Inc. All rights reserved.
3
3
  * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4
4
  * you may not use this file except in compliance with the License. You may obtain a copy
5
5
  * of the License at https://www.apache.org/licenses/LICENSE-2.0
@@ -1,5 +1,5 @@
1
1
  /*
2
- * Copyright 2025 Hypergiant Galactic Systems Inc. All rights reserved.
2
+ * Copyright 2026 Hypergiant Galactic Systems Inc. All rights reserved.
3
3
  * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4
4
  * you may not use this file except in compliance with the License. You may obtain a copy
5
5
  * of the License at https://www.apache.org/licenses/LICENSE-2.0
@@ -1,5 +1,5 @@
1
1
  /*
2
- * Copyright 2025 Hypergiant Galactic Systems Inc. All rights reserved.
2
+ * Copyright 2026 Hypergiant Galactic Systems Inc. All rights reserved.
3
3
  * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4
4
  * you may not use this file except in compliance with the License. You may obtain a copy
5
5
  * of the License at https://www.apache.org/licenses/LICENSE-2.0
@@ -0,0 +1,86 @@
1
+ /*
2
+ * Copyright 2026 Hypergiant Galactic Systems Inc. All rights reserved.
3
+ * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4
+ * you may not use this file except in compliance with the License. You may obtain a copy
5
+ * of the License at https://www.apache.org/licenses/LICENSE-2.0
6
+ *
7
+ * Unless required by applicable law or agreed to in writing, software distributed under
8
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9
+ * OF ANY KIND, either express or implied. See the License for the specific language
10
+ * governing permissions and limitations under the License.
11
+ */
12
+
13
+ import { GridPartsResult } from "../utm/parts.js";
14
+
15
+ //#region src/coordinates/mgrs/parts.d.ts
16
+ /**
17
+ * Structured MGRS grid parts for a signed `[lat, lon]` coordinate.
18
+ *
19
+ * `zone`/`band`/`e100k`/`n100k` identify the grid square; `easting`/`northing`
20
+ * are the geodesy within-square metre values (`0`–`99999`) prior to truncation.
21
+ */
22
+ type MgrsParts = {
23
+ zone: number;
24
+ band: string;
25
+ e100k: string;
26
+ n100k: string;
27
+ easting: number;
28
+ northing: number;
29
+ };
30
+ /**
31
+ * Converts a signed `[lat, lon]` coordinate into MGRS grid parts.
32
+ *
33
+ * Reads the geodesy `Mgrs` object fields directly instead of parsing a
34
+ * formatted string. The UTM/MGRS grid is defined for `-80 ≤ lat ≤ 84`
35
+ * inclusive; latitudes outside that band, a longitude of exactly `+180°` (the
36
+ * antimeridian zone singularity), or non-finite input, produce
37
+ * `{ ok: false, reason: 'out-of-range' }`.
38
+ *
39
+ * @param coordinate - Signed `[latitude, longitude]` tuple.
40
+ * @returns A discriminated result with `{ zone, band, e100k, n100k, easting, northing }` on success.
41
+ *
42
+ * @remarks pure function
43
+ *
44
+ * @example
45
+ * ```typescript
46
+ * toMgrsParts([38.8977, -77.0365]);
47
+ * // { ok: true, value: { zone: 18, band: 'S', e100k: 'U', n100k: 'J', easting: 23394.29…, northing: 7395.63… } }
48
+ * ```
49
+ *
50
+ * @example
51
+ * ```typescript
52
+ * toMgrsParts([-81, 0]);
53
+ * // { ok: false, reason: 'out-of-range' }
54
+ * ```
55
+ */
56
+ declare const toMgrsParts: ([lat, lon]: [number, number]) => GridPartsResult<MgrsParts>;
57
+ /**
58
+ * Renders MGRS grid parts as their canonical coordinate string.
59
+ *
60
+ * Mirrors geodesy's `Mgrs.toString(10)`: floors the within-square metres,
61
+ * left-pads the zone to two digits and easting/northing to five, and joins the
62
+ * zone+band, the `e100k`/`n100k` grid-square pair, and the padded metres with
63
+ * single spaces.
64
+ *
65
+ * @param parts - The MGRS grid parts to render.
66
+ * @returns The canonical MGRS string, e.g. `"18S UJ 23394 07396"`.
67
+ *
68
+ * @remarks pure function
69
+ *
70
+ * @example
71
+ * ```typescript
72
+ * formatMgrsParts({ zone: 18, band: 'S', e100k: 'U', n100k: 'J', easting: 23394, northing: 7396 });
73
+ * // '18S UJ 23394 07396'
74
+ * ```
75
+ */
76
+ declare const formatMgrsParts: ({
77
+ zone,
78
+ band,
79
+ e100k,
80
+ n100k,
81
+ easting,
82
+ northing
83
+ }: MgrsParts) => string;
84
+ //#endregion
85
+ export { MgrsParts, formatMgrsParts, toMgrsParts };
86
+ //# sourceMappingURL=parts.d.ts.map
@@ -0,0 +1,94 @@
1
+ /*
2
+ * Copyright 2026 Hypergiant Galactic Systems Inc. All rights reserved.
3
+ * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4
+ * you may not use this file except in compliance with the License. You may obtain a copy
5
+ * of the License at https://www.apache.org/licenses/LICENSE-2.0
6
+ *
7
+ * Unless required by applicable law or agreed to in writing, software distributed under
8
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9
+ * OF ANY KIND, either express or implied. See the License for the specific language
10
+ * governing permissions and limitations under the License.
11
+ */
12
+
13
+
14
+ import { isGridProjectable } from "../utm/parts.js";
15
+ import { LatLon } from "geodesy/mgrs";
16
+
17
+ //#region src/coordinates/mgrs/parts.ts
18
+ /**
19
+ * Converts a signed `[lat, lon]` coordinate into MGRS grid parts.
20
+ *
21
+ * Reads the geodesy `Mgrs` object fields directly instead of parsing a
22
+ * formatted string. The UTM/MGRS grid is defined for `-80 ≤ lat ≤ 84`
23
+ * inclusive; latitudes outside that band, a longitude of exactly `+180°` (the
24
+ * antimeridian zone singularity), or non-finite input, produce
25
+ * `{ ok: false, reason: 'out-of-range' }`.
26
+ *
27
+ * @param coordinate - Signed `[latitude, longitude]` tuple.
28
+ * @returns A discriminated result with `{ zone, band, e100k, n100k, easting, northing }` on success.
29
+ *
30
+ * @remarks pure function
31
+ *
32
+ * @example
33
+ * ```typescript
34
+ * toMgrsParts([38.8977, -77.0365]);
35
+ * // { ok: true, value: { zone: 18, band: 'S', e100k: 'U', n100k: 'J', easting: 23394.29…, northing: 7395.63… } }
36
+ * ```
37
+ *
38
+ * @example
39
+ * ```typescript
40
+ * toMgrsParts([-81, 0]);
41
+ * // { ok: false, reason: 'out-of-range' }
42
+ * ```
43
+ */
44
+ const toMgrsParts = ([lat, lon]) => {
45
+ if (!isGridProjectable([lat, lon])) return {
46
+ ok: false,
47
+ reason: "out-of-range"
48
+ };
49
+ try {
50
+ const mgrs = new LatLon(lat, lon).toUtm().toMgrs();
51
+ return {
52
+ ok: true,
53
+ value: {
54
+ zone: mgrs.zone,
55
+ band: mgrs.band,
56
+ e100k: mgrs.e100k,
57
+ n100k: mgrs.n100k,
58
+ easting: mgrs.easting,
59
+ northing: mgrs.northing
60
+ }
61
+ };
62
+ } catch {
63
+ return {
64
+ ok: false,
65
+ reason: "out-of-range"
66
+ };
67
+ }
68
+ };
69
+ /**
70
+ * Renders MGRS grid parts as their canonical coordinate string.
71
+ *
72
+ * Mirrors geodesy's `Mgrs.toString(10)`: floors the within-square metres,
73
+ * left-pads the zone to two digits and easting/northing to five, and joins the
74
+ * zone+band, the `e100k`/`n100k` grid-square pair, and the padded metres with
75
+ * single spaces.
76
+ *
77
+ * @param parts - The MGRS grid parts to render.
78
+ * @returns The canonical MGRS string, e.g. `"18S UJ 23394 07396"`.
79
+ *
80
+ * @remarks pure function
81
+ *
82
+ * @example
83
+ * ```typescript
84
+ * formatMgrsParts({ zone: 18, band: 'S', e100k: 'U', n100k: 'J', easting: 23394, northing: 7396 });
85
+ * // '18S UJ 23394 07396'
86
+ * ```
87
+ */
88
+ const formatMgrsParts = ({ zone, band, e100k, n100k, easting, northing }) => {
89
+ return `${zone.toString().padStart(2, "0")}${band} ${e100k}${n100k} ${Math.floor(easting).toString().padStart(5, "0")} ${Math.floor(northing).toString().padStart(5, "0")}`;
90
+ };
91
+
92
+ //#endregion
93
+ export { formatMgrsParts, toMgrsParts };
94
+ //# sourceMappingURL=parts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parts.js","names":[],"sources":["../../../src/coordinates/mgrs/parts.ts"],"sourcesContent":["/*\n * Copyright 2026 Hypergiant Galactic Systems Inc. All rights reserved.\n * This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License. You may obtain a copy\n * of the License at https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under\n * the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n * OF ANY KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\n\nimport { LatLon } from 'geodesy/mgrs';\nimport { type GridPartsResult, isGridProjectable } from '../utm/parts';\n\n/**\n * Structured MGRS grid parts for a signed `[lat, lon]` coordinate.\n *\n * `zone`/`band`/`e100k`/`n100k` identify the grid square; `easting`/`northing`\n * are the geodesy within-square metre values (`0`–`99999`) prior to truncation.\n */\nexport type MgrsParts = {\n zone: number;\n band: string;\n e100k: string;\n n100k: string;\n easting: number;\n northing: number;\n};\n\n/**\n * Converts a signed `[lat, lon]` coordinate into MGRS grid parts.\n *\n * Reads the geodesy `Mgrs` object fields directly instead of parsing a\n * formatted string. The UTM/MGRS grid is defined for `-80 ≤ lat ≤ 84`\n * inclusive; latitudes outside that band, a longitude of exactly `+180°` (the\n * antimeridian zone singularity), or non-finite input, produce\n * `{ ok: false, reason: 'out-of-range' }`.\n *\n * @param coordinate - Signed `[latitude, longitude]` tuple.\n * @returns A discriminated result with `{ zone, band, e100k, n100k, easting, northing }` on success.\n *\n * @remarks pure function\n *\n * @example\n * ```typescript\n * toMgrsParts([38.8977, -77.0365]);\n * // { ok: true, value: { zone: 18, band: 'S', e100k: 'U', n100k: 'J', easting: 23394.29…, northing: 7395.63… } }\n * ```\n *\n * @example\n * ```typescript\n * toMgrsParts([-81, 0]);\n * // { ok: false, reason: 'out-of-range' }\n * ```\n */\nexport const toMgrsParts = ([lat, lon]: [\n number,\n number,\n]): GridPartsResult<MgrsParts> => {\n if (!isGridProjectable([lat, lon])) {\n return { ok: false, reason: 'out-of-range' };\n }\n\n try {\n const mgrs = new LatLon(lat, lon).toUtm().toMgrs();\n\n return {\n ok: true,\n value: {\n zone: mgrs.zone,\n band: mgrs.band,\n e100k: mgrs.e100k,\n n100k: mgrs.n100k,\n easting: mgrs.easting,\n northing: mgrs.northing,\n },\n };\n } catch {\n // geodesy re-verifies UTM bounds when building the MGRS reference; keep\n // the result total rather than leaking a RangeError.\n return { ok: false, reason: 'out-of-range' };\n }\n};\n\n/**\n * Renders MGRS grid parts as their canonical coordinate string.\n *\n * Mirrors geodesy's `Mgrs.toString(10)`: floors the within-square metres,\n * left-pads the zone to two digits and easting/northing to five, and joins the\n * zone+band, the `e100k`/`n100k` grid-square pair, and the padded metres with\n * single spaces.\n *\n * @param parts - The MGRS grid parts to render.\n * @returns The canonical MGRS string, e.g. `\"18S UJ 23394 07396\"`.\n *\n * @remarks pure function\n *\n * @example\n * ```typescript\n * formatMgrsParts({ zone: 18, band: 'S', e100k: 'U', n100k: 'J', easting: 23394, northing: 7396 });\n * // '18S UJ 23394 07396'\n * ```\n */\nexport const formatMgrsParts = ({\n zone,\n band,\n e100k,\n n100k,\n easting,\n northing,\n}: MgrsParts): string => {\n const zonePadded = zone.toString().padStart(2, '0');\n const eastingPadded = Math.floor(easting).toString().padStart(5, '0');\n const northingPadded = Math.floor(northing).toString().padStart(5, '0');\n\n return `${zonePadded}${band} ${e100k}${n100k} ${eastingPadded} ${northingPadded}`;\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAwDA,MAAa,eAAe,CAAC,KAAK,SAGA;AAChC,KAAI,CAAC,kBAAkB,CAAC,KAAK,IAAI,CAAC,CAChC,QAAO;EAAE,IAAI;EAAO,QAAQ;EAAgB;AAG9C,KAAI;EACF,MAAM,OAAO,IAAI,OAAO,KAAK,IAAI,CAAC,OAAO,CAAC,QAAQ;AAElD,SAAO;GACL,IAAI;GACJ,OAAO;IACL,MAAM,KAAK;IACX,MAAM,KAAK;IACX,OAAO,KAAK;IACZ,OAAO,KAAK;IACZ,SAAS,KAAK;IACd,UAAU,KAAK;IAChB;GACF;SACK;AAGN,SAAO;GAAE,IAAI;GAAO,QAAQ;GAAgB;;;;;;;;;;;;;;;;;;;;;;AAuBhD,MAAa,mBAAmB,EAC9B,MACA,MACA,OACA,OACA,SACA,eACuB;AAKvB,QAAO,GAJY,KAAK,UAAU,CAAC,SAAS,GAAG,IAAI,GAI5B,KAAK,GAAG,QAAQ,MAAM,GAHvB,KAAK,MAAM,QAAQ,CAAC,UAAU,CAAC,SAAS,GAAG,IAAI,CAGP,GAFvC,KAAK,MAAM,SAAS,CAAC,UAAU,CAAC,SAAS,GAAG,IAAI"}
@@ -1,5 +1,5 @@
1
1
  /*
2
- * Copyright 2025 Hypergiant Galactic Systems Inc. All rights reserved.
2
+ * Copyright 2026 Hypergiant Galactic Systems Inc. All rights reserved.
3
3
  * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4
4
  * you may not use this file except in compliance with the License. You may obtain a copy
5
5
  * of the License at https://www.apache.org/licenses/LICENSE-2.0
@@ -1,5 +1,5 @@
1
1
  /*
2
- * Copyright 2025 Hypergiant Galactic Systems Inc. All rights reserved.
2
+ * Copyright 2026 Hypergiant Galactic Systems Inc. All rights reserved.
3
3
  * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4
4
  * you may not use this file except in compliance with the License. You may obtain a copy
5
5
  * of the License at https://www.apache.org/licenses/LICENSE-2.0
@@ -13,7 +13,7 @@
13
13
 
14
14
  import { SYMBOL_PATTERNS } from "../latlon/internal/index.js";
15
15
  import { parseMGRS } from "./parser.js";
16
- import { LatLon } from "geodesy/mgrs";
16
+ import { formatMgrsParts, toMgrsParts } from "./parts.js";
17
17
 
18
18
  //#region src/coordinates/mgrs/system.ts
19
19
  /**
@@ -55,7 +55,9 @@ const systemMGRS = {
55
55
  },
56
56
  toFormat(format, [left, right]) {
57
57
  const { LAT, LON } = Object.fromEntries([[format.slice(0, 3), left], [format.slice(3), right]]);
58
- return new LatLon(LAT, LON).toUtm().toMgrs().toString();
58
+ const result = toMgrsParts([LAT, LON]);
59
+ if (!result.ok) throw new RangeError(`Coordinate [${LAT}, ${LON}] cannot be represented in MGRS (outside 80°S–84°N, on the +180° antimeridian, or not finite).`);
60
+ return formatMgrsParts(result.value);
59
61
  }
60
62
  };
61
63
 
@@ -1 +1 @@
1
- {"version":3,"file":"system.js","names":["systemMGRS: CoordinateSystem"],"sources":["../../../src/coordinates/mgrs/system.ts"],"sourcesContent":["// __private-exports\n/*\n * Copyright 2024 Hypergiant Galactic Systems Inc. All rights reserved.\n * This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License. You may obtain a copy\n * of the License at https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under\n * the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n * OF ANY KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\n\nimport { LatLon } from 'geodesy/mgrs';\nimport { type Compass, type Format, SYMBOL_PATTERNS } from '../latlon/internal';\nimport { parseMGRS } from './parser';\nimport type { CoordinateSystem } from '../latlon/internal/coordinate-system';\n\n/**\n * Military Grid Reference System (MGRS) coordinate system implementation.\n *\n * Provides parsing, conversion, and formatting for MGRS coordinates. MGRS is a geocoordinate\n * standard used by NATO militaries for locating points on Earth, based on the UTM coordinate\n * system. Format: Grid Zone Designation + 100km Square ID + Numerical Location (e.g., '31U BF 12345 67890').\n *\n * @property name - Human-readable name: 'Military Grid Reference System'.\n * @property parse - Parses MGRS coordinate strings into latitude/longitude values.\n * @property toFloat - Converts coordinate component with bearing to signed float value.\n * @property toFormat - Formats latitude/longitude pair back to MGRS coordinate string.\n *\n * @example\n * ```typescript\n * systemMGRS.parse(null, '31U BF 12345 67890');\n * // [[['48.123456', 'N'], ['11.234567', 'E']], []]\n * ```\n *\n * @example\n * ```typescript\n * systemMGRS.toFormat('LATLON', [48.123456, 11.234567]);\n * // '31U BF 12345 67890'\n * ```\n *\n * @example\n * ```typescript\n * systemMGRS.toFloat(['48.123456', 'N']);\n * // 48.123456\n * ```\n */\nexport const systemMGRS: CoordinateSystem = {\n name: 'Military Grid Reference System',\n\n parse: parseMGRS,\n\n toFloat(arg) {\n const [num, bear] = arg as [string, Compass];\n\n return (\n Number.parseFloat(num) *\n (SYMBOL_PATTERNS.NEGATIVE_BEARINGS.test(bear) ? -1 : 1)\n );\n },\n\n toFormat(format: Format, [left, right]: [number, number]) {\n const { LAT, LON } = Object.fromEntries([\n [format.slice(0, 3), left],\n [format.slice(3), right],\n ]) as Record<'LAT' | 'LON', number>;\n\n const latlon = new LatLon(LAT, LON);\n\n return latlon.toUtm().toMgrs().toString();\n },\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgDA,MAAaA,aAA+B;CAC1C,MAAM;CAEN,OAAO;CAEP,QAAQ,KAAK;EACX,MAAM,CAAC,KAAK,QAAQ;AAEpB,SACE,OAAO,WAAW,IAAI,IACrB,gBAAgB,kBAAkB,KAAK,KAAK,GAAG,KAAK;;CAIzD,SAAS,QAAgB,CAAC,MAAM,QAA0B;EACxD,MAAM,EAAE,KAAK,QAAQ,OAAO,YAAY,CACtC,CAAC,OAAO,MAAM,GAAG,EAAE,EAAE,KAAK,EAC1B,CAAC,OAAO,MAAM,EAAE,EAAE,MAAM,CACzB,CAAC;AAIF,SAFe,IAAI,OAAO,KAAK,IAAI,CAErB,OAAO,CAAC,QAAQ,CAAC,UAAU;;CAE5C"}
1
+ {"version":3,"file":"system.js","names":["systemMGRS: CoordinateSystem"],"sources":["../../../src/coordinates/mgrs/system.ts"],"sourcesContent":["// __private-exports\n/*\n * Copyright 2024 Hypergiant Galactic Systems Inc. All rights reserved.\n * This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License. You may obtain a copy\n * of the License at https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under\n * the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n * OF ANY KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\n\nimport { type Compass, type Format, SYMBOL_PATTERNS } from '../latlon/internal';\nimport { parseMGRS } from './parser';\nimport { formatMgrsParts, toMgrsParts } from './parts';\nimport type { CoordinateSystem } from '../latlon/internal/coordinate-system';\n\n/**\n * Military Grid Reference System (MGRS) coordinate system implementation.\n *\n * Provides parsing, conversion, and formatting for MGRS coordinates. MGRS is a geocoordinate\n * standard used by NATO militaries for locating points on Earth, based on the UTM coordinate\n * system. Format: Grid Zone Designation + 100km Square ID + Numerical Location (e.g., '31U BF 12345 67890').\n *\n * @property name - Human-readable name: 'Military Grid Reference System'.\n * @property parse - Parses MGRS coordinate strings into latitude/longitude values.\n * @property toFloat - Converts coordinate component with bearing to signed float value.\n * @property toFormat - Formats latitude/longitude pair back to MGRS coordinate string.\n *\n * @example\n * ```typescript\n * systemMGRS.parse(null, '31U BF 12345 67890');\n * // [[['48.123456', 'N'], ['11.234567', 'E']], []]\n * ```\n *\n * @example\n * ```typescript\n * systemMGRS.toFormat('LATLON', [48.123456, 11.234567]);\n * // '31U BF 12345 67890'\n * ```\n *\n * @example\n * ```typescript\n * systemMGRS.toFloat(['48.123456', 'N']);\n * // 48.123456\n * ```\n */\nexport const systemMGRS: CoordinateSystem = {\n name: 'Military Grid Reference System',\n\n parse: parseMGRS,\n\n toFloat(arg) {\n const [num, bear] = arg as [string, Compass];\n\n return (\n Number.parseFloat(num) *\n (SYMBOL_PATTERNS.NEGATIVE_BEARINGS.test(bear) ? -1 : 1)\n );\n },\n\n toFormat(format: Format, [left, right]: [number, number]) {\n const { LAT, LON } = Object.fromEntries([\n [format.slice(0, 3), left],\n [format.slice(3), right],\n ]) as Record<'LAT' | 'LON', number>;\n\n const result = toMgrsParts([LAT, LON]);\n\n if (!result.ok) {\n // The legacy `toFormat` contract throws for coordinates the grid cannot\n // represent; the parts API returns `{ ok: false }` for the same inputs.\n throw new RangeError(\n `Coordinate [${LAT}, ${LON}] cannot be represented in MGRS (outside 80°S–84°N, on the +180° antimeridian, or not finite).`,\n );\n }\n\n return formatMgrsParts(result.value);\n },\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgDA,MAAaA,aAA+B;CAC1C,MAAM;CAEN,OAAO;CAEP,QAAQ,KAAK;EACX,MAAM,CAAC,KAAK,QAAQ;AAEpB,SACE,OAAO,WAAW,IAAI,IACrB,gBAAgB,kBAAkB,KAAK,KAAK,GAAG,KAAK;;CAIzD,SAAS,QAAgB,CAAC,MAAM,QAA0B;EACxD,MAAM,EAAE,KAAK,QAAQ,OAAO,YAAY,CACtC,CAAC,OAAO,MAAM,GAAG,EAAE,EAAE,KAAK,EAC1B,CAAC,OAAO,MAAM,EAAE,EAAE,MAAM,CACzB,CAAC;EAEF,MAAM,SAAS,YAAY,CAAC,KAAK,IAAI,CAAC;AAEtC,MAAI,CAAC,OAAO,GAGV,OAAM,IAAI,WACR,eAAe,IAAI,IAAI,IAAI,gGAC5B;AAGH,SAAO,gBAAgB,OAAO,MAAM;;CAEvC"}
@@ -1,5 +1,5 @@
1
1
  /*
2
- * Copyright 2025 Hypergiant Galactic Systems Inc. All rights reserved.
2
+ * Copyright 2026 Hypergiant Galactic Systems Inc. All rights reserved.
3
3
  * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4
4
  * you may not use this file except in compliance with the License. You may obtain a copy
5
5
  * of the License at https://www.apache.org/licenses/LICENSE-2.0
@@ -1,5 +1,5 @@
1
1
  /*
2
- * Copyright 2025 Hypergiant Galactic Systems Inc. All rights reserved.
2
+ * Copyright 2026 Hypergiant Galactic Systems Inc. All rights reserved.
3
3
  * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4
4
  * you may not use this file except in compliance with the License. You may obtain a copy
5
5
  * of the License at https://www.apache.org/licenses/LICENSE-2.0
@@ -0,0 +1,134 @@
1
+ //#region src/coordinates/utm/parts.d.ts
2
+ /**
3
+ * Lowest latitude (degrees) the UTM/MGRS grid is defined for, inclusive.
4
+ *
5
+ * Matches the patched geodesy valid range; latitudes below this yield an
6
+ * out-of-range result rather than a projected grid reference.
7
+ */
8
+ declare const GRID_LATITUDE_MIN = -80;
9
+ /**
10
+ * Highest latitude (degrees) the UTM/MGRS grid is defined for, inclusive.
11
+ *
12
+ * Matches the patched geodesy valid range; latitudes above this yield an
13
+ * out-of-range result rather than a projected grid reference.
14
+ */
15
+ declare const GRID_LATITUDE_MAX = 84;
16
+ /**
17
+ * Discriminated result for a grid-parts conversion.
18
+ *
19
+ * `ok: true` carries the structured grid parts; `ok: false` carries a typed
20
+ * `reason` so consumers branch on a field instead of matching error text.
21
+ *
22
+ * @template Value - The grid parts shape carried on success.
23
+ */
24
+ type GridPartsResult<Value> = {
25
+ ok: true;
26
+ value: Value;
27
+ } | {
28
+ ok: false;
29
+ reason: 'out-of-range';
30
+ };
31
+ /**
32
+ * Structured UTM grid parts for a signed `[lat, lon]` coordinate.
33
+ *
34
+ * `easting` and `northing` are the rounded integer metres geodesy yields.
35
+ */
36
+ type UtmParts = {
37
+ zone: number;
38
+ hemisphere: 'N' | 'S';
39
+ easting: number;
40
+ northing: number;
41
+ };
42
+ /**
43
+ * Reports whether a latitude falls within the inclusive UTM/MGRS grid band.
44
+ *
45
+ * @param lat - The latitude in degrees.
46
+ * @returns `true` when `-80 ≤ lat ≤ 84`.
47
+ *
48
+ * @remarks pure function
49
+ */
50
+ declare const isWithinGridBand: (lat: number) => boolean;
51
+ /**
52
+ * Reports whether a longitude sits on the eastern antimeridian singularity.
53
+ *
54
+ * At exactly `+180°` the UTM zone formula yields the nonexistent zone `61`,
55
+ * for which geodesy throws. `-180°` and values beyond `±180°` project
56
+ * normally, so only `+180°` is excluded here — the same point is reachable as
57
+ * `-180°`, or via a caller's own longitude normalization.
58
+ *
59
+ * @param lon - The longitude in degrees.
60
+ * @returns `true` when `lon` is exactly `180`.
61
+ *
62
+ * @remarks pure function
63
+ */
64
+ declare const isOnEasternAntimeridian: (lon: number) => boolean;
65
+ /**
66
+ * Reports whether a signed `[lat, lon]` coordinate can be projected to a UTM
67
+ * zone (and therefore to MGRS).
68
+ *
69
+ * A coordinate is projectable when it is finite and in range, its latitude is
70
+ * within the inclusive `80°S`–`84°N` grid band, and it does not sit on the
71
+ * `+180°` antimeridian singularity. This is the single validity gate shared by
72
+ * {@link toUtmParts} and {@link toMgrsParts}.
73
+ *
74
+ * @param coordinate - Signed `[latitude, longitude]` tuple.
75
+ * @returns `true` when the coordinate projects to a grid reference.
76
+ *
77
+ * @remarks pure function
78
+ */
79
+ declare const isGridProjectable: ([lat, lon]: [number, number]) => boolean;
80
+ /**
81
+ * Converts a signed `[lat, lon]` coordinate into UTM grid parts.
82
+ *
83
+ * Reads the geodesy `Utm` fields directly and rounds `easting`/`northing` to
84
+ * integer metres (matching the existing UTM string renderer). The UTM/MGRS
85
+ * grid is defined for `-80 ≤ lat ≤ 84` inclusive; latitudes outside that band,
86
+ * a longitude of exactly `+180°` (the antimeridian zone singularity), or
87
+ * non-finite input, produce `{ ok: false, reason: 'out-of-range' }`.
88
+ *
89
+ * @param coordinate - Signed `[latitude, longitude]` tuple.
90
+ * @returns A discriminated result with `{ zone, hemisphere, easting, northing }` on success.
91
+ *
92
+ * @remarks pure function
93
+ *
94
+ * @example
95
+ * ```typescript
96
+ * toUtmParts([38.8977, -77.0365]);
97
+ * // { ok: true, value: { zone: 18, hemisphere: 'N', easting: 323394, northing: 4307396 } }
98
+ * ```
99
+ *
100
+ * @example
101
+ * ```typescript
102
+ * toUtmParts([85, 0]);
103
+ * // { ok: false, reason: 'out-of-range' }
104
+ * ```
105
+ */
106
+ declare const toUtmParts: ([lat, lon]: [number, number]) => GridPartsResult<UtmParts>;
107
+ /**
108
+ * Renders UTM grid parts as their canonical coordinate string.
109
+ *
110
+ * Left-pads the zone to two digits and joins zone+hemisphere, easting, and
111
+ * northing with single spaces, matching geodesy's `Utm.toString()` output.
112
+ * `easting`/`northing` are already the rounded integer metres `toUtmParts`
113
+ * yields, so no further rounding is applied here.
114
+ *
115
+ * @param parts - The UTM grid parts to render.
116
+ * @returns The canonical UTM string, e.g. `"18N 323394 4307396"`.
117
+ *
118
+ * @remarks pure function
119
+ *
120
+ * @example
121
+ * ```typescript
122
+ * formatUtmParts({ zone: 18, hemisphere: 'N', easting: 323394, northing: 4307396 });
123
+ * // '18N 323394 4307396'
124
+ * ```
125
+ */
126
+ declare const formatUtmParts: ({
127
+ zone,
128
+ hemisphere,
129
+ easting,
130
+ northing
131
+ }: UtmParts) => string;
132
+ //#endregion
133
+ export { GRID_LATITUDE_MAX, GRID_LATITUDE_MIN, GridPartsResult, UtmParts, formatUtmParts, isGridProjectable, isOnEasternAntimeridian, isWithinGridBand, toUtmParts };
134
+ //# sourceMappingURL=parts.d.ts.map
@@ -0,0 +1,142 @@
1
+ /*
2
+ * Copyright 2026 Hypergiant Galactic Systems Inc. All rights reserved.
3
+ * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4
+ * you may not use this file except in compliance with the License. You may obtain a copy
5
+ * of the License at https://www.apache.org/licenses/LICENSE-2.0
6
+ *
7
+ * Unless required by applicable law or agreed to in writing, software distributed under
8
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9
+ * OF ANY KIND, either express or implied. See the License for the specific language
10
+ * governing permissions and limitations under the License.
11
+ */
12
+
13
+
14
+ import { isValidNumericCoordinate } from "../latlon/internal/validate.js";
15
+ import { LatLon } from "geodesy/utm";
16
+
17
+ //#region src/coordinates/utm/parts.ts
18
+ /**
19
+ * Lowest latitude (degrees) the UTM/MGRS grid is defined for, inclusive.
20
+ *
21
+ * Matches the patched geodesy valid range; latitudes below this yield an
22
+ * out-of-range result rather than a projected grid reference.
23
+ */
24
+ const GRID_LATITUDE_MIN = -80;
25
+ /**
26
+ * Highest latitude (degrees) the UTM/MGRS grid is defined for, inclusive.
27
+ *
28
+ * Matches the patched geodesy valid range; latitudes above this yield an
29
+ * out-of-range result rather than a projected grid reference.
30
+ */
31
+ const GRID_LATITUDE_MAX = 84;
32
+ /**
33
+ * Reports whether a latitude falls within the inclusive UTM/MGRS grid band.
34
+ *
35
+ * @param lat - The latitude in degrees.
36
+ * @returns `true` when `-80 ≤ lat ≤ 84`.
37
+ *
38
+ * @remarks pure function
39
+ */
40
+ const isWithinGridBand = (lat) => lat >= GRID_LATITUDE_MIN && lat <= GRID_LATITUDE_MAX;
41
+ /**
42
+ * Reports whether a longitude sits on the eastern antimeridian singularity.
43
+ *
44
+ * At exactly `+180°` the UTM zone formula yields the nonexistent zone `61`,
45
+ * for which geodesy throws. `-180°` and values beyond `±180°` project
46
+ * normally, so only `+180°` is excluded here — the same point is reachable as
47
+ * `-180°`, or via a caller's own longitude normalization.
48
+ *
49
+ * @param lon - The longitude in degrees.
50
+ * @returns `true` when `lon` is exactly `180`.
51
+ *
52
+ * @remarks pure function
53
+ */
54
+ const isOnEasternAntimeridian = (lon) => lon === 180;
55
+ /**
56
+ * Reports whether a signed `[lat, lon]` coordinate can be projected to a UTM
57
+ * zone (and therefore to MGRS).
58
+ *
59
+ * A coordinate is projectable when it is finite and in range, its latitude is
60
+ * within the inclusive `80°S`–`84°N` grid band, and it does not sit on the
61
+ * `+180°` antimeridian singularity. This is the single validity gate shared by
62
+ * {@link toUtmParts} and {@link toMgrsParts}.
63
+ *
64
+ * @param coordinate - Signed `[latitude, longitude]` tuple.
65
+ * @returns `true` when the coordinate projects to a grid reference.
66
+ *
67
+ * @remarks pure function
68
+ */
69
+ const isGridProjectable = ([lat, lon]) => isValidNumericCoordinate(lat, lon) && isWithinGridBand(lat) && !isOnEasternAntimeridian(lon);
70
+ /**
71
+ * Converts a signed `[lat, lon]` coordinate into UTM grid parts.
72
+ *
73
+ * Reads the geodesy `Utm` fields directly and rounds `easting`/`northing` to
74
+ * integer metres (matching the existing UTM string renderer). The UTM/MGRS
75
+ * grid is defined for `-80 ≤ lat ≤ 84` inclusive; latitudes outside that band,
76
+ * a longitude of exactly `+180°` (the antimeridian zone singularity), or
77
+ * non-finite input, produce `{ ok: false, reason: 'out-of-range' }`.
78
+ *
79
+ * @param coordinate - Signed `[latitude, longitude]` tuple.
80
+ * @returns A discriminated result with `{ zone, hemisphere, easting, northing }` on success.
81
+ *
82
+ * @remarks pure function
83
+ *
84
+ * @example
85
+ * ```typescript
86
+ * toUtmParts([38.8977, -77.0365]);
87
+ * // { ok: true, value: { zone: 18, hemisphere: 'N', easting: 323394, northing: 4307396 } }
88
+ * ```
89
+ *
90
+ * @example
91
+ * ```typescript
92
+ * toUtmParts([85, 0]);
93
+ * // { ok: false, reason: 'out-of-range' }
94
+ * ```
95
+ */
96
+ const toUtmParts = ([lat, lon]) => {
97
+ if (!isGridProjectable([lat, lon])) return {
98
+ ok: false,
99
+ reason: "out-of-range"
100
+ };
101
+ try {
102
+ const utm = new LatLon(lat, lon).toUtm();
103
+ return {
104
+ ok: true,
105
+ value: {
106
+ zone: utm.zone,
107
+ hemisphere: utm.hemisphere,
108
+ easting: Math.round(utm.easting),
109
+ northing: Math.round(utm.northing)
110
+ }
111
+ };
112
+ } catch {
113
+ return {
114
+ ok: false,
115
+ reason: "out-of-range"
116
+ };
117
+ }
118
+ };
119
+ /**
120
+ * Renders UTM grid parts as their canonical coordinate string.
121
+ *
122
+ * Left-pads the zone to two digits and joins zone+hemisphere, easting, and
123
+ * northing with single spaces, matching geodesy's `Utm.toString()` output.
124
+ * `easting`/`northing` are already the rounded integer metres `toUtmParts`
125
+ * yields, so no further rounding is applied here.
126
+ *
127
+ * @param parts - The UTM grid parts to render.
128
+ * @returns The canonical UTM string, e.g. `"18N 323394 4307396"`.
129
+ *
130
+ * @remarks pure function
131
+ *
132
+ * @example
133
+ * ```typescript
134
+ * formatUtmParts({ zone: 18, hemisphere: 'N', easting: 323394, northing: 4307396 });
135
+ * // '18N 323394 4307396'
136
+ * ```
137
+ */
138
+ const formatUtmParts = ({ zone, hemisphere, easting, northing }) => `${zone.toString().padStart(2, "0")}${hemisphere} ${easting} ${northing}`;
139
+
140
+ //#endregion
141
+ export { GRID_LATITUDE_MAX, GRID_LATITUDE_MIN, formatUtmParts, isGridProjectable, isOnEasternAntimeridian, isWithinGridBand, toUtmParts };
142
+ //# sourceMappingURL=parts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parts.js","names":[],"sources":["../../../src/coordinates/utm/parts.ts"],"sourcesContent":["/*\n * Copyright 2026 Hypergiant Galactic Systems Inc. All rights reserved.\n * This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License. You may obtain a copy\n * of the License at https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under\n * the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n * OF ANY KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\n\nimport { LatLon } from 'geodesy/utm';\nimport { isValidNumericCoordinate } from '../latlon/internal';\n\n/**\n * Lowest latitude (degrees) the UTM/MGRS grid is defined for, inclusive.\n *\n * Matches the patched geodesy valid range; latitudes below this yield an\n * out-of-range result rather than a projected grid reference.\n */\nexport const GRID_LATITUDE_MIN = -80;\n\n/**\n * Highest latitude (degrees) the UTM/MGRS grid is defined for, inclusive.\n *\n * Matches the patched geodesy valid range; latitudes above this yield an\n * out-of-range result rather than a projected grid reference.\n */\nexport const GRID_LATITUDE_MAX = 84;\n\n/**\n * Discriminated result for a grid-parts conversion.\n *\n * `ok: true` carries the structured grid parts; `ok: false` carries a typed\n * `reason` so consumers branch on a field instead of matching error text.\n *\n * @template Value - The grid parts shape carried on success.\n */\nexport type GridPartsResult<Value> =\n | { ok: true; value: Value }\n | { ok: false; reason: 'out-of-range' };\n\n/**\n * Structured UTM grid parts for a signed `[lat, lon]` coordinate.\n *\n * `easting` and `northing` are the rounded integer metres geodesy yields.\n */\nexport type UtmParts = {\n zone: number;\n hemisphere: 'N' | 'S';\n easting: number;\n northing: number;\n};\n\n/**\n * Reports whether a latitude falls within the inclusive UTM/MGRS grid band.\n *\n * @param lat - The latitude in degrees.\n * @returns `true` when `-80 ≤ lat ≤ 84`.\n *\n * @remarks pure function\n */\nexport const isWithinGridBand = (lat: number): boolean =>\n lat >= GRID_LATITUDE_MIN && lat <= GRID_LATITUDE_MAX;\n\n/**\n * Reports whether a longitude sits on the eastern antimeridian singularity.\n *\n * At exactly `+180°` the UTM zone formula yields the nonexistent zone `61`,\n * for which geodesy throws. `-180°` and values beyond `±180°` project\n * normally, so only `+180°` is excluded here — the same point is reachable as\n * `-180°`, or via a caller's own longitude normalization.\n *\n * @param lon - The longitude in degrees.\n * @returns `true` when `lon` is exactly `180`.\n *\n * @remarks pure function\n */\nexport const isOnEasternAntimeridian = (lon: number): boolean => lon === 180;\n\n/**\n * Reports whether a signed `[lat, lon]` coordinate can be projected to a UTM\n * zone (and therefore to MGRS).\n *\n * A coordinate is projectable when it is finite and in range, its latitude is\n * within the inclusive `80°S`–`84°N` grid band, and it does not sit on the\n * `+180°` antimeridian singularity. This is the single validity gate shared by\n * {@link toUtmParts} and {@link toMgrsParts}.\n *\n * @param coordinate - Signed `[latitude, longitude]` tuple.\n * @returns `true` when the coordinate projects to a grid reference.\n *\n * @remarks pure function\n */\nexport const isGridProjectable = ([lat, lon]: [number, number]): boolean =>\n isValidNumericCoordinate(lat, lon) &&\n isWithinGridBand(lat) &&\n !isOnEasternAntimeridian(lon);\n\n/**\n * Converts a signed `[lat, lon]` coordinate into UTM grid parts.\n *\n * Reads the geodesy `Utm` fields directly and rounds `easting`/`northing` to\n * integer metres (matching the existing UTM string renderer). The UTM/MGRS\n * grid is defined for `-80 ≤ lat ≤ 84` inclusive; latitudes outside that band,\n * a longitude of exactly `+180°` (the antimeridian zone singularity), or\n * non-finite input, produce `{ ok: false, reason: 'out-of-range' }`.\n *\n * @param coordinate - Signed `[latitude, longitude]` tuple.\n * @returns A discriminated result with `{ zone, hemisphere, easting, northing }` on success.\n *\n * @remarks pure function\n *\n * @example\n * ```typescript\n * toUtmParts([38.8977, -77.0365]);\n * // { ok: true, value: { zone: 18, hemisphere: 'N', easting: 323394, northing: 4307396 } }\n * ```\n *\n * @example\n * ```typescript\n * toUtmParts([85, 0]);\n * // { ok: false, reason: 'out-of-range' }\n * ```\n */\nexport const toUtmParts = ([lat, lon]: [\n number,\n number,\n]): GridPartsResult<UtmParts> => {\n if (!isGridProjectable([lat, lon])) {\n return { ok: false, reason: 'out-of-range' };\n }\n\n try {\n const utm = new LatLon(lat, lon).toUtm();\n\n return {\n ok: true,\n value: {\n zone: utm.zone,\n hemisphere: utm.hemisphere as 'N' | 'S',\n easting: Math.round(utm.easting),\n northing: Math.round(utm.northing),\n },\n };\n } catch {\n // geodesy applies its own bounds after projecting; keep the result total\n // rather than leaking a RangeError (e.g. an unpatched geodesy at 84°N).\n return { ok: false, reason: 'out-of-range' };\n }\n};\n\n/**\n * Renders UTM grid parts as their canonical coordinate string.\n *\n * Left-pads the zone to two digits and joins zone+hemisphere, easting, and\n * northing with single spaces, matching geodesy's `Utm.toString()` output.\n * `easting`/`northing` are already the rounded integer metres `toUtmParts`\n * yields, so no further rounding is applied here.\n *\n * @param parts - The UTM grid parts to render.\n * @returns The canonical UTM string, e.g. `\"18N 323394 4307396\"`.\n *\n * @remarks pure function\n *\n * @example\n * ```typescript\n * formatUtmParts({ zone: 18, hemisphere: 'N', easting: 323394, northing: 4307396 });\n * // '18N 323394 4307396'\n * ```\n */\nexport const formatUtmParts = ({\n zone,\n hemisphere,\n easting,\n northing,\n}: UtmParts): string =>\n `${zone.toString().padStart(2, '0')}${hemisphere} ${easting} ${northing}`;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAqBA,MAAa,oBAAoB;;;;;;;AAQjC,MAAa,oBAAoB;;;;;;;;;AAkCjC,MAAa,oBAAoB,QAC/B,OAAO,qBAAqB,OAAO;;;;;;;;;;;;;;AAerC,MAAa,2BAA2B,QAAyB,QAAQ;;;;;;;;;;;;;;;AAgBzE,MAAa,qBAAqB,CAAC,KAAK,SACtC,yBAAyB,KAAK,IAAI,IAClC,iBAAiB,IAAI,IACrB,CAAC,wBAAwB,IAAI;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4B/B,MAAa,cAAc,CAAC,KAAK,SAGA;AAC/B,KAAI,CAAC,kBAAkB,CAAC,KAAK,IAAI,CAAC,CAChC,QAAO;EAAE,IAAI;EAAO,QAAQ;EAAgB;AAG9C,KAAI;EACF,MAAM,MAAM,IAAI,OAAO,KAAK,IAAI,CAAC,OAAO;AAExC,SAAO;GACL,IAAI;GACJ,OAAO;IACL,MAAM,IAAI;IACV,YAAY,IAAI;IAChB,SAAS,KAAK,MAAM,IAAI,QAAQ;IAChC,UAAU,KAAK,MAAM,IAAI,SAAS;IACnC;GACF;SACK;AAGN,SAAO;GAAE,IAAI;GAAO,QAAQ;GAAgB;;;;;;;;;;;;;;;;;;;;;;AAuBhD,MAAa,kBAAkB,EAC7B,MACA,YACA,SACA,eAEA,GAAG,KAAK,UAAU,CAAC,SAAS,GAAG,IAAI,GAAG,WAAW,GAAG,QAAQ,GAAG"}
@@ -1,5 +1,5 @@
1
1
  /*
2
- * Copyright 2025 Hypergiant Galactic Systems Inc. All rights reserved.
2
+ * Copyright 2026 Hypergiant Galactic Systems Inc. All rights reserved.
3
3
  * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4
4
  * you may not use this file except in compliance with the License. You may obtain a copy
5
5
  * of the License at https://www.apache.org/licenses/LICENSE-2.0
@@ -1,5 +1,5 @@
1
1
  /*
2
- * Copyright 2025 Hypergiant Galactic Systems Inc. All rights reserved.
2
+ * Copyright 2026 Hypergiant Galactic Systems Inc. All rights reserved.
3
3
  * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4
4
  * you may not use this file except in compliance with the License. You may obtain a copy
5
5
  * of the License at https://www.apache.org/licenses/LICENSE-2.0
@@ -12,8 +12,8 @@
12
12
 
13
13
 
14
14
  import { SYMBOL_PATTERNS } from "../latlon/internal/index.js";
15
+ import { formatUtmParts, toUtmParts } from "./parts.js";
15
16
  import { parseUTM } from "./parser.js";
16
- import { LatLon } from "geodesy/utm";
17
17
 
18
18
  //#region src/coordinates/utm/system.ts
19
19
  /**
@@ -45,8 +45,9 @@ const systemUTM = {
45
45
  },
46
46
  toFormat(format, [left, right]) {
47
47
  const { LAT, LON } = Object.fromEntries([[format.slice(0, 3), left], [format.slice(3), right]]);
48
- const utm = new LatLon(LAT, LON).toUtm();
49
- return `${utm.zone.toString().padStart(2, "0")}${utm.hemisphere} ${Math.round(utm.easting).toString()} ${Math.round(utm.northing).toString()}`;
48
+ const result = toUtmParts([LAT, LON]);
49
+ if (!result.ok) throw new RangeError(`Coordinate [${LAT}, ${LON}] cannot be represented in UTM (outside 80°S–84°N, on the +180° antimeridian, or not finite).`);
50
+ return formatUtmParts(result.value);
50
51
  }
51
52
  };
52
53
 
@@ -1 +1 @@
1
- {"version":3,"file":"system.js","names":["systemUTM: CoordinateSystem"],"sources":["../../../src/coordinates/utm/system.ts"],"sourcesContent":["// __private-exports\n/*\n * Copyright 2024 Hypergiant Galactic Systems Inc. All rights reserved.\n * This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License. You may obtain a copy\n * of the License at https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under\n * the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n * OF ANY KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\n\nimport { LatLon } from 'geodesy/utm';\nimport { type Compass, type Format, SYMBOL_PATTERNS } from '../latlon/internal';\nimport { parseUTM } from './parser';\nimport type { CoordinateSystem } from '../latlon/internal/coordinate-system';\n\n/**\n * Universal Transverse Mercator (UTM) coordinate system implementation.\n *\n * Provides parsing, conversion, and formatting for UTM coordinates. UTM divides\n * the Earth into 60 zones, each 6 degrees of longitude wide, using a transverse\n * Mercator projection. Coordinates are expressed as zone number, hemisphere (N/S),\n * easting, and northing values.\n *\n * @example\n * ```typescript\n * systemUTM.parse(null, '18N 585628 4511644');\n * // [['40.7128', '/', '-74.0060'], []]\n * ```\n *\n * @example\n * ```typescript\n * systemUTM.toFormat('LATLON', [40.7128, -74.0060]);\n * // '18N 585628 4511644'\n * ```\n */\nexport const systemUTM: CoordinateSystem = {\n name: 'Universal Transverse Mercator',\n\n parse: parseUTM,\n\n toFloat(arg) {\n const [num, bear] = arg as [string, Compass];\n\n return (\n Number.parseFloat(num) *\n (SYMBOL_PATTERNS.NEGATIVE_BEARINGS.test(bear) ? -1 : 1)\n );\n },\n\n toFormat(format: Format, [left, right]: [number, number]) {\n const { LAT, LON } = Object.fromEntries([\n [format.slice(0, 3), left],\n [format.slice(3), right],\n ]) as Record<'LAT' | 'LON', number>;\n\n const latlon = new LatLon(LAT, LON);\n const utm = latlon.toUtm();\n\n // Format UTM coordinates manually to ensure correct format\n // Expected format: \"18N 585628 4511644\" (zone hemisphere easting northing)\n const zone = utm.zone.toString().padStart(2, '0');\n const hemisphere = utm.hemisphere;\n const easting = Math.round(utm.easting).toString();\n const northing = Math.round(utm.northing).toString();\n\n return `${zone}${hemisphere} ${easting} ${northing}`;\n },\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsCA,MAAaA,YAA8B;CACzC,MAAM;CAEN,OAAO;CAEP,QAAQ,KAAK;EACX,MAAM,CAAC,KAAK,QAAQ;AAEpB,SACE,OAAO,WAAW,IAAI,IACrB,gBAAgB,kBAAkB,KAAK,KAAK,GAAG,KAAK;;CAIzD,SAAS,QAAgB,CAAC,MAAM,QAA0B;EACxD,MAAM,EAAE,KAAK,QAAQ,OAAO,YAAY,CACtC,CAAC,OAAO,MAAM,GAAG,EAAE,EAAE,KAAK,EAC1B,CAAC,OAAO,MAAM,EAAE,EAAE,MAAM,CACzB,CAAC;EAGF,MAAM,MADS,IAAI,OAAO,KAAK,IAAI,CAChB,OAAO;AAS1B,SAAO,GALM,IAAI,KAAK,UAAU,CAAC,SAAS,GAAG,IAAI,GAC9B,IAAI,WAIK,GAHZ,KAAK,MAAM,IAAI,QAAQ,CAAC,UAAU,CAGX,GAFtB,KAAK,MAAM,IAAI,SAAS,CAAC,UAAU;;CAIvD"}
1
+ {"version":3,"file":"system.js","names":["systemUTM: CoordinateSystem"],"sources":["../../../src/coordinates/utm/system.ts"],"sourcesContent":["// __private-exports\n/*\n * Copyright 2024 Hypergiant Galactic Systems Inc. All rights reserved.\n * This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License. You may obtain a copy\n * of the License at https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under\n * the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n * OF ANY KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\n\nimport { type Compass, type Format, SYMBOL_PATTERNS } from '../latlon/internal';\nimport { parseUTM } from './parser';\nimport { formatUtmParts, toUtmParts } from './parts';\nimport type { CoordinateSystem } from '../latlon/internal/coordinate-system';\n\n/**\n * Universal Transverse Mercator (UTM) coordinate system implementation.\n *\n * Provides parsing, conversion, and formatting for UTM coordinates. UTM divides\n * the Earth into 60 zones, each 6 degrees of longitude wide, using a transverse\n * Mercator projection. Coordinates are expressed as zone number, hemisphere (N/S),\n * easting, and northing values.\n *\n * @example\n * ```typescript\n * systemUTM.parse(null, '18N 585628 4511644');\n * // [['40.7128', '/', '-74.0060'], []]\n * ```\n *\n * @example\n * ```typescript\n * systemUTM.toFormat('LATLON', [40.7128, -74.0060]);\n * // '18N 585628 4511644'\n * ```\n */\nexport const systemUTM: CoordinateSystem = {\n name: 'Universal Transverse Mercator',\n\n parse: parseUTM,\n\n toFloat(arg) {\n const [num, bear] = arg as [string, Compass];\n\n return (\n Number.parseFloat(num) *\n (SYMBOL_PATTERNS.NEGATIVE_BEARINGS.test(bear) ? -1 : 1)\n );\n },\n\n toFormat(format: Format, [left, right]: [number, number]) {\n const { LAT, LON } = Object.fromEntries([\n [format.slice(0, 3), left],\n [format.slice(3), right],\n ]) as Record<'LAT' | 'LON', number>;\n\n const result = toUtmParts([LAT, LON]);\n\n if (!result.ok) {\n // The legacy `toFormat` contract throws for coordinates the grid cannot\n // represent; the parts API returns `{ ok: false }` for the same inputs.\n throw new RangeError(\n `Coordinate [${LAT}, ${LON}] cannot be represented in UTM (outside 80°S–84°N, on the +180° antimeridian, or not finite).`,\n );\n }\n\n return formatUtmParts(result.value);\n },\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsCA,MAAaA,YAA8B;CACzC,MAAM;CAEN,OAAO;CAEP,QAAQ,KAAK;EACX,MAAM,CAAC,KAAK,QAAQ;AAEpB,SACE,OAAO,WAAW,IAAI,IACrB,gBAAgB,kBAAkB,KAAK,KAAK,GAAG,KAAK;;CAIzD,SAAS,QAAgB,CAAC,MAAM,QAA0B;EACxD,MAAM,EAAE,KAAK,QAAQ,OAAO,YAAY,CACtC,CAAC,OAAO,MAAM,GAAG,EAAE,EAAE,KAAK,EAC1B,CAAC,OAAO,MAAM,EAAE,EAAE,MAAM,CACzB,CAAC;EAEF,MAAM,SAAS,WAAW,CAAC,KAAK,IAAI,CAAC;AAErC,MAAI,CAAC,OAAO,GAGV,OAAM,IAAI,WACR,eAAe,IAAI,IAAI,IAAI,+FAC5B;AAGH,SAAO,eAAe,OAAO,MAAM;;CAEtC"}