@accelint/geo 0.5.1 → 0.6.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 (112) hide show
  1. package/CHANGELOG.md +6 -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 +2 -0
  6. package/dist/cartesian.js.map +1 -1
  7. package/dist/coordinates/coordinate.d.ts +41 -7
  8. package/dist/coordinates/coordinate.js +102 -12
  9. package/dist/coordinates/coordinate.js.map +1 -1
  10. package/dist/coordinates/latlon/decimal-degrees/formatter.d.ts +20 -0
  11. package/dist/coordinates/latlon/decimal-degrees/formatter.js +38 -0
  12. package/dist/coordinates/latlon/decimal-degrees/formatter.js.map +1 -1
  13. package/dist/coordinates/latlon/decimal-degrees/parser.d.ts +68 -2
  14. package/dist/coordinates/latlon/decimal-degrees/parser.js +66 -5
  15. package/dist/coordinates/latlon/decimal-degrees/parser.js.map +1 -1
  16. package/dist/coordinates/latlon/decimal-degrees/system.d.ts +32 -0
  17. package/dist/coordinates/latlon/decimal-degrees/system.js +31 -0
  18. package/dist/coordinates/latlon/decimal-degrees/system.js.map +1 -1
  19. package/dist/coordinates/latlon/degrees-decimal-minutes/formatter.d.ts +20 -0
  20. package/dist/coordinates/latlon/degrees-decimal-minutes/formatter.js +37 -0
  21. package/dist/coordinates/latlon/degrees-decimal-minutes/formatter.js.map +1 -1
  22. package/dist/coordinates/latlon/degrees-decimal-minutes/parser.d.ts +72 -2
  23. package/dist/coordinates/latlon/degrees-decimal-minutes/parser.js +66 -3
  24. package/dist/coordinates/latlon/degrees-decimal-minutes/parser.js.map +1 -1
  25. package/dist/coordinates/latlon/degrees-decimal-minutes/system.d.ts +32 -0
  26. package/dist/coordinates/latlon/degrees-decimal-minutes/system.js +31 -0
  27. package/dist/coordinates/latlon/degrees-decimal-minutes/system.js.map +1 -1
  28. package/dist/coordinates/latlon/degrees-minutes-seconds/formatter.d.ts +20 -0
  29. package/dist/coordinates/latlon/degrees-minutes-seconds/formatter.js +37 -0
  30. package/dist/coordinates/latlon/degrees-minutes-seconds/formatter.js.map +1 -1
  31. package/dist/coordinates/latlon/degrees-minutes-seconds/parser.d.ts +74 -2
  32. package/dist/coordinates/latlon/degrees-minutes-seconds/parser.js +66 -3
  33. package/dist/coordinates/latlon/degrees-minutes-seconds/parser.js.map +1 -1
  34. package/dist/coordinates/latlon/degrees-minutes-seconds/system.d.ts +32 -0
  35. package/dist/coordinates/latlon/degrees-minutes-seconds/system.js +31 -0
  36. package/dist/coordinates/latlon/degrees-minutes-seconds/system.js.map +1 -1
  37. package/dist/coordinates/latlon/internal/coordinate-system.d.ts +22 -0
  38. package/dist/coordinates/latlon/internal/create-cache.d.ts +17 -1
  39. package/dist/coordinates/latlon/internal/create-cache.js +19 -3
  40. package/dist/coordinates/latlon/internal/create-cache.js.map +1 -1
  41. package/dist/coordinates/latlon/internal/exhaustive-errors.d.ts +15 -0
  42. package/dist/coordinates/latlon/internal/exhaustive-errors.js +28 -0
  43. package/dist/coordinates/latlon/internal/exhaustive-errors.js.map +1 -1
  44. package/dist/coordinates/latlon/internal/format.d.ts +20 -0
  45. package/dist/coordinates/latlon/internal/format.js +20 -0
  46. package/dist/coordinates/latlon/internal/format.js.map +1 -1
  47. package/dist/coordinates/latlon/internal/in-range.d.ts +23 -0
  48. package/dist/coordinates/latlon/internal/in-range.js +24 -0
  49. package/dist/coordinates/latlon/internal/in-range.js.map +1 -1
  50. package/dist/coordinates/latlon/internal/index.d.ts +16 -1
  51. package/dist/coordinates/latlon/internal/index.js +25 -1
  52. package/dist/coordinates/latlon/internal/index.js.map +1 -1
  53. package/dist/coordinates/latlon/internal/lexer.d.ts +2 -0
  54. package/dist/coordinates/latlon/internal/lexer.js +26 -0
  55. package/dist/coordinates/latlon/internal/lexer.js.map +1 -1
  56. package/dist/coordinates/latlon/internal/normalize.d.ts +67 -0
  57. package/dist/coordinates/latlon/internal/normalize.js +87 -0
  58. package/dist/coordinates/latlon/internal/normalize.js.map +1 -0
  59. package/dist/coordinates/latlon/internal/ordinal.d.ts +25 -0
  60. package/dist/coordinates/latlon/internal/ordinal.js +25 -0
  61. package/dist/coordinates/latlon/internal/ordinal.js.map +1 -1
  62. package/dist/coordinates/latlon/internal/parse-format.d.ts +22 -0
  63. package/dist/coordinates/latlon/internal/parse-format.js +43 -1
  64. package/dist/coordinates/latlon/internal/parse-format.js.map +1 -1
  65. package/dist/coordinates/latlon/internal/parse.d.ts +2 -0
  66. package/dist/coordinates/latlon/internal/parse.js +4 -1
  67. package/dist/coordinates/latlon/internal/parse.js.map +1 -1
  68. package/dist/coordinates/latlon/internal/pipes/check-ambiguous.d.ts +17 -0
  69. package/dist/coordinates/latlon/internal/pipes/check-ambiguous.js +16 -0
  70. package/dist/coordinates/latlon/internal/pipes/check-ambiguous.js.map +1 -1
  71. package/dist/coordinates/latlon/internal/pipes/check-numbers.d.ts +25 -0
  72. package/dist/coordinates/latlon/internal/pipes/check-numbers.js +33 -0
  73. package/dist/coordinates/latlon/internal/pipes/check-numbers.js.map +1 -1
  74. package/dist/coordinates/latlon/internal/pipes/fix-bearings.d.ts +17 -0
  75. package/dist/coordinates/latlon/internal/pipes/fix-bearings.js +31 -0
  76. package/dist/coordinates/latlon/internal/pipes/fix-bearings.js.map +1 -1
  77. package/dist/coordinates/latlon/internal/pipes/fix-dividers.d.ts +17 -0
  78. package/dist/coordinates/latlon/internal/pipes/fix-dividers.js +29 -0
  79. package/dist/coordinates/latlon/internal/pipes/fix-dividers.js.map +1 -1
  80. package/dist/coordinates/latlon/internal/pipes/genome.d.ts +16 -0
  81. package/dist/coordinates/latlon/internal/pipes/genome.js +41 -0
  82. package/dist/coordinates/latlon/internal/pipes/genome.js.map +1 -1
  83. package/dist/coordinates/latlon/internal/pipes/index.d.ts +32 -2
  84. package/dist/coordinates/latlon/internal/pipes/index.js +57 -4
  85. package/dist/coordinates/latlon/internal/pipes/index.js.map +1 -1
  86. package/dist/coordinates/latlon/internal/pipes/simpler.d.ts +16 -3
  87. package/dist/coordinates/latlon/internal/pipes/simpler.js +15 -3
  88. package/dist/coordinates/latlon/internal/pipes/simpler.js.map +1 -1
  89. package/dist/coordinates/latlon/internal/validate.d.ts +75 -0
  90. package/dist/coordinates/latlon/internal/validate.js +105 -0
  91. package/dist/coordinates/latlon/internal/validate.js.map +1 -0
  92. package/dist/coordinates/latlon/internal/violation.d.ts +18 -0
  93. package/dist/coordinates/latlon/internal/violation.js +18 -0
  94. package/dist/coordinates/latlon/internal/violation.js.map +1 -1
  95. package/dist/coordinates/mgrs/parser.d.ts +24 -0
  96. package/dist/coordinates/mgrs/parser.js +56 -0
  97. package/dist/coordinates/mgrs/parser.js.map +1 -1
  98. package/dist/coordinates/mgrs/system.d.ts +31 -0
  99. package/dist/coordinates/mgrs/system.js +30 -0
  100. package/dist/coordinates/mgrs/system.js.map +1 -1
  101. package/dist/coordinates/utm/parser.d.ts +24 -0
  102. package/dist/coordinates/utm/parser.js +56 -0
  103. package/dist/coordinates/utm/parser.js.map +1 -1
  104. package/dist/coordinates/utm/system.d.ts +21 -0
  105. package/dist/coordinates/utm/system.js +20 -0
  106. package/dist/coordinates/utm/system.js.map +1 -1
  107. package/dist/index.d.ts +3 -1
  108. package/dist/index.js +3 -1
  109. package/dist/patterning.d.ts +12 -2
  110. package/dist/patterning.js +12 -2
  111. package/dist/patterning.js.map +1 -1
  112. package/package.json +3 -1
@@ -12,6 +12,31 @@
12
12
 
13
13
 
14
14
  //#region src/coordinates/latlon/internal/ordinal.ts
