@accelint/geo 0.5.1 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (113) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +233 -1
  3. package/catalog-info.yaml +1 -1
  4. package/dist/cartesian.d.ts +2 -0
  5. package/dist/cartesian.js +3 -1
  6. package/dist/cartesian.js.map +1 -1
  7. package/dist/coordinates/coordinate.d.ts +42 -8
  8. package/dist/coordinates/coordinate.js +103 -13
  9. package/dist/coordinates/coordinate.js.map +1 -1
  10. package/dist/coordinates/latlon/decimal-degrees/formatter.d.ts +21 -1
  11. package/dist/coordinates/latlon/decimal-degrees/formatter.js +39 -1
  12. package/dist/coordinates/latlon/decimal-degrees/formatter.js.map +1 -1
  13. package/dist/coordinates/latlon/decimal-degrees/parser.d.ts +69 -3
  14. package/dist/coordinates/latlon/decimal-degrees/parser.js +67 -6
  15. package/dist/coordinates/latlon/decimal-degrees/parser.js.map +1 -1
  16. package/dist/coordinates/latlon/decimal-degrees/system.d.ts +33 -1
  17. package/dist/coordinates/latlon/decimal-degrees/system.js +32 -1
  18. package/dist/coordinates/latlon/decimal-degrees/system.js.map +1 -1
  19. package/dist/coordinates/latlon/degrees-decimal-minutes/formatter.d.ts +21 -1
  20. package/dist/coordinates/latlon/degrees-decimal-minutes/formatter.js +43 -3
  21. package/dist/coordinates/latlon/degrees-decimal-minutes/formatter.js.map +1 -1
  22. package/dist/coordinates/latlon/degrees-decimal-minutes/parser.d.ts +73 -3
  23. package/dist/coordinates/latlon/degrees-decimal-minutes/parser.js +67 -4
  24. package/dist/coordinates/latlon/degrees-decimal-minutes/parser.js.map +1 -1
  25. package/dist/coordinates/latlon/degrees-decimal-minutes/system.d.ts +33 -1
  26. package/dist/coordinates/latlon/degrees-decimal-minutes/system.js +32 -1
  27. package/dist/coordinates/latlon/degrees-decimal-minutes/system.js.map +1 -1
  28. package/dist/coordinates/latlon/degrees-minutes-seconds/formatter.d.ts +21 -1
  29. package/dist/coordinates/latlon/degrees-minutes-seconds/formatter.js +46 -4
  30. package/dist/coordinates/latlon/degrees-minutes-seconds/formatter.js.map +1 -1
  31. package/dist/coordinates/latlon/degrees-minutes-seconds/parser.d.ts +75 -3
  32. package/dist/coordinates/latlon/degrees-minutes-seconds/parser.js +67 -4
  33. package/dist/coordinates/latlon/degrees-minutes-seconds/parser.js.map +1 -1
  34. package/dist/coordinates/latlon/degrees-minutes-seconds/system.d.ts +33 -1
  35. package/dist/coordinates/latlon/degrees-minutes-seconds/system.js +32 -1
  36. package/dist/coordinates/latlon/degrees-minutes-seconds/system.js.map +1 -1
  37. package/dist/coordinates/latlon/internal/coordinate-system.d.ts +23 -1
  38. package/dist/coordinates/latlon/internal/coordinate-system.js +1 -1
  39. package/dist/coordinates/latlon/internal/create-cache.d.ts +18 -2
  40. package/dist/coordinates/latlon/internal/create-cache.js +20 -4
  41. package/dist/coordinates/latlon/internal/create-cache.js.map +1 -1
  42. package/dist/coordinates/latlon/internal/exhaustive-errors.d.ts +15 -0
  43. package/dist/coordinates/latlon/internal/exhaustive-errors.js +29 -1
  44. package/dist/coordinates/latlon/internal/exhaustive-errors.js.map +1 -1
  45. package/dist/coordinates/latlon/internal/format.d.ts +20 -0
  46. package/dist/coordinates/latlon/internal/format.js +21 -1
  47. package/dist/coordinates/latlon/internal/format.js.map +1 -1
  48. package/dist/coordinates/latlon/internal/in-range.d.ts +23 -0
  49. package/dist/coordinates/latlon/internal/in-range.js +25 -1
  50. package/dist/coordinates/latlon/internal/in-range.js.map +1 -1
  51. package/dist/coordinates/latlon/internal/index.d.ts +16 -1
  52. package/dist/coordinates/latlon/internal/index.js +26 -2
  53. package/dist/coordinates/latlon/internal/index.js.map +1 -1
  54. package/dist/coordinates/latlon/internal/lexer.d.ts +2 -0
  55. package/dist/coordinates/latlon/internal/lexer.js +27 -1
  56. package/dist/coordinates/latlon/internal/lexer.js.map +1 -1
  57. package/dist/coordinates/latlon/internal/normalize.d.ts +67 -0
  58. package/dist/coordinates/latlon/internal/normalize.js +87 -0
  59. package/dist/coordinates/latlon/internal/normalize.js.map +1 -0
  60. package/dist/coordinates/latlon/internal/ordinal.d.ts +25 -0
  61. package/dist/coordinates/latlon/internal/ordinal.js +26 -1
  62. package/dist/coordinates/latlon/internal/ordinal.js.map +1 -1
  63. package/dist/coordinates/latlon/internal/parse-format.d.ts +23 -1
  64. package/dist/coordinates/latlon/internal/parse-format.js +44 -2
  65. package/dist/coordinates/latlon/internal/parse-format.js.map +1 -1
  66. package/dist/coordinates/latlon/internal/parse.d.ts +3 -1
  67. package/dist/coordinates/latlon/internal/parse.js +5 -2
  68. package/dist/coordinates/latlon/internal/parse.js.map +1 -1
  69. package/dist/coordinates/latlon/internal/pipes/check-ambiguous.d.ts +18 -1
  70. package/dist/coordinates/latlon/internal/pipes/check-ambiguous.js +17 -1
  71. package/dist/coordinates/latlon/internal/pipes/check-ambiguous.js.map +1 -1
  72. package/dist/coordinates/latlon/internal/pipes/check-numbers.d.ts +26 -1
  73. package/dist/coordinates/latlon/internal/pipes/check-numbers.js +34 -1
  74. package/dist/coordinates/latlon/internal/pipes/check-numbers.js.map +1 -1
  75. package/dist/coordinates/latlon/internal/pipes/fix-bearings.d.ts +18 -1
  76. package/dist/coordinates/latlon/internal/pipes/fix-bearings.js +32 -1
  77. package/dist/coordinates/latlon/internal/pipes/fix-bearings.js.map +1 -1
  78. package/dist/coordinates/latlon/internal/pipes/fix-dividers.d.ts +18 -1
  79. package/dist/coordinates/latlon/internal/pipes/fix-dividers.js +30 -1
  80. package/dist/coordinates/latlon/internal/pipes/fix-dividers.js.map +1 -1
  81. package/dist/coordinates/latlon/internal/pipes/genome.d.ts +17 -1
  82. package/dist/coordinates/latlon/internal/pipes/genome.js +42 -1
  83. package/dist/coordinates/latlon/internal/pipes/genome.js.map +1 -1
  84. package/dist/coordinates/latlon/internal/pipes/index.d.ts +33 -3
  85. package/dist/coordinates/latlon/internal/pipes/index.js +58 -5
  86. package/dist/coordinates/latlon/internal/pipes/index.js.map +1 -1
  87. package/dist/coordinates/latlon/internal/pipes/simpler.d.ts +17 -4
  88. package/dist/coordinates/latlon/internal/pipes/simpler.js +16 -4
  89. package/dist/coordinates/latlon/internal/pipes/simpler.js.map +1 -1
  90. package/dist/coordinates/latlon/internal/validate.d.ts +75 -0
  91. package/dist/coordinates/latlon/internal/validate.js +105 -0
  92. package/dist/coordinates/latlon/internal/validate.js.map +1 -0
  93. package/dist/coordinates/latlon/internal/violation.d.ts +18 -0
  94. package/dist/coordinates/latlon/internal/violation.js +19 -1
  95. package/dist/coordinates/latlon/internal/violation.js.map +1 -1
  96. package/dist/coordinates/mgrs/parser.d.ts +25 -1
  97. package/dist/coordinates/mgrs/parser.js +57 -1
  98. package/dist/coordinates/mgrs/parser.js.map +1 -1
  99. package/dist/coordinates/mgrs/system.d.ts +32 -1
  100. package/dist/coordinates/mgrs/system.js +31 -1
  101. package/dist/coordinates/mgrs/system.js.map +1 -1
  102. package/dist/coordinates/utm/parser.d.ts +25 -1
  103. package/dist/coordinates/utm/parser.js +57 -1
  104. package/dist/coordinates/utm/parser.js.map +1 -1
  105. package/dist/coordinates/utm/system.d.ts +22 -1
  106. package/dist/coordinates/utm/system.js +21 -1
  107. package/dist/coordinates/utm/system.js.map +1 -1
  108. package/dist/index.d.ts +4 -2
  109. package/dist/index.js +4 -2
  110. package/dist/patterning.d.ts +12 -2
  111. package/dist/patterning.js +13 -3
  112. package/dist/patterning.js.map +1 -1
  113. package/package.json +5 -3
@@ -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
@@ -19,7 +19,9 @@ import { inRange } from "../internal/in-range.js";
19
19
  //#region src/coordinates/latlon/degrees-decimal-minutes/parser.ts
