@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.
- package/CHANGELOG.md +6 -0
- package/README.md +233 -1
- package/catalog-info.yaml +1 -1
- package/dist/cartesian.d.ts +2 -0
- package/dist/cartesian.js +2 -0
- package/dist/cartesian.js.map +1 -1
- package/dist/coordinates/coordinate.d.ts +41 -7
- package/dist/coordinates/coordinate.js +102 -12
- package/dist/coordinates/coordinate.js.map +1 -1
- package/dist/coordinates/latlon/decimal-degrees/formatter.d.ts +20 -0
- package/dist/coordinates/latlon/decimal-degrees/formatter.js +38 -0
- package/dist/coordinates/latlon/decimal-degrees/formatter.js.map +1 -1
- package/dist/coordinates/latlon/decimal-degrees/parser.d.ts +68 -2
- package/dist/coordinates/latlon/decimal-degrees/parser.js +66 -5
- package/dist/coordinates/latlon/decimal-degrees/parser.js.map +1 -1
- package/dist/coordinates/latlon/decimal-degrees/system.d.ts +32 -0
- package/dist/coordinates/latlon/decimal-degrees/system.js +31 -0
- package/dist/coordinates/latlon/decimal-degrees/system.js.map +1 -1
- package/dist/coordinates/latlon/degrees-decimal-minutes/formatter.d.ts +20 -0
- package/dist/coordinates/latlon/degrees-decimal-minutes/formatter.js +37 -0
- package/dist/coordinates/latlon/degrees-decimal-minutes/formatter.js.map +1 -1
- package/dist/coordinates/latlon/degrees-decimal-minutes/parser.d.ts +72 -2
- package/dist/coordinates/latlon/degrees-decimal-minutes/parser.js +66 -3
- package/dist/coordinates/latlon/degrees-decimal-minutes/parser.js.map +1 -1
- package/dist/coordinates/latlon/degrees-decimal-minutes/system.d.ts +32 -0
- package/dist/coordinates/latlon/degrees-decimal-minutes/system.js +31 -0
- package/dist/coordinates/latlon/degrees-decimal-minutes/system.js.map +1 -1
- package/dist/coordinates/latlon/degrees-minutes-seconds/formatter.d.ts +20 -0
- package/dist/coordinates/latlon/degrees-minutes-seconds/formatter.js +37 -0
- package/dist/coordinates/latlon/degrees-minutes-seconds/formatter.js.map +1 -1
- package/dist/coordinates/latlon/degrees-minutes-seconds/parser.d.ts +74 -2
- package/dist/coordinates/latlon/degrees-minutes-seconds/parser.js +66 -3
- package/dist/coordinates/latlon/degrees-minutes-seconds/parser.js.map +1 -1
- package/dist/coordinates/latlon/degrees-minutes-seconds/system.d.ts +32 -0
- package/dist/coordinates/latlon/degrees-minutes-seconds/system.js +31 -0
- package/dist/coordinates/latlon/degrees-minutes-seconds/system.js.map +1 -1
- package/dist/coordinates/latlon/internal/coordinate-system.d.ts +22 -0
- package/dist/coordinates/latlon/internal/create-cache.d.ts +17 -1
- package/dist/coordinates/latlon/internal/create-cache.js +19 -3
- package/dist/coordinates/latlon/internal/create-cache.js.map +1 -1
- package/dist/coordinates/latlon/internal/exhaustive-errors.d.ts +15 -0
- package/dist/coordinates/latlon/internal/exhaustive-errors.js +28 -0
- package/dist/coordinates/latlon/internal/exhaustive-errors.js.map +1 -1
- package/dist/coordinates/latlon/internal/format.d.ts +20 -0
- package/dist/coordinates/latlon/internal/format.js +20 -0
- package/dist/coordinates/latlon/internal/format.js.map +1 -1
- package/dist/coordinates/latlon/internal/in-range.d.ts +23 -0
- package/dist/coordinates/latlon/internal/in-range.js +24 -0
- package/dist/coordinates/latlon/internal/in-range.js.map +1 -1
- package/dist/coordinates/latlon/internal/index.d.ts +16 -1
- package/dist/coordinates/latlon/internal/index.js +25 -1
- package/dist/coordinates/latlon/internal/index.js.map +1 -1
- package/dist/coordinates/latlon/internal/lexer.d.ts +2 -0
- package/dist/coordinates/latlon/internal/lexer.js +26 -0
- package/dist/coordinates/latlon/internal/lexer.js.map +1 -1
- package/dist/coordinates/latlon/internal/normalize.d.ts +67 -0
- package/dist/coordinates/latlon/internal/normalize.js +87 -0
- package/dist/coordinates/latlon/internal/normalize.js.map +1 -0
- package/dist/coordinates/latlon/internal/ordinal.d.ts +25 -0
- package/dist/coordinates/latlon/internal/ordinal.js +25 -0
- package/dist/coordinates/latlon/internal/ordinal.js.map +1 -1
- package/dist/coordinates/latlon/internal/parse-format.d.ts +22 -0
- package/dist/coordinates/latlon/internal/parse-format.js +43 -1
- package/dist/coordinates/latlon/internal/parse-format.js.map +1 -1
- package/dist/coordinates/latlon/internal/parse.d.ts +2 -0
- package/dist/coordinates/latlon/internal/parse.js +4 -1
- package/dist/coordinates/latlon/internal/parse.js.map +1 -1
- package/dist/coordinates/latlon/internal/pipes/check-ambiguous.d.ts +17 -0
- package/dist/coordinates/latlon/internal/pipes/check-ambiguous.js +16 -0
- package/dist/coordinates/latlon/internal/pipes/check-ambiguous.js.map +1 -1
- package/dist/coordinates/latlon/internal/pipes/check-numbers.d.ts +25 -0
- package/dist/coordinates/latlon/internal/pipes/check-numbers.js +33 -0
- package/dist/coordinates/latlon/internal/pipes/check-numbers.js.map +1 -1
- package/dist/coordinates/latlon/internal/pipes/fix-bearings.d.ts +17 -0
- package/dist/coordinates/latlon/internal/pipes/fix-bearings.js +31 -0
- package/dist/coordinates/latlon/internal/pipes/fix-bearings.js.map +1 -1
- package/dist/coordinates/latlon/internal/pipes/fix-dividers.d.ts +17 -0
- package/dist/coordinates/latlon/internal/pipes/fix-dividers.js +29 -0
- package/dist/coordinates/latlon/internal/pipes/fix-dividers.js.map +1 -1
- package/dist/coordinates/latlon/internal/pipes/genome.d.ts +16 -0
- package/dist/coordinates/latlon/internal/pipes/genome.js +41 -0
- package/dist/coordinates/latlon/internal/pipes/genome.js.map +1 -1
- package/dist/coordinates/latlon/internal/pipes/index.d.ts +32 -2
- package/dist/coordinates/latlon/internal/pipes/index.js +57 -4
- package/dist/coordinates/latlon/internal/pipes/index.js.map +1 -1
- package/dist/coordinates/latlon/internal/pipes/simpler.d.ts +16 -3
- package/dist/coordinates/latlon/internal/pipes/simpler.js +15 -3
- package/dist/coordinates/latlon/internal/pipes/simpler.js.map +1 -1
- package/dist/coordinates/latlon/internal/validate.d.ts +75 -0
- package/dist/coordinates/latlon/internal/validate.js +105 -0
- package/dist/coordinates/latlon/internal/validate.js.map +1 -0
- package/dist/coordinates/latlon/internal/violation.d.ts +18 -0
- package/dist/coordinates/latlon/internal/violation.js +18 -0
- package/dist/coordinates/latlon/internal/violation.js.map +1 -1
- package/dist/coordinates/mgrs/parser.d.ts +24 -0
- package/dist/coordinates/mgrs/parser.js +56 -0
- package/dist/coordinates/mgrs/parser.js.map +1 -1
- package/dist/coordinates/mgrs/system.d.ts +31 -0
- package/dist/coordinates/mgrs/system.js +30 -0
- package/dist/coordinates/mgrs/system.js.map +1 -1
- package/dist/coordinates/utm/parser.d.ts +24 -0
- package/dist/coordinates/utm/parser.js +56 -0
- package/dist/coordinates/utm/parser.js.map +1 -1
- package/dist/coordinates/utm/system.d.ts +21 -0
- package/dist/coordinates/utm/system.js +20 -0
- package/dist/coordinates/utm/system.js.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +3 -1
- package/dist/patterning.d.ts +12 -2
- package/dist/patterning.js +12 -2
- package/dist/patterning.js.map +1 -1
- package/package.json +3 -1
|
@@ -95,11 +95,39 @@ const systems = [
|
|
|
95
95
|
/**
|
|
96
96
|
* A collection of input strings each with exactly one error in a unique
|
|
97
97
|
* position for each format (LATLON and LONLAT) in each system (DD, DDM, DMS).
|
|
98
|
+
*
|
|
99
|
+
* Used for comprehensive error validation testing. Each entry contains coordinate
|
|
100
|
+
* strings with systematic errors across different notation systems.
|
|
101
|
+
*
|
|
102
|
+
* @example
|
|
103
|
+
* ```typescript
|
|
104
|
+
* EXHAUSTIVE_ERRORS.DD.LATLON;
|
|
105
|
+
* // Array of decimal degrees strings with errors like '91 N / 179 E'
|
|
106
|
+
* ```
|
|
107
|
+
*
|
|
108
|
+
* @example
|
|
109
|
+
* ```typescript
|
|
110
|
+
* EXHAUSTIVE_ERRORS.DMS.LONLAT;
|
|
111
|
+
* // Array of degrees-minutes-seconds strings with errors
|
|
112
|
+
* ```
|
|
98
113
|
*/
|
|
99
114
|
const EXHAUSTIVE_ERRORS = Object.fromEntries(systems.map(({ designation, ...system }) => {
|
|
100
115
|
const options = ["LAT LON", "LON LAT"].map((format) => [format.replace(" ", ""), cartesian(...format.split(" ").map((key) => system[key])).map((pair) => pair.join(" / ")).flatMap((t) => fillTemplate(t, values))]);
|
|
101
116
|
return [designation, Object.fromEntries(options)];
|
|
102
117
|
}));
|
|
118
|
+
/**
|
|
119
|
+
* Fills a coordinate template with test values, generating variations with errors.
|
|
120
|
+
*
|
|
121
|
+
* @param template - Template string with placeholders like 'DLAT MM BLAT / DLON MM BLON'.
|
|
122
|
+
* @param values - Object containing valid and invalid test values for each placeholder.
|
|
123
|
+
* @returns Array of coordinate strings with systematic errors injected.
|
|
124
|
+
*
|
|
125
|
+
* @example
|
|
126
|
+
* ```typescript
|
|
127
|
+
* fillTemplate('DLAT MM BLAT / DLON MM BLON', values);
|
|
128
|
+
* // ['91 30 N / 179 30 E', 'nope 30 N / 179 30 E', ...]
|
|
129
|
+
* ```
|
|
130
|
+
*/
|
|
103
131
|
function fillTemplate(template, values$1) {
|
|
104
132
|
return template.split(" ").flatMap((key, i, original) => {
|
|
105
133
|
if (!values$1.invalid[key]) return "";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"exhaustive-errors.js","names":["values: Values","values"],"sources":["../../../../src/coordinates/latlon/internal/exhaustive-errors.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 { cartesian } from '@/cartesian';\n\ntype Values = {\n invalid: Record<string, string[]>;\n valid: Record<string, string>;\n};\n\nconst values: Values = {\n invalid: {\n BLAT: ['X', 'random garbage'],\n BLON: ['X', 'random garbage'],\n DLAT: ['91', 'nope'],\n DDLAT: ['90.1', 'nope'],\n DLON: ['181', 'nope'],\n DDLON: ['180.1', 'nope'],\n M: ['-1', '61', 'nope'],\n MM: ['-0.1', '60.1', 'nope'],\n SS: ['-0.1', '60.1', 'nope'],\n },\n valid: {\n '/': '/',\n BLAT: 'N',\n BLON: 'E',\n DLAT: '89',\n DDLAT: '89.999999999',\n DLON: '179',\n DDLON: '179.999999999',\n M: '59',\n MM: '59.999999999',\n SS: '59.999999999',\n },\n};\n\nconst systems = [\n {\n designation: 'DD',\n LAT: ['DDLAT', 'BLAT DDLAT', 'DDLAT BLAT'],\n LON: ['DDLON', 'BLON DDLON', 'DDLON BLON'],\n },\n {\n designation: 'DDM',\n LAT: ['DLAT MM', 'BLAT DLAT MM', 'DLAT MM BLAT'],\n LON: ['DLON MM', 'BLON DLON MM', 'DLON MM BLON'],\n },\n {\n designation: 'DMS',\n LAT: ['DLAT M SS', 'BLAT DLAT M SS', 'DLAT M SS BLAT'],\n LON: ['DLON M SS', 'BLON DLON M SS', 'DLON M SS BLON'],\n },\n];\n\n/**\n * A collection of input strings each with exactly one error in a unique\n * position for each format (LATLON and LONLAT) in each system (DD, DDM, DMS).\n */\nexport const EXHAUSTIVE_ERRORS = Object.fromEntries(\n systems.map(({ designation, ...system }) => {\n // for both format options\n const options = ['LAT LON', 'LON LAT'].map((format) => [\n // create object key: 'LATLON' or 'LONLAT'\n format.replace(' ', ''),\n\n // cross-join each variation of LAT with each variation of LON in the system\n cartesian(\n ...format.split(' ').map((key) => system[key as keyof typeof system]),\n )\n // input not including this isn't an error so no need for variation\n .map((pair) => pair.join(' / '))\n // fill the generated template with actual values\n .flatMap((t) => fillTemplate(t, values)),\n ]);\n\n return [designation, Object.fromEntries(options)];\n }),\n);\n\nfunction fillTemplate(template: string, values: Values) {\n return template\n .split(' ')\n .flatMap((key, i, original) => {\n if (!values.invalid[key]) {\n return '';\n }\n\n return (values.invalid[key] as string[]).map((opt) =>\n [...original.slice(0, i), opt, ...original.slice(i + 1)]\n .map((token) => (token in values.valid ? values.valid[token] : token))\n .join(' '),\n );\n })\n .filter(Boolean);\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAoBA,MAAMA,SAAiB;CACrB,SAAS;EACP,MAAM,CAAC,KAAK,iBAAiB;EAC7B,MAAM,CAAC,KAAK,iBAAiB;EAC7B,MAAM,CAAC,MAAM,OAAO;EACpB,OAAO,CAAC,QAAQ,OAAO;EACvB,MAAM,CAAC,OAAO,OAAO;EACrB,OAAO,CAAC,SAAS,OAAO;EACxB,GAAG;GAAC;GAAM;GAAM;GAAO;EACvB,IAAI;GAAC;GAAQ;GAAQ;GAAO;EAC5B,IAAI;GAAC;GAAQ;GAAQ;GAAO;EAC7B;CACD,OAAO;EACL,KAAK;EACL,MAAM;EACN,MAAM;EACN,MAAM;EACN,OAAO;EACP,MAAM;EACN,OAAO;EACP,GAAG;EACH,IAAI;EACJ,IAAI;EACL;CACF;AAED,MAAM,UAAU;CACd;EACE,aAAa;EACb,KAAK;GAAC;GAAS;GAAc;GAAa;EAC1C,KAAK;GAAC;GAAS;GAAc;GAAa;EAC3C;CACD;EACE,aAAa;EACb,KAAK;GAAC;GAAW;GAAgB;GAAe;EAChD,KAAK;GAAC;GAAW;GAAgB;GAAe;EACjD;CACD;EACE,aAAa;EACb,KAAK;GAAC;GAAa;GAAkB;GAAiB;EACtD,KAAK;GAAC;GAAa;GAAkB;GAAiB;EACvD;CACF
|
|
1
|
+
{"version":3,"file":"exhaustive-errors.js","names":["values: Values","values"],"sources":["../../../../src/coordinates/latlon/internal/exhaustive-errors.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 { cartesian } from '@/cartesian';\n\ntype Values = {\n invalid: Record<string, string[]>;\n valid: Record<string, string>;\n};\n\nconst values: Values = {\n invalid: {\n BLAT: ['X', 'random garbage'],\n BLON: ['X', 'random garbage'],\n DLAT: ['91', 'nope'],\n DDLAT: ['90.1', 'nope'],\n DLON: ['181', 'nope'],\n DDLON: ['180.1', 'nope'],\n M: ['-1', '61', 'nope'],\n MM: ['-0.1', '60.1', 'nope'],\n SS: ['-0.1', '60.1', 'nope'],\n },\n valid: {\n '/': '/',\n BLAT: 'N',\n BLON: 'E',\n DLAT: '89',\n DDLAT: '89.999999999',\n DLON: '179',\n DDLON: '179.999999999',\n M: '59',\n MM: '59.999999999',\n SS: '59.999999999',\n },\n};\n\nconst systems = [\n {\n designation: 'DD',\n LAT: ['DDLAT', 'BLAT DDLAT', 'DDLAT BLAT'],\n LON: ['DDLON', 'BLON DDLON', 'DDLON BLON'],\n },\n {\n designation: 'DDM',\n LAT: ['DLAT MM', 'BLAT DLAT MM', 'DLAT MM BLAT'],\n LON: ['DLON MM', 'BLON DLON MM', 'DLON MM BLON'],\n },\n {\n designation: 'DMS',\n LAT: ['DLAT M SS', 'BLAT DLAT M SS', 'DLAT M SS BLAT'],\n LON: ['DLON M SS', 'BLON DLON M SS', 'DLON M SS BLON'],\n },\n];\n\n/**\n * A collection of input strings each with exactly one error in a unique\n * position for each format (LATLON and LONLAT) in each system (DD, DDM, DMS).\n *\n * Used for comprehensive error validation testing. Each entry contains coordinate\n * strings with systematic errors across different notation systems.\n *\n * @example\n * ```typescript\n * EXHAUSTIVE_ERRORS.DD.LATLON;\n * // Array of decimal degrees strings with errors like '91 N / 179 E'\n * ```\n *\n * @example\n * ```typescript\n * EXHAUSTIVE_ERRORS.DMS.LONLAT;\n * // Array of degrees-minutes-seconds strings with errors\n * ```\n */\nexport const EXHAUSTIVE_ERRORS = Object.fromEntries(\n systems.map(({ designation, ...system }) => {\n // for both format options\n const options = ['LAT LON', 'LON LAT'].map((format) => [\n // create object key: 'LATLON' or 'LONLAT'\n format.replace(' ', ''),\n\n // cross-join each variation of LAT with each variation of LON in the system\n cartesian(\n ...format.split(' ').map((key) => system[key as keyof typeof system]),\n )\n // input not including this isn't an error so no need for variation\n .map((pair) => pair.join(' / '))\n // fill the generated template with actual values\n .flatMap((t) => fillTemplate(t, values)),\n ]);\n\n return [designation, Object.fromEntries(options)];\n }),\n);\n\n/**\n * Fills a coordinate template with test values, generating variations with errors.\n *\n * @param template - Template string with placeholders like 'DLAT MM BLAT / DLON MM BLON'.\n * @param values - Object containing valid and invalid test values for each placeholder.\n * @returns Array of coordinate strings with systematic errors injected.\n *\n * @example\n * ```typescript\n * fillTemplate('DLAT MM BLAT / DLON MM BLON', values);\n * // ['91 30 N / 179 30 E', 'nope 30 N / 179 30 E', ...]\n * ```\n */\nfunction fillTemplate(template: string, values: Values) {\n return template\n .split(' ')\n .flatMap((key, i, original) => {\n if (!values.invalid[key]) {\n return '';\n }\n\n return (values.invalid[key] as string[]).map((opt) =>\n [...original.slice(0, i), opt, ...original.slice(i + 1)]\n .map((token) => (token in values.valid ? values.valid[token] : token))\n .join(' '),\n );\n })\n .filter(Boolean);\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAoBA,MAAMA,SAAiB;CACrB,SAAS;EACP,MAAM,CAAC,KAAK,iBAAiB;EAC7B,MAAM,CAAC,KAAK,iBAAiB;EAC7B,MAAM,CAAC,MAAM,OAAO;EACpB,OAAO,CAAC,QAAQ,OAAO;EACvB,MAAM,CAAC,OAAO,OAAO;EACrB,OAAO,CAAC,SAAS,OAAO;EACxB,GAAG;GAAC;GAAM;GAAM;GAAO;EACvB,IAAI;GAAC;GAAQ;GAAQ;GAAO;EAC5B,IAAI;GAAC;GAAQ;GAAQ;GAAO;EAC7B;CACD,OAAO;EACL,KAAK;EACL,MAAM;EACN,MAAM;EACN,MAAM;EACN,OAAO;EACP,MAAM;EACN,OAAO;EACP,GAAG;EACH,IAAI;EACJ,IAAI;EACL;CACF;AAED,MAAM,UAAU;CACd;EACE,aAAa;EACb,KAAK;GAAC;GAAS;GAAc;GAAa;EAC1C,KAAK;GAAC;GAAS;GAAc;GAAa;EAC3C;CACD;EACE,aAAa;EACb,KAAK;GAAC;GAAW;GAAgB;GAAe;EAChD,KAAK;GAAC;GAAW;GAAgB;GAAe;EACjD;CACD;EACE,aAAa;EACb,KAAK;GAAC;GAAa;GAAkB;GAAiB;EACtD,KAAK;GAAC;GAAa;GAAkB;GAAiB;EACvD;CACF;;;;;;;;;;;;;;;;;;;;AAqBD,MAAa,oBAAoB,OAAO,YACtC,QAAQ,KAAK,EAAE,aAAa,GAAG,aAAa;CAE1C,MAAM,UAAU,CAAC,WAAW,UAAU,CAAC,KAAK,WAAW,CAErD,OAAO,QAAQ,KAAK,GAAG,EAGvB,UACE,GAAG,OAAO,MAAM,IAAI,CAAC,KAAK,QAAQ,OAAO,KAA4B,CACtE,CAEE,KAAK,SAAS,KAAK,KAAK,MAAM,CAAC,CAE/B,SAAS,MAAM,aAAa,GAAG,OAAO,CAAC,CAC3C,CAAC;AAEF,QAAO,CAAC,aAAa,OAAO,YAAY,QAAQ,CAAC;EACjD,CACH;;;;;;;;;;;;;;AAeD,SAAS,aAAa,UAAkB,UAAgB;AACtD,QAAO,SACJ,MAAM,IAAI,CACV,SAAS,KAAK,GAAG,aAAa;AAC7B,MAAI,CAACC,SAAO,QAAQ,KAClB,QAAO;AAGT,SAAQA,SAAO,QAAQ,KAAkB,KAAK,QAC5C;GAAC,GAAG,SAAS,MAAM,GAAG,EAAE;GAAE;GAAK,GAAG,SAAS,MAAM,IAAI,EAAE;GAAC,CACrD,KAAK,UAAW,SAASA,SAAO,QAAQA,SAAO,MAAM,SAAS,MAAO,CACrE,KAAK,IAAI,CACb;GACD,CACD,OAAO,QAAQ"}
|
|
@@ -5,6 +5,26 @@ interface FormatOptions {
|
|
|
5
5
|
separator: string;
|
|
6
6
|
withOrdinal?: boolean;
|
|
7
7
|
}
|
|
8
|
+
/**
|
|
9
|
+
* Creates a coordinate formatter function from a coordinate conversion function.
|
|
10
|
+
*
|
|
11
|
+
* @param fn - Function that converts a single coordinate value to a formatted string.
|
|
12
|
+
* @returns Formatter function that takes coordinate pair and optional config.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```typescript
|
|
16
|
+
* const formatDD = createFormatter((num) => `${num.toFixed(6)}°`);
|
|
17
|
+
* formatDD([37.7749, -122.4194]);
|
|
18
|
+
* // '37.774900° N, 122.419400° W'
|
|
19
|
+
* ```
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```typescript
|
|
23
|
+
* const formatDMS = createFormatter(toDegreesMinutesSeconds);
|
|
24
|
+
* formatDMS([37.7749, -122.4194], { separator: ' / ', withOrdinal: true });
|
|
25
|
+
* // '37° 46' 29.64″ N / 122° 25' 9.84″ W'
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
8
28
|
declare const createFormatter: (fn: (coord: number, withOrdinal?: boolean) => string) => (coordinates: [number, number], config?: FormatOptions) => string;
|
|
9
29
|
//#endregion
|
|
10
30
|
export { FormatOptions, createFormatter };
|
|
@@ -14,6 +14,26 @@
|
|
|
14
14
|
import { getOrdinal } from "./ordinal.js";
|
|
15
15
|
|
|
16
16
|
//#region src/coordinates/latlon/internal/format.ts
|
|
17
|
+
/**
|
|
18
|
+
* Creates a coordinate formatter function from a coordinate conversion function.
|
|
19
|
+
*
|
|
20
|
+
* @param fn - Function that converts a single coordinate value to a formatted string.
|
|
21
|
+
* @returns Formatter function that takes coordinate pair and optional config.
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* ```typescript
|
|
25
|
+
* const formatDD = createFormatter((num) => `${num.toFixed(6)}°`);
|
|
26
|
+
* formatDD([37.7749, -122.4194]);
|
|
27
|
+
* // '37.774900° N, 122.419400° W'
|
|
28
|
+
* ```
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* ```typescript
|
|
32
|
+
* const formatDMS = createFormatter(toDegreesMinutesSeconds);
|
|
33
|
+
* formatDMS([37.7749, -122.4194], { separator: ' / ', withOrdinal: true });
|
|
34
|
+
* // '37° 46' 29.64″ N / 122° 25' 9.84″ W'
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
17
37
|
const createFormatter = (fn) => (coordinates, config) => {
|
|
18
38
|
const [latitude, longitude] = coordinates;
|
|
19
39
|
const latOrdinal = `${config?.withOrdinal ? ` ${getOrdinal(latitude, true)}` : ""}`;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"format.js","names":[],"sources":["../../../../src/coordinates/latlon/internal/format.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\nimport { getOrdinal } from './ordinal';\n\nexport interface FormatOptions {\n prefix: string;\n suffix: string;\n separator: string;\n withOrdinal?: boolean;\n}\n\nexport const createFormatter =\n (fn: (coord: number, withOrdinal?: boolean) => string) =>\n (coordinates: [number, number], config?: FormatOptions): string => {\n const [latitude, longitude] = coordinates;\n const latOrdinal = `${config?.withOrdinal ? ` ${getOrdinal(latitude, true)}` : ''}`;\n const lonOrdinal = `${config?.withOrdinal ? ` ${getOrdinal(longitude, false)}` : ''}`;\n const lat = fn(latitude, config?.withOrdinal);\n const lon = fn(longitude, config?.withOrdinal);\n const prefix = config?.prefix ?? '';\n const suffix = config?.suffix ?? '';\n const separator = config?.separator ?? ', ';\n\n return `${prefix}${lat}${latOrdinal}${separator}${lon}${lonOrdinal}${suffix}`;\n };\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"format.js","names":[],"sources":["../../../../src/coordinates/latlon/internal/format.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\nimport { getOrdinal } from './ordinal';\n\nexport interface FormatOptions {\n prefix: string;\n suffix: string;\n separator: string;\n withOrdinal?: boolean;\n}\n\n/**\n * Creates a coordinate formatter function from a coordinate conversion function.\n *\n * @param fn - Function that converts a single coordinate value to a formatted string.\n * @returns Formatter function that takes coordinate pair and optional config.\n *\n * @example\n * ```typescript\n * const formatDD = createFormatter((num) => `${num.toFixed(6)}°`);\n * formatDD([37.7749, -122.4194]);\n * // '37.774900° N, 122.419400° W'\n * ```\n *\n * @example\n * ```typescript\n * const formatDMS = createFormatter(toDegreesMinutesSeconds);\n * formatDMS([37.7749, -122.4194], { separator: ' / ', withOrdinal: true });\n * // '37° 46' 29.64″ N / 122° 25' 9.84″ W'\n * ```\n */\nexport const createFormatter =\n (fn: (coord: number, withOrdinal?: boolean) => string) =>\n (coordinates: [number, number], config?: FormatOptions): string => {\n const [latitude, longitude] = coordinates;\n const latOrdinal = `${config?.withOrdinal ? ` ${getOrdinal(latitude, true)}` : ''}`;\n const lonOrdinal = `${config?.withOrdinal ? ` ${getOrdinal(longitude, false)}` : ''}`;\n const lat = fn(latitude, config?.withOrdinal);\n const lon = fn(longitude, config?.withOrdinal);\n const prefix = config?.prefix ?? '';\n const suffix = config?.suffix ?? '';\n const separator = config?.separator ?? ', ';\n\n return `${prefix}${lat}${latOrdinal}${separator}${lon}${lonOrdinal}${suffix}`;\n };\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA2CA,MAAa,mBACV,QACA,aAA+B,WAAmC;CACjE,MAAM,CAAC,UAAU,aAAa;CAC9B,MAAM,aAAa,GAAG,QAAQ,cAAc,IAAI,WAAW,UAAU,KAAK,KAAK;CAC/E,MAAM,aAAa,GAAG,QAAQ,cAAc,IAAI,WAAW,WAAW,MAAM,KAAK;CACjF,MAAM,MAAM,GAAG,UAAU,QAAQ,YAAY;CAC7C,MAAM,MAAM,GAAG,WAAW,QAAQ,YAAY;CAC9C,MAAM,SAAS,QAAQ,UAAU;CACjC,MAAM,SAAS,QAAQ,UAAU;AAGjC,QAAO,GAAG,SAAS,MAAM,aAFP,QAAQ,aAAa,OAEW,MAAM,aAAa"}
|
|
@@ -2,6 +2,29 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* Return an error string if the value is outside the range where the limits
|
|
4
4
|
* are 0-limit.
|
|
5
|
+
*
|
|
6
|
+
* @param label - Descriptive label for the value being validated (e.g., "Minutes", "Seconds").
|
|
7
|
+
* @param value - String representation of the numeric value to validate.
|
|
8
|
+
* @param limit - Maximum allowed value (minimum is always 0).
|
|
9
|
+
* @returns Error message string if out of range, undefined if valid.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```typescript
|
|
13
|
+
* inRange('Minutes', '45', 59);
|
|
14
|
+
* // undefined (valid)
|
|
15
|
+
* ```
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```typescript
|
|
19
|
+
* inRange('Minutes', '61', 59);
|
|
20
|
+
* // 'Minutes value (61) exceeds max value (59).'
|
|
21
|
+
* ```
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* ```typescript
|
|
25
|
+
* inRange('Seconds', '-5', 59);
|
|
26
|
+
* // 'Seconds value (-5) must be positive.'
|
|
27
|
+
* ```
|
|
5
28
|
*/
|
|
6
29
|
declare const inRange: (label: string, value: string, limit: number) => string | undefined;
|
|
7
30
|
//#endregion
|
|
@@ -15,9 +15,33 @@
|
|
|
15
15
|
/**
|
|
16
16
|
* Return an error string if the value is outside the range where the limits
|
|
17
17
|
* are 0-limit.
|
|
18
|
+
*
|
|
19
|
+
* @param label - Descriptive label for the value being validated (e.g., "Minutes", "Seconds").
|
|
20
|
+
* @param value - String representation of the numeric value to validate.
|
|
21
|
+
* @param limit - Maximum allowed value (minimum is always 0).
|
|
22
|
+
* @returns Error message string if out of range, undefined if valid.
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```typescript
|
|
26
|
+
* inRange('Minutes', '45', 59);
|
|
27
|
+
* // undefined (valid)
|
|
28
|
+
* ```
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* ```typescript
|
|
32
|
+
* inRange('Minutes', '61', 59);
|
|
33
|
+
* // 'Minutes value (61) exceeds max value (59).'
|
|
34
|
+
* ```
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* ```typescript
|
|
38
|
+
* inRange('Seconds', '-5', 59);
|
|
39
|
+
* // 'Seconds value (-5) must be positive.'
|
|
40
|
+
* ```
|
|
18
41
|
*/
|
|
19
42
|
const inRange = (label, value, limit) => {
|
|
20
43
|
const num = Number.parseFloat(value);
|
|
44
|
+
if (value !== "" && Number.isNaN(num)) return `${label} value (${value}) is not a valid number.`;
|
|
21
45
|
if (limit < num) return `${label} value (${value}) exceeds max value (${limit}).`;
|
|
22
46
|
if (num < 0) return `${label} value (${value}) must be positive.`;
|
|
23
47
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"in-range.js","names":[],"sources":["../../../../src/coordinates/latlon/internal/in-range.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\n/**\n * Return an error string if the value is outside the range where the limits\n * are 0-limit.\n */\nexport const inRange = (label: string, value: string, limit: number) => {\n const num = Number.parseFloat(value);\n\n if (limit < num) {\n return `${label} value (${value}) exceeds max value (${limit}).`;\n }\n\n if (num < 0) {\n return `${label} value (${value}) must be positive.`;\n }\n};\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"in-range.js","names":[],"sources":["../../../../src/coordinates/latlon/internal/in-range.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\n/**\n * Return an error string if the value is outside the range where the limits\n * are 0-limit.\n *\n * @param label - Descriptive label for the value being validated (e.g., \"Minutes\", \"Seconds\").\n * @param value - String representation of the numeric value to validate.\n * @param limit - Maximum allowed value (minimum is always 0).\n * @returns Error message string if out of range, undefined if valid.\n *\n * @example\n * ```typescript\n * inRange('Minutes', '45', 59);\n * // undefined (valid)\n * ```\n *\n * @example\n * ```typescript\n * inRange('Minutes', '61', 59);\n * // 'Minutes value (61) exceeds max value (59).'\n * ```\n *\n * @example\n * ```typescript\n * inRange('Seconds', '-5', 59);\n * // 'Seconds value (-5) must be positive.'\n * ```\n */\nexport const inRange = (label: string, value: string, limit: number) => {\n const num = Number.parseFloat(value);\n\n if (value !== '' && Number.isNaN(num)) {\n return `${label} value (${value}) is not a valid number.`;\n }\n\n if (limit < num) {\n return `${label} value (${value}) exceeds max value (${limit}).`;\n }\n\n if (num < 0) {\n return `${label} value (${value}) must be positive.`;\n }\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAwCA,MAAa,WAAW,OAAe,OAAe,UAAkB;CACtE,MAAM,MAAM,OAAO,WAAW,MAAM;AAEpC,KAAI,UAAU,MAAM,OAAO,MAAM,IAAI,CACnC,QAAO,GAAG,MAAM,UAAU,MAAM;AAGlC,KAAI,QAAQ,IACV,QAAO,GAAG,MAAM,UAAU,MAAM,uBAAuB,MAAM;AAG/D,KAAI,MAAM,EACR,QAAO,GAAG,MAAM,UAAU,MAAM"}
|
|
@@ -1,3 +1,18 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2025 Hypergiant Galactic Systems Inc. All rights reserved.
|
|
3
|
+
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
* you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
* of the License at https://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
*
|
|
7
|
+
* Unless required by applicable law or agreed to in writing, software distributed under
|
|
8
|
+
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
9
|
+
* OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
|
+
* governing permissions and limitations under the License.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { CoordinateInput, CoordinateInternalValue, CoordinateObject, CoordinateTuple, LatLonTuple, LonLatTuple, isCoordinateObject, isCoordinateTuple, normalizeObjectToLatLon, tupleToLatLon } from "./normalize.js";
|
|
14
|
+
import { isFiniteNumber, validateNumericCoordinate, validateSignedRange } from "./validate.js";
|
|
15
|
+
|
|
1
16
|
//#region src/coordinates/latlon/internal/index.d.ts
|
|
2
17
|
type Axes = 'LAT' | 'LON';
|
|
3
18
|
type Compass = 'N' | 'S' | 'E' | 'W';
|
|
@@ -57,5 +72,5 @@ declare const PARTIAL_PATTERNS: {
|
|
|
57
72
|
readonly secDec: RegExp;
|
|
58
73
|
};
|
|
59
74
|
//#endregion
|
|
60
|
-
export { Axes, BEARINGS, Compass, Errors, FORMATS, FORMATS_DEFAULT, Format, LIMITS, PARTIAL_PATTERNS, SYMBOLS, SYMBOL_PATTERNS };
|
|
75
|
+
export { Axes, BEARINGS, Compass, type CoordinateInput, type CoordinateInternalValue, type CoordinateObject, type CoordinateTuple, Errors, FORMATS, FORMATS_DEFAULT, Format, LIMITS, type LatLonTuple, type LonLatTuple, PARTIAL_PATTERNS, SYMBOLS, SYMBOL_PATTERNS, isCoordinateObject, isCoordinateTuple, isFiniteNumber, normalizeObjectToLatLon, tupleToLatLon, validateNumericCoordinate, validateSignedRange };
|
|
61
76
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -12,6 +12,8 @@
|
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
import { capture, merge, optional } from "../../../patterning.js";
|
|
15
|
+
import { isCoordinateObject, isCoordinateTuple, normalizeObjectToLatLon, tupleToLatLon } from "./normalize.js";
|
|
16
|
+
import { isFiniteNumber, validateNumericCoordinate, validateSignedRange } from "./validate.js";
|
|
15
17
|
|
|
16
18
|
//#region src/coordinates/latlon/internal/index.ts
|
|
17
19
|
/**
|
|
@@ -58,6 +60,28 @@ const SYMBOL_PATTERNS = {
|
|
|
58
60
|
SYMBOLS.SECONDS
|
|
59
61
|
].join("")}]`)
|
|
60
62
|
};
|
|
63
|
+
/**
|
|
64
|
+
* Creates a regex pattern for matching decimal minutes or seconds values.
|
|
65
|
+
*
|
|
66
|
+
* Generates a pattern that matches numeric values in the range 0-59.999... with optional
|
|
67
|
+
* leading zeros, decimal points, and symbol indicators, using lookbehind and lookahead
|
|
68
|
+
* to prevent partial matches within larger numbers.
|
|
69
|
+
*
|
|
70
|
+
* @param symbol - Regular expression for the symbol (minutes ' or seconds " indicator).
|
|
71
|
+
* @returns Combined regex pattern with precise boundary matching.
|
|
72
|
+
*
|
|
73
|
+
* @example
|
|
74
|
+
* ```typescript
|
|
75
|
+
* const minutesPattern = decimalSecAndMin(SYMBOL_PATTERNS.MINUTES);
|
|
76
|
+
* // Matches: "30.5'", "59.999999999", ".5'", "001'", etc.
|
|
77
|
+
* ```
|
|
78
|
+
*
|
|
79
|
+
* @example
|
|
80
|
+
* ```typescript
|
|
81
|
+
* const secondsPattern = decimalSecAndMin(SYMBOL_PATTERNS.SECONDS);
|
|
82
|
+
* // Matches: '45.23"', '0.5', '59.9999999999"', etc.
|
|
83
|
+
* ```
|
|
84
|
+
*/
|
|
61
85
|
const decimalSecAndMin = (symbol) => optional(/(?<!\d)/, /([-+]?0*(?:[0-5]?\d|\.\d{1,10})(?:\.\d{1,10})?)/, optional(symbol), /(?!\d)/);
|
|
62
86
|
const PARTIAL_PATTERNS = {
|
|
63
87
|
" ": /\s*/,
|
|
@@ -74,5 +98,5 @@ const PARTIAL_PATTERNS = {
|
|
|
74
98
|
};
|
|
75
99
|
|
|
76
100
|
//#endregion
|
|
77
|
-
export { BEARINGS, FORMATS, FORMATS_DEFAULT, LIMITS, PARTIAL_PATTERNS, SYMBOLS, SYMBOL_PATTERNS };
|
|
101
|
+
export { BEARINGS, FORMATS, FORMATS_DEFAULT, LIMITS, PARTIAL_PATTERNS, SYMBOLS, SYMBOL_PATTERNS, isCoordinateObject, isCoordinateTuple, isFiniteNumber, normalizeObjectToLatLon, tupleToLatLon, validateNumericCoordinate, validateSignedRange };
|
|
78
102
|
//# sourceMappingURL=index.js.map
|
|
@@ -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\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"],"mappings":"
|
|
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"}
|
|
@@ -13,8 +13,10 @@ type Tokens = ReturnType<typeof lexer>;
|
|
|
13
13
|
* pure function
|
|
14
14
|
*
|
|
15
15
|
* @example
|
|
16
|
+
* ```typescript
|
|
16
17
|
* lexer('N 55,E 44') === ['N' '55', '/', 'E', '44']
|
|
17
18
|
* lexer(` + 89 ° 59 59.999 " N, 179° 59 59.999" `) === ['89', '59', '59.999', 'N', '/', '179', '59', '59.999', 'E']
|
|
19
|
+
* ```
|
|
18
20
|
*/
|
|
19
21
|
declare function lexer(input: string): string[];
|
|
20
22
|
//#endregion
|
|
@@ -36,6 +36,30 @@ const SIGNS = /([-+])\s*/g;
|
|
|
36
36
|
* - [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)
|
|
37
37
|
*/
|
|
38
38
|
const TOKENS = new RegExp(merge(DIVIDERS, /|/, SYMBOL_PATTERNS.NSEW, /|/, group(/[-+]?/, group(/(?:\d+(?:\.\d*)?)|/, /(?:\.\d+)/), optional(SYMBOL_PATTERNS.DMS))), "gi");
|
|
39
|
+
/**
|
|
40
|
+
* Remove trailing zeros '?.0' and ensure leading zero '0.?' in numbers.
|
|
41
|
+
*
|
|
42
|
+
* @param t - Token string to normalize.
|
|
43
|
+
* @returns Normalized token with cleaned numeric formatting.
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* ```typescript
|
|
47
|
+
* fixLeadingAndTrailing('45.0°');
|
|
48
|
+
* // '45°'
|
|
49
|
+
* ```
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* ```typescript
|
|
53
|
+
* fixLeadingAndTrailing('.5'');
|
|
54
|
+
* // '0.5''
|
|
55
|
+
* ```
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* ```typescript
|
|
59
|
+
* fixLeadingAndTrailing('-122.00');
|
|
60
|
+
* // '-122'
|
|
61
|
+
* ```
|
|
62
|
+
*/
|
|
39
63
|
function fixLeadingAndTrailing(t) {
|
|
40
64
|
const [sign, num, pos] = (FLOATS.exec(t) ?? []).slice(1);
|
|
41
65
|
if (num) return `${sign}${Number.parseFloat(num)}${pos}`;
|
|
@@ -54,8 +78,10 @@ function fixLeadingAndTrailing(t) {
|
|
|
54
78
|
* pure function
|
|
55
79
|
*
|
|
56
80
|
* @example
|
|
81
|
+
* ```typescript
|
|
57
82
|
* lexer('N 55,E 44') === ['N' '55', '/', 'E', '44']
|
|
58
83
|
* lexer(` + 89 ° 59 59.999 " N, 179° 59 59.999" `) === ['89', '59', '59.999', 'N', '/', '179', '59', '59.999', 'E']
|
|
84
|
+
* ```
|
|
59
85
|
*/
|
|
60
86
|
function lexer(input) {
|
|
61
87
|
return input.trim().toUpperCase().replace(POSITIVE, "").replace(POSITIONAL, "$1 ").replace(SIGNS, "$1").replace(DIVIDERS, SYMBOLS.DIVIDER).match(TOKENS)?.map(fixLeadingAndTrailing)?.slice() ?? [];
|
|
@@ -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
|
|
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"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
//#region src/coordinates/latlon/internal/normalize.d.ts
|
|
2
|
+
type Format = 'LATLON' | 'LONLAT';
|
|
3
|
+
type CoordinateInternalValue = {
|
|
4
|
+
LAT: number;
|
|
5
|
+
LON: number;
|
|
6
|
+
};
|
|
7
|
+
/** Tuple in `[latitude, longitude]` order. */
|
|
8
|
+
type LatLonTuple = readonly [latitude: number, longitude: number];
|
|
9
|
+
/** Tuple in `[longitude, latitude]` order (GeoJSON convention). */
|
|
10
|
+
type LonLatTuple = readonly [longitude: number, latitude: number];
|
|
11
|
+
/** A coordinate tuple in either lat/lon or lon/lat order. */
|
|
12
|
+
type CoordinateTuple = LatLonTuple | LonLatTuple;
|
|
13
|
+
/**
|
|
14
|
+
* Object representation of a coordinate with explicit lat/lon keys.
|
|
15
|
+
* Accepts objects with case-insensitive variations: lat/LAT/latitude/LATITUDE and lon/LON/longitude/LONGITUDE.
|
|
16
|
+
* Runtime validation via `isCoordinateObject` ensures proper keys are present.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```typescript
|
|
20
|
+
* { lat: 40.7128, lon: -74.0060 }
|
|
21
|
+
* { latitude: 40.7128, longitude: -74.0060 }
|
|
22
|
+
* { LAT: 40.7128, LON: -74.0060 }
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
type CoordinateObject = Record<string, number>;
|
|
26
|
+
type CoordinateInput = string | CoordinateTuple | CoordinateObject;
|
|
27
|
+
declare function isCoordinateTuple(input: CoordinateInput): input is CoordinateTuple;
|
|
28
|
+
declare function isCoordinateObject(input: CoordinateInput): input is CoordinateObject;
|
|
29
|
+
/**
|
|
30
|
+
* Normalizes a coordinate object to a consistent lat/lon format.
|
|
31
|
+
*
|
|
32
|
+
* Accepts coordinate objects with case-insensitive key variations (lat/LAT/latitude/LATITUDE
|
|
33
|
+
* and lon/LON/longitude/LONGITUDE) and returns a normalized object with lowercase 'lat' and 'lon' keys.
|
|
34
|
+
* Returns null if required keys are not found.
|
|
35
|
+
*
|
|
36
|
+
* @param obj - Coordinate object with lat/lon keys (case-insensitive)
|
|
37
|
+
* @returns Normalized object with lat and lon keys, or null if invalid
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* ```typescript
|
|
41
|
+
* normalizeObjectToLatLon({ lat: 40.7128, lon: -74.0060 });
|
|
42
|
+
* // => { lat: 40.7128, lon: -74.0060 }
|
|
43
|
+
* ```
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* ```typescript
|
|
47
|
+
* normalizeObjectToLatLon({ LATITUDE: 40.7128, LONGITUDE: -74.0060 });
|
|
48
|
+
* // => { lat: 40.7128, lon: -74.0060 }
|
|
49
|
+
* ```
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* ```typescript
|
|
53
|
+
* normalizeObjectToLatLon({ x: 10, y: 20 });
|
|
54
|
+
* // => null (missing required keys)
|
|
55
|
+
* ```
|
|
56
|
+
*/
|
|
57
|
+
declare function normalizeObjectToLatLon(obj: CoordinateObject): {
|
|
58
|
+
lat: number;
|
|
59
|
+
lon: number;
|
|
60
|
+
} | null;
|
|
61
|
+
declare function tupleToLatLon(format: Format, tuple: CoordinateTuple): {
|
|
62
|
+
lat: number;
|
|
63
|
+
lon: number;
|
|
64
|
+
};
|
|
65
|
+
//#endregion
|
|
66
|
+
export { CoordinateInput, CoordinateInternalValue, CoordinateObject, CoordinateTuple, LatLonTuple, LonLatTuple, isCoordinateObject, isCoordinateTuple, normalizeObjectToLatLon, tupleToLatLon };
|
|
67
|
+
//# sourceMappingURL=normalize.d.ts.map
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2025 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/normalize.ts
|
|
15
|
+
const LAT_KEYS = ["lat", "latitude"];
|
|
16
|
+
const LON_KEYS = ["lon", "longitude"];
|
|
17
|
+
function isCoordinateTuple(input) {
|
|
18
|
+
return Array.isArray(input) && input.length === 2 && typeof input[0] === "number" && typeof input[1] === "number";
|
|
19
|
+
}
|
|
20
|
+
function isCoordinateObject(input) {
|
|
21
|
+
if (input === null || typeof input !== "object" || Array.isArray(input)) return false;
|
|
22
|
+
const keys = Object.keys(input).map((k) => k.toLowerCase());
|
|
23
|
+
const hasLat = LAT_KEYS.some((k) => keys.includes(k));
|
|
24
|
+
const hasLon = LON_KEYS.some((k) => keys.includes(k));
|
|
25
|
+
return hasLat && hasLon;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Normalizes a coordinate object to a consistent lat/lon format.
|
|
29
|
+
*
|
|
30
|
+
* Accepts coordinate objects with case-insensitive key variations (lat/LAT/latitude/LATITUDE
|
|
31
|
+
* and lon/LON/longitude/LONGITUDE) and returns a normalized object with lowercase 'lat' and 'lon' keys.
|
|
32
|
+
* Returns null if required keys are not found.
|
|
33
|
+
*
|
|
34
|
+
* @param obj - Coordinate object with lat/lon keys (case-insensitive)
|
|
35
|
+
* @returns Normalized object with lat and lon keys, or null if invalid
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* ```typescript
|
|
39
|
+
* normalizeObjectToLatLon({ lat: 40.7128, lon: -74.0060 });
|
|
40
|
+
* // => { lat: 40.7128, lon: -74.0060 }
|
|
41
|
+
* ```
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* ```typescript
|
|
45
|
+
* normalizeObjectToLatLon({ LATITUDE: 40.7128, LONGITUDE: -74.0060 });
|
|
46
|
+
* // => { lat: 40.7128, lon: -74.0060 }
|
|
47
|
+
* ```
|
|
48
|
+
*
|
|
49
|
+
* @example
|
|
50
|
+
* ```typescript
|
|
51
|
+
* normalizeObjectToLatLon({ x: 10, y: 20 });
|
|
52
|
+
* // => null (missing required keys)
|
|
53
|
+
* ```
|
|
54
|
+
*/
|
|
55
|
+
function normalizeObjectToLatLon(obj) {
|
|
56
|
+
const lower = /* @__PURE__ */ new Map();
|
|
57
|
+
for (const [key, value] of Object.entries(obj)) lower.set(key.toLowerCase(), value);
|
|
58
|
+
let lat;
|
|
59
|
+
let lon;
|
|
60
|
+
for (const k of LAT_KEYS) if (lower.has(k)) {
|
|
61
|
+
lat = lower.get(k);
|
|
62
|
+
break;
|
|
63
|
+
}
|
|
64
|
+
for (const k of LON_KEYS) if (lower.has(k)) {
|
|
65
|
+
lon = lower.get(k);
|
|
66
|
+
break;
|
|
67
|
+
}
|
|
68
|
+
if (lat === void 0 || lon === void 0) return null;
|
|
69
|
+
return {
|
|
70
|
+
lat,
|
|
71
|
+
lon
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
function tupleToLatLon(format, tuple) {
|
|
75
|
+
if (format === "LATLON") return {
|
|
76
|
+
lat: tuple[0],
|
|
77
|
+
lon: tuple[1]
|
|
78
|
+
};
|
|
79
|
+
return {
|
|
80
|
+
lat: tuple[1],
|
|
81
|
+
lon: tuple[0]
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
//#endregion
|
|
86
|
+
export { isCoordinateObject, isCoordinateTuple, normalizeObjectToLatLon, tupleToLatLon };
|
|
87
|
+
//# sourceMappingURL=normalize.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"normalize.js","names":["lat: number | undefined","lon: number | undefined"],"sources":["../../../../src/coordinates/latlon/internal/normalize.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\ntype Format = 'LATLON' | 'LONLAT';\n\n// biome-ignore lint/style/useNamingConvention: consistency with Axes type\nexport type CoordinateInternalValue = { LAT: number; LON: number };\n\n/** Tuple in `[latitude, longitude]` order. */\nexport type LatLonTuple = readonly [latitude: number, longitude: number];\n\n/** Tuple in `[longitude, latitude]` order (GeoJSON convention). */\nexport type LonLatTuple = readonly [longitude: number, latitude: number];\n\n/** A coordinate tuple in either lat/lon or lon/lat order. */\nexport type CoordinateTuple = LatLonTuple | LonLatTuple;\n\n/**\n * Object representation of a coordinate with explicit lat/lon keys.\n * Accepts objects with case-insensitive variations: lat/LAT/latitude/LATITUDE and lon/LON/longitude/LONGITUDE.\n * Runtime validation via `isCoordinateObject` ensures proper keys are present.\n *\n * @example\n * ```typescript\n * { lat: 40.7128, lon: -74.0060 }\n * { latitude: 40.7128, longitude: -74.0060 }\n * { LAT: 40.7128, LON: -74.0060 }\n * ```\n */\nexport type CoordinateObject = Record<string, number>;\n\nexport type CoordinateInput = string | CoordinateTuple | CoordinateObject;\n\nconst LAT_KEYS = ['lat', 'latitude'];\nconst LON_KEYS = ['lon', 'longitude'];\n\nexport function isCoordinateTuple(\n input: CoordinateInput,\n): input is CoordinateTuple {\n return (\n Array.isArray(input) &&\n input.length === 2 &&\n typeof input[0] === 'number' &&\n typeof input[1] === 'number'\n );\n}\n\nexport function isCoordinateObject(\n input: CoordinateInput,\n): input is CoordinateObject {\n if (input === null || typeof input !== 'object' || Array.isArray(input)) {\n return false;\n }\n const keys = Object.keys(input).map((k) => k.toLowerCase());\n const hasLat = LAT_KEYS.some((k) => keys.includes(k));\n const hasLon = LON_KEYS.some((k) => keys.includes(k));\n return hasLat && hasLon;\n}\n\n/**\n * Normalizes a coordinate object to a consistent lat/lon format.\n *\n * Accepts coordinate objects with case-insensitive key variations (lat/LAT/latitude/LATITUDE\n * and lon/LON/longitude/LONGITUDE) and returns a normalized object with lowercase 'lat' and 'lon' keys.\n * Returns null if required keys are not found.\n *\n * @param obj - Coordinate object with lat/lon keys (case-insensitive)\n * @returns Normalized object with lat and lon keys, or null if invalid\n *\n * @example\n * ```typescript\n * normalizeObjectToLatLon({ lat: 40.7128, lon: -74.0060 });\n * // => { lat: 40.7128, lon: -74.0060 }\n * ```\n *\n * @example\n * ```typescript\n * normalizeObjectToLatLon({ LATITUDE: 40.7128, LONGITUDE: -74.0060 });\n * // => { lat: 40.7128, lon: -74.0060 }\n * ```\n *\n * @example\n * ```typescript\n * normalizeObjectToLatLon({ x: 10, y: 20 });\n * // => null (missing required keys)\n * ```\n */\nexport function normalizeObjectToLatLon(obj: CoordinateObject): {\n lat: number;\n lon: number;\n} | null {\n const lower = new Map<string, number>();\n for (const [key, value] of Object.entries(obj)) {\n lower.set(key.toLowerCase(), value);\n }\n\n let lat: number | undefined;\n let lon: number | undefined;\n\n for (const k of LAT_KEYS) {\n if (lower.has(k)) {\n lat = lower.get(k);\n break;\n }\n }\n\n for (const k of LON_KEYS) {\n if (lower.has(k)) {\n lon = lower.get(k);\n break;\n }\n }\n\n if (lat === undefined || lon === undefined) {\n return null;\n }\n\n return { lat, lon };\n}\n\nexport function tupleToLatLon(\n format: Format,\n tuple: CoordinateTuple,\n): { lat: number; lon: number } {\n // LATLON = [lat, lon], LONLAT = [lon, lat]\n if (format === 'LATLON') {\n return { lat: tuple[0], lon: tuple[1] };\n }\n return { lat: tuple[1], lon: tuple[0] };\n}\n"],"mappings":";;;;;;;;;;;;;;AA0CA,MAAM,WAAW,CAAC,OAAO,WAAW;AACpC,MAAM,WAAW,CAAC,OAAO,YAAY;AAErC,SAAgB,kBACd,OAC0B;AAC1B,QACE,MAAM,QAAQ,MAAM,IACpB,MAAM,WAAW,KACjB,OAAO,MAAM,OAAO,YACpB,OAAO,MAAM,OAAO;;AAIxB,SAAgB,mBACd,OAC2B;AAC3B,KAAI,UAAU,QAAQ,OAAO,UAAU,YAAY,MAAM,QAAQ,MAAM,CACrE,QAAO;CAET,MAAM,OAAO,OAAO,KAAK,MAAM,CAAC,KAAK,MAAM,EAAE,aAAa,CAAC;CAC3D,MAAM,SAAS,SAAS,MAAM,MAAM,KAAK,SAAS,EAAE,CAAC;CACrD,MAAM,SAAS,SAAS,MAAM,MAAM,KAAK,SAAS,EAAE,CAAC;AACrD,QAAO,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+BnB,SAAgB,wBAAwB,KAG/B;CACP,MAAM,wBAAQ,IAAI,KAAqB;AACvC,MAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,IAAI,CAC5C,OAAM,IAAI,IAAI,aAAa,EAAE,MAAM;CAGrC,IAAIA;CACJ,IAAIC;AAEJ,MAAK,MAAM,KAAK,SACd,KAAI,MAAM,IAAI,EAAE,EAAE;AAChB,QAAM,MAAM,IAAI,EAAE;AAClB;;AAIJ,MAAK,MAAM,KAAK,SACd,KAAI,MAAM,IAAI,EAAE,EAAE;AAChB,QAAM,MAAM,IAAI,EAAE;AAClB;;AAIJ,KAAI,QAAQ,UAAa,QAAQ,OAC/B,QAAO;AAGT,QAAO;EAAE;EAAK;EAAK;;AAGrB,SAAgB,cACd,QACA,OAC8B;AAE9B,KAAI,WAAW,SACb,QAAO;EAAE,KAAK,MAAM;EAAI,KAAK,MAAM;EAAI;AAEzC,QAAO;EAAE,KAAK,MAAM;EAAI,KAAK,MAAM;EAAI"}
|
|
@@ -1,4 +1,29 @@
|
|
|
1
1
|
//#region src/coordinates/latlon/internal/ordinal.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Gets the ordinal direction (N/S/E/W) for a coordinate value.
|
|
4
|
+
*
|
|
5
|
+
* @param num - The coordinate value (positive or negative).
|
|
6
|
+
* @param isLatitude - Whether this is a latitude coordinate (true) or longitude (false).
|
|
7
|
+
* @returns Ordinal direction character: 'N', 'S', 'E', or 'W'.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```typescript
|
|
11
|
+
* getOrdinal(37.7749, true);
|
|
12
|
+
* // 'N'
|
|
13
|
+
* ```
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```typescript
|
|
17
|
+
* getOrdinal(-122.4194, false);
|
|
18
|
+
* // 'W'
|
|
19
|
+
* ```
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```typescript
|
|
23
|
+
* getOrdinal(-45, true);
|
|
24
|
+
* // 'S'
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
2
27
|
declare const getOrdinal: (num: number, isLatitude: boolean) => string;
|
|
3
28
|
//#endregion
|
|
4
29
|
export { getOrdinal };
|