15
+ /**
16
+ * Gets the ordinal direction (N/S/E/W) for a coordinate value.
17
+ *
18
+ * @param num - The coordinate value (positive or negative).
19
+ * @param isLatitude - Whether this is a latitude coordinate (true) or longitude (false).
20
+ * @returns Ordinal direction character: 'N', 'S', 'E', or 'W'.
21
+ *
22
+ * @example
23
+ * ```typescript
24
+ * getOrdinal(37.7749, true);
25
+ * // 'N'
26
+ * ```
27
+ *
28
+ * @example
29
+ * ```typescript
30
+ * getOrdinal(-122.4194, false);
31
+ * // 'W'
32
+ * ```
33
+ *
34
+ * @example
35
+ * ```typescript
36
+ * getOrdinal(-45, true);
37
+ * // 'S'
38
+ * ```
39
+ */
15
40
  const getOrdinal = (num, isLatitude) => {
16
41
  if (isLatitude) return num >= 0 ? "N" : "S";
17
42
  return num >= 0 ? "E" : "W";
@@ -1 +1 @@
1
- {"version":3,"file":"ordinal.js","names":[],"sources":["../../../../src/coordinates/latlon/internal/ordinal.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\n// __private-exports\n\nexport const getOrdinal = (num: number, isLatitude: boolean): string => {\n if (isLatitude) {\n return num >= 0 ? 'N' : 'S';\n }\n return num >= 0 ? 'E' : 'W';\n};\n"],"mappings":";;;;;;;;;;;;;;AAcA,MAAa,cAAc,KAAa,eAAgC;AACtE,KAAI,WACF,QAAO,OAAO,IAAI,MAAM;AAE1B,QAAO,OAAO,IAAI,MAAM"}
1
+ {"version":3,"file":"ordinal.js","names":[],"sources":["../../../../src/coordinates/latlon/internal/ordinal.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\n// __private-exports\n\n/**\n * Gets the ordinal direction (N/S/E/W) for a coordinate value.\n *\n * @param num - The coordinate value (positive or negative).\n * @param isLatitude - Whether this is a latitude coordinate (true) or longitude (false).\n * @returns Ordinal direction character: 'N', 'S', 'E', or 'W'.\n *\n * @example\n * ```typescript\n * getOrdinal(37.7749, true);\n * // 'N'\n * ```\n *\n * @example\n * ```typescript\n * getOrdinal(-122.4194, false);\n * // 'W'\n * ```\n *\n * @example\n * ```typescript\n * getOrdinal(-45, true);\n * // 'S'\n * ```\n */\nexport const getOrdinal = (num: number, isLatitude: boolean): string => {\n if (isLatitude) {\n return num >= 0 ? 'N' : 'S';\n }\n return num >= 0 ? 'E' : 'W';\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAuCA,MAAa,cAAc,KAAa,eAAgC;AACtE,KAAI,WACF,QAAO,OAAO,IAAI,MAAM;AAE1B,QAAO,OAAO,IAAI,MAAM"}
@@ -22,6 +22,28 @@ type FormatParserConfig<T> = {
22
22
  identifyErrors: (format: Format) => (arg: T, i: number) => ParseResults;
23
23
  identifyPieces: (half: string[]) => T;
24
24
  };
25
+ /**
26
+ * Creates a coordinate parser function from a format parser configuration.
27
+ *
28
+ * @param config - Parser configuration with format patterns, error identification, and piece identification.
29
+ * @returns Parser function that takes format and input string, returns ParseResults.
30
+ *
31
+ * @example
32
+ * ```typescript
33
+ * const parseDD = createParser({
34
+ * formats: { LATLON: /pattern/, LONLAT: /pattern/ },
35
+ * identifyErrors: (format) => (arg, i) => [tokens, errors],
36
+ * identifyPieces: (half) => ({ deg, bear })
37
+ * });
38
+ * ```
39
+ *
40
+ * @example
41
+ * ```typescript
42
+ * const parser = createParser(config);
43
+ * parser('LATLON', '37.7749° N / 122.4194° W');
44
+ * // [[37.7749, -122.4194], []]
45
+ * ```
46
+ */
25
47
  declare const createParser: <T>(config: FormatParserConfig<T | undefined>) => (format: Format, input: string) => ParseResults;
26
48
  //#endregion
27
49
  export { createParser };
@@ -11,13 +11,55 @@
11
11
  */
12
12
 
13
13
 
14
- import { SYMBOLS, SYMBOL_PATTERNS } from "./index.js";
15
14
  import { violation } from "./violation.js";
15
+ import { SYMBOLS, SYMBOL_PATTERNS } from "./index.js";
16
16
  import { parse } from "./parse.js";
17
17
 
18
18
  //#region src/coordinates/latlon/internal/parse-format.ts
19
19
  const axisTypeTest = (k, t) => SYMBOL_PATTERNS[k].test(t) && k;
20
+ /**
21
+ * Creates a coordinate parser function from a format parser configuration.
22
+ *
23
+ * @param config - Parser configuration with format patterns, error identification, and piece identification.
24
+ * @returns Parser function that takes format and input string, returns ParseResults.
25
+ *
26
+ * @example
27
+ * ```typescript
28
+ * const parseDD = createParser({
29
+ * formats: { LATLON: /pattern/, LONLAT: /pattern/ },
30
+ * identifyErrors: (format) => (arg, i) => [tokens, errors],
31
+ * identifyPieces: (half) => ({ deg, bear })
32
+ * });
33
+ * ```
34
+ *
35
+ * @example
36
+ * ```typescript
37
+ * const parser = createParser(config);
38
+ * parser('LATLON', '37.7749° N / 122.4194° W');
39
+ * // [[37.7749, -122.4194], []]
40
+ * ```
41
+ */
20
42
  const createParser = (config) => (format, input) => parseWithConfig(config, format, input);
43
+ /**
44
+ * Parses a coordinate string using the provided configuration.
45
+ *
46
+ * @param config - Parser configuration with format patterns and validation functions.
47
+ * @param format - Expected coordinate format (LATLON or LONLAT).
48
+ * @param input - Raw coordinate string to parse.
49
+ * @returns Parse results with coordinate values or error messages.
50
+ *
51
+ * @example
52
+ * ```typescript
53
+ * parseWithConfig(config, 'LATLON', '37° N / 122° W');
54
+ * // [['37', 'N', '122', 'W'], []]
55
+ * ```
56
+ *
57
+ * @example
58
+ * ```typescript
59
+ * parseWithConfig(config, 'LATLON', '91° N / 122° W');
60
+ * // [[], ['[ERROR] Degrees value (91) exceeds max value (90).']]
61
+ * ```
62
+ */
21
63
  function parseWithConfig(config, format, input) {
22
64
  const [parseTokens, violations] = parse(input, format);
23
65
  const foundFormat = parseTokens.reduce((acc, t) => acc + (axisTypeTest("LAT", t) || axisTypeTest("LON", t) || ""), "");
@@ -1 +1 @@
1
- {"version":3,"file":"parse-format.js","names":[],"sources":["../../../../src/coordinates/latlon/internal/parse-format.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 Format, SYMBOL_PATTERNS, SYMBOLS } from '.';\nimport { type ParseResults, parse } from './parse';\nimport { violation } from './violation';\n\ntype FormatParserConfig<T> = {\n formats: {\n // biome-ignore lint/style/useNamingConvention: Name is chosen to stand out amongst other formats\n LATLON: RegExp;\n // biome-ignore lint/style/useNamingConvention: Name is chosen to stand out amongst other formats\n LONLAT: RegExp;\n };\n identifyErrors: (format: Format) => (arg: T, i: number) => ParseResults;\n identifyPieces: (half: string[]) => T;\n};\n\nconst axisTypeTest = (k: keyof typeof SYMBOL_PATTERNS, t: string) =>\n SYMBOL_PATTERNS[k].test(t) && k;\n\nexport const createParser =\n <T>(config: FormatParserConfig<T | undefined>) =>\n (format: Format, input: string) =>\n parseWithConfig(config, format, input);\n\nfunction parseWithConfig<T>(\n config: FormatParserConfig<T>,\n format: Format,\n input: string,\n): ParseResults {\n const [parseTokens, violations] = parse(input, format);\n\n const foundFormat = parseTokens.reduce(\n (acc, t) => acc + (axisTypeTest('LAT', t) || axisTypeTest('LON', t) || ''),\n '',\n );\n\n if (!violations.length && foundFormat && foundFormat !== format) {\n return [\n [],\n [\n violation(\n `Mismatched formats: \"${format}\" expected, \"${foundFormat}\" found.`,\n ),\n ],\n ];\n }\n\n const hasErrors = !parseTokens.length && violations.length;\n\n if (hasErrors) {\n return [[], violations];\n }\n\n const [tokens, errors] = [\n parseTokens.slice(0, parseTokens.indexOf(SYMBOLS.DIVIDER)), // left of divider\n parseTokens.slice(1 + parseTokens.indexOf(SYMBOLS.DIVIDER)), // right of divider\n ]\n .map(config.identifyPieces)\n .map(config.identifyErrors(format))\n .reduce((a, b) => [\n [...a[0], SYMBOLS.DIVIDER, ...b[0]],\n [...a[1], ...b[1]].map(violation),\n ]);\n\n if (errors.length) {\n return [\n [],\n // dedupe errors\n Array.from(new Set(errors)),\n ];\n }\n\n const matches = (config.formats[format].exec(tokens.join(' ')) || []).slice(\n 1,\n );\n\n if (!matches?.length) {\n throw new Error(\n [\n 'A validation check seems to be missing because the cleaned and normalized input value (',\n tokens.join(' '),\n ') is not matching the pattern (',\n config.formats[format],\n ').',\n ].join(''),\n );\n }\n\n return [matches, []];\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AA4BA,MAAM,gBAAgB,GAAiC,MACrD,gBAAgB,GAAG,KAAK,EAAE,IAAI;AAEhC,MAAa,gBACP,YACH,QAAgB,UACf,gBAAgB,QAAQ,QAAQ,MAAM;AAE1C,SAAS,gBACP,QACA,QACA,OACc;CACd,MAAM,CAAC,aAAa,cAAc,MAAM,OAAO,OAAO;CAEtD,MAAM,cAAc,YAAY,QAC7B,KAAK,MAAM,OAAO,aAAa,OAAO,EAAE,IAAI,aAAa,OAAO,EAAE,IAAI,KACvE,GACD;AAED,KAAI,CAAC,WAAW,UAAU,eAAe,gBAAgB,OACvD,QAAO,CACL,EAAE,EACF,CACE,UACE,wBAAwB,OAAO,eAAe,YAAY,UAC3D,CACF,CACF;AAKH,KAFkB,CAAC,YAAY,UAAU,WAAW,OAGlD,QAAO,CAAC,EAAE,EAAE,WAAW;CAGzB,MAAM,CAAC,QAAQ,UAAU,CACvB,YAAY,MAAM,GAAG,YAAY,QAAQ,QAAQ,QAAQ,CAAC,EAC1D,YAAY,MAAM,IAAI,YAAY,QAAQ,QAAQ,QAAQ,CAAC,CAC5D,CACE,IAAI,OAAO,eAAe,CAC1B,IAAI,OAAO,eAAe,OAAO,CAAC,CAClC,QAAQ,GAAG,MAAM,CAChB;EAAC,GAAG,EAAE;EAAI,QAAQ;EAAS,GAAG,EAAE;EAAG,EACnC,CAAC,GAAG,EAAE,IAAI,GAAG,EAAE,GAAG,CAAC,IAAI,UAAU,CAClC,CAAC;AAEJ,KAAI,OAAO,OACT,QAAO,CACL,EAAE,EAEF,MAAM,KAAK,IAAI,IAAI,OAAO,CAAC,CAC5B;CAGH,MAAM,WAAW,OAAO,QAAQ,QAAQ,KAAK,OAAO,KAAK,IAAI,CAAC,IAAI,EAAE,EAAE,MACpE,EACD;AAED,KAAI,CAAC,SAAS,OACZ,OAAM,IAAI,MACR;EACE;EACA,OAAO,KAAK,IAAI;EAChB;EACA,OAAO,QAAQ;EACf;EACD,CAAC,KAAK,GAAG,CACX;AAGH,QAAO,CAAC,SAAS,EAAE,CAAC"}
1
+ {"version":3,"file":"parse-format.js","names":[],"sources":["../../../../src/coordinates/latlon/internal/parse-format.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 Format, SYMBOL_PATTERNS, SYMBOLS } from '.';\nimport { type ParseResults, parse } from './parse';\nimport { violation } from './violation';\n\ntype FormatParserConfig<T> = {\n formats: {\n // biome-ignore lint/style/useNamingConvention: Name is chosen to stand out amongst other formats\n LATLON: RegExp;\n // biome-ignore lint/style/useNamingConvention: Name is chosen to stand out amongst other formats\n LONLAT: RegExp;\n };\n identifyErrors: (format: Format) => (arg: T, i: number) => ParseResults;\n identifyPieces: (half: string[]) => T;\n};\n\nconst axisTypeTest = (k: keyof typeof SYMBOL_PATTERNS, t: string) =>\n SYMBOL_PATTERNS[k].test(t) && k;\n\n/**\n * Creates a coordinate parser function from a format parser configuration.\n *\n * @param config - Parser configuration with format patterns, error identification, and piece identification.\n * @returns Parser function that takes format and input string, returns ParseResults.\n *\n * @example\n * ```typescript\n * const parseDD = createParser({\n * formats: { LATLON: /pattern/, LONLAT: /pattern/ },\n * identifyErrors: (format) => (arg, i) => [tokens, errors],\n * identifyPieces: (half) => ({ deg, bear })\n * });\n * ```\n *\n * @example\n * ```typescript\n * const parser = createParser(config);\n * parser('LATLON', '37.7749° N / 122.4194° W');\n * // [[37.7749, -122.4194], []]\n * ```\n */\nexport const createParser =\n <T>(config: FormatParserConfig<T | undefined>) =>\n (format: Format, input: string) =>\n parseWithConfig(config, format, input);\n\n/**\n * Parses a coordinate string using the provided configuration.\n *\n * @param config - Parser configuration with format patterns and validation functions.\n * @param format - Expected coordinate format (LATLON or LONLAT).\n * @param input - Raw coordinate string to parse.\n * @returns Parse results with coordinate values or error messages.\n *\n * @example\n * ```typescript\n * parseWithConfig(config, 'LATLON', '37° N / 122° W');\n * // [['37', 'N', '122', 'W'], []]\n * ```\n *\n * @example\n * ```typescript\n * parseWithConfig(config, 'LATLON', '91° N / 122° W');\n * // [[], ['[ERROR] Degrees value (91) exceeds max value (90).']]\n * ```\n */\nfunction parseWithConfig<T>(\n config: FormatParserConfig<T>,\n format: Format,\n input: string,\n): ParseResults {\n const [parseTokens, violations] = parse(input, format);\n\n const foundFormat = parseTokens.reduce(\n (acc, t) => acc + (axisTypeTest('LAT', t) || axisTypeTest('LON', t) || ''),\n '',\n );\n\n if (!violations.length && foundFormat && foundFormat !== format) {\n return [\n [],\n [\n violation(\n `Mismatched formats: \"${format}\" expected, \"${foundFormat}\" found.`,\n ),\n ],\n ];\n }\n\n const hasErrors = !parseTokens.length && violations.length;\n\n if (hasErrors) {\n return [[], violations];\n }\n\n const [tokens, errors] = [\n parseTokens.slice(0, parseTokens.indexOf(SYMBOLS.DIVIDER)), // left of divider\n parseTokens.slice(1 + parseTokens.indexOf(SYMBOLS.DIVIDER)), // right of divider\n ]\n .map(config.identifyPieces)\n .map(config.identifyErrors(format))\n .reduce((a, b) => [\n [...a[0], SYMBOLS.DIVIDER, ...b[0]],\n [...a[1], ...b[1]].map(violation),\n ]);\n\n if (errors.length) {\n return [\n [],\n // dedupe errors\n Array.from(new Set(errors)),\n ];\n }\n\n const matches = (config.formats[format].exec(tokens.join(' ')) || []).slice(\n 1,\n );\n\n if (!matches?.length) {\n throw new Error(\n [\n 'A validation check seems to be missing because the cleaned and normalized input value (',\n tokens.join(' '),\n ') is not matching the pattern (',\n config.formats[format],\n ').',\n ].join(''),\n );\n }\n\n return [matches, []];\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AA4BA,MAAM,gBAAgB,GAAiC,MACrD,gBAAgB,GAAG,KAAK,EAAE,IAAI;;;;;;;;;;;;;;;;;;;;;;;AAwBhC,MAAa,gBACP,YACH,QAAgB,UACf,gBAAgB,QAAQ,QAAQ,MAAM;;;;;;;;;;;;;;;;;;;;;AAsB1C,SAAS,gBACP,QACA,QACA,OACc;CACd,MAAM,CAAC,aAAa,cAAc,MAAM,OAAO,OAAO;CAEtD,MAAM,cAAc,YAAY,QAC7B,KAAK,MAAM,OAAO,aAAa,OAAO,EAAE,IAAI,aAAa,OAAO,EAAE,IAAI,KACvE,GACD;AAED,KAAI,CAAC,WAAW,UAAU,eAAe,gBAAgB,OACvD,QAAO,CACL,EAAE,EACF,CACE,UACE,wBAAwB,OAAO,eAAe,YAAY,UAC3D,CACF,CACF;AAKH,KAFkB,CAAC,YAAY,UAAU,WAAW,OAGlD,QAAO,CAAC,EAAE,EAAE,WAAW;CAGzB,MAAM,CAAC,QAAQ,UAAU,CACvB,YAAY,MAAM,GAAG,YAAY,QAAQ,QAAQ,QAAQ,CAAC,EAC1D,YAAY,MAAM,IAAI,YAAY,QAAQ,QAAQ,QAAQ,CAAC,CAC5D,CACE,IAAI,OAAO,eAAe,CAC1B,IAAI,OAAO,eAAe,OAAO,CAAC,CAClC,QAAQ,GAAG,MAAM,CAChB;EAAC,GAAG,EAAE;EAAI,QAAQ;EAAS,GAAG,EAAE;EAAG,EACnC,CAAC,GAAG,EAAE,IAAI,GAAG,EAAE,GAAG,CAAC,IAAI,UAAU,CAClC,CAAC;AAEJ,KAAI,OAAO,OACT,QAAO,CACL,EAAE,EAEF,MAAM,KAAK,IAAI,IAAI,OAAO,CAAC,CAC5B;CAGH,MAAM,WAAW,OAAO,QAAQ,QAAQ,KAAK,OAAO,KAAK,IAAI,CAAC,IAAI,EAAE,EAAE,MACpE,EACD;AAED,KAAI,CAAC,SAAS,OACZ,OAAM,IAAI,MACR;EACE;EACA,OAAO,KAAK,IAAI;EAChB;EACA,OAAO,QAAQ;EACf;EACD,CAAC,KAAK,GAAG,CACX;AAGH,QAAO,CAAC,SAAS,EAAE,CAAC"}
@@ -32,8 +32,10 @@ type ParseResults = [Tokens, Errors];
32
32
  * pure function
33
33
  *
34
34
  * @example
35
+ * ```typescript
35
36
  * const input = '1 2 3 / 4 5 6'
36
37
  * parse(input); // [['1', '2', '3', '/', '4', '5', '6'], []]
38
+ * ```
37
39
  *
38
40
  * @description
39
41
  * __Assumptions/Specification__
@@ -11,9 +11,9 @@
11
11
  */
12
12
 
13
13
 
14
+ import { violation } from "./violation.js";
14
15
  import { lexer } from "./lexer.js";
15
16
  import { pipesRunner } from "./pipes/index.js";
16
- import { violation } from "./violation.js";
17
17
 
18
18
  //#region src/coordinates/latlon/internal/parse.ts
19
19
  /**
@@ -33,8 +33,10 @@ import { violation } from "./violation.js";
33
33
  * pure function
34
34
  *
35
35
  * @example
36
+ * ```typescript
36
37
  * const input = '1 2 3 / 4 5 6'
37
38
  * parse(input); // [['1', '2', '3', '/', '4', '5', '6'], []]
39
+ * ```
38
40
  *
39
41
  * @description
40
42
  * __Assumptions/Specification__
@@ -52,6 +54,7 @@ import { violation } from "./violation.js";
52
54
  */
53
55
  function parse(input, format) {
54
56
  if (!input?.length) return [[], [violation("No input.")]];
57
+ if (input.match(/(?<![a-z])[a-z](?![a-z])/gi)?.some((ch) => !/^[nsew]$/i.test(ch))) return [[], [violation("Unrecognized characters in input.")]];
55
58
  const [tokens, errors] = pipesRunner(lexer(input), format);
56
59
  return [tokens, errors?.length ? errors.map(violation) : []];
57
60
  }
@@ -1 +1 @@
1
- {"version":3,"file":"parse.js","names":[],"sources":["../../../../src/coordinates/latlon/internal/parse.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 { lexer, type Tokens } from './lexer';\nimport { pipesRunner } from './pipes';\nimport { violation } from './violation';\nimport type { Errors, Format } from '.';\n\nexport type ParseResults = [Tokens, Errors];\n\n/**\n * Parse a raw input string into a validated and normalized coordinate\n * primitive ready for further refinement/validation by a more specific parser.\n *\n * @param input\n * raw input, from a user or external system, of unknown validity\n *\n * @param format\n * the expected format - LATLON or LONLAT - the coordinate should conform to\n *\n * @returns\n * tuple with two values: tokens, and errors\n *\n * @remarks\n * pure function\n *\n * @example\n * const input = '1 2 3 / 4 5 6'\n * parse(input); // [['1', '2', '3', '/', '4', '5', '6'], []]\n *\n * @description\n * __Assumptions/Specification__\n * 1. Decimals are indicated by a \".\" and not \",\"\n * 2. A degrees indicator is \"°\"\n * 3. A minutes indicator is \"'\"\n * 4. A seconds indicator is '\"'\n * 5. Each indicator is expected to follow the number it is annotating\n * 6. Numeric parts - degrees, minutes, and seconds - are positional and can\n * not be arranged in alternative orders and be considered valid\n * 7. Output will have explicit bearings characters (NSEW) and not rely on\n * positive and negative values; when bearings are available. Negative\n * values will only be replaced with absolute values if bearings are available.\n *\n */\nexport function parse(input: string, format?: Format): ParseResults {\n if (!input?.length) {\n return [[], [violation('No input.')]];\n }\n\n const [tokens, errors] = pipesRunner(lexer(input), format);\n\n return [tokens, errors?.length ? errors.map(violation) : []];\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsDA,SAAgB,MAAM,OAAe,QAA+B;AAClE,KAAI,CAAC,OAAO,OACV,QAAO,CAAC,EAAE,EAAE,CAAC,UAAU,YAAY,CAAC,CAAC;CAGvC,MAAM,CAAC,QAAQ,UAAU,YAAY,MAAM,MAAM,EAAE,OAAO;AAE1D,QAAO,CAAC,QAAQ,QAAQ,SAAS,OAAO,IAAI,UAAU,GAAG,EAAE,CAAC"}
1
+ {"version":3,"file":"parse.js","names":[],"sources":["../../../../src/coordinates/latlon/internal/parse.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 { lexer, type Tokens } from './lexer';\nimport { pipesRunner } from './pipes';\nimport { violation } from './violation';\nimport type { Errors, Format } from '.';\n\nexport type ParseResults = [Tokens, Errors];\n\n/**\n * Parse a raw input string into a validated and normalized coordinate\n * primitive ready for further refinement/validation by a more specific parser.\n *\n * @param input\n * raw input, from a user or external system, of unknown validity\n *\n * @param format\n * the expected format - LATLON or LONLAT - the coordinate should conform to\n *\n * @returns\n * tuple with two values: tokens, and errors\n *\n * @remarks\n * pure function\n *\n * @example\n * ```typescript\n * const input = '1 2 3 / 4 5 6'\n * parse(input); // [['1', '2', '3', '/', '4', '5', '6'], []]\n * ```\n *\n * @description\n * __Assumptions/Specification__\n * 1. Decimals are indicated by a \".\" and not \",\"\n * 2. A degrees indicator is \"°\"\n * 3. A minutes indicator is \"'\"\n * 4. A seconds indicator is '\"'\n * 5. Each indicator is expected to follow the number it is annotating\n * 6. Numeric parts - degrees, minutes, and seconds - are positional and can\n * not be arranged in alternative orders and be considered valid\n * 7. Output will have explicit bearings characters (NSEW) and not rely on\n * positive and negative values; when bearings are available. Negative\n * values will only be replaced with absolute values if bearings are available.\n *\n */\nexport function parse(input: string, format?: Format): ParseResults {\n if (!input?.length) {\n return [[], [violation('No input.')]];\n }\n\n // Find isolated letters (not part of a larger word)\n const isolatedLetters = input.match(/(?<![a-z])[a-z](?![a-z])/gi);\n\n // Reject if any isolated letter is not a valid cardinal direction (N, S, E, W)\n if (isolatedLetters?.some((ch) => !/^[nsew]$/i.test(ch))) {\n return [[], [violation('Unrecognized characters in input.')]];\n }\n\n const [tokens, errors] = pipesRunner(lexer(input), format);\n\n return [tokens, errors?.length ? errors.map(violation) : []];\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAwDA,SAAgB,MAAM,OAAe,QAA+B;AAClE,KAAI,CAAC,OAAO,OACV,QAAO,CAAC,EAAE,EAAE,CAAC,UAAU,YAAY,CAAC,CAAC;AAOvC,KAHwB,MAAM,MAAM,6BAA6B,EAG5C,MAAM,OAAO,CAAC,YAAY,KAAK,GAAG,CAAC,CACtD,QAAO,CAAC,EAAE,EAAE,CAAC,UAAU,oCAAoC,CAAC,CAAC;CAG/D,MAAM,CAAC,QAAQ,UAAU,YAAY,MAAM,MAAM,EAAE,OAAO;AAE1D,QAAO,CAAC,QAAQ,QAAQ,SAAS,OAAO,IAAI,UAAU,GAAG,EAAE,CAAC"}
@@ -14,9 +14,26 @@ import { Format } from "../index.js";
14
14
  import { Tokens } from "../lexer.js";
15
15
 
16
16
  //#region src/coordinates/latlon/internal/pipes/check-ambiguous.d.ts
17
+
17
18
  /**
18
19
  * Look for groupings of numbers that are ambiguous; no indicators, or no
19
20
  * dividers and not possibility of deducing where a divider should be inserted.
21
+ *
22
+ * @param tokens - Array of parsed coordinate tokens.
23
+ * @param _format - Optional coordinate format (LATLON or LONLAT), currently unused.
24
+ * @returns Pipe result with tokens and error status (true if ambiguous, false otherwise).
25
+ *
26
+ * @example
27
+ * ```typescript
28
+ * checkAmbiguousGrouping(['45', '30', '15', 'N'], 'LATLON');
29
+ * // Returns tokens with error=true if grouping is ambiguous
30
+ * ```
31
+ *
32
+ * @example
33
+ * ```typescript
34
+ * checkAmbiguousGrouping(['45', '30', 'N', '/', '122', '15', 'W'], 'LATLON');
35
+ * // Returns tokens with error=false (divider present, not ambiguous)
36
+ * ```
20
37
  */
21
38
  declare function checkAmbiguousGrouping(tokens: Tokens, _format?: Format): [string[], string | boolean];
22
39
  //#endregion
@@ -19,6 +19,22 @@ import { pipesResult } from "./index.js";
19
19
  /**
20
20
  * Look for groupings of numbers that are ambiguous; no indicators, or no
21
21
  * dividers and not possibility of deducing where a divider should be inserted.
22
+ *
23
+ * @param tokens - Array of parsed coordinate tokens.
24
+ * @param _format - Optional coordinate format (LATLON or LONLAT), currently unused.
25
+ * @returns Pipe result with tokens and error status (true if ambiguous, false otherwise).
26
+ *
27
+ * @example
28
+ * ```typescript
29
+ * checkAmbiguousGrouping(['45', '30', '15', 'N'], 'LATLON');
30
+ * // Returns tokens with error=true if grouping is ambiguous
31
+ * ```
32
+ *
33
+ * @example
34
+ * ```typescript
35
+ * checkAmbiguousGrouping(['45', '30', 'N', '/', '122', '15', 'W'], 'LATLON');
36
+ * // Returns tokens with error=false (divider present, not ambiguous)
37
+ * ```
22
38
  */
23
39
  function checkAmbiguousGrouping(tokens, _format) {
24
40
  if (tokens.includes(SYMBOLS.DIVIDER)) return pipesResult(tokens, false);
@@ -1 +1 @@
1
- {"version":3,"file":"check-ambiguous.js","names":[],"sources":["../../../../../src/coordinates/latlon/internal/pipes/check-ambiguous.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 Format, SYMBOL_PATTERNS, SYMBOLS } from '..';\nimport { pipesResult } from '../pipes';\nimport { simpler } from './simpler';\nimport type { Tokens } from '../lexer';\n\n/**\n * Look for groupings of numbers that are ambiguous; no indicators, or no\n * dividers and not possibility of deducing where a divider should be inserted.\n */\nexport function checkAmbiguousGrouping(tokens: Tokens, _format?: Format) {\n if (tokens.includes(SYMBOLS.DIVIDER)) {\n return pipesResult(tokens, false);\n }\n\n const simple = simpler(tokens);\n\n // 3-5 numbers after or before a single bearing indicator BNNN, NNNB\n const ambiguous = /^(?:(?:BN{3,5})|(?:N{3,5}B))$/.test(simple);\n // 3-5 numbers between 2 bearings indicators BNNNB\n const bookends = /^BN{3,5}B$/.test(simple);\n // \"helpful\" indicators:\n // 1. degrees number in the right-of-divider position e.g. # #°\n // 2. seconds number in the left-of-divider position e.g. # #\" #\n // 3. trailing bearings in the left-of-divider position e.g. # N #\n // 4. leading bearings in the right-of-divider position e.g. N # E #\n const helpful = tokens\n // helpful tokens are only helpful in the middle of the token list\n // not the beginning or end of the list\n .slice(1, -1)\n .reduce((acc, t) => {\n const helps =\n t.includes(SYMBOLS.DEGREES) ||\n t.includes(SYMBOLS.SECONDS) ||\n SYMBOL_PATTERNS.NSEW.test(t);\n\n return `${acc}${helps ? 'H' : '_'}`;\n }, '')\n .includes('H');\n // 3-5 numbers with no bearings indicators, and no helpful indicators\n const hopeless = /^N{3,5}$/.test(simple) && !helpful;\n\n return pipesResult(tokens, ambiguous || bookends || hopeless);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAsBA,SAAgB,uBAAuB,QAAgB,SAAkB;AACvE,KAAI,OAAO,SAAS,QAAQ,QAAQ,CAClC,QAAO,YAAY,QAAQ,MAAM;CAGnC,MAAM,SAAS,QAAQ,OAAO;CAG9B,MAAM,YAAY,gCAAgC,KAAK,OAAO;CAE9D,MAAM,WAAW,aAAa,KAAK,OAAO;CAM1C,MAAM,UAAU,OAGb,MAAM,GAAG,GAAG,CACZ,QAAQ,KAAK,MAAM;AAMlB,SAAO,GAAG,MAJR,EAAE,SAAS,QAAQ,QAAQ,IAC3B,EAAE,SAAS,QAAQ,QAAQ,IAC3B,gBAAgB,KAAK,KAAK,EAAE,GAEN,MAAM;IAC7B,GAAG,CACL,SAAS,IAAI;CAEhB,MAAM,WAAW,WAAW,KAAK,OAAO,IAAI,CAAC;AAE7C,QAAO,YAAY,QAAQ,aAAa,YAAY,SAAS"}
1
+ {"version":3,"file":"check-ambiguous.js","names":[],"sources":["../../../../../src/coordinates/latlon/internal/pipes/check-ambiguous.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 Format, SYMBOL_PATTERNS, SYMBOLS } from '..';\nimport { pipesResult } from '../pipes';\nimport { simpler } from './simpler';\nimport type { Tokens } from '../lexer';\n\n/**\n * Look for groupings of numbers that are ambiguous; no indicators, or no\n * dividers and not possibility of deducing where a divider should be inserted.\n *\n * @param tokens - Array of parsed coordinate tokens.\n * @param _format - Optional coordinate format (LATLON or LONLAT), currently unused.\n * @returns Pipe result with tokens and error status (true if ambiguous, false otherwise).\n *\n * @example\n * ```typescript\n * checkAmbiguousGrouping(['45', '30', '15', 'N'], 'LATLON');\n * // Returns tokens with error=true if grouping is ambiguous\n * ```\n *\n * @example\n * ```typescript\n * checkAmbiguousGrouping(['45', '30', 'N', '/', '122', '15', 'W'], 'LATLON');\n * // Returns tokens with error=false (divider present, not ambiguous)\n * ```\n */\nexport function checkAmbiguousGrouping(tokens: Tokens, _format?: Format) {\n if (tokens.includes(SYMBOLS.DIVIDER)) {\n return pipesResult(tokens, false);\n }\n\n const simple = simpler(tokens);\n\n // 3-5 numbers after or before a single bearing indicator BNNN, NNNB\n const ambiguous = /^(?:(?:BN{3,5})|(?:N{3,5}B))$/.test(simple);\n // 3-5 numbers between 2 bearings indicators BNNNB\n const bookends = /^BN{3,5}B$/.test(simple);\n // \"helpful\" indicators:\n // 1. degrees number in the right-of-divider position e.g. # #°\n // 2. seconds number in the left-of-divider position e.g. # #\" #\n // 3. trailing bearings in the left-of-divider position e.g. # N #\n // 4. leading bearings in the right-of-divider position e.g. N # E #\n const helpful = tokens\n // helpful tokens are only helpful in the middle of the token list\n // not the beginning or end of the list\n .slice(1, -1)\n .reduce((acc, t) => {\n const helps =\n t.includes(SYMBOLS.DEGREES) ||\n t.includes(SYMBOLS.SECONDS) ||\n SYMBOL_PATTERNS.NSEW.test(t);\n\n return `${acc}${helps ? 'H' : '_'}`;\n }, '')\n .includes('H');\n // 3-5 numbers with no bearings indicators, and no helpful indicators\n const hopeless = /^N{3,5}$/.test(simple) && !helpful;\n\n return pipesResult(tokens, ambiguous || bookends || hopeless);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsCA,SAAgB,uBAAuB,QAAgB,SAAkB;AACvE,KAAI,OAAO,SAAS,QAAQ,QAAQ,CAClC,QAAO,YAAY,QAAQ,MAAM;CAGnC,MAAM,SAAS,QAAQ,OAAO;CAG9B,MAAM,YAAY,gCAAgC,KAAK,OAAO;CAE9D,MAAM,WAAW,aAAa,KAAK,OAAO;CAM1C,MAAM,UAAU,OAGb,MAAM,GAAG,GAAG,CACZ,QAAQ,KAAK,MAAM;AAMlB,SAAO,GAAG,MAJR,EAAE,SAAS,QAAQ,QAAQ,IAC3B,EAAE,SAAS,QAAQ,QAAQ,IAC3B,gBAAgB,KAAK,KAAK,EAAE,GAEN,MAAM;IAC7B,GAAG,CACL,SAAS,IAAI;CAEhB,MAAM,WAAW,WAAW,KAAK,OAAO,IAAI,CAAC;AAE7C,QAAO,YAAY,QAAQ,aAAa,YAAY,SAAS"}
@@ -13,8 +13,33 @@
13
13
  import { Tokens } from "../lexer.js";
14
14
 
15
15
  //#region src/coordinates/latlon/internal/pipes/check-numbers.d.ts
16
+
16
17
  /**
17
18
  * Check for problems in the numeric values.
19
+ *
20
+ * Validates that there are at least 2 numbers, no more than 6 numbers total,
21
+ * and that negative values only appear in the first position (degrees).
22
+ *
23
+ * @param tokens - Array of parsed coordinate tokens.
24
+ * @returns Pipe result with tokens and error message (or false if valid).
25
+ *
26
+ * @example
27
+ * ```typescript
28
+ * checkNumberValues(['45', '30']);
29
+ * // Returns tokens with error=false (valid number count)
30
+ * ```
31
+ *
32
+ * @example
33
+ * ```typescript
34
+ * checkNumberValues(['45']);
35
+ * // Returns error: 'Too few numbers.'
36
+ * ```
37
+ *
38
+ * @example
39
+ * ```typescript
40
+ * checkNumberValues(['1', '2', '3', '4', '5', '6', '7']);
41
+ * // Returns error: 'Too many numbers.'
42
+ * ```
18
43
  */
19
44
  declare function checkNumberValues(tokens: Tokens): [string[], string | boolean];
20
45
  //#endregion
@@ -11,12 +11,37 @@
11
11
  */
12
12
 
13
13
 
14
+ import { SYMBOLS } from "../index.js";
14
15
  import { simpler } from "./simpler.js";
15
16
  import { pipesResult } from "./index.js";
16
17
 
17
18
  //#region src/coordinates/latlon/internal/pipes/check-numbers.ts
18
19
  /**
19
20
  * Check for problems in the numeric values.
21
+ *
22
+ * Validates that there are at least 2 numbers, no more than 6 numbers total,
23
+ * and that negative values only appear in the first position (degrees).
24
+ *
25
+ * @param tokens - Array of parsed coordinate tokens.
26
+ * @returns Pipe result with tokens and error message (or false if valid).
27
+ *
28
+ * @example
29
+ * ```typescript
30
+ * checkNumberValues(['45', '30']);
31
+ * // Returns tokens with error=false (valid number count)
32
+ * ```
33
+ *
34
+ * @example
35
+ * ```typescript
36
+ * checkNumberValues(['45']);
37
+ * // Returns error: 'Too few numbers.'
38
+ * ```
39
+ *
40
+ * @example
41
+ * ```typescript
42
+ * checkNumberValues(['1', '2', '3', '4', '5', '6', '7']);
43
+ * // Returns error: 'Too many numbers.'
44
+ * ```
20
45
  */
21
46
  function checkNumberValues(tokens) {
22
47
  const simple = simpler(tokens);
@@ -28,6 +53,14 @@ function checkNumberValues(tokens) {
28
53
  return acc;
29
54
  }, []).join("");
30
55
  if (!!pattern.match(/[^_]-./) && pattern !== "_--_") return pipesResult(tokens, "Negative value for non-degrees value found.");
56
+ if (tokens.includes(SYMBOLS.DIVIDER)) {
57
+ const divIdx = tokens.indexOf(SYMBOLS.DIVIDER);
58
+ const halves = [tokens.slice(0, divIdx), tokens.slice(divIdx + 1)];
59
+ for (const half of halves) {
60
+ const nums = half.filter((t) => /\d/.test(t));
61
+ if (nums.length > 1 && nums.some((n, i) => i > 0 && Number.parseFloat(n) < 0)) return pipesResult(tokens, "Negative value for non-degrees value found.");
62
+ }
63
+ }
31
64
  return pipesResult(tokens, false);
32
65
  }
33
66
 
@@ -1 +1 @@
1
- {"version":3,"file":"check-numbers.js","names":[],"sources":["../../../../../src/coordinates/latlon/internal/pipes/check-numbers.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 { pipesResult } from '../pipes';\nimport { simpler } from './simpler';\nimport type { Tokens } from '../lexer';\n\n/**\n * Check for problems in the numeric values.\n */\nexport function checkNumberValues(tokens: Tokens) {\n const simple = simpler(tokens);\n\n if ((simple.match(/N/g) ?? []).length < 2) {\n return pipesResult(tokens, 'Too few numbers.');\n }\n\n const error =\n // 4 consecutive numbers in specific formation is not going to be valid\n /(?:N{4,}BN+)|(?:N+BN{4,})/.test(simple) ||\n // more than 6 numbers total\n (simple.match(/N/g) ?? []).length > 6;\n\n if (error) {\n return pipesResult(tokens, 'Too many numbers.');\n }\n\n const pattern = tokens\n .reduce((acc, t) => {\n if (/\\d/.test(t)) {\n acc.push(Number.parseFloat(t) < 0 ? '-' : '+');\n } else {\n acc.push('_');\n }\n\n return acc;\n }, [] as string[])\n .join('');\n\n const matches = pattern.match(/[^_]-./);\n\n // special case '_--_' when the input is something like 'S -1 -1 W'\n // which is invalid for other reasons and will be caught elsewhere\n if (!!matches && pattern !== '_--_') {\n return pipesResult(tokens, 'Negative value for non-degrees value found.');\n }\n\n return pipesResult(tokens, false);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAoBA,SAAgB,kBAAkB,QAAgB;CAChD,MAAM,SAAS,QAAQ,OAAO;AAE9B,MAAK,OAAO,MAAM,KAAK,IAAI,EAAE,EAAE,SAAS,EACtC,QAAO,YAAY,QAAQ,mBAAmB;AAShD,KAJE,4BAA4B,KAAK,OAAO,KAEvC,OAAO,MAAM,KAAK,IAAI,EAAE,EAAE,SAAS,EAGpC,QAAO,YAAY,QAAQ,oBAAoB;CAGjD,MAAM,UAAU,OACb,QAAQ,KAAK,MAAM;AAClB,MAAI,KAAK,KAAK,EAAE,CACd,KAAI,KAAK,OAAO,WAAW,EAAE,GAAG,IAAI,MAAM,IAAI;MAE9C,KAAI,KAAK,IAAI;AAGf,SAAO;IACN,EAAE,CAAa,CACjB,KAAK,GAAG;AAMX,KAAI,CAAC,CAJW,QAAQ,MAAM,SAAS,IAItB,YAAY,OAC3B,QAAO,YAAY,QAAQ,8CAA8C;AAG3E,QAAO,YAAY,QAAQ,MAAM"}
1
+ {"version":3,"file":"check-numbers.js","names":[],"sources":["../../../../../src/coordinates/latlon/internal/pipes/check-numbers.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 { SYMBOLS } from '..';\nimport { pipesResult } from '../pipes';\nimport { simpler } from './simpler';\nimport type { Tokens } from '../lexer';\n\n/**\n * Check for problems in the numeric values.\n *\n * Validates that there are at least 2 numbers, no more than 6 numbers total,\n * and that negative values only appear in the first position (degrees).\n *\n * @param tokens - Array of parsed coordinate tokens.\n * @returns Pipe result with tokens and error message (or false if valid).\n *\n * @example\n * ```typescript\n * checkNumberValues(['45', '30']);\n * // Returns tokens with error=false (valid number count)\n * ```\n *\n * @example\n * ```typescript\n * checkNumberValues(['45']);\n * // Returns error: 'Too few numbers.'\n * ```\n *\n * @example\n * ```typescript\n * checkNumberValues(['1', '2', '3', '4', '5', '6', '7']);\n * // Returns error: 'Too many numbers.'\n * ```\n */\nexport function checkNumberValues(tokens: Tokens) {\n const simple = simpler(tokens);\n\n if ((simple.match(/N/g) ?? []).length < 2) {\n return pipesResult(tokens, 'Too few numbers.');\n }\n\n const error =\n // 4 consecutive numbers in specific formation is not going to be valid\n /(?:N{4,}BN+)|(?:N+BN{4,})/.test(simple) ||\n // more than 6 numbers total\n (simple.match(/N/g) ?? []).length > 6;\n\n if (error) {\n return pipesResult(tokens, 'Too many numbers.');\n }\n\n const pattern = tokens\n .reduce((acc, t) => {\n if (/\\d/.test(t)) {\n acc.push(Number.parseFloat(t) < 0 ? '-' : '+');\n } else {\n acc.push('_');\n }\n\n return acc;\n }, [] as string[])\n .join('');\n\n const matches = pattern.match(/[^_]-./);\n\n // special case '_--_' when the input is something like 'S -1 -1 W'\n // which is invalid for other reasons and will be caught elsewhere\n if (!!matches && pattern !== '_--_') {\n return pipesResult(tokens, 'Negative value for non-degrees value found.');\n }\n\n if (tokens.includes(SYMBOLS.DIVIDER)) {\n const divIdx = tokens.indexOf(SYMBOLS.DIVIDER);\n const halves = [tokens.slice(0, divIdx), tokens.slice(divIdx + 1)];\n\n for (const half of halves) {\n const nums = half.filter((t) => /\\d/.test(t));\n\n if (\n nums.length > 1 &&\n nums.some((n, i) => i > 0 && Number.parseFloat(n) < 0)\n ) {\n return pipesResult(\n tokens,\n 'Negative value for non-degrees value found.',\n );\n }\n }\n }\n\n return pipesResult(tokens, false);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6CA,SAAgB,kBAAkB,QAAgB;CAChD,MAAM,SAAS,QAAQ,OAAO;AAE9B,MAAK,OAAO,MAAM,KAAK,IAAI,EAAE,EAAE,SAAS,EACtC,QAAO,YAAY,QAAQ,mBAAmB;AAShD,KAJE,4BAA4B,KAAK,OAAO,KAEvC,OAAO,MAAM,KAAK,IAAI,EAAE,EAAE,SAAS,EAGpC,QAAO,YAAY,QAAQ,oBAAoB;CAGjD,MAAM,UAAU,OACb,QAAQ,KAAK,MAAM;AAClB,MAAI,KAAK,KAAK,EAAE,CACd,KAAI,KAAK,OAAO,WAAW,EAAE,GAAG,IAAI,MAAM,IAAI;MAE9C,KAAI,KAAK,IAAI;AAGf,SAAO;IACN,EAAE,CAAa,CACjB,KAAK,GAAG;AAMX,KAAI,CAAC,CAJW,QAAQ,MAAM,SAAS,IAItB,YAAY,OAC3B,QAAO,YAAY,QAAQ,8CAA8C;AAG3E,KAAI,OAAO,SAAS,QAAQ,QAAQ,EAAE;EACpC,MAAM,SAAS,OAAO,QAAQ,QAAQ,QAAQ;EAC9C,MAAM,SAAS,CAAC,OAAO,MAAM,GAAG,OAAO,EAAE,OAAO,MAAM,SAAS,EAAE,CAAC;AAElE,OAAK,MAAM,QAAQ,QAAQ;GACzB,MAAM,OAAO,KAAK,QAAQ,MAAM,KAAK,KAAK,EAAE,CAAC;AAE7C,OACE,KAAK,SAAS,KACd,KAAK,MAAM,GAAG,MAAM,IAAI,KAAK,OAAO,WAAW,EAAE,GAAG,EAAE,CAEtD,QAAO,YACL,QACA,8CACD;;;AAKP,QAAO,YAAY,QAAQ,MAAM"}
@@ -15,10 +15,27 @@ import { Tokens } from "../lexer.js";
15
15
  import { PipeResult } from "./index.js";
16
16
 
17
17
  //#region src/coordinates/latlon/internal/pipes/fix-bearings.d.ts
18
+
18
19
  /**
19
20
  * Normalize bearings - negative and positive numeric values to NSEW - and
20
21
  * positioning of bearings - after the numeric values - and fill in any missing
21
22
  * bearings if only one is provided.
23
+ *
24
+ * @param tokens - Array of parsed coordinate tokens containing a divider.
25
+ * @param format - Optional coordinate format (LATLON or LONLAT) to infer missing bearings.
26
+ * @returns Pipe result with normalized tokens and error status.
27
+ *
28
+ * @example
29
+ * ```typescript
30
+ * fixBearings(['45', 'N', '/', '122', 'W'], 'LATLON');
31
+ * // Returns ['45', 'N', '/', '122', 'W'] with error=false
32
+ * ```
33
+ *
34
+ * @example
35
+ * ```typescript
36
+ * fixBearings(['-45', '/', '122'], 'LATLON');
37
+ * // Returns ['45', 'S', '/', '122', 'E'] with error=false
38
+ * ```
22
39
  */
23
40
  declare function fixBearings(tokens: Tokens, format?: Format): PipeResult;
24
41
  //#endregion
@@ -27,6 +27,22 @@ const conflict = ([a, b]) => `Bearing (${a}) conflicts with negative number (${b
27
27
  * Normalize bearings - negative and positive numeric values to NSEW - and
28
28
  * positioning of bearings - after the numeric values - and fill in any missing
29
29
  * bearings if only one is provided.
30
+ *
31
+ * @param tokens - Array of parsed coordinate tokens containing a divider.
32
+ * @param format - Optional coordinate format (LATLON or LONLAT) to infer missing bearings.
33
+ * @returns Pipe result with normalized tokens and error status.
34
+ *
35
+ * @example
36
+ * ```typescript
37
+ * fixBearings(['45', 'N', '/', '122', 'W'], 'LATLON');
38
+ * // Returns ['45', 'N', '/', '122', 'W'] with error=false
39
+ * ```
40
+ *
41
+ * @example
42
+ * ```typescript
43
+ * fixBearings(['-45', '/', '122'], 'LATLON');
44
+ * // Returns ['45', 'S', '/', '122', 'E'] with error=false
45
+ * ```
30
46
  */
31
47
  function fixBearings(tokens, format) {
32
48
  const [left, right] = [tokens.slice(0, tokens.indexOf(SYMBOLS.DIVIDER)), tokens.slice(1 + tokens.indexOf(SYMBOLS.DIVIDER))].map(moveBearingsToHead);
@@ -65,6 +81,21 @@ function fixBearings(tokens, format) {
65
81
  * module only - so that it is easier to work with; moving a bearing to the
66
82
  * "head" allows for `push()` of subsequent number processing will keep the
67
83
  * order of the numeric values intact.
84
+ *
85
+ * @param coord - Array of tokens for one coordinate half (before or after divider).
86
+ * @returns New array with bearing moved to first position.
87
+ *
88
+ * @example
89
+ * ```typescript
90
+ * moveBearingsToHead(['45', '30', 'N']);
91
+ * // ['N', '45', '30']
92
+ * ```
93
+ *
94
+ * @example
95
+ * ```typescript
96
+ * moveBearingsToHead(['122', '15']);
97
+ * // ['122', '15']
98
+ * ```
68
99
  */
69
100
  function moveBearingsToHead(coord) {
70
101
  return coord.reduce((acc, t) => {
@@ -1 +1 @@
1
- {"version":3,"file":"fix-bearings.js","names":[],"sources":["../../../../../src/coordinates/latlon/internal/pipes/fix-bearings.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 { BEARINGS, type Format, SYMBOL_PATTERNS, SYMBOLS } from '..';\nimport type { Tokens } from '../lexer';\nimport type { PipeResult } from '../pipes';\n\nconst orthogonal = {\n N: BEARINGS.LON,\n S: BEARINGS.LON,\n E: BEARINGS.LAT,\n W: BEARINGS.LAT,\n};\n\nconst bearingConflictsWithNumber = (tokens: Tokens) =>\n tokens[0] &&\n tokens[1] &&\n SYMBOL_PATTERNS.NEGATIVE_SIGN.test(tokens[1]) &&\n SYMBOL_PATTERNS.NSEW.test(tokens[0]) &&\n !SYMBOL_PATTERNS.NEGATIVE_BEARINGS.test(tokens[0]);\n\nconst bePositive = (n: string) => n.replace(SYMBOL_PATTERNS.NEGATIVE_SIGN, '');\n\nconst conflict = ([a, b]: Tokens) =>\n `Bearing (${a}) conflicts with negative number (${b}).`;\n\n/**\n * Normalize bearings - negative and positive numeric values to NSEW - and\n * positioning of bearings - after the numeric values - and fill in any missing\n * bearings if only one is provided.\n */\nexport function fixBearings(tokens: Tokens, format?: Format): PipeResult {\n const [left, right] = [\n tokens.slice(0, tokens.indexOf(SYMBOLS.DIVIDER)),\n tokens.slice(1 + tokens.indexOf(SYMBOLS.DIVIDER)),\n ].map(moveBearingsToHead) as [Tokens, Tokens];\n\n if (bearingConflictsWithNumber(left)) {\n return [[], conflict(left)];\n }\n\n if (bearingConflictsWithNumber(right)) {\n return [[], conflict(right)];\n }\n\n const [leftHasBearing, rightHasBearing] = [left, right].map(\n (list) => !!(list?.[0] && SYMBOL_PATTERNS.NSEW.test(list[0])),\n );\n\n let leftBearing = '';\n let rightBearing = '';\n\n if (leftHasBearing && rightHasBearing) {\n leftBearing = left.shift() ?? '';\n rightBearing = right.shift() ?? '';\n } else if (leftHasBearing) {\n leftBearing = left.shift() ?? '';\n rightBearing =\n orthogonal[leftBearing as keyof typeof orthogonal][\n +SYMBOL_PATTERNS.NEGATIVE_SIGN.test(right[0] ?? '') as 0 | 1\n ];\n } else if (rightHasBearing) {\n rightBearing = right.shift() ?? '';\n leftBearing =\n orthogonal[rightBearing as keyof typeof orthogonal][\n +SYMBOL_PATTERNS.NEGATIVE_SIGN.test(right[0] ?? '') as 0 | 1\n ];\n } else if (format) {\n leftBearing = `${BEARINGS[format][0][+SYMBOL_PATTERNS.NEGATIVE_SIGN.test(`${left[0]}`)]}`;\n rightBearing = `${BEARINGS[format][1][+SYMBOL_PATTERNS.NEGATIVE_SIGN.test(`${right[0]}`)]}`;\n } else {\n // neither exist\n return [[...left, SYMBOLS.DIVIDER, ...right], false];\n }\n\n return [\n [\n ...left.map(bePositive),\n leftBearing,\n SYMBOLS.DIVIDER,\n ...right.map(bePositive),\n rightBearing,\n ],\n false,\n ];\n}\n\n/**\n * Move the bearings indicators to the first element in the list - in this\n * module only - so that it is easier to work with; moving a bearing to the\n * \"head\" allows for `push()` of subsequent number processing will keep the\n * order of the numeric values intact.\n */\nfunction moveBearingsToHead(coord: Tokens) {\n return coord.reduce((acc, t) => {\n if (/\\d/.test(t)) {\n acc.push(t);\n } else {\n acc.unshift(t);\n }\n\n return acc;\n }, [] as Tokens);\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAiBA,MAAM,aAAa;CACjB,GAAG,SAAS;CACZ,GAAG,SAAS;CACZ,GAAG,SAAS;CACZ,GAAG,SAAS;CACb;AAED,MAAM,8BAA8B,WAClC,OAAO,MACP,OAAO,MACP,gBAAgB,cAAc,KAAK,OAAO,GAAG,IAC7C,gBAAgB,KAAK,KAAK,OAAO,GAAG,IACpC,CAAC,gBAAgB,kBAAkB,KAAK,OAAO,GAAG;AAEpD,MAAM,cAAc,MAAc,EAAE,QAAQ,gBAAgB,eAAe,GAAG;AAE9E,MAAM,YAAY,CAAC,GAAG,OACpB,YAAY,EAAE,oCAAoC,EAAE;;;;;;AAOtD,SAAgB,YAAY,QAAgB,QAA6B;CACvE,MAAM,CAAC,MAAM,SAAS,CACpB,OAAO,MAAM,GAAG,OAAO,QAAQ,QAAQ,QAAQ,CAAC,EAChD,OAAO,MAAM,IAAI,OAAO,QAAQ,QAAQ,QAAQ,CAAC,CAClD,CAAC,IAAI,mBAAmB;AAEzB,KAAI,2BAA2B,KAAK,CAClC,QAAO,CAAC,EAAE,EAAE,SAAS,KAAK,CAAC;AAG7B,KAAI,2BAA2B,MAAM,CACnC,QAAO,CAAC,EAAE,EAAE,SAAS,MAAM,CAAC;CAG9B,MAAM,CAAC,gBAAgB,mBAAmB,CAAC,MAAM,MAAM,CAAC,KACrD,SAAS,CAAC,EAAE,OAAO,MAAM,gBAAgB,KAAK,KAAK,KAAK,GAAG,EAC7D;CAED,IAAI,cAAc;CAClB,IAAI,eAAe;AAEnB,KAAI,kBAAkB,iBAAiB;AACrC,gBAAc,KAAK,OAAO,IAAI;AAC9B,iBAAe,MAAM,OAAO,IAAI;YACvB,gBAAgB;AACzB,gBAAc,KAAK,OAAO,IAAI;AAC9B,iBACE,WAAW,aACT,CAAC,gBAAgB,cAAc,KAAK,MAAM,MAAM,GAAG;YAE9C,iBAAiB;AAC1B,iBAAe,MAAM,OAAO,IAAI;AAChC,gBACE,WAAW,cACT,CAAC,gBAAgB,cAAc,KAAK,MAAM,MAAM,GAAG;YAE9C,QAAQ;AACjB,gBAAc,GAAG,SAAS,QAAQ,GAAG,CAAC,gBAAgB,cAAc,KAAK,GAAG,KAAK,KAAK;AACtF,iBAAe,GAAG,SAAS,QAAQ,GAAG,CAAC,gBAAgB,cAAc,KAAK,GAAG,MAAM,KAAK;OAGxF,QAAO,CAAC;EAAC,GAAG;EAAM,QAAQ;EAAS,GAAG;EAAM,EAAE,MAAM;AAGtD,QAAO,CACL;EACE,GAAG,KAAK,IAAI,WAAW;EACvB;EACA,QAAQ;EACR,GAAG,MAAM,IAAI,WAAW;EACxB;EACD,EACD,MACD;;;;;;;;AASH,SAAS,mBAAmB,OAAe;AACzC,QAAO,MAAM,QAAQ,KAAK,MAAM;AAC9B,MAAI,KAAK,KAAK,EAAE,CACd,KAAI,KAAK,EAAE;MAEX,KAAI,QAAQ,EAAE;AAGhB,SAAO;IACN,EAAE,CAAW"}
1
+ {"version":3,"file":"fix-bearings.js","names":[],"sources":["../../../../../src/coordinates/latlon/internal/pipes/fix-bearings.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 { BEARINGS, type Format, SYMBOL_PATTERNS, SYMBOLS } from '..';\nimport type { Tokens } from '../lexer';\nimport type { PipeResult } from '../pipes';\n\nconst orthogonal = {\n N: BEARINGS.LON,\n S: BEARINGS.LON,\n E: BEARINGS.LAT,\n W: BEARINGS.LAT,\n};\n\nconst bearingConflictsWithNumber = (tokens: Tokens) =>\n tokens[0] &&\n tokens[1] &&\n SYMBOL_PATTERNS.NEGATIVE_SIGN.test(tokens[1]) &&\n SYMBOL_PATTERNS.NSEW.test(tokens[0]) &&\n !SYMBOL_PATTERNS.NEGATIVE_BEARINGS.test(tokens[0]);\n\nconst bePositive = (n: string) => n.replace(SYMBOL_PATTERNS.NEGATIVE_SIGN, '');\n\nconst conflict = ([a, b]: Tokens) =>\n `Bearing (${a}) conflicts with negative number (${b}).`;\n\n/**\n * Normalize bearings - negative and positive numeric values to NSEW - and\n * positioning of bearings - after the numeric values - and fill in any missing\n * bearings if only one is provided.\n *\n * @param tokens - Array of parsed coordinate tokens containing a divider.\n * @param format - Optional coordinate format (LATLON or LONLAT) to infer missing bearings.\n * @returns Pipe result with normalized tokens and error status.\n *\n * @example\n * ```typescript\n * fixBearings(['45', 'N', '/', '122', 'W'], 'LATLON');\n * // Returns ['45', 'N', '/', '122', 'W'] with error=false\n * ```\n *\n * @example\n * ```typescript\n * fixBearings(['-45', '/', '122'], 'LATLON');\n * // Returns ['45', 'S', '/', '122', 'E'] with error=false\n * ```\n */\nexport function fixBearings(tokens: Tokens, format?: Format): PipeResult {\n const [left, right] = [\n tokens.slice(0, tokens.indexOf(SYMBOLS.DIVIDER)),\n tokens.slice(1 + tokens.indexOf(SYMBOLS.DIVIDER)),\n ].map(moveBearingsToHead) as [Tokens, Tokens];\n\n if (bearingConflictsWithNumber(left)) {\n return [[], conflict(left)];\n }\n\n if (bearingConflictsWithNumber(right)) {\n return [[], conflict(right)];\n }\n\n const [leftHasBearing, rightHasBearing] = [left, right].map(\n (list) => !!(list?.[0] && SYMBOL_PATTERNS.NSEW.test(list[0])),\n );\n\n let leftBearing = '';\n let rightBearing = '';\n\n if (leftHasBearing && rightHasBearing) {\n leftBearing = left.shift() ?? '';\n rightBearing = right.shift() ?? '';\n } else if (leftHasBearing) {\n leftBearing = left.shift() ?? '';\n rightBearing =\n orthogonal[leftBearing as keyof typeof orthogonal][\n +SYMBOL_PATTERNS.NEGATIVE_SIGN.test(right[0] ?? '') as 0 | 1\n ];\n } else if (rightHasBearing) {\n rightBearing = right.shift() ?? '';\n leftBearing =\n orthogonal[rightBearing as keyof typeof orthogonal][\n +SYMBOL_PATTERNS.NEGATIVE_SIGN.test(right[0] ?? '') as 0 | 1\n ];\n } else if (format) {\n leftBearing = `${BEARINGS[format][0][+SYMBOL_PATTERNS.NEGATIVE_SIGN.test(`${left[0]}`)]}`;\n rightBearing = `${BEARINGS[format][1][+SYMBOL_PATTERNS.NEGATIVE_SIGN.test(`${right[0]}`)]}`;\n } else {\n // neither exist\n return [[...left, SYMBOLS.DIVIDER, ...right], false];\n }\n\n return [\n [\n ...left.map(bePositive),\n leftBearing,\n SYMBOLS.DIVIDER,\n ...right.map(bePositive),\n rightBearing,\n ],\n false,\n ];\n}\n\n/**\n * Move the bearings indicators to the first element in the list - in this\n * module only - so that it is easier to work with; moving a bearing to the\n * \"head\" allows for `push()` of subsequent number processing will keep the\n * order of the numeric values intact.\n *\n * @param coord - Array of tokens for one coordinate half (before or after divider).\n * @returns New array with bearing moved to first position.\n *\n * @example\n * ```typescript\n * moveBearingsToHead(['45', '30', 'N']);\n * // ['N', '45', '30']\n * ```\n *\n * @example\n * ```typescript\n * moveBearingsToHead(['122', '15']);\n * // ['122', '15']\n * ```\n */\nfunction moveBearingsToHead(coord: Tokens) {\n return coord.reduce((acc, t) => {\n if (/\\d/.test(t)) {\n acc.push(t);\n } else {\n acc.unshift(t);\n }\n\n return acc;\n }, [] as Tokens);\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAiBA,MAAM,aAAa;CACjB,GAAG,SAAS;CACZ,GAAG,SAAS;CACZ,GAAG,SAAS;CACZ,GAAG,SAAS;CACb;AAED,MAAM,8BAA8B,WAClC,OAAO,MACP,OAAO,MACP,gBAAgB,cAAc,KAAK,OAAO,GAAG,IAC7C,gBAAgB,KAAK,KAAK,OAAO,GAAG,IACpC,CAAC,gBAAgB,kBAAkB,KAAK,OAAO,GAAG;AAEpD,MAAM,cAAc,MAAc,EAAE,QAAQ,gBAAgB,eAAe,GAAG;AAE9E,MAAM,YAAY,CAAC,GAAG,OACpB,YAAY,EAAE,oCAAoC,EAAE;;;;;;;;;;;;;;;;;;;;;;AAuBtD,SAAgB,YAAY,QAAgB,QAA6B;CACvE,MAAM,CAAC,MAAM,SAAS,CACpB,OAAO,MAAM,GAAG,OAAO,QAAQ,QAAQ,QAAQ,CAAC,EAChD,OAAO,MAAM,IAAI,OAAO,QAAQ,QAAQ,QAAQ,CAAC,CAClD,CAAC,IAAI,mBAAmB;AAEzB,KAAI,2BAA2B,KAAK,CAClC,QAAO,CAAC,EAAE,EAAE,SAAS,KAAK,CAAC;AAG7B,KAAI,2BAA2B,MAAM,CACnC,QAAO,CAAC,EAAE,EAAE,SAAS,MAAM,CAAC;CAG9B,MAAM,CAAC,gBAAgB,mBAAmB,CAAC,MAAM,MAAM,CAAC,KACrD,SAAS,CAAC,EAAE,OAAO,MAAM,gBAAgB,KAAK,KAAK,KAAK,GAAG,EAC7D;CAED,IAAI,cAAc;CAClB,IAAI,eAAe;AAEnB,KAAI,kBAAkB,iBAAiB;AACrC,gBAAc,KAAK,OAAO,IAAI;AAC9B,iBAAe,MAAM,OAAO,IAAI;YACvB,gBAAgB;AACzB,gBAAc,KAAK,OAAO,IAAI;AAC9B,iBACE,WAAW,aACT,CAAC,gBAAgB,cAAc,KAAK,MAAM,MAAM,GAAG;YAE9C,iBAAiB;AAC1B,iBAAe,MAAM,OAAO,IAAI;AAChC,gBACE,WAAW,cACT,CAAC,gBAAgB,cAAc,KAAK,MAAM,MAAM,GAAG;YAE9C,QAAQ;AACjB,gBAAc,GAAG,SAAS,QAAQ,GAAG,CAAC,gBAAgB,cAAc,KAAK,GAAG,KAAK,KAAK;AACtF,iBAAe,GAAG,SAAS,QAAQ,GAAG,CAAC,gBAAgB,cAAc,KAAK,GAAG,MAAM,KAAK;OAGxF,QAAO,CAAC;EAAC,GAAG;EAAM,QAAQ;EAAS,GAAG;EAAM,EAAE,MAAM;AAGtD,QAAO,CACL;EACE,GAAG,KAAK,IAAI,WAAW;EACvB;EACA,QAAQ;EACR,GAAG,MAAM,IAAI,WAAW;EACxB;EACD,EACD,MACD;;;;;;;;;;;;;;;;;;;;;;;AAwBH,SAAS,mBAAmB,OAAe;AACzC,QAAO,MAAM,QAAQ,KAAK,MAAM;AAC9B,MAAI,KAAK,KAAK,EAAE,CACd,KAAI,KAAK,EAAE;MAEX,KAAI,QAAQ,EAAE;AAGhB,SAAO;IACN,EAAE,CAAW"}
@@ -15,11 +15,28 @@ import { Tokens } from "../lexer.js";
15
15
  import { PipeResult } from "./index.js";
16
16
 
17
17
  //#region src/coordinates/latlon/internal/pipes/fix-dividers.d.ts
18
+
18
19
  /**
19
20
  * For tokens lists without a divider, `fixDivider` attempts to determine the
20
21
  * __safe__ location to add a divider based on the existing formatting of the
21
22
  * coordinate: numbers, number positions, and number indicators.
22
23
  *
24
+ * @param original - Array of coordinate tokens without a divider.
25
+ * @param _format - Optional coordinate format (LATLON or LONLAT), currently unused.
26
+ * @returns Pipe result with divider inserted, or error=true if no safe location found.
27
+ *
28
+ * @example
29
+ * ```typescript
30
+ * fixDivider(['45°', '30'', 'N', '122°', '15'', 'W']);
31
+ * // Returns tokens with divider inserted between latitude and longitude
32
+ * ```
33
+ *
34
+ * @example
35
+ * ```typescript
36
+ * fixDivider(['45', '30', 'N']);
37
+ * // Returns error=true (cannot safely determine divider position)
38
+ * ```
39
+ *
23
40
  * @remarks
24
41
  * pure function
25
42
  */
@@ -22,6 +22,19 @@ const SIMPLER_PATTERNS = {
22
22
  BNNB: 2,
23
23
  BNN: 2
24
24
  };
25
+ /**
26
+ * Inserts a divider token at the specified index in the tokens array.
27
+ *
28
+ * @param tokens - Array of coordinate tokens without a divider.
29
+ * @param index - Position where the divider should be inserted.
30
+ * @returns Pipe result with divider inserted and error=false.
31
+ *
32
+ * @example
33
+ * ```typescript
34
+ * insertDivider(['45', '30', 'N', '122', '15', 'W'], 3);
35
+ * // [['45', '30', 'N', '/', '122', '15', 'W'], false]
36
+ * ```
37
+ */
25
38
  const insertDivider = (tokens, index) => [[
26
39
  ...tokens.slice(0, index),
27
40
  SYMBOLS.DIVIDER,
@@ -32,6 +45,22 @@ const insertDivider = (tokens, index) => [[
32
45
  * __safe__ location to add a divider based on the existing formatting of the
33
46
  * coordinate: numbers, number positions, and number indicators.
34
47
  *
48
+ * @param original - Array of coordinate tokens without a divider.
49
+ * @param _format - Optional coordinate format (LATLON or LONLAT), currently unused.
50
+ * @returns Pipe result with divider inserted, or error=true if no safe location found.
51
+ *
52
+ * @example
53
+ * ```typescript
54
+ * fixDivider(['45°', '30'', 'N', '122°', '15'', 'W']);
55
+ * // Returns tokens with divider inserted between latitude and longitude
56
+ * ```
57
+ *
58
+ * @example
59
+ * ```typescript
60
+ * fixDivider(['45', '30', 'N']);
61
+ * // Returns error=true (cannot safely determine divider position)
62
+ * ```
63
+ *
35
64
  * @remarks
36
65
  * pure function
37
66
  */
@@ -1 +1 @@
1
- {"version":3,"file":"fix-dividers.js","names":[],"sources":["../../../../../src/coordinates/latlon/internal/pipes/fix-dividers.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 Format, SYMBOLS } from '..';\nimport { getGenomeIndex } from './genome';\nimport { simpler } from './simpler';\nimport type { Tokens } from '../lexer';\nimport type { PipeResult } from '../pipes';\n\n// N = number\n// B = bearing\nconst SIMPLER_PATTERNS = {\n NN: 1,\n NNB: 1,\n BNNB: 2,\n BNN: 2,\n};\n\nconst insertDivider = (tokens: Tokens, index: number): PipeResult => [\n [...tokens.slice(0, index), SYMBOLS.DIVIDER, ...tokens.slice(index)],\n false,\n];\n\n/**\n * For tokens lists without a divider, `fixDivider` attempts to determine the\n * __safe__ location to add a divider based on the existing formatting of the\n * coordinate: numbers, number positions, and number indicators.\n *\n * @remarks\n * pure function\n */\nexport function fixDivider(original: Tokens, _format?: Format): PipeResult {\n // if there is already a divider then there is nothing to do\n if (original.includes(SYMBOLS.DIVIDER)) {\n return [original, false];\n }\n\n // disconnect from argument memory space so we aren't working on shared memory\n const tokens = original.slice(0);\n\n const genomeIndex = getGenomeIndex(tokens);\n\n if (genomeIndex) {\n return insertDivider(tokens, genomeIndex);\n }\n\n const simple = simpler(tokens) as keyof typeof SIMPLER_PATTERNS;\n\n if (SIMPLER_PATTERNS[simple]) {\n return insertDivider(tokens, SIMPLER_PATTERNS[simple]);\n }\n\n // no position is found to be a safe location to insert a divider; any placement\n // would be a guess and therefor only has a 50% chance of being wrong or right\n return [[], true];\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAqBA,MAAM,mBAAmB;CACvB,IAAI;CACJ,KAAK;CACL,MAAM;CACN,KAAK;CACN;AAED,MAAM,iBAAiB,QAAgB,UAA8B,CACnE;CAAC,GAAG,OAAO,MAAM,GAAG,MAAM;CAAE,QAAQ;CAAS,GAAG,OAAO,MAAM,MAAM;CAAC,EACpE,MACD;;;;;;;;;AAUD,SAAgB,WAAW,UAAkB,SAA8B;AAEzE,KAAI,SAAS,SAAS,QAAQ,QAAQ,CACpC,QAAO,CAAC,UAAU,MAAM;CAI1B,MAAM,SAAS,SAAS,MAAM,EAAE;CAEhC,MAAM,cAAc,eAAe,OAAO;AAE1C,KAAI,YACF,QAAO,cAAc,QAAQ,YAAY;CAG3C,MAAM,SAAS,QAAQ,OAAO;AAE9B,KAAI,iBAAiB,QACnB,QAAO,cAAc,QAAQ,iBAAiB,QAAQ;AAKxD,QAAO,CAAC,EAAE,EAAE,KAAK"}
1
+ {"version":3,"file":"fix-dividers.js","names":[],"sources":["../../../../../src/coordinates/latlon/internal/pipes/fix-dividers.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 Format, SYMBOLS } from '..';\nimport { getGenomeIndex } from './genome';\nimport { simpler } from './simpler';\nimport type { Tokens } from '../lexer';\nimport type { PipeResult } from '../pipes';\n\n// N = number\n// B = bearing\nconst SIMPLER_PATTERNS = {\n NN: 1,\n NNB: 1,\n BNNB: 2,\n BNN: 2,\n};\n\n/**\n * Inserts a divider token at the specified index in the tokens array.\n *\n * @param tokens - Array of coordinate tokens without a divider.\n * @param index - Position where the divider should be inserted.\n * @returns Pipe result with divider inserted and error=false.\n *\n * @example\n * ```typescript\n * insertDivider(['45', '30', 'N', '122', '15', 'W'], 3);\n * // [['45', '30', 'N', '/', '122', '15', 'W'], false]\n * ```\n */\nconst insertDivider = (tokens: Tokens, index: number): PipeResult => [\n [...tokens.slice(0, index), SYMBOLS.DIVIDER, ...tokens.slice(index)],\n false,\n];\n\n/**\n * For tokens lists without a divider, `fixDivider` attempts to determine the\n * __safe__ location to add a divider based on the existing formatting of the\n * coordinate: numbers, number positions, and number indicators.\n *\n * @param original - Array of coordinate tokens without a divider.\n * @param _format - Optional coordinate format (LATLON or LONLAT), currently unused.\n * @returns Pipe result with divider inserted, or error=true if no safe location found.\n *\n * @example\n * ```typescript\n * fixDivider(['45°', '30'', 'N', '122°', '15'', 'W']);\n * // Returns tokens with divider inserted between latitude and longitude\n * ```\n *\n * @example\n * ```typescript\n * fixDivider(['45', '30', 'N']);\n * // Returns error=true (cannot safely determine divider position)\n * ```\n *\n * @remarks\n * pure function\n */\nexport function fixDivider(original: Tokens, _format?: Format): PipeResult {\n // if there is already a divider then there is nothing to do\n if (original.includes(SYMBOLS.DIVIDER)) {\n return [original, false];\n }\n\n // disconnect from argument memory space so we aren't working on shared memory\n const tokens = original.slice(0);\n\n const genomeIndex = getGenomeIndex(tokens);\n\n if (genomeIndex) {\n return insertDivider(tokens, genomeIndex);\n }\n\n const simple = simpler(tokens) as keyof typeof SIMPLER_PATTERNS;\n\n if (SIMPLER_PATTERNS[simple]) {\n return insertDivider(tokens, SIMPLER_PATTERNS[simple]);\n }\n\n // no position is found to be a safe location to insert a divider; any placement\n // would be a guess and therefor only has a 50% chance of being wrong or right\n return [[], true];\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAqBA,MAAM,mBAAmB;CACvB,IAAI;CACJ,KAAK;CACL,MAAM;CACN,KAAK;CACN;;;;;;;;;;;;;;AAeD,MAAM,iBAAiB,QAAgB,UAA8B,CACnE;CAAC,GAAG,OAAO,MAAM,GAAG,MAAM;CAAE,QAAQ;CAAS,GAAG,OAAO,MAAM,MAAM;CAAC,EACpE,MACD;;;;;;;;;;;;;;;;;;;;;;;;;AA0BD,SAAgB,WAAW,UAAkB,SAA8B;AAEzE,KAAI,SAAS,SAAS,QAAQ,QAAQ,CACpC,QAAO,CAAC,UAAU,MAAM;CAI1B,MAAM,SAAS,SAAS,MAAM,EAAE;CAEhC,MAAM,cAAc,eAAe,OAAO;AAE1C,KAAI,YACF,QAAO,cAAc,QAAQ,YAAY;CAG3C,MAAM,SAAS,QAAQ,OAAO;AAE9B,KAAI,iBAAiB,QACnB,QAAO,cAAc,QAAQ,iBAAiB,QAAQ;AAKxD,QAAO,CAAC,EAAE,EAAE,KAAK"}
@@ -13,9 +13,25 @@
13
13
  import { Tokens } from "../lexer.js";
14
14
 
15
15
  //#region src/coordinates/latlon/internal/pipes/genome.d.ts
16
+
16
17
  /**
17
18
  * Use the "genome" sequence of the token list to find the index for inserting
18
19
  * a missing divider token.
20
+ *
21
+ * @param tokens - Array of coordinate tokens to analyze.
22
+ * @returns Index position where divider should be inserted, or 0 if pattern doesn't match.
23
+ *
24
+ * @example
25
+ * ```typescript
26
+ * getGenomeIndex(['45°', '30'', 'N', '122°', '15'', 'W']);
27
+ * // 3 (insert divider after latitude components)
28
+ * ```
29
+ *
30
+ * @example
31
+ * ```typescript
32
+ * getGenomeIndex(['45', '30', '15']);
33
+ * // 0 (pattern doesn't match genome sequence)
34
+ * ```
19
35
  */
20
36
  declare function getGenomeIndex(tokens: Tokens): number;
21
37
  //#endregion