20
20
  const checks = {
21
21
  deg: (deg, limit) => {
22
- if (Number.parseFloat(deg) > limit) return `Degrees value (${deg}) exceeds max value (${limit}).`;
22
+ const num = Number.parseFloat(deg);
23
+ if (Number.isNaN(num)) return `Degrees value (${deg}) is not a valid number.`;
24
+ if (num > limit) return `Degrees value (${deg}) exceeds max value (${limit}).`;
23
25
  if (/\./.test(deg)) return `Degrees value (${deg}) must not include decimal value.`;
24
26
  },
25
27
  min: (min) => inRange("Minutes", min, 59.999999999),
@@ -31,6 +33,22 @@ const formats = {
31
33
  LATLON: fromTemplate(PARTIAL_PATTERNS, `degLat minDec NS ${SYMBOLS.DIVIDER} degLon minDec EW`),
32
34
  LONLAT: fromTemplate(PARTIAL_PATTERNS, `degLon minDec EW ${SYMBOLS.DIVIDER} degLat minDec NS`)
33
35
  };
36
+ /**
37
+ * Creates an error identification function for degrees decimal minutes coordinates.
38
+ *
39
+ * Validates that degree values are integers, minutes are in valid range (0-59.999...),
40
+ * and that seconds indicators are not present.
41
+ *
42
+ * @param format - The coordinate format (LATLON or LONLAT).
43
+ * @returns Function that validates coordinate components and returns parse results.
44
+ *
45
+ * @example
46
+ * ```typescript
47
+ * const validator = identifyErrors('LATLON');
48
+ * const result = validator({ deg: '91', min: '0', bear: 'N' }, 0);
49
+ * // [[], ['Degrees value (91) exceeds max value (90).']]
50
+ * ```
51
+ */
34
52
  function identifyErrors(format) {
35
53
  return (arg, i) => {
36
54
  if (!arg) return [[], ["Invalid coordinate value."]];
@@ -60,6 +78,27 @@ function identifyErrors(format) {
60
78
  ], []];
61
79
  };
62
80
  }
