@ham2k/lib-operation-data 0.9.0 → 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/{index.js → index.ts} +1 -0
- package/src/lib/{bands.js → bands.ts} +14 -11
- package/src/lib/{modes.spec.js → modes.spec.ts} +1 -0
- package/src/lib/{modes.js → modes.ts} +46 -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
|
}
|
|
@@ -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
|
+
|
|
@@ -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
|
|
102
|
+
|
|
103
|
+
export type AdifMode = typeof ADIF_MODES[number]
|
|
95
104
|
|
|
96
|
-
export const ADIF_SUBMODES = {
|
|
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,27 +156,38 @@ 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) => {
|
|
@@ -180,11 +200,12 @@ export function modeForFrequency(frequency, { ituRegion, countryCode, entityPref
|
|
|
180
200
|
if (countryCode && !a.countries[countryCode] && b.countries[countryCode]) return 1
|
|
181
201
|
if (countryCode && a.countries[countryCode] && !b.countries[countryCode]) return -1
|
|
182
202
|
|
|
183
|
-
if (ituRegion && !a.regions[ituRegion] && b.regions[ituRegion]) return 1
|
|
184
|
-
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
|
|
185
205
|
|
|
186
206
|
return a.width - b.width
|
|
187
207
|
})
|
|
188
208
|
|
|
189
209
|
return sortedSegments[0]?.mode
|
|
190
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
|
+
|