@accelint/geo 0.6.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/README.md +98 -1
  3. package/catalog-info.yaml +1 -1
  4. package/dist/cartesian.js +1 -1
  5. package/dist/coordinates/coordinate.d.ts +1 -1
  6. package/dist/coordinates/coordinate.js +1 -1
  7. package/dist/coordinates/latlon/decimal-degrees/formatter.d.ts +36 -3
  8. package/dist/coordinates/latlon/decimal-degrees/formatter.js +34 -5
  9. package/dist/coordinates/latlon/decimal-degrees/formatter.js.map +1 -1
  10. package/dist/coordinates/latlon/decimal-degrees/parser.d.ts +1 -1
  11. package/dist/coordinates/latlon/decimal-degrees/parser.js +1 -1
  12. package/dist/coordinates/latlon/decimal-degrees/system.d.ts +1 -1
  13. package/dist/coordinates/latlon/decimal-degrees/system.js +6 -4
  14. package/dist/coordinates/latlon/decimal-degrees/system.js.map +1 -1
  15. package/dist/coordinates/latlon/degrees-decimal-minutes/formatter.d.ts +43 -3
  16. package/dist/coordinates/latlon/degrees-decimal-minutes/formatter.js +61 -6
  17. package/dist/coordinates/latlon/degrees-decimal-minutes/formatter.js.map +1 -1
  18. package/dist/coordinates/latlon/degrees-decimal-minutes/parser.d.ts +1 -1
  19. package/dist/coordinates/latlon/degrees-decimal-minutes/parser.js +1 -1
  20. package/dist/coordinates/latlon/degrees-decimal-minutes/system.d.ts +1 -1
  21. package/dist/coordinates/latlon/degrees-decimal-minutes/system.js +8 -7
  22. package/dist/coordinates/latlon/degrees-decimal-minutes/system.js.map +1 -1
  23. package/dist/coordinates/latlon/degrees-minutes-seconds/formatter.d.ts +46 -3
  24. package/dist/coordinates/latlon/degrees-minutes-seconds/formatter.js +67 -8
  25. package/dist/coordinates/latlon/degrees-minutes-seconds/formatter.js.map +1 -1
  26. package/dist/coordinates/latlon/degrees-minutes-seconds/parser.d.ts +1 -1
  27. package/dist/coordinates/latlon/degrees-minutes-seconds/parser.js +1 -1
  28. package/dist/coordinates/latlon/degrees-minutes-seconds/system.d.ts +1 -1
  29. package/dist/coordinates/latlon/degrees-minutes-seconds/system.js +8 -7
  30. package/dist/coordinates/latlon/degrees-minutes-seconds/system.js.map +1 -1
  31. package/dist/coordinates/latlon/internal/coordinate-system.d.ts +1 -1
  32. package/dist/coordinates/latlon/internal/coordinate-system.js +1 -1
  33. package/dist/coordinates/latlon/internal/create-cache.d.ts +1 -1
  34. package/dist/coordinates/latlon/internal/create-cache.js +1 -1
  35. package/dist/coordinates/latlon/internal/exhaustive-errors.js +1 -1
  36. package/dist/coordinates/latlon/internal/format.d.ts +50 -5
  37. package/dist/coordinates/latlon/internal/format.js +40 -10
  38. package/dist/coordinates/latlon/internal/format.js.map +1 -1
  39. package/dist/coordinates/latlon/internal/in-range.js +1 -1
  40. package/dist/coordinates/latlon/internal/index.d.ts +4 -3
  41. package/dist/coordinates/latlon/internal/index.js +4 -3
  42. package/dist/coordinates/latlon/internal/index.js.map +1 -1
  43. package/dist/coordinates/latlon/internal/lexer.js +3 -2
  44. package/dist/coordinates/latlon/internal/lexer.js.map +1 -1
  45. package/dist/coordinates/latlon/internal/normalize.js +1 -1
  46. package/dist/coordinates/latlon/internal/ordinal.d.ts +39 -9
  47. package/dist/coordinates/latlon/internal/ordinal.js +32 -13
  48. package/dist/coordinates/latlon/internal/ordinal.js.map +1 -1
  49. package/dist/coordinates/latlon/internal/parse-format.d.ts +1 -1
  50. package/dist/coordinates/latlon/internal/parse-format.js +1 -1
  51. package/dist/coordinates/latlon/internal/parse.d.ts +1 -1
  52. package/dist/coordinates/latlon/internal/parse.js +1 -1
  53. package/dist/coordinates/latlon/internal/pipes/check-ambiguous.d.ts +1 -1
  54. package/dist/coordinates/latlon/internal/pipes/check-ambiguous.js +1 -1
  55. package/dist/coordinates/latlon/internal/pipes/check-numbers.d.ts +1 -1
  56. package/dist/coordinates/latlon/internal/pipes/check-numbers.js +1 -1
  57. package/dist/coordinates/latlon/internal/pipes/fix-bearings.d.ts +1 -1
  58. package/dist/coordinates/latlon/internal/pipes/fix-bearings.js +1 -1
  59. package/dist/coordinates/latlon/internal/pipes/fix-dividers.d.ts +1 -1
  60. package/dist/coordinates/latlon/internal/pipes/fix-dividers.js +1 -1
  61. package/dist/coordinates/latlon/internal/pipes/genome.d.ts +1 -1
  62. package/dist/coordinates/latlon/internal/pipes/genome.js +1 -1
  63. package/dist/coordinates/latlon/internal/pipes/index.d.ts +1 -1
  64. package/dist/coordinates/latlon/internal/pipes/index.js +1 -1
  65. package/dist/coordinates/latlon/internal/pipes/simpler.d.ts +1 -1
  66. package/dist/coordinates/latlon/internal/pipes/simpler.js +1 -1
  67. package/dist/coordinates/latlon/internal/plain-decimal.d.ts +35 -0
  68. package/dist/coordinates/latlon/internal/plain-decimal.js +59 -0
  69. package/dist/coordinates/latlon/internal/plain-decimal.js.map +1 -0
  70. package/dist/coordinates/latlon/internal/validate.d.ts +28 -1
  71. package/dist/coordinates/latlon/internal/validate.js +31 -2
  72. package/dist/coordinates/latlon/internal/validate.js.map +1 -1
  73. package/dist/coordinates/latlon/internal/violation.js +1 -1
  74. package/dist/coordinates/mgrs/parser.d.ts +1 -1
  75. package/dist/coordinates/mgrs/parser.js +1 -1
  76. package/dist/coordinates/mgrs/parts.d.ts +86 -0
  77. package/dist/coordinates/mgrs/parts.js +94 -0
  78. package/dist/coordinates/mgrs/parts.js.map +1 -0
  79. package/dist/coordinates/mgrs/system.d.ts +1 -1
  80. package/dist/coordinates/mgrs/system.js +5 -3
  81. package/dist/coordinates/mgrs/system.js.map +1 -1
  82. package/dist/coordinates/utm/parser.d.ts +1 -1
  83. package/dist/coordinates/utm/parser.js +1 -1
  84. package/dist/coordinates/utm/parts.d.ts +134 -0
  85. package/dist/coordinates/utm/parts.js +142 -0
  86. package/dist/coordinates/utm/parts.js.map +1 -0
  87. package/dist/coordinates/utm/system.d.ts +1 -1
  88. package/dist/coordinates/utm/system.js +5 -4
  89. package/dist/coordinates/utm/system.js.map +1 -1
  90. package/dist/index.d.ts +11 -8
  91. package/dist/index.js +11 -8
  92. package/dist/patterning.js +1 -1
  93. package/package.json +4 -1
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["Patterning.optional","Patterning.capture","Patterning.merge"],"sources":["../../../../src/coordinates/latlon/internal/index.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 * as Patterning from '@/patterning';\n\nexport type Axes = 'LAT' | 'LON';\nexport type Compass = 'N' | 'S' | 'E' | 'W';\nexport type Errors = string[];\nexport type Format = (typeof FORMATS)[number];\n\n/**\n * Bearings are the consistent/explicit identifiers of directionality of a\n * coordinate component; this library has opted for these over implicit\n * indication by number sign not because there is an inherent superiority\n * but because something had to be chosen.\n *\n * NOTE: these arrays are position-important; negative values are [1] and\n * positive values are [0] so that they can be consistently indexed using\n * an `isNegative` boolean to reference the negative bearing of each axis\n */\nexport const BEARINGS = {\n LAT: ['N', 'S'],\n LON: ['E', 'W'],\n LATLON: [\n ['N', 'S'],\n ['E', 'W'],\n ],\n LONLAT: [\n ['E', 'W'],\n ['N', 'S'],\n ],\n} as const;\n\nexport const FORMATS = ['LATLON', 'LONLAT'] as const;\nexport const FORMATS_DEFAULT = FORMATS[0];\n\nexport const LIMITS = { LATLON: [90, 180], LONLAT: [180, 90] } as const;\n\nexport const SYMBOLS = {\n DEGREES: '°',\n MINUTES: \"'\",\n SECONDS: '\"',\n DIVIDER: '/',\n};\n\nexport const SYMBOL_PATTERNS = {\n LAT: new RegExp(`[${BEARINGS.LAT.join('')}]`),\n LON: new RegExp(`[${BEARINGS.LON.join('')}]`),\n NSEW: new RegExp(`[${[...BEARINGS.LAT, ...BEARINGS.LON].join('')}]`),\n NEGATIVE_BEARINGS: /[SW]/i,\n NEGATIVE_SIGN: /-/,\n\n DEGREES: new RegExp(SYMBOLS.DEGREES),\n MINUTES: new RegExp(SYMBOLS.MINUTES),\n SECONDS: new RegExp(SYMBOLS.SECONDS),\n\n DIVIDER: new RegExp(SYMBOLS.DIVIDER),\n\n DMS: new RegExp(\n `[${[SYMBOLS.DEGREES, SYMBOLS.MINUTES, SYMBOLS.SECONDS].join('')}]`,\n ),\n\n // divider: {\n // first: /(?<NAMED_SEPARATOR>:?)/,\n // follow: new RegExp(`\\\\s?\\\\k<${'NAMED_SEPARATOR'}>\\\\s?`),\n // },\n} as const;\n\n/**\n * Creates a regex pattern for matching decimal minutes or seconds values.\n *\n * Generates a pattern that matches numeric values in the range 0-59.999... with optional\n * leading zeros, decimal points, and symbol indicators, using lookbehind and lookahead\n * to prevent partial matches within larger numbers.\n *\n * @param symbol - Regular expression for the symbol (minutes ' or seconds \" indicator).\n * @returns Combined regex pattern with precise boundary matching.\n *\n * @example\n * ```typescript\n * const minutesPattern = decimalSecAndMin(SYMBOL_PATTERNS.MINUTES);\n * // Matches: \"30.5'\", \"59.999999999\", \".5'\", \"001'\", etc.\n * ```\n *\n * @example\n * ```typescript\n * const secondsPattern = decimalSecAndMin(SYMBOL_PATTERNS.SECONDS);\n * // Matches: '45.23\"', '0.5', '59.9999999999\"', etc.\n * ```\n */\nconst decimalSecAndMin = (symbol: RegExp) =>\n Patterning.optional(\n // Negative lookbehind\n // to ensure that the match is not preceded by a digit,\n // avoiding partial matches within larger numbers.\n /(?<!\\d)/,\n\n // 0-59 including 10 decimal places and leading zeros or no number before\n // acceptable values: 0, 0.1234567890, .9876543210, 001, 59.9999999999\n /([-+]?0*(?:[0-5]?\\d|\\.\\d{1,10})(?:\\.\\d{1,10})?)/,\n\n Patterning.optional(symbol),\n\n // Negative lookahead\n // to ensure that the match is not followed by a digit,\n // avoiding partial matches within larger numbers.\n /(?!\\d)/,\n );\n\nexport const PARTIAL_PATTERNS = {\n ' ': /\\s*/,\n '/': Patterning.capture(SYMBOL_PATTERNS.DIVIDER),\n NS: Patterning.optional(Patterning.capture(SYMBOL_PATTERNS.LAT)),\n EW: Patterning.optional(Patterning.capture(SYMBOL_PATTERNS.LON)),\n\n degLatDec: Patterning.merge(\n Patterning.capture(\n /0*(?:90(?:\\.0{1,10})?)/, // 90[.0]\n /|/,\n /(?:0?[0-8]?\\d(?:\\.\\d{1,10})?)/, // [0]0[.0]-89[.9]\n ),\n Patterning.optional(SYMBOL_PATTERNS.DEGREES),\n ),\n degLonDec: Patterning.merge(\n Patterning.capture(\n /(?:180(?:\\.0{1,10})?)/, // 180[.0]\n /|/,\n /(?:0*(?:\\d{1,2}|1[0-7]\\d)(?:\\.\\d{1,10})?)/, // [00]0[.0]-179[.9]\n ),\n Patterning.optional(SYMBOL_PATTERNS.DEGREES),\n ),\n degLat: Patterning.merge(\n Patterning.capture(\n /(?:0?90)/, // 90\n /|/,\n /(?:0?[0-8]?\\d)/, // [0]0-89\n ),\n Patterning.optional(SYMBOL_PATTERNS.DEGREES),\n ),\n degLon: Patterning.merge(\n Patterning.capture(\n /(?:180)/, // 180\n /|/,\n /(?:0*(?:\\d{1,2}|1[0-7]\\d))/, // [00]0-179\n ),\n Patterning.optional(SYMBOL_PATTERNS.DEGREES),\n ),\n min: Patterning.merge(\n Patterning.optional(\n Patterning.capture(\n /(?:0?[0-5]?\\d)?/, // [0]0-59\n ),\n Patterning.optional(SYMBOL_PATTERNS.MINUTES),\n ),\n ),\n minDec: decimalSecAndMin(SYMBOL_PATTERNS.MINUTES),\n secDec: decimalSecAndMin(SYMBOL_PATTERNS.SECONDS),\n} as const;\n\nexport {\n type CoordinateInput,\n type CoordinateInternalValue,\n type CoordinateObject,\n type CoordinateTuple,\n type LatLonTuple,\n type LonLatTuple,\n isCoordinateObject,\n isCoordinateTuple,\n normalizeObjectToLatLon,\n tupleToLatLon,\n} from './normalize';\nexport {\n isFiniteNumber,\n validateNumericCoordinate,\n validateSignedRange,\n} from './validate';\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8BA,MAAa,WAAW;CACtB,KAAK,CAAC,KAAK,IAAI;CACf,KAAK,CAAC,KAAK,IAAI;CACf,QAAQ,CACN,CAAC,KAAK,IAAI,EACV,CAAC,KAAK,IAAI,CACX;CACD,QAAQ,CACN,CAAC,KAAK,IAAI,EACV,CAAC,KAAK,IAAI,CACX;CACF;AAED,MAAa,UAAU,CAAC,UAAU,SAAS;AAC3C,MAAa,kBAAkB,QAAQ;AAEvC,MAAa,SAAS;CAAE,QAAQ,CAAC,IAAI,IAAI;CAAE,QAAQ,CAAC,KAAK,GAAG;CAAE;AAE9D,MAAa,UAAU;CACrB,SAAS;CACT,SAAS;CACT,SAAS;CACT,SAAS;CACV;AAED,MAAa,kBAAkB;CAC7B,qBAAK,IAAI,OAAO,IAAI,SAAS,IAAI,KAAK,GAAG,CAAC,GAAG;CAC7C,qBAAK,IAAI,OAAO,IAAI,SAAS,IAAI,KAAK,GAAG,CAAC,GAAG;CAC7C,sBAAM,IAAI,OAAO,IAAI,CAAC,GAAG,SAAS,KAAK,GAAG,SAAS,IAAI,CAAC,KAAK,GAAG,CAAC,GAAG;CACpE,mBAAmB;CACnB,eAAe;CAEf,SAAS,IAAI,OAAO,QAAQ,QAAQ;CACpC,SAAS,IAAI,OAAO,QAAQ,QAAQ;CACpC,SAAS,IAAI,OAAO,QAAQ,QAAQ;CAEpC,SAAS,IAAI,OAAO,QAAQ,QAAQ;CAEpC,qBAAK,IAAI,OACP,IAAI;EAAC,QAAQ;EAAS,QAAQ;EAAS,QAAQ;EAAQ,CAAC,KAAK,GAAG,CAAC,GAClE;CAMF;;;;;;;;;;;;;;;;;;;;;;;AAwBD,MAAM,oBAAoB,WACxBA,SAIE,WAIA,mDAEAA,SAAoB,OAAO,EAK3B,SACD;AAEH,MAAa,mBAAmB;CAC9B,KAAK;CACL,KAAKC,QAAmB,gBAAgB,QAAQ;CAChD,IAAID,SAAoBC,QAAmB,gBAAgB,IAAI,CAAC;CAChE,IAAID,SAAoBC,QAAmB,gBAAgB,IAAI,CAAC;CAEhE,WAAWC,MACTD,QACE,0BACA,KACA,gCACD,EACDD,SAAoB,gBAAgB,QAAQ,CAC7C;CACD,WAAWE,MACTD,QACE,yBACA,KACA,4CACD,EACDD,SAAoB,gBAAgB,QAAQ,CAC7C;CACD,QAAQE,MACND,QACE,YACA,KACA,iBACD,EACDD,SAAoB,gBAAgB,QAAQ,CAC7C;CACD,QAAQE,MACND,QACE,WACA,KACA,6BACD,EACDD,SAAoB,gBAAgB,QAAQ,CAC7C;CACD,KAAKE,MACHF,SACEC,QACE,kBACD,EACDD,SAAoB,gBAAgB,QAAQ,CAC7C,CACF;CACD,QAAQ,iBAAiB,gBAAgB,QAAQ;CACjD,QAAQ,iBAAiB,gBAAgB,QAAQ;CAClD"}
1
+ {"version":3,"file":"index.js","names":["Patterning.optional","Patterning.capture","Patterning.merge"],"sources":["../../../../src/coordinates/latlon/internal/index.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 * as Patterning from '@/patterning';\n\nexport type Axes = 'LAT' | 'LON';\nexport type Compass = 'N' | 'S' | 'E' | 'W';\nexport type Errors = string[];\nexport type Format = (typeof FORMATS)[number];\n\n/**\n * Bearings are the consistent/explicit identifiers of directionality of a\n * coordinate component; this library has opted for these over implicit\n * indication by number sign not because there is an inherent superiority\n * but because something had to be chosen.\n *\n * NOTE: these arrays are position-important; negative values are [1] and\n * positive values are [0] so that they can be consistently indexed using\n * an `isNegative` boolean to reference the negative bearing of each axis\n */\nexport const BEARINGS = {\n LAT: ['N', 'S'],\n LON: ['E', 'W'],\n LATLON: [\n ['N', 'S'],\n ['E', 'W'],\n ],\n LONLAT: [\n ['E', 'W'],\n ['N', 'S'],\n ],\n} as const;\n\nexport const FORMATS = ['LATLON', 'LONLAT'] as const;\nexport const FORMATS_DEFAULT = FORMATS[0];\n\nexport const LIMITS = { LATLON: [90, 180], LONLAT: [180, 90] } as const;\n\nexport const SYMBOLS = {\n DEGREES: '°',\n MINUTES: \"'\",\n SECONDS: '\"',\n DIVIDER: '/',\n};\n\nexport const SYMBOL_PATTERNS = {\n LAT: new RegExp(`[${BEARINGS.LAT.join('')}]`),\n LON: new RegExp(`[${BEARINGS.LON.join('')}]`),\n NSEW: new RegExp(`[${[...BEARINGS.LAT, ...BEARINGS.LON].join('')}]`),\n NEGATIVE_BEARINGS: /[SW]/i,\n NEGATIVE_SIGN: /-/,\n\n DEGREES: new RegExp(SYMBOLS.DEGREES),\n MINUTES: new RegExp(SYMBOLS.MINUTES),\n SECONDS: new RegExp(SYMBOLS.SECONDS),\n\n DIVIDER: new RegExp(SYMBOLS.DIVIDER),\n\n DMS: new RegExp(\n `[${[SYMBOLS.DEGREES, SYMBOLS.MINUTES, SYMBOLS.SECONDS].join('')}]`,\n ),\n\n // divider: {\n // first: /(?<NAMED_SEPARATOR>:?)/,\n // follow: new RegExp(`\\\\s?\\\\k<${'NAMED_SEPARATOR'}>\\\\s?`),\n // },\n} as const;\n\n/**\n * Creates a regex pattern for matching decimal minutes or seconds values.\n *\n * Generates a pattern that matches numeric values in the range 0-59.999... with optional\n * leading zeros, decimal points, and symbol indicators, using lookbehind and lookahead\n * to prevent partial matches within larger numbers.\n *\n * @param symbol - Regular expression for the symbol (minutes ' or seconds \" indicator).\n * @returns Combined regex pattern with precise boundary matching.\n *\n * @example\n * ```typescript\n * const minutesPattern = decimalSecAndMin(SYMBOL_PATTERNS.MINUTES);\n * // Matches: \"30.5'\", \"59.999999999\", \".5'\", \"001'\", etc.\n * ```\n *\n * @example\n * ```typescript\n * const secondsPattern = decimalSecAndMin(SYMBOL_PATTERNS.SECONDS);\n * // Matches: '45.23\"', '0.5', '59.9999999999\"', etc.\n * ```\n */\nconst decimalSecAndMin = (symbol: RegExp) =>\n Patterning.optional(\n // Negative lookbehind\n // to ensure that the match is not preceded by a digit,\n // avoiding partial matches within larger numbers.\n /(?<!\\d)/,\n\n // 0-59 including 10 decimal places and leading zeros or no number before\n // acceptable values: 0, 0.1234567890, .9876543210, 001, 59.9999999999\n /([-+]?0*(?:[0-5]?\\d|\\.\\d{1,10})(?:\\.\\d{1,10})?)/,\n\n Patterning.optional(symbol),\n\n // Negative lookahead\n // to ensure that the match is not followed by a digit,\n // avoiding partial matches within larger numbers.\n /(?!\\d)/,\n );\n\nexport const PARTIAL_PATTERNS = {\n ' ': /\\s*/,\n '/': Patterning.capture(SYMBOL_PATTERNS.DIVIDER),\n NS: Patterning.optional(Patterning.capture(SYMBOL_PATTERNS.LAT)),\n EW: Patterning.optional(Patterning.capture(SYMBOL_PATTERNS.LON)),\n\n degLatDec: Patterning.merge(\n Patterning.capture(\n /0*(?:90(?:\\.0{1,10})?)/, // 90[.0]\n /|/,\n /(?:0?[0-8]?\\d(?:\\.\\d{1,10})?)/, // [0]0[.0]-89[.9]\n ),\n Patterning.optional(SYMBOL_PATTERNS.DEGREES),\n ),\n degLonDec: Patterning.merge(\n Patterning.capture(\n /(?:180(?:\\.0{1,10})?)/, // 180[.0]\n /|/,\n /(?:0*(?:\\d{1,2}|1[0-7]\\d)(?:\\.\\d{1,10})?)/, // [00]0[.0]-179[.9]\n ),\n Patterning.optional(SYMBOL_PATTERNS.DEGREES),\n ),\n degLat: Patterning.merge(\n Patterning.capture(\n /(?:0?90)/, // 90\n /|/,\n /(?:0?[0-8]?\\d)/, // [0]0-89\n ),\n Patterning.optional(SYMBOL_PATTERNS.DEGREES),\n ),\n degLon: Patterning.merge(\n Patterning.capture(\n /(?:180)/, // 180\n /|/,\n /(?:0*(?:\\d{1,2}|1[0-7]\\d))/, // [00]0-179\n ),\n Patterning.optional(SYMBOL_PATTERNS.DEGREES),\n ),\n min: Patterning.merge(\n Patterning.optional(\n Patterning.capture(\n /(?:0?[0-5]?\\d)?/, // [0]0-59\n ),\n Patterning.optional(SYMBOL_PATTERNS.MINUTES),\n ),\n ),\n minDec: decimalSecAndMin(SYMBOL_PATTERNS.MINUTES),\n secDec: decimalSecAndMin(SYMBOL_PATTERNS.SECONDS),\n} as const;\n\nexport {\n type CoordinateInput,\n type CoordinateInternalValue,\n type CoordinateObject,\n type CoordinateTuple,\n isCoordinateObject,\n isCoordinateTuple,\n type LatLonTuple,\n type LonLatTuple,\n normalizeObjectToLatLon,\n tupleToLatLon,\n} from './normalize';\nexport { toPlainDecimalString } from './plain-decimal';\nexport {\n isFiniteNumber,\n isValidNumericCoordinate,\n validateNumericCoordinate,\n validateSignedRange,\n} from './validate';\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8BA,MAAa,WAAW;CACtB,KAAK,CAAC,KAAK,IAAI;CACf,KAAK,CAAC,KAAK,IAAI;CACf,QAAQ,CACN,CAAC,KAAK,IAAI,EACV,CAAC,KAAK,IAAI,CACX;CACD,QAAQ,CACN,CAAC,KAAK,IAAI,EACV,CAAC,KAAK,IAAI,CACX;CACF;AAED,MAAa,UAAU,CAAC,UAAU,SAAS;AAC3C,MAAa,kBAAkB,QAAQ;AAEvC,MAAa,SAAS;CAAE,QAAQ,CAAC,IAAI,IAAI;CAAE,QAAQ,CAAC,KAAK,GAAG;CAAE;AAE9D,MAAa,UAAU;CACrB,SAAS;CACT,SAAS;CACT,SAAS;CACT,SAAS;CACV;AAED,MAAa,kBAAkB;CAC7B,qBAAK,IAAI,OAAO,IAAI,SAAS,IAAI,KAAK,GAAG,CAAC,GAAG;CAC7C,qBAAK,IAAI,OAAO,IAAI,SAAS,IAAI,KAAK,GAAG,CAAC,GAAG;CAC7C,sBAAM,IAAI,OAAO,IAAI,CAAC,GAAG,SAAS,KAAK,GAAG,SAAS,IAAI,CAAC,KAAK,GAAG,CAAC,GAAG;CACpE,mBAAmB;CACnB,eAAe;CAEf,SAAS,IAAI,OAAO,QAAQ,QAAQ;CACpC,SAAS,IAAI,OAAO,QAAQ,QAAQ;CACpC,SAAS,IAAI,OAAO,QAAQ,QAAQ;CAEpC,SAAS,IAAI,OAAO,QAAQ,QAAQ;CAEpC,qBAAK,IAAI,OACP,IAAI;EAAC,QAAQ;EAAS,QAAQ;EAAS,QAAQ;EAAQ,CAAC,KAAK,GAAG,CAAC,GAClE;CAMF;;;;;;;;;;;;;;;;;;;;;;;AAwBD,MAAM,oBAAoB,WACxBA,SAIE,WAIA,mDAEAA,SAAoB,OAAO,EAK3B,SACD;AAEH,MAAa,mBAAmB;CAC9B,KAAK;CACL,KAAKC,QAAmB,gBAAgB,QAAQ;CAChD,IAAID,SAAoBC,QAAmB,gBAAgB,IAAI,CAAC;CAChE,IAAID,SAAoBC,QAAmB,gBAAgB,IAAI,CAAC;CAEhE,WAAWC,MACTD,QACE,0BACA,KACA,gCACD,EACDD,SAAoB,gBAAgB,QAAQ,CAC7C;CACD,WAAWE,MACTD,QACE,yBACA,KACA,4CACD,EACDD,SAAoB,gBAAgB,QAAQ,CAC7C;CACD,QAAQE,MACND,QACE,YACA,KACA,iBACD,EACDD,SAAoB,gBAAgB,QAAQ,CAC7C;CACD,QAAQE,MACND,QACE,WACA,KACA,6BACD,EACDD,SAAoB,gBAAgB,QAAQ,CAC7C;CACD,KAAKE,MACHF,SACEC,QACE,kBACD,EACDD,SAAoB,gBAAgB,QAAQ,CAC7C,CACF;CACD,QAAQ,iBAAiB,gBAAgB,QAAQ;CACjD,QAAQ,iBAAiB,gBAAgB,QAAQ;CAClD"}
@@ -1,5 +1,5 @@
1
1
  /*
2
- * Copyright 2025 Hypergiant Galactic Systems Inc. All rights reserved.
2
+ * Copyright 2026 Hypergiant Galactic Systems Inc. All rights reserved.
3
3
  * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4
4
  * you may not use this file except in compliance with the License. You may obtain a copy
5
5
  * of the License at https://www.apache.org/licenses/LICENSE-2.0
@@ -12,6 +12,7 @@
12
12
 
13
13
 
14
14
  import { capture, group, merge, optional } from "../../../patterning.js";
15
+ import { toPlainDecimalString } from "./plain-decimal.js";
15
16
  import { SYMBOLS, SYMBOL_PATTERNS } from "./index.js";
16
17
 
17
18
  //#region src/coordinates/latlon/internal/lexer.ts
@@ -62,7 +63,7 @@ const TOKENS = new RegExp(merge(DIVIDERS, /|/, SYMBOL_PATTERNS.NSEW, /|/, group(
62
63
  */
63
64
  function fixLeadingAndTrailing(t) {
64
65
  const [sign, num, pos] = (FLOATS.exec(t) ?? []).slice(1);
65
- if (num) return `${sign}${Number.parseFloat(num)}${pos}`;
66
+ if (num) return `${sign}${toPlainDecimalString(Number.parseFloat(num))}${pos}`;
66
67
  return t;
67
68
  }
68
69
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"lexer.js","names":["Patterning.merge","Patterning.capture","Patterning.group","Patterning.optional"],"sources":["../../../../src/coordinates/latlon/internal/lexer.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 * as Patterning from '@/patterning';\nimport { SYMBOL_PATTERNS, SYMBOLS } from '.';\n\nexport type Tokens = ReturnType<typeof lexer>;\n\n/**\n * Separating latitude from longitude portions of a coordinate. At this level\n * of pattern matching this list can not include the \"space\" character since\n * that is valid between components of either side of a divider; higher level\n * parsers will be able to make up for this shortcoming and be more intelligent\n * about deducing where a divider could be added or would not be valid.\n */\nconst DIVIDERS = /[,/]/g;\nconst FLOATS = /^(-?)([\\d.]+)([^.\\d]?)$/;\n/** Positional indicators for: degrees, minutes, and seconds */\nconst POSITIONAL = new RegExp(\n Patterning.merge(/\\s*/, Patterning.capture(SYMBOL_PATTERNS.DMS), /\\s*/),\n 'g',\n);\nconst POSITIVE = /\\+/g;\nconst SIGNS = /([-+])\\s*/g;\n/**\n * Any recognizably significant tokens anywhere (non-positional-y) within a\n * string; because at this level (lexing) actual position is not important.\n *\n * - [Regex Vis](https://regex-vis.com/?r=%2F%5B%2C%2F%5D%7C%5BNSEW%5D%7C%28%3F%3A%5B-%2B%5D%3F%28%3F%3A%28%3F%3A%5Cd%2B%28%3F%3A%5C.%5Cd*%29%3F%29%7C%28%3F%3A%5C.%5Cd%2B%29%29%28%3F%3A%5B%C2%B0%27%22%5D%29%3F%29%2Fgi)\n * - [Nodexr](https://www.nodexr.net/?parse=%2F%5B,%2F%5D%7C%5BNSEW%5D%7C(%3F%3A%5B-%2B%5D%3F(%3F%3A(%3F%3A%5Cd%2B(%3F%3A%5C.%5Cd*)%3F)%7C(%3F%3A%5C.%5Cd%2B))%5B%C2%B0%27%22%5D%3F)%2Fgi)\n */\n// NOTE: the links (above) for \"Regex Vis\" and \"Nodexr\" would need to be updated if/when the pattern is changed.\nconst TOKENS = new RegExp(\n Patterning.merge(\n DIVIDERS,\n /|/,\n SYMBOL_PATTERNS.NSEW,\n /|/,\n Patterning.group(\n /[-+]?/,\n Patterning.group(\n // left of decimal REQUIRED, right of decimal optional\n /(?:\\d+(?:\\.\\d*)?)|/,\n // left of decimal omitted, right of decimal REQUIRED\n /(?:\\.\\d+)/,\n ),\n Patterning.optional(SYMBOL_PATTERNS.DMS),\n ),\n ),\n 'gi',\n);\n\n/**\n * Remove trailing zeros '?.0' and ensure leading zero '0.?' in numbers.\n *\n * @param t - Token string to normalize.\n * @returns Normalized token with cleaned numeric formatting.\n *\n * @example\n * ```typescript\n * fixLeadingAndTrailing('45.0°');\n * // '45°'\n * ```\n *\n * @example\n * ```typescript\n * fixLeadingAndTrailing('.5'');\n * // '0.5''\n * ```\n *\n * @example\n * ```typescript\n * fixLeadingAndTrailing('-122.00');\n * // '-122'\n * ```\n */\nfunction fixLeadingAndTrailing(t: string) {\n const [sign, num, pos] = (FLOATS.exec(t) ?? []).slice(1);\n\n if (num) {\n return `${sign}${Number.parseFloat(num)}${pos}`;\n }\n\n return t;\n}\n\n/**\n * Take an input string - possibly from user input - and clean it up enough to\n * be something to work with at a higher level of processing (with more\n * information) than is available at this level. Generating a list of \"tokens\"\n * that are potentially valid parts of a coordinate. The values being looked\n * for are: numbers (with positional indicators) and axes (NSEW).\n *\n * NOTE: No validation is done at this level to keep it simple as agnostic.\n *\n * @remarks\n * pure function\n *\n * @example\n * ```typescript\n * lexer('N 55,E 44') === ['N' '55', '/', 'E', '44']\n * lexer(` + 89 ° 59 59.999 \" N, 179° 59 59.999\" `) === ['89', '59', '59.999', 'N', '/', '179', '59', '59.999', 'E']\n * ```\n */\nexport function lexer(input: string) {\n const tokens =\n input\n .trim()\n .toUpperCase()\n .replace(POSITIVE, '') // positive signs are redundant\n .replace(POSITIONAL, '$1 ') // group positional indicators with numbers\n .replace(SIGNS, '$1') // group signs with numbers\n .replace(DIVIDERS, SYMBOLS.DIVIDER) // standardize the divider\n .match(TOKENS)\n ?.map(fixLeadingAndTrailing)\n ?.slice() ?? [];\n\n return tokens;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AAyBA,MAAM,WAAW;AACjB,MAAM,SAAS;;AAEf,MAAM,aAAa,IAAI,OACrBA,MAAiB,OAAOC,QAAmB,gBAAgB,IAAI,EAAE,MAAM,EACvE,IACD;AACD,MAAM,WAAW;AACjB,MAAM,QAAQ;;;;;;;;AASd,MAAM,SAAS,IAAI,OACjBD,MACE,UACA,KACA,gBAAgB,MAChB,KACAE,MACE,SACAA,MAEE,sBAEA,YACD,EACDC,SAAoB,gBAAgB,IAAI,CACzC,CACF,EACD,KACD;;;;;;;;;;;;;;;;;;;;;;;;;AA0BD,SAAS,sBAAsB,GAAW;CACxC,MAAM,CAAC,MAAM,KAAK,QAAQ,OAAO,KAAK,EAAE,IAAI,EAAE,EAAE,MAAM,EAAE;AAExD,KAAI,IACF,QAAO,GAAG,OAAO,OAAO,WAAW,IAAI,GAAG;AAG5C,QAAO;;;;;;;;;;;;;;;;;;;;AAqBT,SAAgB,MAAM,OAAe;AAanC,QAXE,MACG,MAAM,CACN,aAAa,CACb,QAAQ,UAAU,GAAG,CACrB,QAAQ,YAAY,MAAM,CAC1B,QAAQ,OAAO,KAAK,CACpB,QAAQ,UAAU,QAAQ,QAAQ,CAClC,MAAM,OAAO,EACZ,IAAI,sBAAsB,EAC1B,OAAO,IAAI,EAAE"}
1
+ {"version":3,"file":"lexer.js","names":["Patterning.merge","Patterning.capture","Patterning.group","Patterning.optional"],"sources":["../../../../src/coordinates/latlon/internal/lexer.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 * as Patterning from '@/patterning';\nimport { SYMBOL_PATTERNS, SYMBOLS } from '.';\nimport { toPlainDecimalString } from './plain-decimal';\n\nexport type Tokens = ReturnType<typeof lexer>;\n\n/**\n * Separating latitude from longitude portions of a coordinate. At this level\n * of pattern matching this list can not include the \"space\" character since\n * that is valid between components of either side of a divider; higher level\n * parsers will be able to make up for this shortcoming and be more intelligent\n * about deducing where a divider could be added or would not be valid.\n */\nconst DIVIDERS = /[,/]/g;\nconst FLOATS = /^(-?)([\\d.]+)([^.\\d]?)$/;\n/** Positional indicators for: degrees, minutes, and seconds */\nconst POSITIONAL = new RegExp(\n Patterning.merge(/\\s*/, Patterning.capture(SYMBOL_PATTERNS.DMS), /\\s*/),\n 'g',\n);\nconst POSITIVE = /\\+/g;\nconst SIGNS = /([-+])\\s*/g;\n/**\n * Any recognizably significant tokens anywhere (non-positional-y) within a\n * string; because at this level (lexing) actual position is not important.\n *\n * - [Regex Vis](https://regex-vis.com/?r=%2F%5B%2C%2F%5D%7C%5BNSEW%5D%7C%28%3F%3A%5B-%2B%5D%3F%28%3F%3A%28%3F%3A%5Cd%2B%28%3F%3A%5C.%5Cd*%29%3F%29%7C%28%3F%3A%5C.%5Cd%2B%29%29%28%3F%3A%5B%C2%B0%27%22%5D%29%3F%29%2Fgi)\n * - [Nodexr](https://www.nodexr.net/?parse=%2F%5B,%2F%5D%7C%5BNSEW%5D%7C(%3F%3A%5B-%2B%5D%3F(%3F%3A(%3F%3A%5Cd%2B(%3F%3A%5C.%5Cd*)%3F)%7C(%3F%3A%5C.%5Cd%2B))%5B%C2%B0%27%22%5D%3F)%2Fgi)\n */\n// NOTE: the links (above) for \"Regex Vis\" and \"Nodexr\" would need to be updated if/when the pattern is changed.\nconst TOKENS = new RegExp(\n Patterning.merge(\n DIVIDERS,\n /|/,\n SYMBOL_PATTERNS.NSEW,\n /|/,\n Patterning.group(\n /[-+]?/,\n Patterning.group(\n // left of decimal REQUIRED, right of decimal optional\n /(?:\\d+(?:\\.\\d*)?)|/,\n // left of decimal omitted, right of decimal REQUIRED\n /(?:\\.\\d+)/,\n ),\n Patterning.optional(SYMBOL_PATTERNS.DMS),\n ),\n ),\n 'gi',\n);\n\n/**\n * Remove trailing zeros '?.0' and ensure leading zero '0.?' in numbers.\n *\n * @param t - Token string to normalize.\n * @returns Normalized token with cleaned numeric formatting.\n *\n * @example\n * ```typescript\n * fixLeadingAndTrailing('45.0°');\n * // '45°'\n * ```\n *\n * @example\n * ```typescript\n * fixLeadingAndTrailing('.5'');\n * // '0.5''\n * ```\n *\n * @example\n * ```typescript\n * fixLeadingAndTrailing('-122.00');\n * // '-122'\n * ```\n */\nfunction fixLeadingAndTrailing(t: string) {\n const [sign, num, pos] = (FLOATS.exec(t) ?? []).slice(1);\n\n if (num) {\n // Plain notation: `${1e-7}` would be '1e-7', whose '-7' re-lexes as a sign.\n return `${sign}${toPlainDecimalString(Number.parseFloat(num))}${pos}`;\n }\n\n return t;\n}\n\n/**\n * Take an input string - possibly from user input - and clean it up enough to\n * be something to work with at a higher level of processing (with more\n * information) than is available at this level. Generating a list of \"tokens\"\n * that are potentially valid parts of a coordinate. The values being looked\n * for are: numbers (with positional indicators) and axes (NSEW).\n *\n * NOTE: No validation is done at this level to keep it simple as agnostic.\n *\n * @remarks\n * pure function\n *\n * @example\n * ```typescript\n * lexer('N 55,E 44') === ['N' '55', '/', 'E', '44']\n * lexer(` + 89 ° 59 59.999 \" N, 179° 59 59.999\" `) === ['89', '59', '59.999', 'N', '/', '179', '59', '59.999', 'E']\n * ```\n */\nexport function lexer(input: string) {\n const tokens =\n input\n .trim()\n .toUpperCase()\n .replace(POSITIVE, '') // positive signs are redundant\n .replace(POSITIONAL, '$1 ') // group positional indicators with numbers\n .replace(SIGNS, '$1') // group signs with numbers\n .replace(DIVIDERS, SYMBOLS.DIVIDER) // standardize the divider\n .match(TOKENS)\n ?.map(fixLeadingAndTrailing)\n ?.slice() ?? [];\n\n return tokens;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AA0BA,MAAM,WAAW;AACjB,MAAM,SAAS;;AAEf,MAAM,aAAa,IAAI,OACrBA,MAAiB,OAAOC,QAAmB,gBAAgB,IAAI,EAAE,MAAM,EACvE,IACD;AACD,MAAM,WAAW;AACjB,MAAM,QAAQ;;;;;;;;AASd,MAAM,SAAS,IAAI,OACjBD,MACE,UACA,KACA,gBAAgB,MAChB,KACAE,MACE,SACAA,MAEE,sBAEA,YACD,EACDC,SAAoB,gBAAgB,IAAI,CACzC,CACF,EACD,KACD;;;;;;;;;;;;;;;;;;;;;;;;;AA0BD,SAAS,sBAAsB,GAAW;CACxC,MAAM,CAAC,MAAM,KAAK,QAAQ,OAAO,KAAK,EAAE,IAAI,EAAE,EAAE,MAAM,EAAE;AAExD,KAAI,IAEF,QAAO,GAAG,OAAO,qBAAqB,OAAO,WAAW,IAAI,CAAC,GAAG;AAGlE,QAAO;;;;;;;;;;;;;;;;;;;;AAqBT,SAAgB,MAAM,OAAe;AAanC,QAXE,MACG,MAAM,CACN,aAAa,CACb,QAAQ,UAAU,GAAG,CACrB,QAAQ,YAAY,MAAM,CAC1B,QAAQ,OAAO,KAAK,CACpB,QAAQ,UAAU,QAAQ,QAAQ,CAClC,MAAM,OAAO,EACZ,IAAI,sBAAsB,EAC1B,OAAO,IAAI,EAAE"}
@@ -1,5 +1,5 @@
1
1
  /*
2
- * Copyright 2025 Hypergiant Galactic Systems Inc. All rights reserved.
2
+ * Copyright 2026 Hypergiant Galactic Systems Inc. All rights reserved.
3
3
  * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4
4
  * you may not use this file except in compliance with the License. You may obtain a copy
5
5
  * of the License at https://www.apache.org/licenses/LICENSE-2.0
@@ -1,11 +1,47 @@
1
1
  //#region src/coordinates/latlon/internal/ordinal.d.ts
2
+ /**
3
+ * Axis discriminator for a single coordinate value.
4
+ *
5
+ * `'lat'` selects the N/S hemisphere pair; `'lon'` selects E/W.
6
+ */
7
+ type Axis = 'lat' | 'lon';
8
+ /**
9
+ * Hemisphere letter for a coordinate value, following geo's `>= 0`
10
+ * convention (0 maps to `N` on the lat axis and `E` on the lon axis).
11
+ */
12
+ type Hemisphere = 'N' | 'S' | 'E' | 'W';
13
+ /**
14
+ * Gets the typed hemisphere letter for a signed coordinate value on an axis.
15
+ *
16
+ * Follows the same `>= 0` convention as {@link getOrdinal}: a value of exactly
17
+ * `0` maps to `N` on the latitude axis and `E` on the longitude axis.
18
+ *
19
+ * @param value - The signed coordinate value.
20
+ * @param axis - Whether the value is a latitude (`'lat'`) or longitude (`'lon'`).
21
+ * @returns Hemisphere letter: `'N'`, `'S'`, `'E'`, or `'W'`.
22
+ *
23
+ * @remarks pure function
24
+ *
25
+ * @example
26
+ * ```typescript
27
+ * getHemisphere(-77.0369, 'lon');
28
+ * // 'W'
29
+ * ```
30
+ */
31
+ declare const getHemisphere: (value: number, axis: Axis) => Hemisphere;
2
32
  /**
3
33
  * Gets the ordinal direction (N/S/E/W) for a coordinate value.
4
34
  *
5
- * @param num - The coordinate value (positive or negative).
35
+ * Retained as the established public API. Adapts {@link getHemisphere} — which
36
+ * owns the `>= 0` convention — to a boolean axis and the wider `string` return
37
+ * its existing callers expect.
38
+ *
39
+ * @param value - The coordinate value (positive or negative).
6
40
  * @param isLatitude - Whether this is a latitude coordinate (true) or longitude (false).
7
41
  * @returns Ordinal direction character: 'N', 'S', 'E', or 'W'.
8
42
  *
43
+ * @remarks pure function
44
+ *
9
45
  * @example
10
46
  * ```typescript
11
47
  * getOrdinal(37.7749, true);
@@ -17,14 +53,8 @@
17
53
  * getOrdinal(-122.4194, false);
18
54
  * // 'W'
19
55
  * ```
20
- *
21
- * @example
22
- * ```typescript
23
- * getOrdinal(-45, true);
24
- * // 'S'
25
- * ```
26
56
  */
27
- declare const getOrdinal: (num: number, isLatitude: boolean) => string;
57
+ declare const getOrdinal: (value: number, isLatitude: boolean) => string;
28
58
  //#endregion
29
- export { getOrdinal };
59
+ export { Axis, Hemisphere, getHemisphere, getOrdinal };
30
60
  //# sourceMappingURL=ordinal.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
@@ -13,12 +13,40 @@
13
13
 
14
14
  //#region src/coordinates/latlon/internal/ordinal.ts
15
15
  /**
16
+ * Gets the typed hemisphere letter for a signed coordinate value on an axis.
17
+ *
18
+ * Follows the same `>= 0` convention as {@link getOrdinal}: a value of exactly
19
+ * `0` maps to `N` on the latitude axis and `E` on the longitude axis.
20
+ *
21
+ * @param value - The signed coordinate value.
22
+ * @param axis - Whether the value is a latitude (`'lat'`) or longitude (`'lon'`).
23
+ * @returns Hemisphere letter: `'N'`, `'S'`, `'E'`, or `'W'`.
24
+ *
25
+ * @remarks pure function
26
+ *
27
+ * @example
28
+ * ```typescript
29
+ * getHemisphere(-77.0369, 'lon');
30
+ * // 'W'
31
+ * ```
32
+ */
33
+ const getHemisphere = (value, axis) => {
34
+ if (axis === "lat") return value >= 0 ? "N" : "S";
35
+ return value >= 0 ? "E" : "W";
36
+ };
37
+ /**
16
38
  * Gets the ordinal direction (N/S/E/W) for a coordinate value.
17
39
  *
18
- * @param num - The coordinate value (positive or negative).
40
+ * Retained as the established public API. Adapts {@link getHemisphere} — which
41
+ * owns the `>= 0` convention — to a boolean axis and the wider `string` return
42
+ * its existing callers expect.
43
+ *
44
+ * @param value - The coordinate value (positive or negative).
19
45
  * @param isLatitude - Whether this is a latitude coordinate (true) or longitude (false).
20
46
  * @returns Ordinal direction character: 'N', 'S', 'E', or 'W'.
21
47
  *
48
+ * @remarks pure function
49
+ *
22
50
  * @example
23
51
  * ```typescript
24
52
  * getOrdinal(37.7749, true);
@@ -30,18 +58,9 @@
30
58
  * getOrdinal(-122.4194, false);
31
59
  * // 'W'
32
60
  * ```
33
- *
34
- * @example
35
- * ```typescript
36
- * getOrdinal(-45, true);
37
- * // 'S'
38
- * ```
39
61
  */
40
- const getOrdinal = (num, isLatitude) => {
41
- if (isLatitude) return num >= 0 ? "N" : "S";
42
- return num >= 0 ? "E" : "W";
43
- };
62
+ const getOrdinal = (value, isLatitude) => getHemisphere(value, isLatitude ? "lat" : "lon");
44
63
 
45
64
  //#endregion
46
- export { getOrdinal };
65
+ export { getHemisphere, getOrdinal };
47
66
  //# sourceMappingURL=ordinal.js.map
@@ -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\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"}
1
+ {"version":3,"file":"ordinal.js","names":[],"sources":["../../../../src/coordinates/latlon/internal/ordinal.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\n// __private-exports\n\n/**\n * Axis discriminator for a single coordinate value.\n *\n * `'lat'` selects the N/S hemisphere pair; `'lon'` selects E/W.\n */\nexport type Axis = 'lat' | 'lon';\n\n/**\n * Hemisphere letter for a coordinate value, following geo's `>= 0`\n * convention (0 maps to `N` on the lat axis and `E` on the lon axis).\n */\nexport type Hemisphere = 'N' | 'S' | 'E' | 'W';\n\n/**\n * Gets the typed hemisphere letter for a signed coordinate value on an axis.\n *\n * Follows the same `>= 0` convention as {@link getOrdinal}: a value of exactly\n * `0` maps to `N` on the latitude axis and `E` on the longitude axis.\n *\n * @param value - The signed coordinate value.\n * @param axis - Whether the value is a latitude (`'lat'`) or longitude (`'lon'`).\n * @returns Hemisphere letter: `'N'`, `'S'`, `'E'`, or `'W'`.\n *\n * @remarks pure function\n *\n * @example\n * ```typescript\n * getHemisphere(-77.0369, 'lon');\n * // 'W'\n * ```\n */\nexport const getHemisphere = (value: number, axis: Axis): Hemisphere => {\n if (axis === 'lat') {\n return value >= 0 ? 'N' : 'S';\n }\n\n return value >= 0 ? 'E' : 'W';\n};\n\n/**\n * Gets the ordinal direction (N/S/E/W) for a coordinate value.\n *\n * Retained as the established public API. Adapts {@link getHemisphere} — which\n * owns the `>= 0` convention — to a boolean axis and the wider `string` return\n * its existing callers expect.\n *\n * @param value - 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 * @remarks pure function\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 */\nexport const getOrdinal = (value: number, isLatitude: boolean): string =>\n getHemisphere(value, isLatitude ? 'lat' : 'lon');\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6CA,MAAa,iBAAiB,OAAe,SAA2B;AACtE,KAAI,SAAS,MACX,QAAO,SAAS,IAAI,MAAM;AAG5B,QAAO,SAAS,IAAI,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4B5B,MAAa,cAAc,OAAe,eACxC,cAAc,OAAO,aAAa,QAAQ,MAAM"}
@@ -1,5 +1,5 @@
1
1
  /*
2
- * Copyright 2025 Hypergiant Galactic Systems Inc. All rights reserved.
2
+ * Copyright 2026 Hypergiant Galactic Systems Inc. All rights reserved.
3
3
  * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4
4
  * you may not use this file except in compliance with the License. You may obtain a copy
5
5
  * of the License at https://www.apache.org/licenses/LICENSE-2.0
@@ -1,5 +1,5 @@
1
1
  /*
2
- * Copyright 2025 Hypergiant Galactic Systems Inc. All rights reserved.
2
+ * Copyright 2026 Hypergiant Galactic Systems Inc. All rights reserved.
3
3
  * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4
4
  * you may not use this file except in compliance with the License. You may obtain a copy
5
5
  * of the License at https://www.apache.org/licenses/LICENSE-2.0
@@ -1,5 +1,5 @@
1
1
  /*
2
- * Copyright 2025 Hypergiant Galactic Systems Inc. All rights reserved.
2
+ * Copyright 2026 Hypergiant Galactic Systems Inc. All rights reserved.
3
3
  * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4
4
  * you may not use this file except in compliance with the License. You may obtain a copy
5
5
  * of the License at https://www.apache.org/licenses/LICENSE-2.0
@@ -1,5 +1,5 @@
1
1
  /*
2
- * Copyright 2025 Hypergiant Galactic Systems Inc. All rights reserved.
2
+ * Copyright 2026 Hypergiant Galactic Systems Inc. All rights reserved.
3
3
  * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4
4
  * you may not use this file except in compliance with the License. You may obtain a copy
5
5
  * of the License at https://www.apache.org/licenses/LICENSE-2.0
@@ -1,5 +1,5 @@
1
1
  /*
2
- * Copyright 2025 Hypergiant Galactic Systems Inc. All rights reserved.
2
+ * Copyright 2026 Hypergiant Galactic Systems Inc. All rights reserved.
3
3
  * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4
4
  * you may not use this file except in compliance with the License. You may obtain a copy
5
5
  * of the License at https://www.apache.org/licenses/LICENSE-2.0
@@ -1,5 +1,5 @@
1
1
  /*
2
- * Copyright 2025 Hypergiant Galactic Systems Inc. All rights reserved.
2
+ * Copyright 2026 Hypergiant Galactic Systems Inc. All rights reserved.
3
3
  * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4
4
  * you may not use this file except in compliance with the License. You may obtain a copy
5
5
  * of the License at https://www.apache.org/licenses/LICENSE-2.0
@@ -1,5 +1,5 @@
1
1
  /*
2
- * Copyright 2025 Hypergiant Galactic Systems Inc. All rights reserved.
2
+ * Copyright 2026 Hypergiant Galactic Systems Inc. All rights reserved.
3
3
  * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4
4
  * you may not use this file except in compliance with the License. You may obtain a copy
5
5
  * of the License at https://www.apache.org/licenses/LICENSE-2.0
@@ -1,5 +1,5 @@
1
1
  /*
2
- * Copyright 2025 Hypergiant Galactic Systems Inc. All rights reserved.
2
+ * Copyright 2026 Hypergiant Galactic Systems Inc. All rights reserved.
3
3
  * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4
4
  * you may not use this file except in compliance with the License. You may obtain a copy
5
5
  * of the License at https://www.apache.org/licenses/LICENSE-2.0
@@ -1,5 +1,5 @@
1
1
  /*
2
- * Copyright 2025 Hypergiant Galactic Systems Inc. All rights reserved.
2
+ * Copyright 2026 Hypergiant Galactic Systems Inc. All rights reserved.
3
3
  * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4
4
  * you may not use this file except in compliance with the License. You may obtain a copy
5
5
  * of the License at https://www.apache.org/licenses/LICENSE-2.0
@@ -1,5 +1,5 @@
1
1
  /*
2
- * Copyright 2025 Hypergiant Galactic Systems Inc. All rights reserved.
2
+ * Copyright 2026 Hypergiant Galactic Systems Inc. All rights reserved.
3
3
  * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4
4
  * you may not use this file except in compliance with the License. You may obtain a copy
5
5
  * of the License at https://www.apache.org/licenses/LICENSE-2.0
@@ -1,5 +1,5 @@
1
1
  /*
2
- * Copyright 2025 Hypergiant Galactic Systems Inc. All rights reserved.
2
+ * Copyright 2026 Hypergiant Galactic Systems Inc. All rights reserved.
3
3
  * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4
4
  * you may not use this file except in compliance with the License. You may obtain a copy
5
5
  * of the License at https://www.apache.org/licenses/LICENSE-2.0
@@ -1,5 +1,5 @@
1
1
  /*
2
- * Copyright 2025 Hypergiant Galactic Systems Inc. All rights reserved.
2
+ * Copyright 2026 Hypergiant Galactic Systems Inc. All rights reserved.
3
3
  * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4
4
  * you may not use this file except in compliance with the License. You may obtain a copy
5
5
  * of the License at https://www.apache.org/licenses/LICENSE-2.0
@@ -1,5 +1,5 @@
1
1
  /*
2
- * Copyright 2025 Hypergiant Galactic Systems Inc. All rights reserved.
2
+ * Copyright 2026 Hypergiant Galactic Systems Inc. All rights reserved.
3
3
  * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4
4
  * you may not use this file except in compliance with the License. You may obtain a copy
5
5
  * of the License at https://www.apache.org/licenses/LICENSE-2.0
@@ -1,5 +1,5 @@
1
1
  /*
2
- * Copyright 2025 Hypergiant Galactic Systems Inc. All rights reserved.
2
+ * Copyright 2026 Hypergiant Galactic Systems Inc. All rights reserved.
3
3
  * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4
4
  * you may not use this file except in compliance with the License. You may obtain a copy
5
5
  * of the License at https://www.apache.org/licenses/LICENSE-2.0
@@ -1,5 +1,5 @@
1
1
  /*
2
- * Copyright 2025 Hypergiant Galactic Systems Inc. All rights reserved.
2
+ * Copyright 2026 Hypergiant Galactic Systems Inc. All rights reserved.
3
3
  * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4
4
  * you may not use this file except in compliance with the License. You may obtain a copy
5
5
  * of the License at https://www.apache.org/licenses/LICENSE-2.0
@@ -1,5 +1,5 @@
1
1
  /*
2
- * Copyright 2025 Hypergiant Galactic Systems Inc. All rights reserved.
2
+ * Copyright 2026 Hypergiant Galactic Systems Inc. All rights reserved.
3
3
  * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4
4
  * you may not use this file except in compliance with the License. You may obtain a copy
5
5
  * of the License at https://www.apache.org/licenses/LICENSE-2.0
@@ -1,5 +1,5 @@
1
1
  /*
2
- * Copyright 2025 Hypergiant Galactic Systems Inc. All rights reserved.
2
+ * Copyright 2026 Hypergiant Galactic Systems Inc. All rights reserved.
3
3
  * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4
4
  * you may not use this file except in compliance with the License. You may obtain a copy
5
5
  * of the License at https://www.apache.org/licenses/LICENSE-2.0
@@ -1,5 +1,5 @@
1
1
  /*
2
- * Copyright 2025 Hypergiant Galactic Systems Inc. All rights reserved.
2
+ * Copyright 2026 Hypergiant Galactic Systems Inc. All rights reserved.
3
3
  * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4
4
  * you may not use this file except in compliance with the License. You may obtain a copy
5
5
  * of the License at https://www.apache.org/licenses/LICENSE-2.0
@@ -0,0 +1,35 @@
1
+ //#region src/coordinates/latlon/internal/plain-decimal.d.ts
2
+ /**
3
+ * Renders a small number in plain decimal notation instead of exponential.
4
+ *
5
+ * JavaScript's default number-to-string conversion switches to exponential
6
+ * notation below `1e-6` (`String(0.0000001)` is `'1e-7'`), which the
7
+ * coordinate lexer would mis-tokenize (`-7` reads as a sign) and which the
8
+ * round-trip formatters must not emit. Values that already render plainly are
9
+ * returned unchanged, so this is a no-op for ordinary coordinates.
10
+ *
11
+ * Only negative exponents are expanded, to at most 20 fraction digits — enough
12
+ * for any coordinate magnitude. Numbers at or above `1e21` (positive exponent)
13
+ * are returned as JavaScript renders them.
14
+ *
15
+ * @param value - The number to render.
16
+ * @returns The value in plain decimal notation with no trailing zeros.
17
+ *
18
+ * @remarks pure function
19
+ *
20
+ * @example
21
+ * ```typescript
22
+ * toPlainDecimalString(0.0000001);
23
+ * // '0.0000001'
24
+ * ```
25
+ *
26
+ * @example
27
+ * ```typescript
28
+ * toPlainDecimalString(-122.4194);
29
+ * // '-122.4194'
30
+ * ```
31
+ */
32
+ declare const toPlainDecimalString: (value: number) => string;
33
+ //#endregion
34
+ export { toPlainDecimalString };
35
+ //# sourceMappingURL=plain-decimal.d.ts.map
@@ -0,0 +1,59 @@
1
+ /*
2
+ * Copyright 2026 Hypergiant Galactic Systems Inc. All rights reserved.
3
+ * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4
+ * you may not use this file except in compliance with the License. You may obtain a copy
5
+ * of the License at https://www.apache.org/licenses/LICENSE-2.0
6
+ *
7
+ * Unless required by applicable law or agreed to in writing, software distributed under
8
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9
+ * OF ANY KIND, either express or implied. See the License for the specific language
10
+ * governing permissions and limitations under the License.
11
+ */
12
+
13
+
14
+ //#region src/coordinates/latlon/internal/plain-decimal.ts
15
+ /**
16
+ * Fraction digits used to expand an exponential-notation number. Twenty covers
17
+ * every magnitude that is meaningful for a coordinate (`1e-20°` is far below
18
+ * the width of an atom) while staying within `toFixed`'s supported range.
19
+ */
20
+ const EXPANDED_FRACTION_DIGITS = 20;
21
+ /**
22
+ * Renders a small number in plain decimal notation instead of exponential.
23
+ *
24
+ * JavaScript's default number-to-string conversion switches to exponential
25
+ * notation below `1e-6` (`String(0.0000001)` is `'1e-7'`), which the
26
+ * coordinate lexer would mis-tokenize (`-7` reads as a sign) and which the
27
+ * round-trip formatters must not emit. Values that already render plainly are
28
+ * returned unchanged, so this is a no-op for ordinary coordinates.
29
+ *
30
+ * Only negative exponents are expanded, to at most 20 fraction digits — enough
31
+ * for any coordinate magnitude. Numbers at or above `1e21` (positive exponent)
32
+ * are returned as JavaScript renders them.
33
+ *
34
+ * @param value - The number to render.
35
+ * @returns The value in plain decimal notation with no trailing zeros.
36
+ *
37
+ * @remarks pure function
38
+ *
39
+ * @example
40
+ * ```typescript
41
+ * toPlainDecimalString(0.0000001);
42
+ * // '0.0000001'
43
+ * ```
44
+ *
45
+ * @example
46
+ * ```typescript
47
+ * toPlainDecimalString(-122.4194);
48
+ * // '-122.4194'
49
+ * ```
50
+ */
51
+ const toPlainDecimalString = (value) => {
52
+ const rendered = `${value}`;
53
+ if (!rendered.includes("e-")) return rendered;
54
+ return value.toFixed(EXPANDED_FRACTION_DIGITS).replace(/\.?0+$/, "");
55
+ };
56
+
57
+ //#endregion
58
+ export { toPlainDecimalString };
59
+ //# sourceMappingURL=plain-decimal.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plain-decimal.js","names":[],"sources":["../../../../src/coordinates/latlon/internal/plain-decimal.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\n/**\n * Fraction digits used to expand an exponential-notation number. Twenty covers\n * every magnitude that is meaningful for a coordinate (`1e-20°` is far below\n * the width of an atom) while staying within `toFixed`'s supported range.\n */\nconst EXPANDED_FRACTION_DIGITS = 20;\n\n/**\n * Renders a small number in plain decimal notation instead of exponential.\n *\n * JavaScript's default number-to-string conversion switches to exponential\n * notation below `1e-6` (`String(0.0000001)` is `'1e-7'`), which the\n * coordinate lexer would mis-tokenize (`-7` reads as a sign) and which the\n * round-trip formatters must not emit. Values that already render plainly are\n * returned unchanged, so this is a no-op for ordinary coordinates.\n *\n * Only negative exponents are expanded, to at most 20 fraction digits — enough\n * for any coordinate magnitude. Numbers at or above `1e21` (positive exponent)\n * are returned as JavaScript renders them.\n *\n * @param value - The number to render.\n * @returns The value in plain decimal notation with no trailing zeros.\n *\n * @remarks pure function\n *\n * @example\n * ```typescript\n * toPlainDecimalString(0.0000001);\n * // '0.0000001'\n * ```\n *\n * @example\n * ```typescript\n * toPlainDecimalString(-122.4194);\n * // '-122.4194'\n * ```\n */\nexport const toPlainDecimalString = (value: number): string => {\n const rendered = `${value}`;\n\n if (!rendered.includes('e-')) {\n return rendered;\n }\n\n return value.toFixed(EXPANDED_FRACTION_DIGITS).replace(/\\.?0+$/, '');\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAiBA,MAAM,2BAA2B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgCjC,MAAa,wBAAwB,UAA0B;CAC7D,MAAM,WAAW,GAAG;AAEpB,KAAI,CAAC,SAAS,SAAS,KAAK,CAC1B,QAAO;AAGT,QAAO,MAAM,QAAQ,yBAAyB,CAAC,QAAQ,UAAU,GAAG"}
@@ -70,6 +70,33 @@ declare function validateSignedRange(label: string, value: number, limit: number
70
70
  * ```
71
71
  */
72
72
  declare function validateNumericCoordinate(lat: number, lon: number): string[];
73
+ /**
74
+ * Reports whether latitude and longitude are both finite and in range.
75
+ *
76
+ * A boolean-only probe for hot paths that need validity but not the error
77
+ * messages: it short-circuits on the first finite/range failure and allocates
78
+ * nothing, unlike {@link validateNumericCoordinate}, which builds an error
79
+ * array. Use that function when the messages are consumed.
80
+ *
81
+ * @param lat - The latitude value to validate (must be -90 to 90).
82
+ * @param lon - The longitude value to validate (must be -180 to 180).
83
+ * @returns `true` when both values are finite and within range.
84
+ *
85
+ * @remarks pure function
86
+ *
87
+ * @example
88
+ * ```typescript
89
+ * isValidNumericCoordinate(45.5, -122.6);
90
+ * // => true
91
+ * ```
92
+ *
93
+ * @example
94
+ * ```typescript
95
+ * isValidNumericCoordinate(91, -122.6);
96
+ * // => false
97
+ * ```
98
+ */
99
+ declare function isValidNumericCoordinate(lat: number, lon: number): boolean;
73
100
  //#endregion
74
- export { isFiniteNumber, validateNumericCoordinate, validateSignedRange };
101
+ export { isFiniteNumber, isValidNumericCoordinate, validateNumericCoordinate, validateSignedRange };
75
102
  //# sourceMappingURL=validate.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
@@ -99,7 +99,36 @@ function validateNumericCoordinate(lat, lon) {
99
99
  if (lonError) errors.push(lonError);
100
100
  return errors;
101
101
  }
102
+ /**
103
+ * Reports whether latitude and longitude are both finite and in range.
104
+ *
105
+ * A boolean-only probe for hot paths that need validity but not the error
106
+ * messages: it short-circuits on the first finite/range failure and allocates
107
+ * nothing, unlike {@link validateNumericCoordinate}, which builds an error
108
+ * array. Use that function when the messages are consumed.
109
+ *
110
+ * @param lat - The latitude value to validate (must be -90 to 90).
111
+ * @param lon - The longitude value to validate (must be -180 to 180).
112
+ * @returns `true` when both values are finite and within range.
113
+ *
114
+ * @remarks pure function
115
+ *
116
+ * @example
117
+ * ```typescript
118
+ * isValidNumericCoordinate(45.5, -122.6);
119
+ * // => true
120
+ * ```
121
+ *
122
+ * @example
123
+ * ```typescript
124
+ * isValidNumericCoordinate(91, -122.6);
125
+ * // => false
126
+ * ```
127
+ */
128
+ function isValidNumericCoordinate(lat, lon) {
129
+ return isFiniteNumber(lat) && isFiniteNumber(lon) && lat >= -LAT_LIMIT && lat <= LAT_LIMIT && lon >= -LON_LIMIT && lon <= LON_LIMIT;
130
+ }
102
131
 
103
132
  //#endregion
104
- export { isFiniteNumber, validateNumericCoordinate, validateSignedRange };
133
+ export { isFiniteNumber, isValidNumericCoordinate, validateNumericCoordinate, validateSignedRange };
105
134
  //# sourceMappingURL=validate.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"validate.js","names":["errors: string[]"],"sources":["../../../../src/coordinates/latlon/internal/validate.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 { violation } from './violation';\n\nconst LAT_LIMIT = 90;\nconst LON_LIMIT = 180;\n\n/**\n * Checks if a value is a finite number (not NaN, not Infinity, not -Infinity).\n *\n * @param value - The numeric value to check\n * @returns True if the value is a finite number, false otherwise\n *\n * @example\n * ```typescript\n * isFiniteNumber(42); // true\n * isFiniteNumber(NaN); // false\n * isFiniteNumber(Infinity); // false\n * ```\n */\nexport function isFiniteNumber(value: number): boolean {\n return typeof value === 'number' && Number.isFinite(value);\n}\n\n/**\n * Validates that a value is within a signed range (-limit to +limit).\n *\n * @param label - The label for error messages (used as-is for range errors,\n * lowercased for \"Invalid\" errors)\n * @param value - The numeric value to validate\n * @param limit - The absolute limit (validates -limit to +limit)\n * @returns Error message string if validation fails, undefined if valid\n *\n * @example\n * ```typescript\n * validateSignedRange('Latitude', 45, 90);\n * // => undefined\n * ```\n *\n * @example\n * ```typescript\n * validateSignedRange('Latitude', 95, 90);\n * // => '[ERROR] Latitude value (95) is outside valid range (-90 to 90).'\n * ```\n *\n * @example\n * ```typescript\n * validateSignedRange('Longitude', NaN, 180);\n * // => '[ERROR] Invalid longitude value (NaN); expected a finite number.'\n * ```\n */\nexport function validateSignedRange(\n label: string,\n value: number,\n limit: number,\n): string | undefined {\n if (!isFiniteNumber(value)) {\n return violation(\n `Invalid ${label.toLowerCase()} value (${value}); expected a finite number.`,\n );\n }\n\n if (value < -limit || value > limit) {\n return violation(\n `${label} value (${value}) is outside valid range (-${limit} to ${limit}).`,\n );\n }\n}\n\n/**\n * Validates numeric latitude and longitude coordinate values.\n *\n * @param lat - The latitude value to validate (must be -90 to 90)\n * @param lon - The longitude value to validate (must be -180 to 180)\n * @returns Array of error message strings, empty if all validations pass\n *\n * @example\n * ```typescript\n * validateNumericCoordinate(45.5, -122.6);\n * // => []\n * ```\n *\n * @example\n * ```typescript\n * validateNumericCoordinate(91, -122.6);\n * // => ['[ERROR] Latitude value (91) is outside valid range (-90 to 90).']\n * ```\n *\n * @example\n * ```typescript\n * validateNumericCoordinate(NaN, 200);\n * // => [\n * // '[ERROR] Invalid latitude value (NaN); expected a finite number.',\n * // '[ERROR] Longitude value (200) is outside valid range (-180 to 180).'\n * // ]\n * ```\n */\nexport function validateNumericCoordinate(lat: number, lon: number): string[] {\n const errors: string[] = [];\n\n const latError = validateSignedRange('Latitude', lat, LAT_LIMIT);\n if (latError) {\n errors.push(latError);\n }\n\n const lonError = validateSignedRange('Longitude', lon, LON_LIMIT);\n if (lonError) {\n errors.push(lonError);\n }\n\n return errors;\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAcA,MAAM,YAAY;AAClB,MAAM,YAAY;;;;;;;;;;;;;;AAelB,SAAgB,eAAe,OAAwB;AACrD,QAAO,OAAO,UAAU,YAAY,OAAO,SAAS,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8B5D,SAAgB,oBACd,OACA,OACA,OACoB;AACpB,KAAI,CAAC,eAAe,MAAM,CACxB,QAAO,UACL,WAAW,MAAM,aAAa,CAAC,UAAU,MAAM,8BAChD;AAGH,KAAI,QAAQ,CAAC,SAAS,QAAQ,MAC5B,QAAO,UACL,GAAG,MAAM,UAAU,MAAM,6BAA6B,MAAM,MAAM,MAAM,IACzE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgCL,SAAgB,0BAA0B,KAAa,KAAuB;CAC5E,MAAMA,SAAmB,EAAE;CAE3B,MAAM,WAAW,oBAAoB,YAAY,KAAK,UAAU;AAChE,KAAI,SACF,QAAO,KAAK,SAAS;CAGvB,MAAM,WAAW,oBAAoB,aAAa,KAAK,UAAU;AACjE,KAAI,SACF,QAAO,KAAK,SAAS;AAGvB,QAAO"}
1
+ {"version":3,"file":"validate.js","names":["errors: string[]"],"sources":["../../../../src/coordinates/latlon/internal/validate.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 { violation } from './violation';\n\nconst LAT_LIMIT = 90;\nconst LON_LIMIT = 180;\n\n/**\n * Checks if a value is a finite number (not NaN, not Infinity, not -Infinity).\n *\n * @param value - The numeric value to check\n * @returns True if the value is a finite number, false otherwise\n *\n * @example\n * ```typescript\n * isFiniteNumber(42); // true\n * isFiniteNumber(NaN); // false\n * isFiniteNumber(Infinity); // false\n * ```\n */\nexport function isFiniteNumber(value: number): boolean {\n return typeof value === 'number' && Number.isFinite(value);\n}\n\n/**\n * Validates that a value is within a signed range (-limit to +limit).\n *\n * @param label - The label for error messages (used as-is for range errors,\n * lowercased for \"Invalid\" errors)\n * @param value - The numeric value to validate\n * @param limit - The absolute limit (validates -limit to +limit)\n * @returns Error message string if validation fails, undefined if valid\n *\n * @example\n * ```typescript\n * validateSignedRange('Latitude', 45, 90);\n * // => undefined\n * ```\n *\n * @example\n * ```typescript\n * validateSignedRange('Latitude', 95, 90);\n * // => '[ERROR] Latitude value (95) is outside valid range (-90 to 90).'\n * ```\n *\n * @example\n * ```typescript\n * validateSignedRange('Longitude', NaN, 180);\n * // => '[ERROR] Invalid longitude value (NaN); expected a finite number.'\n * ```\n */\nexport function validateSignedRange(\n label: string,\n value: number,\n limit: number,\n): string | undefined {\n if (!isFiniteNumber(value)) {\n return violation(\n `Invalid ${label.toLowerCase()} value (${value}); expected a finite number.`,\n );\n }\n\n if (value < -limit || value > limit) {\n return violation(\n `${label} value (${value}) is outside valid range (-${limit} to ${limit}).`,\n );\n }\n}\n\n/**\n * Validates numeric latitude and longitude coordinate values.\n *\n * @param lat - The latitude value to validate (must be -90 to 90)\n * @param lon - The longitude value to validate (must be -180 to 180)\n * @returns Array of error message strings, empty if all validations pass\n *\n * @example\n * ```typescript\n * validateNumericCoordinate(45.5, -122.6);\n * // => []\n * ```\n *\n * @example\n * ```typescript\n * validateNumericCoordinate(91, -122.6);\n * // => ['[ERROR] Latitude value (91) is outside valid range (-90 to 90).']\n * ```\n *\n * @example\n * ```typescript\n * validateNumericCoordinate(NaN, 200);\n * // => [\n * // '[ERROR] Invalid latitude value (NaN); expected a finite number.',\n * // '[ERROR] Longitude value (200) is outside valid range (-180 to 180).'\n * // ]\n * ```\n */\nexport function validateNumericCoordinate(lat: number, lon: number): string[] {\n const errors: string[] = [];\n\n const latError = validateSignedRange('Latitude', lat, LAT_LIMIT);\n if (latError) {\n errors.push(latError);\n }\n\n const lonError = validateSignedRange('Longitude', lon, LON_LIMIT);\n if (lonError) {\n errors.push(lonError);\n }\n\n return errors;\n}\n\n/**\n * Reports whether latitude and longitude are both finite and in range.\n *\n * A boolean-only probe for hot paths that need validity but not the error\n * messages: it short-circuits on the first finite/range failure and allocates\n * nothing, unlike {@link validateNumericCoordinate}, which builds an error\n * array. Use that function when the messages are consumed.\n *\n * @param lat - The latitude value to validate (must be -90 to 90).\n * @param lon - The longitude value to validate (must be -180 to 180).\n * @returns `true` when both values are finite and within range.\n *\n * @remarks pure function\n *\n * @example\n * ```typescript\n * isValidNumericCoordinate(45.5, -122.6);\n * // => true\n * ```\n *\n * @example\n * ```typescript\n * isValidNumericCoordinate(91, -122.6);\n * // => false\n * ```\n */\nexport function isValidNumericCoordinate(lat: number, lon: number): boolean {\n return (\n isFiniteNumber(lat) &&\n isFiniteNumber(lon) &&\n lat >= -LAT_LIMIT &&\n lat <= LAT_LIMIT &&\n lon >= -LON_LIMIT &&\n lon <= LON_LIMIT\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAcA,MAAM,YAAY;AAClB,MAAM,YAAY;;;;;;;;;;;;;;AAelB,SAAgB,eAAe,OAAwB;AACrD,QAAO,OAAO,UAAU,YAAY,OAAO,SAAS,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8B5D,SAAgB,oBACd,OACA,OACA,OACoB;AACpB,KAAI,CAAC,eAAe,MAAM,CACxB,QAAO,UACL,WAAW,MAAM,aAAa,CAAC,UAAU,MAAM,8BAChD;AAGH,KAAI,QAAQ,CAAC,SAAS,QAAQ,MAC5B,QAAO,UACL,GAAG,MAAM,UAAU,MAAM,6BAA6B,MAAM,MAAM,MAAM,IACzE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgCL,SAAgB,0BAA0B,KAAa,KAAuB;CAC5E,MAAMA,SAAmB,EAAE;CAE3B,MAAM,WAAW,oBAAoB,YAAY,KAAK,UAAU;AAChE,KAAI,SACF,QAAO,KAAK,SAAS;CAGvB,MAAM,WAAW,oBAAoB,aAAa,KAAK,UAAU;AACjE,KAAI,SACF,QAAO,KAAK,SAAS;AAGvB,QAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6BT,SAAgB,yBAAyB,KAAa,KAAsB;AAC1E,QACE,eAAe,IAAI,IACnB,eAAe,IAAI,IACnB,OAAO,CAAC,aACR,OAAO,aACP,OAAO,CAAC,aACR,OAAO"}