@ham2k/lib-operation-data 0.8.4 → 0.10.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/README.md +86 -0
- package/jest.config.js +21 -0
- package/package.json +19 -7
- package/src/data/bandPlans.json +24 -24
- package/src/{index.js → index.ts} +1 -0
- package/src/lib/{bands.js → bands.ts} +14 -11
- package/src/lib/{modes.spec.js → modes.spec.ts} +6 -0
- package/src/lib/{modes.js → modes.ts} +49 -25
- package/src/types.ts +40 -0
- package/tsconfig.json +21 -0
package/README.md
CHANGED
|
@@ -1,3 +1,89 @@
|
|
|
1
1
|
# lib-operation-data
|
|
2
2
|
|
|
3
3
|
Basic data related to Amateur Radio operation, such as bands, modes, etc
|
|
4
|
+
|
|
5
|
+
## Building
|
|
6
|
+
|
|
7
|
+
This module is written in TypeScript and must be compiled before use.
|
|
8
|
+
|
|
9
|
+
### Development
|
|
10
|
+
|
|
11
|
+
To build the module during development:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm run build
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
This will compile TypeScript source files from `src/` to JavaScript in `dist/`, generating:
|
|
18
|
+
- JavaScript files (`.js`)
|
|
19
|
+
- TypeScript declaration files (`.d.ts`)
|
|
20
|
+
- Source maps (`.js.map`, `.d.ts.map`)
|
|
21
|
+
|
|
22
|
+
### Testing
|
|
23
|
+
|
|
24
|
+
Run the test suite:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
npm test
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Distribution
|
|
31
|
+
|
|
32
|
+
### Publishing to npm
|
|
33
|
+
|
|
34
|
+
Before publishing, ensure the module is built:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
npm run build
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
The `prepublishOnly` script will automatically run the build before publishing, so you can also just:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
npm publish
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Package Structure
|
|
47
|
+
|
|
48
|
+
- **Source files**: Located in `src/`
|
|
49
|
+
- **Compiled output**: Generated in `dist/` (excluded from git)
|
|
50
|
+
- **Type definitions**: Automatically generated in `dist/` alongside compiled JavaScript
|
|
51
|
+
|
|
52
|
+
### Module Exports
|
|
53
|
+
|
|
54
|
+
The package exports are configured in `package.json`:
|
|
55
|
+
- **Main entry**: `dist/index.js`
|
|
56
|
+
- **Type definitions**: `dist/index.d.ts`
|
|
57
|
+
- **ESM support**: Configured for ES2020 modules
|
|
58
|
+
|
|
59
|
+
## Usage
|
|
60
|
+
|
|
61
|
+
### Installation
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
npm install @ham2k/lib-operation-data
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Import
|
|
68
|
+
|
|
69
|
+
```javascript
|
|
70
|
+
import { BANDS, bandForFrequency, modeForFrequency } from '@ham2k/lib-operation-data'
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### TypeScript
|
|
74
|
+
|
|
75
|
+
Type definitions are included, so TypeScript projects will automatically get type checking and autocomplete:
|
|
76
|
+
|
|
77
|
+
```typescript
|
|
78
|
+
import { BANDS, Band, bandForFrequency } from '@ham2k/lib-operation-data'
|
|
79
|
+
|
|
80
|
+
const band: Band = bandForFrequency(14255) // '20m'
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Development Setup
|
|
84
|
+
|
|
85
|
+
1. Clone the repository
|
|
86
|
+
2. Install dependencies: `npm install`
|
|
87
|
+
3. Make changes in `src/`
|
|
88
|
+
4. Build: `npm run build`
|
|
89
|
+
5. Test: `npm test`
|
package/jest.config.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
preset: 'ts-jest/presets/default-esm',
|
|
3
|
+
extensionsToTreatAsEsm: ['.ts'],
|
|
4
|
+
testEnvironment: 'node',
|
|
5
|
+
transform: {
|
|
6
|
+
'^.+\\.ts$': ['ts-jest', {
|
|
7
|
+
useESM: true,
|
|
8
|
+
tsconfig: {
|
|
9
|
+
module: 'ES2020',
|
|
10
|
+
moduleResolution: 'node'
|
|
11
|
+
}
|
|
12
|
+
}]
|
|
13
|
+
},
|
|
14
|
+
moduleNameMapper: {
|
|
15
|
+
'^(\\.{1,2}/.*)\\.js$': '$1',
|
|
16
|
+
'^(\\.{1,2}/.*)\\.json$': '$1'
|
|
17
|
+
},
|
|
18
|
+
testMatch: ['**/*.spec.ts', '**/*.test.ts'],
|
|
19
|
+
transformIgnorePatterns: []
|
|
20
|
+
}
|
|
21
|
+
|
package/package.json
CHANGED
|
@@ -1,19 +1,31 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ham2k/lib-operation-data",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"main": "./
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"types": "./dist/index.d.ts",
|
|
6
7
|
"exports": {
|
|
7
|
-
".":
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"import": "./dist/index.js"
|
|
11
|
+
}
|
|
8
12
|
},
|
|
9
13
|
"devDependencies": {
|
|
10
|
-
"jest": "
|
|
11
|
-
"
|
|
14
|
+
"@types/jest": "^30.0.0",
|
|
15
|
+
"@types/node": "^24.10.1",
|
|
16
|
+
"jest": "~30",
|
|
17
|
+
"standard": "^17.1.2",
|
|
18
|
+
"ts-jest": "^29.1.0",
|
|
19
|
+
"typescript": "^5.0.0"
|
|
12
20
|
},
|
|
13
21
|
"scripts": {
|
|
14
|
-
"
|
|
22
|
+
"build": "tsc",
|
|
23
|
+
"test": "node --experimental-vm-modules ./node_modules/.bin/jest",
|
|
24
|
+
"prepublishOnly": "npm run build"
|
|
15
25
|
},
|
|
16
26
|
"standard": {
|
|
17
|
-
"env": [
|
|
27
|
+
"env": [
|
|
28
|
+
"jest"
|
|
29
|
+
]
|
|
18
30
|
}
|
|
19
31
|
}
|
package/src/data/bandPlans.json
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"segments": [
|
|
11
11
|
{ "name": "CW", "mhz": [1800, 1843], "mode": "CW" },
|
|
12
12
|
{ "name": "Data", "mhz": [1800, 1810], "mode": "RTTY" },
|
|
13
|
-
{ "name": "FT8", "mhz": [1840, 1843], "mode": "FT8" },
|
|
13
|
+
{ "name": "FT8", "mhz": [1840, 1843], "mode": "FT8", "priority": 100 },
|
|
14
14
|
{ "name": "Phone", "mhz": [1843, 2000], "mode": "SSB", "submode": "LSB" },
|
|
15
15
|
{ "name": "Australia", "mhz": [1800, 1875], "mode": null, "countries": ["au"] }
|
|
16
16
|
]
|
|
@@ -20,9 +20,9 @@
|
|
|
20
20
|
"segments": [
|
|
21
21
|
{ "name": "CW", "mhz": [3500, 3600], "mode": "CW" },
|
|
22
22
|
{ "name": "Phone", "mhz": [3535, 3800], "mode": "SSB", "submode": "LSB", "regions": [ 3 ] },
|
|
23
|
-
{ "name": "FT8", "mhz": [3568, 3571], "mode": "FT8", "regions": [ 3 ] },
|
|
23
|
+
{ "name": "FT8", "mhz": [3568, 3571], "mode": "FT8", "regions": [ 3 ], "priority": 100 },
|
|
24
24
|
{ "name": "Data", "mhz": [3570, 3600], "mode": "RTTY" },
|
|
25
|
-
{ "name": "FT8", "mhz": [3573, 3576], "mode": "FT8" },
|
|
25
|
+
{ "name": "FT8", "mhz": [3573, 3576], "mode": "FT8", "priority": 100 },
|
|
26
26
|
{ "name": "Phone", "mhz": [3600, 4000], "mode": "SSB", "submode": "LSB" },
|
|
27
27
|
{ "name": "Australia", "mhz": [3500, 3800], "mode": null, "countries": ["au"] }
|
|
28
28
|
]
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"segments": [
|
|
33
33
|
{ "name": "60m Channel 1", "mhz": [5330.5, 5333.5] },
|
|
34
34
|
{ "name": "60m Channel 2", "mhz": [5346.5, 5349.5] },
|
|
35
|
-
{ "name": "60m Channel 3", "mhz": [5357, 5360], "mode": "FT8" },
|
|
35
|
+
{ "name": "60m Channel 3", "mhz": [5357, 5360], "mode": "FT8", "priority": 100 },
|
|
36
36
|
{ "name": "60m Channel 4", "mhz": [5371.5, 5373.5] },
|
|
37
37
|
{ "name": "60m Channel 5", "mhz": [5403.5, 5406.5] }
|
|
38
38
|
]
|
|
@@ -42,9 +42,9 @@
|
|
|
42
42
|
"segments": [
|
|
43
43
|
{ "name": "CW", "mhz": [7000, 7060], "mode": "CW" },
|
|
44
44
|
{ "name": "CW", "mhz": [7000, 7125], "mode": "CW", "countries": [ "us" ] },
|
|
45
|
-
{ "name": "FT4", "mhz": [7047.5, 7050.5], "mode": "FT4" },
|
|
45
|
+
{ "name": "FT4", "mhz": [7047.5, 7050.5], "mode": "FT4", "priority": 100 },
|
|
46
46
|
{ "name": "Phone", "mhz": [7060, 7300], "mode": "SSB", "submode": "LSB" },
|
|
47
|
-
{ "name": "FT8", "mhz": [7074, 7077], "mode": "FT8" },
|
|
47
|
+
{ "name": "FT8", "mhz": [7074, 7077], "mode": "FT8", "priority": 100 },
|
|
48
48
|
{ "name": "Data", "mhz": [7080, 7125], "mode": "RTTY", "countries": [ "us" ] },
|
|
49
49
|
{ "name": "Phone", "mhz": [7125, 7300], "mode": "SSB", "submode": "LSB", "countries": [ "us" ] },
|
|
50
50
|
{ "name": "Australia", "mhz": [7000, 7300], "mode": null, "countries": ["au"] }
|
|
@@ -55,20 +55,20 @@
|
|
|
55
55
|
"segments": [
|
|
56
56
|
{ "name": "CW", "mhz": [10100, 10150], "mode": "CW" },
|
|
57
57
|
{ "name": "Data", "mhz": [10130, 10140], "mode": "RTTY" },
|
|
58
|
-
{ "name": "FT8", "mhz": [10136, 10139], "mode": "FT8" },
|
|
59
|
-
{ "name": "FT4", "mhz": [10140, 10143], "mode": "FT4" },
|
|
58
|
+
{ "name": "FT8", "mhz": [10136, 10139], "mode": "FT8", "priority": 100 },
|
|
59
|
+
{ "name": "FT4", "mhz": [10140, 10143], "mode": "FT4", "priority": 100 },
|
|
60
60
|
{ "name": "Countries with no plan on 30m", "mhz": [10100, 10150], "mode": null, "countries": ["au", "za"] }
|
|
61
61
|
]
|
|
62
62
|
},
|
|
63
63
|
"20m": {
|
|
64
64
|
"mhz": [14000, 14350],
|
|
65
65
|
"segments": [
|
|
66
|
-
{ "name": "CW", "mhz": [14000, 14101], "mode": "CW", "regions": [ 2 ] },
|
|
67
66
|
{ "name": "CW", "mhz": [14000, 14150], "mode": "CW" },
|
|
68
67
|
{ "name": "CW", "mhz": [14000, 14070], "mode": "CW", "regions": [ 1 ] },
|
|
68
|
+
{ "name": "CW", "mhz": [14000, 14101], "mode": "CW", "regions": [ 2 ] },
|
|
69
69
|
{ "name": "Data", "mhz": [14070, 14095], "mode": "RTTY" },
|
|
70
|
-
{ "name": "FT8", "mhz": [14074, 14077], "mode": "FT8" },
|
|
71
|
-
{ "name": "FT4", "mhz": [14080, 14083], "mode": "FT4" },
|
|
70
|
+
{ "name": "FT8", "mhz": [14074, 14077], "mode": "FT8", "priority": 100 },
|
|
71
|
+
{ "name": "FT4", "mhz": [14080, 14083], "mode": "FT4", "priority": 100 },
|
|
72
72
|
{ "name": "Phone", "mhz": [14112, 14350], "mode": "SSB", "submode": "USB", "regions": [ 1, 2 ] },
|
|
73
73
|
{ "name": "Phone", "mhz": [14150, 14350], "mode": "SSB", "submode": "USB", "countries": [ "us" ] },
|
|
74
74
|
{ "name": "Australia", "mhz": [14000, 14350], "mode": null, "countries": ["au"] }
|
|
@@ -79,8 +79,8 @@
|
|
|
79
79
|
"segments": [
|
|
80
80
|
{ "name": "CW", "mhz": [18068, 18110], "mode": "CW" },
|
|
81
81
|
{ "name": "Data", "mhz": [18100, 18105], "mode": "RTTY" },
|
|
82
|
-
{ "name": "FT8", "mhz": [18100, 18103], "mode": "FT8" },
|
|
83
|
-
{ "name": "FT4", "mhz": [18104, 18107], "mode": "FT4" },
|
|
82
|
+
{ "name": "FT8", "mhz": [18100, 18103], "mode": "FT8", "priority": 100 },
|
|
83
|
+
{ "name": "FT4", "mhz": [18104, 18107], "mode": "FT4", "priority": 100 },
|
|
84
84
|
{ "name": "Phone", "mhz": [18110, 18168], "mode": "SSB", "submode": "USB" },
|
|
85
85
|
{ "name": "Australia", "mhz": [18068, 18168], "mode": null, "countries": ["au"] }
|
|
86
86
|
]
|
|
@@ -90,8 +90,8 @@
|
|
|
90
90
|
"segments": [
|
|
91
91
|
{ "name": "CW", "mhz": [21000, 21150], "mode": "CW" },
|
|
92
92
|
{ "name": "Data", "mhz": [21070, 21095], "mode": "RTTY" },
|
|
93
|
-
{ "name": "FT8", "mhz": [21074, 21077], "mode": "FT8" },
|
|
94
|
-
{ "name": "FT4", "mhz": [21140, 21143], "mode": "FT4" },
|
|
93
|
+
{ "name": "FT8", "mhz": [21074, 21077], "mode": "FT8", "priority": 100 },
|
|
94
|
+
{ "name": "FT4", "mhz": [21140, 21143], "mode": "FT4", "priority": 100 },
|
|
95
95
|
{ "name": "Phone", "mhz": [21150, 21450], "mode": "SSB", "submode": "USB" },
|
|
96
96
|
{ "name": "Australia", "mhz": [21000, 21450], "mode": null, "countries": ["au"] }
|
|
97
97
|
]
|
|
@@ -100,8 +100,8 @@
|
|
|
100
100
|
"mhz": [24890, 24990],
|
|
101
101
|
"segments": [
|
|
102
102
|
{ "name": "CW", "mhz": [24890, 24930], "mode": "CW" },
|
|
103
|
-
{ "name": "FT8", "mhz": [24915, 24918], "mode": "FT8" },
|
|
104
|
-
{ "name": "FT4", "mhz": [24919, 23922], "mode": "FT4" },
|
|
103
|
+
{ "name": "FT8", "mhz": [24915, 24918], "mode": "FT8", "priority": 100 },
|
|
104
|
+
{ "name": "FT4", "mhz": [24919, 23922], "mode": "FT4", "priority": 100 },
|
|
105
105
|
{ "name": "Data", "mhz": [24920, 24925], "mode": "RTTY" },
|
|
106
106
|
{ "name": "Phone", "mhz": [24930, 24990], "mode": "SSB", "submode": "USB" },
|
|
107
107
|
{ "name": "Australia", "mhz": [24890, 24990], "mode": null, "countries": ["au"] }
|
|
@@ -112,8 +112,8 @@
|
|
|
112
112
|
"segments": [
|
|
113
113
|
{ "name": "CW", "mhz": [28000, 28190], "mode": "CW" },
|
|
114
114
|
{ "name": "Data", "mhz": [28070, 28150], "mode": "RTTY" },
|
|
115
|
-
{ "name": "FT8", "mhz": [28074, 28077], "mode": "FT8" },
|
|
116
|
-
{ "name": "FT4", "mhz": [28180, 28183], "mode": "FT4" },
|
|
115
|
+
{ "name": "FT8", "mhz": [28074, 28077], "mode": "FT8", "priority": 100 },
|
|
116
|
+
{ "name": "FT4", "mhz": [28180, 28183], "mode": "FT4", "priority": 100 },
|
|
117
117
|
{ "name": "Beacons", "mhz": [28190, 28300] },
|
|
118
118
|
{ "name": "Phone SSB", "mhz": [28300, 29000], "mode": "SSB", "submode": "USB" },
|
|
119
119
|
{ "name": "AM", "mhz": [29000, 29200], "mode": "AM" },
|
|
@@ -132,9 +132,9 @@
|
|
|
132
132
|
{ "name": "Phone", "mhz": [51000, 51110], "mode": "SSB", "submode": "USB" },
|
|
133
133
|
{ "name": "FM", "mhz": [51110, 52100], "mode": "FM" },
|
|
134
134
|
{ "name": "FM", "mhz": [51110, 52100], "mode": "FM" },
|
|
135
|
-
{ "name": "FT8", "mhz": [50313, 50316], "mode": "FT8" },
|
|
136
|
-
{ "name": "FT4", "mhz": [50318, 50321], "mode": "FT4" },
|
|
137
|
-
{ "name": "FT8", "mhz": [50323, 50326], "mode": "FT8" },
|
|
135
|
+
{ "name": "FT8", "mhz": [50313, 50316], "mode": "FT8", "priority": 100 },
|
|
136
|
+
{ "name": "FT4", "mhz": [50318, 50321], "mode": "FT4", "priority": 100 },
|
|
137
|
+
{ "name": "FT8", "mhz": [50323, 50326], "mode": "FT8", "priority": 100 },
|
|
138
138
|
{ "name": "Australia", "mhz": [50000, 54000], "mode": null, "countries": ["au"] }
|
|
139
139
|
]
|
|
140
140
|
},
|
|
@@ -143,8 +143,8 @@
|
|
|
143
143
|
"segments": [
|
|
144
144
|
{ "name": "CW", "mhz": [144000, 144100], "mode": "CW" },
|
|
145
145
|
{ "name": "Data", "mhz": [144100, 144200], "mode": "RTTY" },
|
|
146
|
-
{ "name": "FT8", "mhz": [144174, 144177], "mode": "FT8" },
|
|
147
|
-
{ "name": "
|
|
146
|
+
{ "name": "FT8", "mhz": [144174, 144177], "mode": "FT8", "priority": 100 },
|
|
147
|
+
{ "name": "FT4", "mhz": [144170, 144173], "mode": "FT4", "priority": 100 },
|
|
148
148
|
{ "name": "Phone", "mhz": [144200, 144275], "mode": "SSB", "submode": "USB" },
|
|
149
149
|
{ "name": "FM", "mhz": [144300, 148000], "mode": "FM" }
|
|
150
150
|
]
|
|
@@ -32,22 +32,24 @@ export const BANDS = [
|
|
|
32
32
|
'1mm',
|
|
33
33
|
'submm',
|
|
34
34
|
'other'
|
|
35
|
-
]
|
|
35
|
+
] as const
|
|
36
36
|
|
|
37
|
-
export
|
|
38
|
-
export const VHF_BANDS = ['6m', '5m', '4m', '2m', '1.25m']
|
|
39
|
-
export const UHF_BANDS = ['70cm', '33cm', '23cm', '13cm']
|
|
40
|
-
export const SHF_BANDS = ['9cm', '6cm', '3cm', '1.25cm']
|
|
41
|
-
export const EHF_BANDS = ['6mm', '4mm', '2mm', '2.5mm', '1mm', 'submm']
|
|
37
|
+
export type Band = typeof BANDS[number]
|
|
42
38
|
|
|
43
|
-
export const
|
|
44
|
-
export const
|
|
39
|
+
export const HF_BANDS = ['160m', '80m', '60m', '40m', '30m', '20m', '17m', '15m', '12m', '10m'] as const
|
|
40
|
+
export const VHF_BANDS = ['6m', '5m', '4m', '2m', '1.25m'] as const
|
|
41
|
+
export const UHF_BANDS = ['70cm', '33cm', '23cm', '13cm'] as const
|
|
42
|
+
export const SHF_BANDS = ['9cm', '6cm', '3cm', '1.25cm'] as const
|
|
43
|
+
export const EHF_BANDS = ['6mm', '4mm', '2mm', '2.5mm', '1mm', 'submm'] as const
|
|
45
44
|
|
|
46
|
-
export
|
|
45
|
+
export const POPULAR_BANDS = ['160m', '80m', '60m', '40m', '30m', '20m', '17m', '15m', '12m', '10m', '6m', '2m', '70cm'] as const
|
|
46
|
+
export const CONTEST_BANDS = ['160m', '80m', '40m', '20m', '15m', '10m'] as const
|
|
47
|
+
|
|
48
|
+
export function bandForFrequency(freq: number): Band {
|
|
47
49
|
return bandForExactFrequencyInMHz(freq) ?? bandForExactFrequencyInMHz(freq * 1000) ?? 'other'
|
|
48
50
|
}
|
|
49
51
|
|
|
50
|
-
function bandForExactFrequencyInMHz
|
|
52
|
+
function bandForExactFrequencyInMHz(freq: number): Band | undefined {
|
|
51
53
|
if (freq >= 130 && freq <= 140) return '2190m'
|
|
52
54
|
else if (freq >= 450 && freq <= 499) return '630m'
|
|
53
55
|
else if (freq >= 500 && freq <= 505) return '560m'
|
|
@@ -83,7 +85,7 @@ function bandForExactFrequencyInMHz (freq) {
|
|
|
83
85
|
else return undefined
|
|
84
86
|
}
|
|
85
87
|
|
|
86
|
-
export function frequencyForBand(band, mode) {
|
|
88
|
+
export function frequencyForBand(band: Band, mode?: string): number | undefined {
|
|
87
89
|
if (band === '2190m') return 135.7
|
|
88
90
|
else if (band === '630m') return 472
|
|
89
91
|
else if (band === '560m') return 501
|
|
@@ -169,3 +171,4 @@ export function frequencyForBand(band, mode) {
|
|
|
169
171
|
else if (band === 'submm') return 300000000
|
|
170
172
|
else return undefined
|
|
171
173
|
}
|
|
174
|
+
|
|
@@ -12,6 +12,11 @@ describe('modeForFrequency', () => {
|
|
|
12
12
|
expect(modeForFrequency(14140, { ituRegion: 2 })).toEqual('SSB')
|
|
13
13
|
})
|
|
14
14
|
|
|
15
|
+
it('should handle different priorities', () => {
|
|
16
|
+
// ituRegion 2 should make CW be the match for 14074, but priority 100 for FT8 should make FT8 the match
|
|
17
|
+
expect(modeForFrequency(14074, { ituRegion: 2 })).toEqual('FT8')
|
|
18
|
+
})
|
|
19
|
+
|
|
15
20
|
// The current bandplan data does not have any entity-specific segments
|
|
16
21
|
// TODO: mock data for this test
|
|
17
22
|
// it('should handle different bandplans for different entities', () => {
|
|
@@ -26,3 +31,4 @@ describe('modeForFrequency', () => {
|
|
|
26
31
|
})
|
|
27
32
|
|
|
28
33
|
})
|
|
34
|
+
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import BandPlans from '../data/bandPlans.json'
|
|
2
|
+
import type { BandPlansData, SegmentWithMetadata, ModeForFrequencyOptions } from '../types'
|
|
2
3
|
|
|
3
4
|
export const SUPER_MODES = [
|
|
4
5
|
'CW',
|
|
5
6
|
'PHONE',
|
|
6
7
|
'DATA'
|
|
7
|
-
]
|
|
8
|
+
] as const
|
|
9
|
+
|
|
10
|
+
export type SuperMode = typeof SUPER_MODES[number]
|
|
8
11
|
|
|
9
12
|
export const MAIN_MODES = [
|
|
10
13
|
'CW',
|
|
@@ -14,7 +17,9 @@ export const MAIN_MODES = [
|
|
|
14
17
|
'FT8',
|
|
15
18
|
'FT4',
|
|
16
19
|
'RTTY'
|
|
17
|
-
]
|
|
20
|
+
] as const
|
|
21
|
+
|
|
22
|
+
export type MainMode = typeof MAIN_MODES[number]
|
|
18
23
|
|
|
19
24
|
export const POPULAR_MODES = [
|
|
20
25
|
'CW',
|
|
@@ -42,7 +47,9 @@ export const POPULAR_MODES = [
|
|
|
42
47
|
'PSK',
|
|
43
48
|
'SSTV',
|
|
44
49
|
'WSPR',
|
|
45
|
-
]
|
|
50
|
+
] as const
|
|
51
|
+
|
|
52
|
+
export type PopularMode = typeof POPULAR_MODES[number]
|
|
46
53
|
|
|
47
54
|
export const ADIF_MODES = [
|
|
48
55
|
'AM',
|
|
@@ -91,9 +98,11 @@ export const ADIF_MODES = [
|
|
|
91
98
|
'VOI',
|
|
92
99
|
'WINMOR',
|
|
93
100
|
'WSPR'
|
|
94
|
-
]
|
|
101
|
+
] as const
|
|
95
102
|
|
|
96
|
-
export
|
|
103
|
+
export type AdifMode = typeof ADIF_MODES[number]
|
|
104
|
+
|
|
105
|
+
export const ADIF_SUBMODES: Record<string, readonly string[]> = {
|
|
97
106
|
'CHIP': ['CHIP64', 'CHIP128'],
|
|
98
107
|
'DIGITALVOICE': ['C4FM', 'DMR', 'DSTAR', 'FREEDV', 'M17'],
|
|
99
108
|
'DOMINO': ['DOM-M', 'DOM4', 'DOM5', 'DOM8', 'DOM11', 'DOM16', 'DOM22', 'DOM44', 'DOM88', 'DOMINOEX', 'DOMINOF'],
|
|
@@ -116,18 +125,18 @@ export const ADIF_SUBMODES = {
|
|
|
116
125
|
'THOR': ['THOR-M', 'THOR4', 'THOR5', 'THOR8', 'THOR11', 'THOR16', 'THOR22', 'THOR25X4', 'THOR50X1', 'THOR50X2', 'THOR100'],
|
|
117
126
|
'THRB': ['THRBX', 'THRBX1', 'THRBX2', 'THRBX4', 'THROB1', 'THROB2', 'THROB4'],
|
|
118
127
|
'TOR': ['AMTORFEC', 'GTOR', 'NAVTEX', 'SITORB']
|
|
119
|
-
}
|
|
128
|
+
} as const
|
|
120
129
|
|
|
121
|
-
export const ADIF_MODE_FOR_SUBMODE = {}
|
|
130
|
+
export const ADIF_MODE_FOR_SUBMODE: Record<string, string> = {}
|
|
122
131
|
Object.keys(ADIF_SUBMODES).forEach(mode => {
|
|
123
132
|
ADIF_SUBMODES[mode].forEach(submode => {
|
|
124
133
|
ADIF_MODE_FOR_SUBMODE[submode] = mode
|
|
125
134
|
})
|
|
126
135
|
})
|
|
127
136
|
|
|
128
|
-
export const ADIF_MODES_AND_SUBMODES = [...
|
|
137
|
+
export const ADIF_MODES_AND_SUBMODES: readonly string[] = [...new Set([...ADIF_MODES, ...Object.values(ADIF_SUBMODES).flat()])].sort()
|
|
129
138
|
|
|
130
|
-
export function adifModeAndSubmodeForMode(mode) {
|
|
139
|
+
export function adifModeAndSubmodeForMode(mode: string): string[] {
|
|
131
140
|
if (ADIF_MODE_FOR_SUBMODE[mode]) {
|
|
132
141
|
return [ADIF_MODE_FOR_SUBMODE[mode], mode]
|
|
133
142
|
} else {
|
|
@@ -135,7 +144,7 @@ export function adifModeAndSubmodeForMode(mode) {
|
|
|
135
144
|
}
|
|
136
145
|
}
|
|
137
146
|
|
|
138
|
-
export function superModeForMode(mode) {
|
|
147
|
+
export function superModeForMode(mode: string): SuperMode {
|
|
139
148
|
if (ADIF_MODE_FOR_SUBMODE[mode]) mode = ADIF_MODE_FOR_SUBMODE[mode]
|
|
140
149
|
|
|
141
150
|
if (mode === 'CW') {
|
|
@@ -147,41 +156,56 @@ export function superModeForMode(mode) {
|
|
|
147
156
|
}
|
|
148
157
|
}
|
|
149
158
|
|
|
150
|
-
const
|
|
159
|
+
const bandPlansData = BandPlans as unknown as BandPlansData
|
|
160
|
+
|
|
161
|
+
const AllSegments: SegmentWithMetadata[] = Object.keys(bandPlansData.bands).reduce((segments, band) => {
|
|
151
162
|
return segments.concat(
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
163
|
+
bandPlansData.bands[band].segments.map(segment => {
|
|
164
|
+
const mhz = Array.isArray(segment.mhz) && segment.mhz.length >= 2
|
|
165
|
+
? [segment.mhz[0], segment.mhz[1]] as [number, number]
|
|
166
|
+
: [0, 0] as [number, number]
|
|
167
|
+
return {
|
|
168
|
+
name: segment.name,
|
|
169
|
+
mode: segment.mode,
|
|
170
|
+
submode: segment.submode,
|
|
171
|
+
priority: segment.priority,
|
|
172
|
+
mhz,
|
|
155
173
|
band,
|
|
156
|
-
width:
|
|
157
|
-
regions: (segment.regions || []).reduce((regions, region) => { regions[region] = true; return regions }, {}),
|
|
158
|
-
entities: (segment.entities || []).reduce((entities, entity) => { entities[entity] = true; return entities }, {}),
|
|
159
|
-
countries: (segment.countries || []).reduce((countries, country) => { countries[country] = true; return countries }, {}),
|
|
160
|
-
}
|
|
161
|
-
)
|
|
174
|
+
width: mhz[1] - mhz[0],
|
|
175
|
+
regions: (segment.regions || []).reduce((regions: Record<string, boolean>, region) => { regions[region.toString()] = true; return regions }, {}),
|
|
176
|
+
entities: (segment.entities || []).reduce((entities: Record<string, boolean>, entity) => { entities[entity] = true; return entities }, {}),
|
|
177
|
+
countries: (segment.countries || []).reduce((countries: Record<string, boolean>, country) => { countries[country] = true; return countries }, {}),
|
|
178
|
+
} as SegmentWithMetadata
|
|
179
|
+
})
|
|
162
180
|
)
|
|
163
|
-
}, [])
|
|
181
|
+
}, [] as SegmentWithMetadata[])
|
|
182
|
+
|
|
183
|
+
export function modeForFrequency(frequency: number, options: ModeForFrequencyOptions = {}): string | null | undefined {
|
|
184
|
+
const { ituRegion, countryCode, entityPrefix } = options
|
|
164
185
|
|
|
165
|
-
export function modeForFrequency(frequency, { ituRegion, countryCode, entityPrefix } = {}) {
|
|
166
186
|
const segments = AllSegments.filter(segment => (
|
|
167
187
|
(segment.mhz[0] <= frequency && segment.mhz[1] >= frequency) &&
|
|
168
188
|
(!entityPrefix || Object.keys(segment.entities).length === 0 || segment.entities[entityPrefix]) &&
|
|
169
189
|
(!countryCode || Object.keys(segment.countries).length === 0 || segment.countries[countryCode]) &&
|
|
170
|
-
(!ituRegion || Object.keys(segment.regions).length === 0 || segment.regions[ituRegion])
|
|
190
|
+
(!ituRegion || Object.keys(segment.regions).length === 0 || segment.regions[ituRegion.toString()])
|
|
171
191
|
))
|
|
172
192
|
|
|
173
193
|
const sortedSegments = segments.sort((a, b) => {
|
|
194
|
+
if ((a.priority ?? 0) < (b.priority ?? 0)) return 1
|
|
195
|
+
if ((a.priority ?? 0) > (b.priority ?? 0)) return -1
|
|
196
|
+
|
|
174
197
|
if (entityPrefix && !a.entities[entityPrefix] && b.entities[entityPrefix]) return 1
|
|
175
198
|
if (entityPrefix && a.entities[entityPrefix] && !b.entities[entityPrefix]) return -1
|
|
176
199
|
|
|
177
200
|
if (countryCode && !a.countries[countryCode] && b.countries[countryCode]) return 1
|
|
178
201
|
if (countryCode && a.countries[countryCode] && !b.countries[countryCode]) return -1
|
|
179
202
|
|
|
180
|
-
if (ituRegion && !a.regions[ituRegion] && b.regions[ituRegion]) return 1
|
|
181
|
-
if (ituRegion && a.regions[ituRegion] && !b.regions[ituRegion]) return -1
|
|
203
|
+
if (ituRegion && !a.regions[ituRegion.toString()] && b.regions[ituRegion.toString()]) return 1
|
|
204
|
+
if (ituRegion && a.regions[ituRegion.toString()] && !b.regions[ituRegion.toString()]) return -1
|
|
182
205
|
|
|
183
206
|
return a.width - b.width
|
|
184
207
|
})
|
|
185
208
|
|
|
186
209
|
return sortedSegments[0]?.mode
|
|
187
210
|
}
|
|
211
|
+
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export interface BandPlanSegment {
|
|
2
|
+
name: string
|
|
3
|
+
mhz: [number, number] | number[]
|
|
4
|
+
mode?: string | null
|
|
5
|
+
submode?: string
|
|
6
|
+
priority?: number
|
|
7
|
+
regions?: number[]
|
|
8
|
+
entities?: string[]
|
|
9
|
+
countries?: string[]
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface BandPlan {
|
|
13
|
+
mhz: [number, number] | number[]
|
|
14
|
+
segments: BandPlanSegment[]
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface BandPlansData {
|
|
18
|
+
sources: Array<{ url: string }>
|
|
19
|
+
bands: Record<string, BandPlan>
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface SegmentWithMetadata {
|
|
23
|
+
name: string
|
|
24
|
+
mhz: [number, number]
|
|
25
|
+
mode?: string | null
|
|
26
|
+
submode?: string
|
|
27
|
+
priority?: number
|
|
28
|
+
band: string
|
|
29
|
+
width: number
|
|
30
|
+
regions: Record<string, boolean>
|
|
31
|
+
entities: Record<string, boolean>
|
|
32
|
+
countries: Record<string, boolean>
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface ModeForFrequencyOptions {
|
|
36
|
+
ituRegion?: number
|
|
37
|
+
countryCode?: string
|
|
38
|
+
entityPrefix?: string
|
|
39
|
+
}
|
|
40
|
+
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"module": "ES2020",
|
|
5
|
+
"lib": ["ES2020"],
|
|
6
|
+
"moduleResolution": "node",
|
|
7
|
+
"outDir": "./dist",
|
|
8
|
+
"rootDir": "./src",
|
|
9
|
+
"strict": true,
|
|
10
|
+
"esModuleInterop": true,
|
|
11
|
+
"skipLibCheck": true,
|
|
12
|
+
"forceConsistentCasingInFileNames": true,
|
|
13
|
+
"resolveJsonModule": true,
|
|
14
|
+
"declaration": true,
|
|
15
|
+
"declarationMap": true,
|
|
16
|
+
"sourceMap": true
|
|
17
|
+
},
|
|
18
|
+
"include": ["src/**/*"],
|
|
19
|
+
"exclude": ["node_modules", "dist"]
|
|
20
|
+
}
|
|
21
|
+
|