81
+ /**
82
+ * Identifies and organizes coordinate components from parsed tokens.
83
+ *
84
+ * Separates bearing indicators (N/S/E/W), degree values, and minute values from tokens.
85
+ * Uses symbol patterns to identify component types and positional logic as fallback.
86
+ *
87
+ * @param half - Array of tokens representing one coordinate component.
88
+ * @returns Object with `bear`, `deg`, and `min` properties, or undefined if invalid.
89
+ *
90
+ * @example
91
+ * ```typescript
92
+ * identifyPieces(['45', '30.5', 'N']);
93
+ * // { bear: 'N', deg: '45', min: '30.5' }
94
+ * ```
95
+ *
96
+ * @example
97
+ * ```typescript
98
+ * identifyPieces(['122°', '25.164'', 'W']);
99
+ * // { bear: 'W', deg: '122', min: '25.164' }
100
+ * ```
101
+ */
63
102
  function identifyPieces(half) {
64
103
  if (half.length < 1 || half.length > 3) return;
65
104
  const asString = half.join(" ");
@@ -71,17 +110,41 @@ function identifyPieces(half) {
71
110
  const keys = ["deg", "min"];
72
111
  const test = (r, b, v) => r.test(v) || r.test(asString) && b;
73
112
  return half.reduce((acc, token, i, { length }) => {
113
+ if (!acc) return;
74
114
  if (test(SYMBOL_PATTERNS.NSEW, i === length - 1, token)) acc.bear ||= token;
75
115
  else if (test(SYMBOL_PATTERNS.DEGREES, i === 0, token)) acc.deg ||= token;
76
116
  else if (test(SYMBOL_PATTERNS.MINUTES, i === 1, token)) acc.min ||= token;
77
117
  else {
78
118
  const key = keys.find((k) => !acc[k]);
119
+ if (!key) return;
79
120
  acc[key] = token;
80
121
  }
81
122
  return acc;
82
123
  }, places);
83
124
  }
84
- /** Parse a Degrees Decimal Minutes coordinate. */
125
+ /**
126
+ * Parses a Degrees Decimal Minutes coordinate string.
127
+ *
128
+ * Accepts coordinates in degrees decimal minutes format with bearing indicators.
129
+ * Validates that degrees are integers, minutes are in valid range (0-59.999...),
130
+ * and ensures no seconds indicators are present.
131
+ *
132
+ * @param input - Raw coordinate string to parse.
133
+ * @param format - Expected format (LATLON or LONLAT).
134
+ * @returns Parsed coordinate values or errors.
135
+ *
136
+ * @example
137
+ * ```typescript
138
+ * parseDegreesDecimalMinutes('37° 46.4940' N / 122° 25.1640' W', 'LATLON');
139
+ * // [[37.7749, -122.4194], []]
140
+ * ```
141
+ *
142
+ * @example
143
+ * ```typescript
144
+ * parseDegreesDecimalMinutes('45 30.5 N, 122 15.3 W');
145
+ * // [[45.508333, -122.255], []]
146
+ * ```
147
+ */
85
148
  const parseDegreesDecimalMinutes = createParser({
86
149
  formats,
87
150
  identifyErrors,
@@ -89,5 +152,5 @@ const parseDegreesDecimalMinutes = createParser({
89
152
  });
90
153
 
91
154
  //#endregion
92
- export { parseDegreesDecimalMinutes };
155
+ export { identifyErrors as _identifyErrors, identifyPieces as _identifyPieces, parseDegreesDecimalMinutes };
93
156
  //# sourceMappingURL=parser.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"parser.js","names":["Patterning.fromTemplate","isNegative: 0 | 1"],"sources":["../../../../src/coordinates/latlon/degrees-decimal-minutes/parser.ts"],"sourcesContent":["/*\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 * as Patterning from '@/patterning';\nimport {\n BEARINGS,\n type Format,\n LIMITS,\n PARTIAL_PATTERNS,\n SYMBOL_PATTERNS,\n SYMBOLS,\n} from '../internal';\nimport { inRange } from '../internal/in-range';\nimport { createParser } from '../internal/parse-format';\nimport type { ParseResults } from '../internal/parse';\n\ntype DegreesDecimalMinutes = {\n bear: string;\n deg: string;\n min: string;\n};\n\nconst checks = {\n deg: (deg: string, limit: number) => {\n if (Number.parseFloat(deg) > limit) {\n return `Degrees value (${deg}) exceeds max value (${limit}).`;\n }\n\n if (/\\./.test(deg)) {\n return `Degrees value (${deg}) must not include decimal value.`;\n }\n },\n min: (min: string) => inRange('Minutes', min, 59.999999999),\n sec: (val: string) => {\n if (val.includes(SYMBOLS.SECONDS)) {\n return `Seconds indicator (${SYMBOLS.SECONDS}) not valid in Degree Decimal Minutes.`;\n }\n },\n};\n\nconst formats = {\n LATLON: Patterning.fromTemplate(\n PARTIAL_PATTERNS,\n `degLat minDec NS ${SYMBOLS.DIVIDER} degLon minDec EW`,\n ),\n LONLAT: Patterning.fromTemplate(\n PARTIAL_PATTERNS,\n `degLon minDec EW ${SYMBOLS.DIVIDER} degLat minDec NS`,\n ),\n};\n\nfunction identifyErrors(format: Format) {\n return (arg: DegreesDecimalMinutes | undefined, i: number) => {\n if (!arg) {\n return [[], ['Invalid coordinate value.']] as ParseResults;\n }\n\n let { bear, deg, min } = arg;\n\n deg ??= '0';\n min ??= '0';\n\n let isNegative: 0 | 1 = SYMBOL_PATTERNS.NEGATIVE_SIGN.test(deg) ? 1 : 0;\n const bearingOptions = BEARINGS[format][i] as [string, string];\n\n if (!bear || isNegative) {\n bear = bearingOptions[isNegative];\n deg = Math.abs(Number.parseFloat(deg)).toString();\n isNegative = 0;\n }\n\n const errors = [\n ...[checks.deg(deg, LIMITS[format][i] ?? 0)],\n ...[checks.min(min)],\n ...[checks.sec([bear, deg, min].join(''))],\n ].filter(Boolean);\n\n return (\n errors.length ? [[], errors] : [[deg, min, bear], []]\n ) as ParseResults;\n };\n}\n\nfunction identifyPieces(half: string[]) {\n if (half.length < 1 || half.length > 3) {\n return;\n }\n\n const asString = half.join(' ');\n const places = { bear: '', deg: '', min: '' };\n const keys = ['deg', 'min'] as (keyof typeof places)[];\n const test = (r: RegExp, b: boolean, v: string) =>\n r.test(v) || (r.test(asString) && b);\n\n return half.reduce((acc, token, i, { length }) => {\n if (test(SYMBOL_PATTERNS.NSEW, i === length - 1, token)) {\n acc.bear ||= token;\n } else if (test(SYMBOL_PATTERNS.DEGREES, i === 0, token)) {\n acc.deg ||= token;\n } else if (test(SYMBOL_PATTERNS.MINUTES, i === 1, token)) {\n acc.min ||= token;\n } else {\n const key = keys.find((k) => !acc[k]);\n\n acc[key as keyof typeof acc] = token;\n }\n\n return acc;\n }, places);\n}\n\n/** Parse a Degrees Decimal Minutes coordinate. */\nexport const parseDegreesDecimalMinutes = createParser<DegreesDecimalMinutes>({\n formats,\n identifyErrors,\n identifyPieces,\n});\n"],"mappings":";;;;;;;;;;;;;;;;;;;AA+BA,MAAM,SAAS;CACb,MAAM,KAAa,UAAkB;AACnC,MAAI,OAAO,WAAW,IAAI,GAAG,MAC3B,QAAO,kBAAkB,IAAI,uBAAuB,MAAM;AAG5D,MAAI,KAAK,KAAK,IAAI,CAChB,QAAO,kBAAkB,IAAI;;CAGjC,MAAM,QAAgB,QAAQ,WAAW,KAAK,aAAa;CAC3D,MAAM,QAAgB;AACpB,MAAI,IAAI,SAAS,QAAQ,QAAQ,CAC/B,QAAO,sBAAsB,QAAQ,QAAQ;;CAGlD;AAED,MAAM,UAAU;CACd,QAAQA,aACN,kBACA,oBAAoB,QAAQ,QAAQ,mBACrC;CACD,QAAQA,aACN,kBACA,oBAAoB,QAAQ,QAAQ,mBACrC;CACF;AAED,SAAS,eAAe,QAAgB;AACtC,SAAQ,KAAwC,MAAc;AAC5D,MAAI,CAAC,IACH,QAAO,CAAC,EAAE,EAAE,CAAC,4BAA4B,CAAC;EAG5C,IAAI,EAAE,MAAM,KAAK,QAAQ;AAEzB,UAAQ;AACR,UAAQ;EAER,IAAIC,aAAoB,gBAAgB,cAAc,KAAK,IAAI,GAAG,IAAI;EACtE,MAAM,iBAAiB,SAAS,QAAQ;AAExC,MAAI,CAAC,QAAQ,YAAY;AACvB,UAAO,eAAe;AACtB,SAAM,KAAK,IAAI,OAAO,WAAW,IAAI,CAAC,CAAC,UAAU;AACjD,gBAAa;;EAGf,MAAM,SAAS;GACb,GAAG,CAAC,OAAO,IAAI,KAAK,OAAO,QAAQ,MAAM,EAAE,CAAC;GAC5C,GAAG,CAAC,OAAO,IAAI,IAAI,CAAC;GACpB,GAAG,CAAC,OAAO,IAAI;IAAC;IAAM;IAAK;IAAI,CAAC,KAAK,GAAG,CAAC,CAAC;GAC3C,CAAC,OAAO,QAAQ;AAEjB,SACE,OAAO,SAAS,CAAC,EAAE,EAAE,OAAO,GAAG,CAAC;GAAC;GAAK;GAAK;GAAK,EAAE,EAAE,CAAC;;;AAK3D,SAAS,eAAe,MAAgB;AACtC,KAAI,KAAK,SAAS,KAAK,KAAK,SAAS,EACnC;CAGF,MAAM,WAAW,KAAK,KAAK,IAAI;CAC/B,MAAM,SAAS;EAAE,MAAM;EAAI,KAAK;EAAI,KAAK;EAAI;CAC7C,MAAM,OAAO,CAAC,OAAO,MAAM;CAC3B,MAAM,QAAQ,GAAW,GAAY,MACnC,EAAE,KAAK,EAAE,IAAK,EAAE,KAAK,SAAS,IAAI;AAEpC,QAAO,KAAK,QAAQ,KAAK,OAAO,GAAG,EAAE,aAAa;AAChD,MAAI,KAAK,gBAAgB,MAAM,MAAM,SAAS,GAAG,MAAM,CACrD,KAAI,SAAS;WACJ,KAAK,gBAAgB,SAAS,MAAM,GAAG,MAAM,CACtD,KAAI,QAAQ;WACH,KAAK,gBAAgB,SAAS,MAAM,GAAG,MAAM,CACtD,KAAI,QAAQ;OACP;GACL,MAAM,MAAM,KAAK,MAAM,MAAM,CAAC,IAAI,GAAG;AAErC,OAAI,OAA2B;;AAGjC,SAAO;IACN,OAAO;;;AAIZ,MAAa,6BAA6B,aAAoC;CAC5E;CACA;CACA;CACD,CAAC"}
1
+ {"version":3,"file":"parser.js","names":["Patterning.fromTemplate","isNegative: 0 | 1"],"sources":["../../../../src/coordinates/latlon/degrees-decimal-minutes/parser.ts"],"sourcesContent":["/*\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 * as Patterning from '@/patterning';\nimport {\n BEARINGS,\n type Format,\n LIMITS,\n PARTIAL_PATTERNS,\n SYMBOL_PATTERNS,\n SYMBOLS,\n} from '../internal';\nimport { inRange } from '../internal/in-range';\nimport { createParser } from '../internal/parse-format';\nimport type { ParseResults } from '../internal/parse';\n\ntype DegreesDecimalMinutes = {\n bear: string;\n deg: string;\n min: string;\n};\n\nconst checks = {\n deg: (deg: string, limit: number) => {\n const num = Number.parseFloat(deg);\n if (Number.isNaN(num)) {\n return `Degrees value (${deg}) is not a valid number.`;\n }\n if (num > limit) {\n return `Degrees value (${deg}) exceeds max value (${limit}).`;\n }\n\n if (/\\./.test(deg)) {\n return `Degrees value (${deg}) must not include decimal value.`;\n }\n },\n min: (min: string) => inRange('Minutes', min, 59.999999999),\n sec: (val: string) => {\n if (val.includes(SYMBOLS.SECONDS)) {\n return `Seconds indicator (${SYMBOLS.SECONDS}) not valid in Degree Decimal Minutes.`;\n }\n },\n};\n\nconst formats = {\n LATLON: Patterning.fromTemplate(\n PARTIAL_PATTERNS,\n `degLat minDec NS ${SYMBOLS.DIVIDER} degLon minDec EW`,\n ),\n LONLAT: Patterning.fromTemplate(\n PARTIAL_PATTERNS,\n `degLon minDec EW ${SYMBOLS.DIVIDER} degLat minDec NS`,\n ),\n};\n\n/**\n * Creates an error identification function for degrees decimal minutes coordinates.\n *\n * Validates that degree values are integers, minutes are in valid range (0-59.999...),\n * and that seconds indicators are not present.\n *\n * @param format - The coordinate format (LATLON or LONLAT).\n * @returns Function that validates coordinate components and returns parse results.\n *\n * @example\n * ```typescript\n * const validator = identifyErrors('LATLON');\n * const result = validator({ deg: '91', min: '0', bear: 'N' }, 0);\n * // [[], ['Degrees value (91) exceeds max value (90).']]\n * ```\n */\nfunction identifyErrors(format: Format) {\n return (arg: DegreesDecimalMinutes | undefined, i: number) => {\n if (!arg) {\n return [[], ['Invalid coordinate value.']] as ParseResults;\n }\n\n let { bear, deg, min } = arg;\n\n deg ??= '0';\n min ??= '0';\n\n let isNegative: 0 | 1 = SYMBOL_PATTERNS.NEGATIVE_SIGN.test(deg) ? 1 : 0;\n const bearingOptions = BEARINGS[format][i] as [string, string];\n\n if (!bear || isNegative) {\n bear = bearingOptions[isNegative];\n deg = Math.abs(Number.parseFloat(deg)).toString();\n isNegative = 0;\n }\n\n const errors = [\n ...[checks.deg(deg, LIMITS[format][i] ?? 0)],\n ...[checks.min(min)],\n ...[checks.sec([bear, deg, min].join(''))],\n ].filter(Boolean);\n\n return (\n errors.length ? [[], errors] : [[deg, min, bear], []]\n ) as ParseResults;\n };\n}\n\n/**\n * Identifies and organizes coordinate components from parsed tokens.\n *\n * Separates bearing indicators (N/S/E/W), degree values, and minute values from tokens.\n * Uses symbol patterns to identify component types and positional logic as fallback.\n *\n * @param half - Array of tokens representing one coordinate component.\n * @returns Object with `bear`, `deg`, and `min` properties, or undefined if invalid.\n *\n * @example\n * ```typescript\n * identifyPieces(['45', '30.5', 'N']);\n * // { bear: 'N', deg: '45', min: '30.5' }\n * ```\n *\n * @example\n * ```typescript\n * identifyPieces(['122°', '25.164'', 'W']);\n * // { bear: 'W', deg: '122', min: '25.164' }\n * ```\n */\nfunction identifyPieces(half: string[]) {\n if (half.length < 1 || half.length > 3) {\n return;\n }\n\n const asString = half.join(' ');\n const places = { bear: '', deg: '', min: '' };\n const keys = ['deg', 'min'] as (keyof typeof places)[];\n const test = (r: RegExp, b: boolean, v: string) =>\n r.test(v) || (r.test(asString) && b);\n\n return half.reduce<typeof places | undefined>((acc, token, i, { length }) => {\n if (!acc) {\n return undefined;\n }\n if (test(SYMBOL_PATTERNS.NSEW, i === length - 1, token)) {\n acc.bear ||= token;\n } else if (test(SYMBOL_PATTERNS.DEGREES, i === 0, token)) {\n acc.deg ||= token;\n } else if (test(SYMBOL_PATTERNS.MINUTES, i === 1, token)) {\n acc.min ||= token;\n } else {\n const key = keys.find((k) => !acc[k]);\n if (!key) {\n return undefined;\n }\n acc[key] = token;\n }\n\n return acc;\n }, places);\n}\n\n/**\n * Parses a Degrees Decimal Minutes coordinate string.\n *\n * Accepts coordinates in degrees decimal minutes format with bearing indicators.\n * Validates that degrees are integers, minutes are in valid range (0-59.999...),\n * and ensures no seconds indicators are present.\n *\n * @param input - Raw coordinate string to parse.\n * @param format - Expected format (LATLON or LONLAT).\n * @returns Parsed coordinate values or errors.\n *\n * @example\n * ```typescript\n * parseDegreesDecimalMinutes('37° 46.4940' N / 122° 25.1640' W', 'LATLON');\n * // [[37.7749, -122.4194], []]\n * ```\n *\n * @example\n * ```typescript\n * parseDegreesDecimalMinutes('45 30.5 N, 122 15.3 W');\n * // [[45.508333, -122.255], []]\n * ```\n */\nexport const parseDegreesDecimalMinutes = createParser<DegreesDecimalMinutes>({\n formats,\n identifyErrors,\n identifyPieces,\n});\n\nexport { identifyErrors as _identifyErrors, identifyPieces as _identifyPieces };\n"],"mappings":";;;;;;;;;;;;;;;;;;;AA+BA,MAAM,SAAS;CACb,MAAM,KAAa,UAAkB;EACnC,MAAM,MAAM,OAAO,WAAW,IAAI;AAClC,MAAI,OAAO,MAAM,IAAI,CACnB,QAAO,kBAAkB,IAAI;AAE/B,MAAI,MAAM,MACR,QAAO,kBAAkB,IAAI,uBAAuB,MAAM;AAG5D,MAAI,KAAK,KAAK,IAAI,CAChB,QAAO,kBAAkB,IAAI;;CAGjC,MAAM,QAAgB,QAAQ,WAAW,KAAK,aAAa;CAC3D,MAAM,QAAgB;AACpB,MAAI,IAAI,SAAS,QAAQ,QAAQ,CAC/B,QAAO,sBAAsB,QAAQ,QAAQ;;CAGlD;AAED,MAAM,UAAU;CACd,QAAQA,aACN,kBACA,oBAAoB,QAAQ,QAAQ,mBACrC;CACD,QAAQA,aACN,kBACA,oBAAoB,QAAQ,QAAQ,mBACrC;CACF;;;;;;;;;;;;;;;;;AAkBD,SAAS,eAAe,QAAgB;AACtC,SAAQ,KAAwC,MAAc;AAC5D,MAAI,CAAC,IACH,QAAO,CAAC,EAAE,EAAE,CAAC,4BAA4B,CAAC;EAG5C,IAAI,EAAE,MAAM,KAAK,QAAQ;AAEzB,UAAQ;AACR,UAAQ;EAER,IAAIC,aAAoB,gBAAgB,cAAc,KAAK,IAAI,GAAG,IAAI;EACtE,MAAM,iBAAiB,SAAS,QAAQ;AAExC,MAAI,CAAC,QAAQ,YAAY;AACvB,UAAO,eAAe;AACtB,SAAM,KAAK,IAAI,OAAO,WAAW,IAAI,CAAC,CAAC,UAAU;AACjD,gBAAa;;EAGf,MAAM,SAAS;GACb,GAAG,CAAC,OAAO,IAAI,KAAK,OAAO,QAAQ,MAAM,EAAE,CAAC;GAC5C,GAAG,CAAC,OAAO,IAAI,IAAI,CAAC;GACpB,GAAG,CAAC,OAAO,IAAI;IAAC;IAAM;IAAK;IAAI,CAAC,KAAK,GAAG,CAAC,CAAC;GAC3C,CAAC,OAAO,QAAQ;AAEjB,SACE,OAAO,SAAS,CAAC,EAAE,EAAE,OAAO,GAAG,CAAC;GAAC;GAAK;GAAK;GAAK,EAAE,EAAE,CAAC;;;;;;;;;;;;;;;;;;;;;;;;AA0B3D,SAAS,eAAe,MAAgB;AACtC,KAAI,KAAK,SAAS,KAAK,KAAK,SAAS,EACnC;CAGF,MAAM,WAAW,KAAK,KAAK,IAAI;CAC/B,MAAM,SAAS;EAAE,MAAM;EAAI,KAAK;EAAI,KAAK;EAAI;CAC7C,MAAM,OAAO,CAAC,OAAO,MAAM;CAC3B,MAAM,QAAQ,GAAW,GAAY,MACnC,EAAE,KAAK,EAAE,IAAK,EAAE,KAAK,SAAS,IAAI;AAEpC,QAAO,KAAK,QAAmC,KAAK,OAAO,GAAG,EAAE,aAAa;AAC3E,MAAI,CAAC,IACH;AAEF,MAAI,KAAK,gBAAgB,MAAM,MAAM,SAAS,GAAG,MAAM,CACrD,KAAI,SAAS;WACJ,KAAK,gBAAgB,SAAS,MAAM,GAAG,MAAM,CACtD,KAAI,QAAQ;WACH,KAAK,gBAAgB,SAAS,MAAM,GAAG,MAAM,CACtD,KAAI,QAAQ;OACP;GACL,MAAM,MAAM,KAAK,MAAM,MAAM,CAAC,IAAI,GAAG;AACrC,OAAI,CAAC,IACH;AAEF,OAAI,OAAO;;AAGb,SAAO;IACN,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;AA0BZ,MAAa,6BAA6B,aAAoC;CAC5E;CACA;CACA;CACD,CAAC"}
@@ -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,6 +13,38 @@
13
13
  import { CoordinateSystem } from "../internal/coordinate-system.js";
14
14
 
15
15
  //#region src/coordinates/latlon/degrees-decimal-minutes/system.d.ts
16
+
17
+ /**
18
+ * Degrees Decimal Minutes coordinate system implementation.
19
+ *
20
+ * Provides parsing, conversion, and formatting for coordinates in degrees decimal minutes notation.
21
+ * Coordinates are expressed as integer degrees and decimal minutes (e.g., 37° 46.4940' N).
22
+ *
23
+ * @property name - Human-readable name of the coordinate system.
24
+ * @property parse - Parses degrees decimal minutes coordinate strings.
25
+ * @property toFloat - Converts parsed coordinate components to floating point numbers.
26
+ * @property toFormat - Formats numeric coordinates back to degrees decimal minutes string.
27
+ *
28
+ * @example
29
+ * ```typescript
30
+ * // Parse a coordinate string
31
+ * const [coords, errors] = systemDegreesDecimalMinutes.parse('37° 46.4940' N / 122° 25.1640' W', 'LATLON');
32
+ * ```
33
+ *
34
+ * @example
35
+ * ```typescript
36
+ * // Convert to float
37
+ * const lat = systemDegreesDecimalMinutes.toFloat(['37', '46.4940', 'N']);
38
+ * // 37.7749
39
+ * ```
40
+ *
41
+ * @example
42
+ * ```typescript
43
+ * // Format to string
44
+ * const formatted = systemDegreesDecimalMinutes.toFormat('LATLON', [37.7749, -122.4194]);
45
+ * // '37 46.494 N / 122 25.164 W'
46
+ * ```
47
+ */
16
48
  declare const systemDegreesDecimalMinutes: CoordinateSystem;
17
49
  //#endregion
18
50
  export { systemDegreesDecimalMinutes };
@@ -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
@@ -15,6 +15,37 @@ import { BEARINGS, SYMBOLS, SYMBOL_PATTERNS } from "../internal/index.js";
15
15
  import { parseDegreesDecimalMinutes } from "./parser.js";
16
16
 
17
17
  //#region src/coordinates/latlon/degrees-decimal-minutes/system.ts
18
+ /**
19
+ * Degrees Decimal Minutes coordinate system implementation.
20
+ *
21
+ * Provides parsing, conversion, and formatting for coordinates in degrees decimal minutes notation.
22
+ * Coordinates are expressed as integer degrees and decimal minutes (e.g., 37° 46.4940' N).
23
+ *
24
+ * @property name - Human-readable name of the coordinate system.
25
+ * @property parse - Parses degrees decimal minutes coordinate strings.
26
+ * @property toFloat - Converts parsed coordinate components to floating point numbers.
27
+ * @property toFormat - Formats numeric coordinates back to degrees decimal minutes string.
28
+ *
29
+ * @example
30
+ * ```typescript
31
+ * // Parse a coordinate string
32
+ * const [coords, errors] = systemDegreesDecimalMinutes.parse('37° 46.4940' N / 122° 25.1640' W', 'LATLON');
33
+ * ```
34
+ *
35
+ * @example
36
+ * ```typescript
37
+ * // Convert to float
38
+ * const lat = systemDegreesDecimalMinutes.toFloat(['37', '46.4940', 'N']);
39
+ * // 37.7749
40
+ * ```
41
+ *
42
+ * @example
43
+ * ```typescript
44
+ * // Format to string
45
+ * const formatted = systemDegreesDecimalMinutes.toFormat('LATLON', [37.7749, -122.4194]);
46
+ * // '37 46.494 N / 122 25.164 W'
47
+ * ```
48
+ */
18
49
  const systemDegreesDecimalMinutes = {
19
50
  name: "Degrees Decimal Minutes",
20
51
  parse: parseDegreesDecimalMinutes,
@@ -1 +1 @@
1
- {"version":3,"file":"system.js","names":["systemDegreesDecimalMinutes: CoordinateSystem"],"sources":["../../../../src/coordinates/latlon/degrees-decimal-minutes/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 {\n BEARINGS,\n type Compass,\n type Format,\n SYMBOL_PATTERNS,\n SYMBOLS,\n} from '../internal';\nimport { parseDegreesDecimalMinutes } from './parser';\nimport type { CoordinateSystem } from '../internal/coordinate-system';\n\nexport const systemDegreesDecimalMinutes: CoordinateSystem = {\n name: 'Degrees Decimal Minutes',\n\n parse: parseDegreesDecimalMinutes,\n\n toFloat(arg) {\n const [degrees, minutes, bear] = arg as [string, string, Compass];\n\n return Number.parseFloat(\n (\n (Number.parseFloat(degrees) + Number.parseFloat(minutes) / 60) *\n (SYMBOL_PATTERNS.NEGATIVE_BEARINGS.test(bear) ? -1 : 1)\n ).toFixed(9),\n );\n },\n\n toFormat(format: Format, [left, right]: [number, number]) {\n return [left, right]\n .map((num, index) => {\n const abs = Math.abs(num);\n const deg = Math.floor(abs);\n const min = Number.parseFloat(((abs - deg) * 60).toFixed(10));\n\n return `${deg} ${min} ${BEARINGS[format][index as 0 | 1][+(num < 0)]}`;\n })\n .join(` ${SYMBOLS.DIVIDER} `);\n },\n};\n"],"mappings":";;;;;;;;;;;;;;;;;AAuBA,MAAaA,8BAAgD;CAC3D,MAAM;CAEN,OAAO;CAEP,QAAQ,KAAK;EACX,MAAM,CAAC,SAAS,SAAS,QAAQ;AAEjC,SAAO,OAAO,aAET,OAAO,WAAW,QAAQ,GAAG,OAAO,WAAW,QAAQ,GAAG,OAC1D,gBAAgB,kBAAkB,KAAK,KAAK,GAAG,KAAK,IACrD,QAAQ,EAAE,CACb;;CAGH,SAAS,QAAgB,CAAC,MAAM,QAA0B;AACxD,SAAO,CAAC,MAAM,MAAM,CACjB,KAAK,KAAK,UAAU;GACnB,MAAM,MAAM,KAAK,IAAI,IAAI;GACzB,MAAM,MAAM,KAAK,MAAM,IAAI;AAG3B,UAAO,GAAG,IAAI,GAFF,OAAO,aAAa,MAAM,OAAO,IAAI,QAAQ,GAAG,CAAC,CAExC,GAAG,SAAS,QAAQ,OAAgB,EAAE,MAAM;IACjE,CACD,KAAK,IAAI,QAAQ,QAAQ,GAAG;;CAElC"}
1
+ {"version":3,"file":"system.js","names":["systemDegreesDecimalMinutes: CoordinateSystem"],"sources":["../../../../src/coordinates/latlon/degrees-decimal-minutes/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 {\n BEARINGS,\n type Compass,\n type Format,\n SYMBOL_PATTERNS,\n SYMBOLS,\n} from '../internal';\nimport { parseDegreesDecimalMinutes } from './parser';\nimport type { CoordinateSystem } from '../internal/coordinate-system';\n\n/**\n * Degrees Decimal Minutes coordinate system implementation.\n *\n * Provides parsing, conversion, and formatting for coordinates in degrees decimal minutes notation.\n * Coordinates are expressed as integer degrees and decimal minutes (e.g., 37° 46.4940' N).\n *\n * @property name - Human-readable name of the coordinate system.\n * @property parse - Parses degrees decimal minutes coordinate strings.\n * @property toFloat - Converts parsed coordinate components to floating point numbers.\n * @property toFormat - Formats numeric coordinates back to degrees decimal minutes string.\n *\n * @example\n * ```typescript\n * // Parse a coordinate string\n * const [coords, errors] = systemDegreesDecimalMinutes.parse('37° 46.4940' N / 122° 25.1640' W', 'LATLON');\n * ```\n *\n * @example\n * ```typescript\n * // Convert to float\n * const lat = systemDegreesDecimalMinutes.toFloat(['37', '46.4940', 'N']);\n * // 37.7749\n * ```\n *\n * @example\n * ```typescript\n * // Format to string\n * const formatted = systemDegreesDecimalMinutes.toFormat('LATLON', [37.7749, -122.4194]);\n * // '37 46.494 N / 122 25.164 W'\n * ```\n */\nexport const systemDegreesDecimalMinutes: CoordinateSystem = {\n name: 'Degrees Decimal Minutes',\n\n parse: parseDegreesDecimalMinutes,\n\n toFloat(arg) {\n const [degrees, minutes, bear] = arg as [string, string, Compass];\n\n return Number.parseFloat(\n (\n (Number.parseFloat(degrees) + Number.parseFloat(minutes) / 60) *\n (SYMBOL_PATTERNS.NEGATIVE_BEARINGS.test(bear) ? -1 : 1)\n ).toFixed(9),\n );\n },\n\n toFormat(format: Format, [left, right]: [number, number]) {\n return [left, right]\n .map((num, index) => {\n const abs = Math.abs(num);\n const deg = Math.floor(abs);\n const min = Number.parseFloat(((abs - deg) * 60).toFixed(10));\n\n return `${deg} ${min} ${BEARINGS[format][index as 0 | 1][+(num < 0)]}`;\n })\n .join(` ${SYMBOLS.DIVIDER} `);\n },\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsDA,MAAaA,8BAAgD;CAC3D,MAAM;CAEN,OAAO;CAEP,QAAQ,KAAK;EACX,MAAM,CAAC,SAAS,SAAS,QAAQ;AAEjC,SAAO,OAAO,aAET,OAAO,WAAW,QAAQ,GAAG,OAAO,WAAW,QAAQ,GAAG,OAC1D,gBAAgB,kBAAkB,KAAK,KAAK,GAAG,KAAK,IACrD,QAAQ,EAAE,CACb;;CAGH,SAAS,QAAgB,CAAC,MAAM,QAA0B;AACxD,SAAO,CAAC,MAAM,MAAM,CACjB,KAAK,KAAK,UAAU;GACnB,MAAM,MAAM,KAAK,IAAI,IAAI;GACzB,MAAM,MAAM,KAAK,MAAM,IAAI;AAG3B,UAAO,GAAG,IAAI,GAFF,OAAO,aAAa,MAAM,OAAO,IAAI,QAAQ,GAAG,CAAC,CAExC,GAAG,SAAS,QAAQ,OAAgB,EAAE,MAAM;IACjE,CACD,KAAK,IAAI,QAAQ,QAAQ,GAAG;;CAElC"}
@@ -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,6 +13,26 @@
13
13
  import { FormatOptions } from "../internal/format.js";
14
14
 
15
15
  //#region src/coordinates/latlon/degrees-minutes-seconds/formatter.d.ts
16
+
17
+ /**
18
+ * Formats latitude/longitude coordinates in degrees minutes seconds notation.
19
+ *
20
+ * @param coordinates - Tuple of [latitude, longitude] values.
21
+ * @param config - Optional formatting configuration.
22
+ * @returns Formatted coordinate string in degrees minutes seconds format.
23
+ *
24
+ * @example
25
+ * ```typescript
26
+ * formatDegreesMinutesSeconds([37.7749, -122.4194]);
27
+ * // '37° 46' 29.64″ N, 122° 25' 9.84″ W'
28
+ * ```
29
+ *
30
+ * @example
31
+ * ```typescript
32
+ * formatDegreesMinutesSeconds([37.7749, -122.4194], { separator: ' / ' });
33
+ * // '37° 46' 29.64″ N / 122° 25' 9.84″ W'
34
+ * ```
35
+ */
16
36
  declare const formatDegreesMinutesSeconds: (coordinates: [number, number], config?: FormatOptions) => string;
17
37
  //#endregion
18
38
  export { formatDegreesMinutesSeconds };
@@ -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
@@ -14,12 +14,54 @@
14
14
  import { createFormatter } from "../internal/format.js";
15
15
 
16
16
  //#region src/coordinates/latlon/degrees-minutes-seconds/formatter.ts
17
+ /**
18
+ * Converts a coordinate value to degrees minutes seconds format.
19
+ *
20
+ * @param num - The coordinate value to format.
21
+ * @returns Formatted coordinate string with degrees, minutes, and seconds (e.g., "45° 30' 15.23″").
22
+ *
23
+ * @example
24
+ * ```typescript
25
+ * toDegreesMinutesSeconds(45.5042);
26
+ * // '45° 30' 15.12″'
27
+ * ```
28
+ *
29
+ * @example
30
+ * ```typescript
31
+ * toDegreesMinutesSeconds(-122.4194);
32
+ * // '122° 25' 9.84″'
33
+ * ```
34
+ */
17
35
  const toDegreesMinutesSeconds = (num) => {
18
- const degrees = Math.floor(Math.abs(num));
36
+ let degrees = Math.floor(Math.abs(num));
19
37
  const minutesFull = (Math.abs(num) - degrees) * 60;
20
- const minutes = Math.floor(minutesFull);
21
- return `${degrees}° ${minutes}' ${((minutesFull - minutes) * 60).toFixed(2)}″`;
38
+ let minutes = Math.floor(minutesFull);
39
+ let seconds = Number(((minutesFull - minutes) * 60).toFixed(2));
40
+ minutes += Math.floor(seconds / 60);
41
+ seconds %= 60;
42
+ degrees += Math.floor(minutes / 60);
43
+ minutes %= 60;
44
+ return `${degrees}° ${minutes}' ${seconds.toFixed(2)}″`;
22
45
  };
46
+ /**
47
+ * Formats latitude/longitude coordinates in degrees minutes seconds notation.
48
+ *
49
+ * @param coordinates - Tuple of [latitude, longitude] values.
50
+ * @param config - Optional formatting configuration.
51
+ * @returns Formatted coordinate string in degrees minutes seconds format.
52
+ *
53
+ * @example
54
+ * ```typescript
55
+ * formatDegreesMinutesSeconds([37.7749, -122.4194]);
56
+ * // '37° 46' 29.64″ N, 122° 25' 9.84″ W'
57
+ * ```
58
+ *
59
+ * @example
60
+ * ```typescript
61
+ * formatDegreesMinutesSeconds([37.7749, -122.4194], { separator: ' / ' });
62
+ * // '37° 46' 29.64″ N / 122° 25' 9.84″ W'
63
+ * ```
64
+ */
23
65
  const formatDegreesMinutesSeconds = createFormatter(toDegreesMinutesSeconds);
24
66
 
25
67
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"formatter.js","names":[],"sources":["../../../../src/coordinates/latlon/degrees-minutes-seconds/formatter.ts"],"sourcesContent":["/*\n * Copyright 2025 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 { createFormatter } from '../internal/format';\n\nconst toDegreesMinutesSeconds = (num: number): string => {\n const degrees = Math.floor(Math.abs(num));\n const minutesFull = (Math.abs(num) - degrees) * 60;\n const minutes = Math.floor(minutesFull);\n const seconds = ((minutesFull - minutes) * 60).toFixed(2);\n\n return `${degrees}° ${minutes}' ${seconds}″`;\n};\n\nexport const formatDegreesMinutesSeconds = createFormatter(\n toDegreesMinutesSeconds,\n);\n"],"mappings":";;;;;;;;;;;;;;;;AAcA,MAAM,2BAA2B,QAAwB;CACvD,MAAM,UAAU,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC;CACzC,MAAM,eAAe,KAAK,IAAI,IAAI,GAAG,WAAW;CAChD,MAAM,UAAU,KAAK,MAAM,YAAY;AAGvC,QAAO,GAAG,QAAQ,IAAI,QAAQ,MAFZ,cAAc,WAAW,IAAI,QAAQ,EAAE,CAEf;;AAG5C,MAAa,8BAA8B,gBACzC,wBACD"}
1
+ {"version":3,"file":"formatter.js","names":[],"sources":["../../../../src/coordinates/latlon/degrees-minutes-seconds/formatter.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 { createFormatter } from '../internal/format';\n\n/**\n * Converts a coordinate value to degrees minutes seconds format.\n *\n * @param num - The coordinate value to format.\n * @returns Formatted coordinate string with degrees, minutes, and seconds (e.g., \"45° 30' 15.23″\").\n *\n * @example\n * ```typescript\n * toDegreesMinutesSeconds(45.5042);\n * // '45° 30' 15.12″'\n * ```\n *\n * @example\n * ```typescript\n * toDegreesMinutesSeconds(-122.4194);\n * // '122° 25' 9.84″'\n * ```\n */\nconst toDegreesMinutesSeconds = (num: number): string => {\n let degrees = Math.floor(Math.abs(num));\n const minutesFull = (Math.abs(num) - degrees) * 60;\n let minutes = Math.floor(minutesFull);\n let seconds = Number(((minutesFull - minutes) * 60).toFixed(2));\n\n // Rounding can produce 60 seconds (e.g. 40.9999999 -> 40° 59' 60.00″);\n // carry into minutes (and degrees) so the output stays a valid coordinate.\n minutes += Math.floor(seconds / 60);\n seconds %= 60;\n degrees += Math.floor(minutes / 60);\n minutes %= 60;\n\n return `${degrees}° ${minutes}' ${seconds.toFixed(2)}″`;\n};\n\n/**\n * Formats latitude/longitude coordinates in degrees minutes seconds notation.\n *\n * @param coordinates - Tuple of [latitude, longitude] values.\n * @param config - Optional formatting configuration.\n * @returns Formatted coordinate string in degrees minutes seconds format.\n *\n * @example\n * ```typescript\n * formatDegreesMinutesSeconds([37.7749, -122.4194]);\n * // '37° 46' 29.64″ N, 122° 25' 9.84″ W'\n * ```\n *\n * @example\n * ```typescript\n * formatDegreesMinutesSeconds([37.7749, -122.4194], { separator: ' / ' });\n * // '37° 46' 29.64″ N / 122° 25' 9.84″ W'\n * ```\n */\nexport const formatDegreesMinutesSeconds = createFormatter(\n toDegreesMinutesSeconds,\n);\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgCA,MAAM,2BAA2B,QAAwB;CACvD,IAAI,UAAU,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC;CACvC,MAAM,eAAe,KAAK,IAAI,IAAI,GAAG,WAAW;CAChD,IAAI,UAAU,KAAK,MAAM,YAAY;CACrC,IAAI,UAAU,SAAS,cAAc,WAAW,IAAI,QAAQ,EAAE,CAAC;AAI/D,YAAW,KAAK,MAAM,UAAU,GAAG;AACnC,YAAW;AACX,YAAW,KAAK,MAAM,UAAU,GAAG;AACnC,YAAW;AAEX,QAAO,GAAG,QAAQ,IAAI,QAAQ,IAAI,QAAQ,QAAQ,EAAE,CAAC;;;;;;;;;;;;;;;;;;;;;AAsBvD,MAAa,8BAA8B,gBACzC,wBACD"}
@@ -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
@@ -14,8 +14,80 @@ import { Format } from "../internal/index.js";
14
14
  import { ParseResults } from "../internal/parse.js";
15
15
 
16
16
  //#region src/coordinates/latlon/degrees-minutes-seconds/parser.d.ts
17
- /** Parse a Degrees Minutes Seconds coordinate. */
17
+ type DegreesMinutesSeconds = {
18
+ bear: string;
19
+ deg: string;
20
+ min: string;
21
+ sec: string;
22
+ };
23
+ /**
24
+ * Creates an error identification function for degrees minutes seconds coordinates.
25
+ *
26
+ * Validates that degree values are integers, minutes are in range 0-59,
27
+ * seconds are in range 0-59.999..., and all components are properly formatted.
28
+ *
29
+ * @param format - The coordinate format (LATLON or LONLAT).
30
+ * @returns Function that validates coordinate components and returns parse results.
31
+ *
32
+ * @example
33
+ * ```typescript
34
+ * const validator = identifyErrors('LATLON');
35
+ * const result = validator({ deg: '91', min: '0', sec: '0', bear: 'N' }, 0);
36
+ * // [[], ['Degrees value (91) exceeds max value (90).']]
37
+ * ```
38
+ */
39
+ declare function identifyErrors(format: Format): (arg: DegreesMinutesSeconds | undefined, i: number) => ParseResults;
40
+ /**
41
+ * Identifies and organizes coordinate components from parsed tokens.
42
+ *
43
+ * Separates bearing indicators (N/S/E/W), degree values, minute values, and second values from tokens.
44
+ * Uses symbol patterns to identify component types and positional logic as fallback.
45
+ *
46
+ * @param half - Array of tokens representing one coordinate component.
47
+ * @returns Object with `bear`, `deg`, `min`, and `sec` properties, or undefined if invalid.
48
+ *
49
+ * @example
50
+ * ```typescript
51
+ * identifyPieces(['45', '30', '15.23', 'N']);
52
+ * // { bear: 'N', deg: '45', min: '30', sec: '15.23' }
53
+ * ```
54
+ *
55
+ * @example
56
+ * ```typescript
57
+ * identifyPieces(['122°', '25'', '9.84″', 'W']);
58
+ * // { bear: 'W', deg: '122', min: '25', sec: '9.84' }
59
+ * ```
60
+ */
61
+ declare function identifyPieces(half: string[]): {
62
+ bear: string;
63
+ deg: string;
64
+ min: string;
65
+ sec: string;
66
+ } | undefined;
67
+ /**
68
+ * Parses a Degrees Minutes Seconds coordinate string.
69
+ *
70
+ * Accepts coordinates in degrees minutes seconds format with bearing indicators.
71
+ * Validates that degrees are integers, minutes are in range 0-59,
72
+ * seconds are in range 0-59.999..., and all components are properly formatted.
73
+ *
74
+ * @param input - Raw coordinate string to parse.
75
+ * @param format - Expected format (LATLON or LONLAT).
76
+ * @returns Parsed coordinate values or errors.
77
+ *
78
+ * @example
79
+ * ```typescript
80
+ * parseDegreesMinutesSeconds('37° 46' 29.64″ N / 122° 25' 9.84″ W', 'LATLON');
81
+ * // [[37.7749, -122.4194], []]
82
+ * ```
83
+ *
84
+ * @example
85
+ * ```typescript
86
+ * parseDegreesMinutesSeconds('45 30 15.23 N, 122 25 9.84 W');
87
+ * // [[45.504231, -122.419400], []]
88
+ * ```
89
+ */
18
90
  declare const parseDegreesMinutesSeconds: (format: Format, input: string) => ParseResults;
19
91
  //#endregion
20
- export { parseDegreesMinutesSeconds };
92
+ export { identifyErrors as _identifyErrors, identifyPieces as _identifyPieces, parseDegreesMinutesSeconds };
21
93
  //# sourceMappingURL=parser.d.ts.map
@@ -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
@@ -19,7 +19,9 @@ import { inRange } from "../internal/in-range.js";
19
19
  //#region src/coordinates/latlon/degrees-minutes-seconds/parser.ts
20
20
  const checks = {
21
21
  deg: (deg, limit) => {
22
- if (Number.parseFloat(deg) > limit) return `Degrees value (${deg}) exceeds max value (${limit}).`;
22
+ const num = Number.parseFloat(deg);
23
+ if (Number.isNaN(num)) return `Degrees value (${deg}) is not a valid number.`;
24
+ if (num > limit) return `Degrees value (${deg}) exceeds max value (${limit}).`;
23
25
  if (/\./.test(deg)) return `Degrees value (${deg}) must not include decimal value.`;
24
26
  },
25
27
  min: (min) => inRange("Minutes", min, 59),
@@ -29,6 +31,22 @@ const formats = {
29
31
  LATLON: fromTemplate(PARTIAL_PATTERNS, `degLat min secDec NS ${SYMBOLS.DIVIDER} degLon min secDec EW`),
30
32
  LONLAT: fromTemplate(PARTIAL_PATTERNS, `degLon min secDec EW ${SYMBOLS.DIVIDER} degLat min secDec NS`)
31
33
  };
34
+ /**
35
+ * Creates an error identification function for degrees minutes seconds coordinates.
36
+ *
37
+ * Validates that degree values are integers, minutes are in range 0-59,
38
+ * seconds are in range 0-59.999..., and all components are properly formatted.
39
+ *
40
+ * @param format - The coordinate format (LATLON or LONLAT).
41
+ * @returns Function that validates coordinate components and returns parse results.
42
+ *
43
+ * @example
44
+ * ```typescript
45
+ * const validator = identifyErrors('LATLON');
46
+ * const result = validator({ deg: '91', min: '0', sec: '0', bear: 'N' }, 0);
47
+ * // [[], ['Degrees value (91) exceeds max value (90).']]
48
+ * ```
49
+ */
32
50
  function identifyErrors(format) {
33
51
  return (arg, i) => {
34
52
  if (!arg) return [[], ["Invalid coordinate value."]];
@@ -56,6 +74,27 @@ function identifyErrors(format) {
56
74
  ], []];
57
75
  };
58
76
  }
77
+ /**
78
+ * Identifies and organizes coordinate components from parsed tokens.
79
+ *
80
+ * Separates bearing indicators (N/S/E/W), degree values, minute values, and second values from tokens.
81
+ * Uses symbol patterns to identify component types and positional logic as fallback.
82
+ *
83
+ * @param half - Array of tokens representing one coordinate component.
84
+ * @returns Object with `bear`, `deg`, `min`, and `sec` properties, or undefined if invalid.
85
+ *
86
+ * @example
87
+ * ```typescript
88
+ * identifyPieces(['45', '30', '15.23', 'N']);
89
+ * // { bear: 'N', deg: '45', min: '30', sec: '15.23' }
90
+ * ```
91
+ *
92
+ * @example
93
+ * ```typescript
94
+ * identifyPieces(['122°', '25'', '9.84″', 'W']);
95
+ * // { bear: 'W', deg: '122', min: '25', sec: '9.84' }
96
+ * ```
97
+ */
59
98
  function identifyPieces(half) {
60
99
  if (half.length < 1 || half.length > 4) return;
61
100
  const asString = half.join(" ");
@@ -72,18 +111,42 @@ function identifyPieces(half) {
72
111
  ];
73
112
  const test = (r, b, v) => r.test(v) || r.test(asString) && b;
74
113
  return half.reduce((acc, token, i, { length }) => {
114
+ if (!acc) return;
75
115
  if (test(SYMBOL_PATTERNS.NSEW, i === length - 1, token)) acc.bear ||= token;
76
116
  else if (test(SYMBOL_PATTERNS.DEGREES, i === 0, token)) acc.deg ||= token;
77
117
  else if (test(SYMBOL_PATTERNS.MINUTES, i === 1, token)) acc.min ||= token;
78
118
  else if (test(SYMBOL_PATTERNS.SECONDS, i === 2, token)) acc.sec ||= token;
79
119
  else {
80
120
  const key = keys.find((k) => !acc[k]);
121
+ if (!key) return;
81
122
  acc[key] = token;
82
123
  }
83
124
  return acc;
84
125
  }, places);
85
126
  }
86
- /** Parse a Degrees Minutes Seconds coordinate. */
127
+ /**
128
+ * Parses a Degrees Minutes Seconds coordinate string.
129
+ *
130
+ * Accepts coordinates in degrees minutes seconds format with bearing indicators.
131
+ * Validates that degrees are integers, minutes are in range 0-59,
132
+ * seconds are in range 0-59.999..., and all components are properly formatted.
133
+ *
134
+ * @param input - Raw coordinate string to parse.
135
+ * @param format - Expected format (LATLON or LONLAT).
136
+ * @returns Parsed coordinate values or errors.
137
+ *
138
+ * @example
139
+ * ```typescript
140
+ * parseDegreesMinutesSeconds('37° 46' 29.64″ N / 122° 25' 9.84″ W', 'LATLON');
141
+ * // [[37.7749, -122.4194], []]
142
+ * ```
143
+ *
144
+ * @example
145
+ * ```typescript
146
+ * parseDegreesMinutesSeconds('45 30 15.23 N, 122 25 9.84 W');
147
+ * // [[45.504231, -122.419400], []]
148
+ * ```
149
+ */
87
150
  const parseDegreesMinutesSeconds = createParser({
88
151
  formats,
89
152
  identifyErrors,
@@ -91,5 +154,5 @@ const parseDegreesMinutesSeconds = createParser({
91
154
  });
92
155
 
93
156
  //#endregion
94
- export { parseDegreesMinutesSeconds };
157
+ export { identifyErrors as _identifyErrors, identifyPieces as _identifyPieces, parseDegreesMinutesSeconds };
95
158
  //# sourceMappingURL=parser.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"parser.js","names":["Patterning.fromTemplate","isNegative: 0 | 1"],"sources":["../../../../src/coordinates/latlon/degrees-minutes-seconds/parser.ts"],"sourcesContent":["/*\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 * as Patterning from '@/patterning';\nimport {\n BEARINGS,\n type Format,\n LIMITS,\n PARTIAL_PATTERNS,\n SYMBOL_PATTERNS,\n SYMBOLS,\n} from '../internal';\nimport { inRange } from '../internal/in-range';\nimport { createParser } from '../internal/parse-format';\nimport type { ParseResults } from '../internal/parse';\n\ntype DegreesMinutesSeconds = {\n bear: string;\n deg: string;\n min: string;\n sec: string;\n};\n\nconst checks = {\n deg: (deg: string, limit: number) => {\n if (Number.parseFloat(deg) > limit) {\n return `Degrees value (${deg}) exceeds max value (${limit}).`;\n }\n\n if (/\\./.test(deg)) {\n return `Degrees value (${deg}) must not include decimal value.`;\n }\n },\n min: (min: string) => inRange('Minutes', min, 59),\n sec: (sec: string) => inRange('Seconds', sec, 59.999999999),\n};\n\nconst formats = {\n LATLON: Patterning.fromTemplate(\n PARTIAL_PATTERNS,\n `degLat min secDec NS ${SYMBOLS.DIVIDER} degLon min secDec EW`,\n ),\n LONLAT: Patterning.fromTemplate(\n PARTIAL_PATTERNS,\n `degLon min secDec EW ${SYMBOLS.DIVIDER} degLat min secDec NS`,\n ),\n};\n\nfunction identifyErrors(format: Format) {\n return (arg: DegreesMinutesSeconds | undefined, i: number) => {\n if (!arg) {\n return [[], ['Invalid coordinate value.']] as ParseResults;\n }\n\n let { bear, deg, min, sec } = arg;\n\n deg ??= '0';\n // NOTE: need `||=` not `??=` because empty-string is not nullish\n min ||= '0';\n sec ||= '0';\n\n let isNegative: 0 | 1 = SYMBOL_PATTERNS.NEGATIVE_SIGN.test(deg) ? 1 : 0;\n const bearingOptions = BEARINGS[format][i] as [string, string];\n\n if (!bear || isNegative) {\n bear = bearingOptions[isNegative];\n deg = Math.abs(Number.parseFloat(deg)).toString();\n isNegative = 0;\n }\n\n const errors = [\n ...[checks.deg(deg, LIMITS[format][i] ?? 0)],\n ...[checks.min(min)],\n ...[checks.sec(sec)],\n ].filter(Boolean);\n\n return (\n errors.length ? [[], errors] : [[deg, min, sec, bear], []]\n ) as ParseResults;\n };\n}\n\nfunction identifyPieces(half: string[]) {\n if (half.length < 1 || half.length > 4) {\n return;\n }\n\n const asString = half.join(' ');\n const places = { bear: '', deg: '', min: '', sec: '' };\n const keys = ['deg', 'min', 'sec'] as (keyof typeof places)[];\n const test = (r: RegExp, b: boolean, v: string) =>\n r.test(v) || (r.test(asString) && b);\n\n return half.reduce((acc, token, i, { length }) => {\n if (test(SYMBOL_PATTERNS.NSEW, i === length - 1, token)) {\n acc.bear ||= token;\n } else if (test(SYMBOL_PATTERNS.DEGREES, i === 0, token)) {\n acc.deg ||= token;\n } else if (test(SYMBOL_PATTERNS.MINUTES, i === 1, token)) {\n acc.min ||= token;\n } else if (test(SYMBOL_PATTERNS.SECONDS, i === 2, token)) {\n acc.sec ||= token;\n } else {\n const key = keys.find((k) => !acc[k]);\n\n acc[key as keyof typeof acc] = token;\n }\n\n return acc;\n }, places);\n}\n\n/** Parse a Degrees Minutes Seconds coordinate. */\nexport const parseDegreesMinutesSeconds = createParser<DegreesMinutesSeconds>({\n formats,\n identifyErrors,\n identifyPieces,\n});\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAgCA,MAAM,SAAS;CACb,MAAM,KAAa,UAAkB;AACnC,MAAI,OAAO,WAAW,IAAI,GAAG,MAC3B,QAAO,kBAAkB,IAAI,uBAAuB,MAAM;AAG5D,MAAI,KAAK,KAAK,IAAI,CAChB,QAAO,kBAAkB,IAAI;;CAGjC,MAAM,QAAgB,QAAQ,WAAW,KAAK,GAAG;CACjD,MAAM,QAAgB,QAAQ,WAAW,KAAK,aAAa;CAC5D;AAED,MAAM,UAAU;CACd,QAAQA,aACN,kBACA,wBAAwB,QAAQ,QAAQ,uBACzC;CACD,QAAQA,aACN,kBACA,wBAAwB,QAAQ,QAAQ,uBACzC;CACF;AAED,SAAS,eAAe,QAAgB;AACtC,SAAQ,KAAwC,MAAc;AAC5D,MAAI,CAAC,IACH,QAAO,CAAC,EAAE,EAAE,CAAC,4BAA4B,CAAC;EAG5C,IAAI,EAAE,MAAM,KAAK,KAAK,QAAQ;AAE9B,UAAQ;AAER,UAAQ;AACR,UAAQ;EAER,IAAIC,aAAoB,gBAAgB,cAAc,KAAK,IAAI,GAAG,IAAI;EACtE,MAAM,iBAAiB,SAAS,QAAQ;AAExC,MAAI,CAAC,QAAQ,YAAY;AACvB,UAAO,eAAe;AACtB,SAAM,KAAK,IAAI,OAAO,WAAW,IAAI,CAAC,CAAC,UAAU;AACjD,gBAAa;;EAGf,MAAM,SAAS;GACb,GAAG,CAAC,OAAO,IAAI,KAAK,OAAO,QAAQ,MAAM,EAAE,CAAC;GAC5C,GAAG,CAAC,OAAO,IAAI,IAAI,CAAC;GACpB,GAAG,CAAC,OAAO,IAAI,IAAI,CAAC;GACrB,CAAC,OAAO,QAAQ;AAEjB,SACE,OAAO,SAAS,CAAC,EAAE,EAAE,OAAO,GAAG,CAAC;GAAC;GAAK;GAAK;GAAK;GAAK,EAAE,EAAE,CAAC;;;AAKhE,SAAS,eAAe,MAAgB;AACtC,KAAI,KAAK,SAAS,KAAK,KAAK,SAAS,EACnC;CAGF,MAAM,WAAW,KAAK,KAAK,IAAI;CAC/B,MAAM,SAAS;EAAE,MAAM;EAAI,KAAK;EAAI,KAAK;EAAI,KAAK;EAAI;CACtD,MAAM,OAAO;EAAC;EAAO;EAAO;EAAM;CAClC,MAAM,QAAQ,GAAW,GAAY,MACnC,EAAE,KAAK,EAAE,IAAK,EAAE,KAAK,SAAS,IAAI;AAEpC,QAAO,KAAK,QAAQ,KAAK,OAAO,GAAG,EAAE,aAAa;AAChD,MAAI,KAAK,gBAAgB,MAAM,MAAM,SAAS,GAAG,MAAM,CACrD,KAAI,SAAS;WACJ,KAAK,gBAAgB,SAAS,MAAM,GAAG,MAAM,CACtD,KAAI,QAAQ;WACH,KAAK,gBAAgB,SAAS,MAAM,GAAG,MAAM,CACtD,KAAI,QAAQ;WACH,KAAK,gBAAgB,SAAS,MAAM,GAAG,MAAM,CACtD,KAAI,QAAQ;OACP;GACL,MAAM,MAAM,KAAK,MAAM,MAAM,CAAC,IAAI,GAAG;AAErC,OAAI,OAA2B;;AAGjC,SAAO;IACN,OAAO;;;AAIZ,MAAa,6BAA6B,aAAoC;CAC5E;CACA;CACA;CACD,CAAC"}
1
+ {"version":3,"file":"parser.js","names":["Patterning.fromTemplate","isNegative: 0 | 1"],"sources":["../../../../src/coordinates/latlon/degrees-minutes-seconds/parser.ts"],"sourcesContent":["/*\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 * as Patterning from '@/patterning';\nimport {\n BEARINGS,\n type Format,\n LIMITS,\n PARTIAL_PATTERNS,\n SYMBOL_PATTERNS,\n SYMBOLS,\n} from '../internal';\nimport { inRange } from '../internal/in-range';\nimport { createParser } from '../internal/parse-format';\nimport type { ParseResults } from '../internal/parse';\n\ntype DegreesMinutesSeconds = {\n bear: string;\n deg: string;\n min: string;\n sec: string;\n};\n\nconst checks = {\n deg: (deg: string, limit: number) => {\n const num = Number.parseFloat(deg);\n if (Number.isNaN(num)) {\n return `Degrees value (${deg}) is not a valid number.`;\n }\n if (num > limit) {\n return `Degrees value (${deg}) exceeds max value (${limit}).`;\n }\n\n if (/\\./.test(deg)) {\n return `Degrees value (${deg}) must not include decimal value.`;\n }\n },\n min: (min: string) => inRange('Minutes', min, 59),\n sec: (sec: string) => inRange('Seconds', sec, 59.999999999),\n};\n\nconst formats = {\n LATLON: Patterning.fromTemplate(\n PARTIAL_PATTERNS,\n `degLat min secDec NS ${SYMBOLS.DIVIDER} degLon min secDec EW`,\n ),\n LONLAT: Patterning.fromTemplate(\n PARTIAL_PATTERNS,\n `degLon min secDec EW ${SYMBOLS.DIVIDER} degLat min secDec NS`,\n ),\n};\n\n/**\n * Creates an error identification function for degrees minutes seconds coordinates.\n *\n * Validates that degree values are integers, minutes are in range 0-59,\n * seconds are in range 0-59.999..., and all components are properly formatted.\n *\n * @param format - The coordinate format (LATLON or LONLAT).\n * @returns Function that validates coordinate components and returns parse results.\n *\n * @example\n * ```typescript\n * const validator = identifyErrors('LATLON');\n * const result = validator({ deg: '91', min: '0', sec: '0', bear: 'N' }, 0);\n * // [[], ['Degrees value (91) exceeds max value (90).']]\n * ```\n */\nfunction identifyErrors(format: Format) {\n return (arg: DegreesMinutesSeconds | undefined, i: number) => {\n if (!arg) {\n return [[], ['Invalid coordinate value.']] as ParseResults;\n }\n\n let { bear, deg, min, sec } = arg;\n\n deg ??= '0';\n // NOTE: need `||=` not `??=` because empty-string is not nullish\n min ||= '0';\n sec ||= '0';\n\n let isNegative: 0 | 1 = SYMBOL_PATTERNS.NEGATIVE_SIGN.test(deg) ? 1 : 0;\n const bearingOptions = BEARINGS[format][i] as [string, string];\n\n if (!bear || isNegative) {\n bear = bearingOptions[isNegative];\n deg = Math.abs(Number.parseFloat(deg)).toString();\n isNegative = 0;\n }\n\n const errors = [\n ...[checks.deg(deg, LIMITS[format][i] ?? 0)],\n ...[checks.min(min)],\n ...[checks.sec(sec)],\n ].filter(Boolean);\n\n return (\n errors.length ? [[], errors] : [[deg, min, sec, bear], []]\n ) as ParseResults;\n };\n}\n\n/**\n * Identifies and organizes coordinate components from parsed tokens.\n *\n * Separates bearing indicators (N/S/E/W), degree values, minute values, and second values from tokens.\n * Uses symbol patterns to identify component types and positional logic as fallback.\n *\n * @param half - Array of tokens representing one coordinate component.\n * @returns Object with `bear`, `deg`, `min`, and `sec` properties, or undefined if invalid.\n *\n * @example\n * ```typescript\n * identifyPieces(['45', '30', '15.23', 'N']);\n * // { bear: 'N', deg: '45', min: '30', sec: '15.23' }\n * ```\n *\n * @example\n * ```typescript\n * identifyPieces(['122°', '25'', '9.84″', 'W']);\n * // { bear: 'W', deg: '122', min: '25', sec: '9.84' }\n * ```\n */\nfunction identifyPieces(half: string[]) {\n if (half.length < 1 || half.length > 4) {\n return;\n }\n\n const asString = half.join(' ');\n const places = { bear: '', deg: '', min: '', sec: '' };\n const keys = ['deg', 'min', 'sec'] as (keyof typeof places)[];\n const test = (r: RegExp, b: boolean, v: string) =>\n r.test(v) || (r.test(asString) && b);\n\n return half.reduce<typeof places | undefined>((acc, token, i, { length }) => {\n if (!acc) {\n return undefined;\n }\n if (test(SYMBOL_PATTERNS.NSEW, i === length - 1, token)) {\n acc.bear ||= token;\n } else if (test(SYMBOL_PATTERNS.DEGREES, i === 0, token)) {\n acc.deg ||= token;\n } else if (test(SYMBOL_PATTERNS.MINUTES, i === 1, token)) {\n acc.min ||= token;\n } else if (test(SYMBOL_PATTERNS.SECONDS, i === 2, token)) {\n acc.sec ||= token;\n } else {\n const key = keys.find((k) => !acc[k]);\n if (!key) {\n return undefined;\n }\n acc[key] = token;\n }\n\n return acc;\n }, places);\n}\n\n/**\n * Parses a Degrees Minutes Seconds coordinate string.\n *\n * Accepts coordinates in degrees minutes seconds format with bearing indicators.\n * Validates that degrees are integers, minutes are in range 0-59,\n * seconds are in range 0-59.999..., and all components are properly formatted.\n *\n * @param input - Raw coordinate string to parse.\n * @param format - Expected format (LATLON or LONLAT).\n * @returns Parsed coordinate values or errors.\n *\n * @example\n * ```typescript\n * parseDegreesMinutesSeconds('37° 46' 29.64″ N / 122° 25' 9.84″ W', 'LATLON');\n * // [[37.7749, -122.4194], []]\n * ```\n *\n * @example\n * ```typescript\n * parseDegreesMinutesSeconds('45 30 15.23 N, 122 25 9.84 W');\n * // [[45.504231, -122.419400], []]\n * ```\n */\nexport const parseDegreesMinutesSeconds = createParser<DegreesMinutesSeconds>({\n formats,\n identifyErrors,\n identifyPieces,\n});\n\nexport { identifyErrors as _identifyErrors, identifyPieces as _identifyPieces };\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAgCA,MAAM,SAAS;CACb,MAAM,KAAa,UAAkB;EACnC,MAAM,MAAM,OAAO,WAAW,IAAI;AAClC,MAAI,OAAO,MAAM,IAAI,CACnB,QAAO,kBAAkB,IAAI;AAE/B,MAAI,MAAM,MACR,QAAO,kBAAkB,IAAI,uBAAuB,MAAM;AAG5D,MAAI,KAAK,KAAK,IAAI,CAChB,QAAO,kBAAkB,IAAI;;CAGjC,MAAM,QAAgB,QAAQ,WAAW,KAAK,GAAG;CACjD,MAAM,QAAgB,QAAQ,WAAW,KAAK,aAAa;CAC5D;AAED,MAAM,UAAU;CACd,QAAQA,aACN,kBACA,wBAAwB,QAAQ,QAAQ,uBACzC;CACD,QAAQA,aACN,kBACA,wBAAwB,QAAQ,QAAQ,uBACzC;CACF;;;;;;;;;;;;;;;;;AAkBD,SAAS,eAAe,QAAgB;AACtC,SAAQ,KAAwC,MAAc;AAC5D,MAAI,CAAC,IACH,QAAO,CAAC,EAAE,EAAE,CAAC,4BAA4B,CAAC;EAG5C,IAAI,EAAE,MAAM,KAAK,KAAK,QAAQ;AAE9B,UAAQ;AAER,UAAQ;AACR,UAAQ;EAER,IAAIC,aAAoB,gBAAgB,cAAc,KAAK,IAAI,GAAG,IAAI;EACtE,MAAM,iBAAiB,SAAS,QAAQ;AAExC,MAAI,CAAC,QAAQ,YAAY;AACvB,UAAO,eAAe;AACtB,SAAM,KAAK,IAAI,OAAO,WAAW,IAAI,CAAC,CAAC,UAAU;AACjD,gBAAa;;EAGf,MAAM,SAAS;GACb,GAAG,CAAC,OAAO,IAAI,KAAK,OAAO,QAAQ,MAAM,EAAE,CAAC;GAC5C,GAAG,CAAC,OAAO,IAAI,IAAI,CAAC;GACpB,GAAG,CAAC,OAAO,IAAI,IAAI,CAAC;GACrB,CAAC,OAAO,QAAQ;AAEjB,SACE,OAAO,SAAS,CAAC,EAAE,EAAE,OAAO,GAAG,CAAC;GAAC;GAAK;GAAK;GAAK;GAAK,EAAE,EAAE,CAAC;;;;;;;;;;;;;;;;;;;;;;;;AA0BhE,SAAS,eAAe,MAAgB;AACtC,KAAI,KAAK,SAAS,KAAK,KAAK,SAAS,EACnC;CAGF,MAAM,WAAW,KAAK,KAAK,IAAI;CAC/B,MAAM,SAAS;EAAE,MAAM;EAAI,KAAK;EAAI,KAAK;EAAI,KAAK;EAAI;CACtD,MAAM,OAAO;EAAC;EAAO;EAAO;EAAM;CAClC,MAAM,QAAQ,GAAW,GAAY,MACnC,EAAE,KAAK,EAAE,IAAK,EAAE,KAAK,SAAS,IAAI;AAEpC,QAAO,KAAK,QAAmC,KAAK,OAAO,GAAG,EAAE,aAAa;AAC3E,MAAI,CAAC,IACH;AAEF,MAAI,KAAK,gBAAgB,MAAM,MAAM,SAAS,GAAG,MAAM,CACrD,KAAI,SAAS;WACJ,KAAK,gBAAgB,SAAS,MAAM,GAAG,MAAM,CACtD,KAAI,QAAQ;WACH,KAAK,gBAAgB,SAAS,MAAM,GAAG,MAAM,CACtD,KAAI,QAAQ;WACH,KAAK,gBAAgB,SAAS,MAAM,GAAG,MAAM,CACtD,KAAI,QAAQ;OACP;GACL,MAAM,MAAM,KAAK,MAAM,MAAM,CAAC,IAAI,GAAG;AACrC,OAAI,CAAC,IACH;AAEF,OAAI,OAAO;;AAGb,SAAO;IACN,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;AA0BZ,MAAa,6BAA6B,aAAoC;CAC5E;CACA;CACA;CACD,CAAC"}