Skip to main content

rfham_itu/
allocations.rs

1//!
2//! Provides ..., a one-line description
3//!
4//! More detailed description
5//!
6//! # Examples
7//!
8//! ```rust
9//! ```
10//!
11
12use crate::{bands::FrequencyBand, regions::Region};
13use core::{fmt::Display, str::FromStr};
14use rfham_core::{
15    error::CoreError,
16    frequencies::{Frequency, FrequencyRange, gigahertz, hertz, kilohertz, megahertz},
17};
18use rfham_markdown::error::MarkdownError;
19use rfham_markdown::{
20    Column, ColumnJustification, Table, blank_line, bulleted_list_item, header, link_to_string,
21    plain_text,
22};
23use serde_with::{DeserializeFromStr, SerializeDisplay};
24use std::io::Write;
25
26// ------------------------------------------------------------------------------------------------
27// Public Types
28// ------------------------------------------------------------------------------------------------
29
30#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, DeserializeFromStr, SerializeDisplay)]
31pub enum FrequencyAllocation {
32    Band2200M,
33    Band630M,
34    Band160M,
35    Band80M,
36    Band60M,
37    Band40M,
38    Band30M,
39    Band20M,
40    Band17M,
41    Band15M,
42    Band12M,
43    Band10M,
44    Band8M,
45    Band6M,
46    Band5M,
47    Band4M,
48    Band2M,
49    Band1_25M,
50    Band70Cm,
51    Band33Cm,
52    Band23Cm,
53    Band13Cm,
54    Band9Cm,
55    Band5Cm,
56    Band3Cm,
57    Band1_2Cm,
58    Band6Mm,
59    Band4Mm,
60    Band2_5Mm,
61    Band2Mm,
62    Band1Mm,
63}
64
65// ------------------------------------------------------------------------------------------------
66// Implementations
67// ------------------------------------------------------------------------------------------------
68
69impl Display for FrequencyAllocation {
70    // This is only safe because we call display for the normal format only during the alternate.
71    #[allow(clippy::recursive_format_impl)]
72    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
73        write!(
74            f,
75            "{}",
76            if f.alternate() {
77                match self {
78                    Self::Band2200M => format!("{}/{}/135.7kHz", self, self.band()),
79                    Self::Band630M => format!("{}/{}/472.0kHz", self, self.band()),
80                    Self::Band160M => format!("{}/{}/1.81MHz", self, self.band()),
81                    Self::Band80M => format!("{}/{}/3.5MHz", self, self.band()),
82                    Self::Band60M => format!("{}/{}/5.3515MHz", self, self.band()),
83                    Self::Band40M => format!("{}/{}/7.0MHz", self, self.band()),
84                    Self::Band30M => format!("{}/{}/10.1MHz", self, self.band()),
85                    Self::Band20M => format!("{}/{}/14.0MHz", self, self.band()),
86                    Self::Band17M => format!("{}/{}/18.068MHz", self, self.band()),
87                    Self::Band15M => format!("{}/{}/21.0MHz", self, self.band()),
88                    Self::Band12M => format!("{}/{}/24.89MHz", self, self.band()),
89                    Self::Band10M => format!("{}/{}/28.0MHz", self, self.band()),
90                    Self::Band8M => format!("{}/{}/39.9MHz", self, self.band()),
91                    Self::Band6M => format!("{}/{}/50.0MHz", self, self.band()),
92                    Self::Band5M => format!("{}/{}/59.5.0MHz", self, self.band()),
93                    Self::Band4M => format!("{}/{}/69.9MHz", self, self.band()),
94                    Self::Band2M => format!("{}/{}/144.0MHz", self, self.band()),
95                    Self::Band1_25M => format!("{}/{}/220.0MHz", self, self.band()),
96                    Self::Band70Cm => format!("{}/{}/430.0MHz", self, self.band()),
97                    Self::Band33Cm => format!("{}/{}/902.0MHz", self, self.band()),
98                    Self::Band23Cm => format!("{}/{}/1.24GHz", self, self.band()),
99                    Self::Band13Cm => format!("{}/{}/2.3GHz", self, self.band()),
100                    Self::Band9Cm => format!("{}/{}/3.3GHz", self, self.band()),
101                    Self::Band5Cm => format!("{}/{}/5.65GHz", self, self.band()),
102                    Self::Band3Cm => format!("{}/{}/10.0GHz", self, self.band()),
103                    Self::Band1_2Cm => format!("{}/{}/24.0GHz", self, self.band()),
104                    Self::Band6Mm => format!("{}/{}/47.0GHz", self, self.band()),
105                    Self::Band4Mm => format!("{}/{}/76.0GHz", self, self.band()),
106                    Self::Band2_5Mm => format!("{}/{}/122.25GHz", self, self.band()),
107                    Self::Band2Mm => format!("{}/{}/134.0GHz", self, self.band()),
108                    Self::Band1Mm => format!("{}/{}/241.0GHz", self, self.band()),
109                }
110            } else {
111                match self {
112                    Self::Band2200M => "2200m",
113                    Self::Band630M => "630m",
114                    Self::Band160M => "160m",
115                    Self::Band80M => "80m",
116                    Self::Band60M => "60m",
117                    Self::Band40M => "40m",
118                    Self::Band30M => "30m",
119                    Self::Band20M => "20m",
120                    Self::Band17M => "17m",
121                    Self::Band15M => "15m",
122                    Self::Band12M => "12m",
123                    Self::Band10M => "10m",
124                    Self::Band8M => "8m",
125                    Self::Band6M => "6m",
126                    Self::Band5M => "5m",
127                    Self::Band4M => "4m",
128                    Self::Band2M => "2m",
129                    Self::Band1_25M => "1.25m",
130                    Self::Band70Cm => "70cm",
131                    Self::Band33Cm => "33cm",
132                    Self::Band23Cm => "23cm",
133                    Self::Band13Cm => "13cm",
134                    Self::Band9Cm => "9cm",
135                    Self::Band5Cm => "5cm",
136                    Self::Band3Cm => "3cm",
137                    Self::Band1_2Cm => "1.2cm",
138                    Self::Band6Mm => "6mm",
139                    Self::Band4Mm => "4mm",
140                    Self::Band2_5Mm => "2.5mm",
141                    Self::Band2Mm => "2mm",
142                    Self::Band1Mm => "1mm",
143                }
144                .to_string()
145            }
146        )
147    }
148}
149
150impl FromStr for FrequencyAllocation {
151    type Err = CoreError;
152
153    fn from_str(s: &str) -> Result<Self, Self::Err> {
154        match s {
155            "2200m" => Ok(Self::Band2200M),
156            "630m" => Ok(Self::Band630M),
157            "160m" => Ok(Self::Band160M),
158            "80m" => Ok(Self::Band80M),
159            "60m" => Ok(Self::Band60M),
160            "40m" => Ok(Self::Band40M),
161            "30m" => Ok(Self::Band30M),
162            "20m" => Ok(Self::Band20M),
163            "17m" => Ok(Self::Band17M),
164            "15m" => Ok(Self::Band15M),
165            "12m" => Ok(Self::Band12M),
166            "10m" => Ok(Self::Band10M),
167            "8m" => Ok(Self::Band8M),
168            "6m" => Ok(Self::Band6M),
169            "5m" => Ok(Self::Band5M),
170            "4m" => Ok(Self::Band4M),
171            "2m" => Ok(Self::Band2M),
172            "1.25m" => Ok(Self::Band1_25M),
173            "70cm" => Ok(Self::Band70Cm),
174            "33cm" => Ok(Self::Band33Cm),
175            "23cm" => Ok(Self::Band23Cm),
176            "13cm" => Ok(Self::Band13Cm),
177            "9cm" => Ok(Self::Band9Cm),
178            "5cm" => Ok(Self::Band5Cm),
179            "3cm" => Ok(Self::Band3Cm),
180            "1.2cm" => Ok(Self::Band1_2Cm),
181            "6mm" => Ok(Self::Band6Mm),
182            "4mm" => Ok(Self::Band4Mm),
183            "2.5mm" => Ok(Self::Band2_5Mm),
184            "2mm" => Ok(Self::Band2Mm),
185            "1mm" => Ok(Self::Band1Mm),
186            _ => Err(CoreError::InvalidValueFromStr(
187                s.to_string(),
188                "FrequencyAllocation",
189            )),
190        }
191    }
192}
193
194impl FrequencyAllocation {
195    pub const fn band(&self) -> FrequencyBand {
196        match self {
197            Self::Band2200M => FrequencyBand::Low,
198            Self::Band630M | Self::Band160M => FrequencyBand::Medium,
199            Self::Band80M
200            | Self::Band60M
201            | Self::Band40M
202            | Self::Band30M
203            | Self::Band20M
204            | Self::Band17M
205            | Self::Band15M
206            | Self::Band12M
207            | Self::Band10M => FrequencyBand::High,
208            Self::Band8M
209            | Self::Band6M
210            | Self::Band5M
211            | Self::Band4M
212            | Self::Band2M
213            | Self::Band1_25M => FrequencyBand::VeryHigh,
214            Self::Band70Cm | Self::Band33Cm | Self::Band23Cm | Self::Band13Cm => {
215                FrequencyBand::UltraHigh
216            }
217            Self::Band9Cm | Self::Band5Cm | Self::Band3Cm | Self::Band1_2Cm => {
218                FrequencyBand::SuperHigh
219            }
220            Self::Band6Mm | Self::Band4Mm | Self::Band2_5Mm | Self::Band2Mm | Self::Band1Mm => {
221                FrequencyBand::ExtremelyHigh
222            }
223        }
224    }
225
226    pub fn range(&self, in_region: Region) -> Option<FrequencyRange> {
227        match (in_region, self) {
228            // Common to all regions
229            (_, Self::Band2200M) => Some(FrequencyRange::new(kilohertz(135.7), kilohertz(137.8))),
230            (_, Self::Band630M) => Some(FrequencyRange::new(kilohertz(472.0), kilohertz(479.0))),
231            (_, Self::Band60M) => Some(FrequencyRange::new(megahertz(5.3515), megahertz(5.3665))),
232            (_, Self::Band40M) => Some(FrequencyRange::new(megahertz(7.0), megahertz(7.3))),
233            (_, Self::Band30M) => Some(FrequencyRange::new(megahertz(10.1), megahertz(10.15))),
234            (_, Self::Band20M) => Some(FrequencyRange::new(megahertz(14.0), megahertz(14.35))),
235            (_, Self::Band17M) => Some(FrequencyRange::new(megahertz(18.068), megahertz(18.168))),
236            (_, Self::Band15M) => Some(FrequencyRange::new(megahertz(21.0), megahertz(21.45))),
237            (_, Self::Band12M) => Some(FrequencyRange::new(megahertz(24.89), megahertz(24.99))),
238            (_, Self::Band10M) => Some(FrequencyRange::new(megahertz(28.0), megahertz(29.7))),
239            (_, Self::Band2M) => Some(FrequencyRange::new(megahertz(144.0), megahertz(148.0))),
240            (_, Self::Band70Cm) => Some(FrequencyRange::new(megahertz(430.0), megahertz(440.0))),
241            (_, Self::Band23Cm) => Some(FrequencyRange::new(gigahertz(1.24), gigahertz(1.3))),
242            (_, Self::Band13Cm) => Some(FrequencyRange::new(gigahertz(2.3), gigahertz(2.45))),
243            (_, Self::Band3Cm) => Some(FrequencyRange::new(gigahertz(10.0), gigahertz(10.5))),
244            (_, Self::Band1_2Cm) => Some(FrequencyRange::new(gigahertz(24.0), gigahertz(24.25))),
245            (_, Self::Band6Mm) => Some(FrequencyRange::new(gigahertz(47.0), gigahertz(47.2))),
246            (_, Self::Band4Mm) => Some(FrequencyRange::new(gigahertz(76.0), gigahertz(81.5))),
247            (_, Self::Band2_5Mm) => Some(FrequencyRange::new(gigahertz(122.25), gigahertz(123.0))),
248            (_, Self::Band2Mm) => Some(FrequencyRange::new(gigahertz(134.0), gigahertz(141.0))),
249            (_, Self::Band1Mm) => Some(FrequencyRange::new(gigahertz(241.0), gigahertz(250.0))),
250            // Region 1 specifics
251            (Region::One, Self::Band160M) => {
252                Some(FrequencyRange::new(megahertz(1.81), megahertz(2.0)))
253            }
254            (Region::One, Self::Band80M) => {
255                Some(FrequencyRange::new(megahertz(3.5), megahertz(3.8)))
256            }
257            (Region::One, Self::Band8M) => {
258                Some(FrequencyRange::new(megahertz(39.9), megahertz(40.75)))
259            }
260            (Region::One, Self::Band5M) => {
261                Some(FrequencyRange::new(megahertz(59.5), megahertz(60.1)))
262            }
263            (Region::One, Self::Band4M) => {
264                Some(FrequencyRange::new(megahertz(69.9), megahertz(70.5)))
265            }
266            (Region::One, Self::Band5Cm) => {
267                Some(FrequencyRange::new(gigahertz(5.65), gigahertz(5.85)))
268            }
269            // Common to regions 2 & 3
270            (_, Self::Band6M) => Some(FrequencyRange::new(megahertz(50.0), megahertz(54.0))),
271            (_, Self::Band9Cm) => Some(FrequencyRange::new(gigahertz(3.3), gigahertz(3.5))),
272            // Region 2 specific
273            (Region::Two, Self::Band160M) => {
274                Some(FrequencyRange::new(megahertz(1.8), megahertz(2.0)))
275            }
276            (Region::Two, Self::Band80M) => {
277                Some(FrequencyRange::new(megahertz(3.5), megahertz(4.0)))
278            }
279            (Region::Two, Self::Band1_25M) => {
280                Some(FrequencyRange::new(megahertz(220.0), megahertz(230.0)))
281            }
282            (Region::Two, Self::Band33Cm) => {
283                Some(FrequencyRange::new(megahertz(902.0), megahertz(928.0)))
284            }
285            (Region::Two, Self::Band5Cm) => {
286                Some(FrequencyRange::new(gigahertz(5.65), gigahertz(5.925)))
287            }
288            // Region 3 specific
289            (Region::Three, Self::Band160M) => {
290                Some(FrequencyRange::new(megahertz(1.8), megahertz(2.0)))
291            }
292            (Region::Three, Self::Band80M) => {
293                Some(FrequencyRange::new(megahertz(3.5), megahertz(3.9)))
294            }
295            (Region::Three, Self::Band5Cm) => {
296                Some(FrequencyRange::new(gigahertz(5.65), gigahertz(5.85)))
297            }
298            (_, _) => None,
299        }
300    }
301
302    pub fn total_range(&self) -> FrequencyRange {
303        let bounds = vec![
304            self.range(Region::One),
305            self.range(Region::Two),
306            self.range(Region::Three),
307        ]
308        .into_iter()
309        .flatten()
310        .map(|range| (range.start().value(), range.end().value()))
311        .collect::<Vec<_>>();
312
313        // Note: unwrap is safe as there MUST be at least one range for each allocation.
314        let start = bounds
315            .iter()
316            .map(|(start, _)| start)
317            .min_by(|l, r| l.total_cmp(r))
318            .unwrap();
319        let end = bounds
320            .iter()
321            .map(|(_, end)| end)
322            .max_by(|l, r| l.total_cmp(r))
323            .unwrap();
324        FrequencyRange::new(hertz(*start), hertz(*end))
325    }
326
327    pub fn classify(frequency: Frequency) -> Option<Self> {
328        match frequency.value() {
329            135700.0..137800.0 => Some(Self::Band2200M),
330            472000.0..479000.0 => Some(Self::Band630M),
331            1800000.0..2000000.0 => Some(Self::Band160M),
332            3500000.0..4000000.0 => Some(Self::Band80M),
333            5330000.0..5406000.0 => Some(Self::Band60M),
334            7000000.0..7300000.0 => Some(Self::Band40M),
335            10100000.0..10150000.0 => Some(Self::Band30M),
336            14000000.0..14350000.0 => Some(Self::Band20M),
337            18068000.0..18168000.0 => Some(Self::Band17M),
338            21000000.0..21450000.0 => Some(Self::Band15M),
339            24890000.0..24990000.0 => Some(Self::Band12M),
340            28000000.0..29700000.0 => Some(Self::Band10M),
341            39900000.0..40750000.0 => Some(Self::Band8M),
342            50000000.0..54000000.0 => Some(Self::Band6M),
343            144000000.0..148000000.0 => Some(Self::Band2M),
344            219000000.0..220000000.0 | 222000000.0..225000000.0 => Some(Self::Band1_25M),
345            420000000.0..450000000.0 => Some(Self::Band70Cm),
346            902000000.0..928000000.0 => Some(Self::Band33Cm),
347            1240000000.0..1300000000.0 => Some(Self::Band23Cm),
348            2300000000.0..2310000000.0 | 2390000000.0..2450000000.0 => Some(Self::Band13Cm),
349            3300000000.0..3500000000.0 => Some(Self::Band9Cm),
350            5650000000.0..5925000000.0 => Some(Self::Band5Cm),
351            10000000000.0..10500000000.0 => Some(Self::Band3Cm),
352            24000000000.0..24250000000.0 => Some(Self::Band1_2Cm),
353            47000000000.0..47200000000.0 => Some(Self::Band6Mm),
354            75500000000.0..81000000000.0 => Some(Self::Band4Mm),
355            122250000000.0..123000000000.0 => Some(Self::Band2_5Mm),
356            134000000000.0..141000000000.0 => Some(Self::Band2Mm),
357            241000000000.0..250000000000.0 => Some(Self::Band1Mm),
358            _ => None,
359        }
360    }
361
362    pub fn write_markdown<W: Write>(writer: &mut W) -> Result<(), MarkdownError> {
363        const NAME_COL_WIDTH: usize = 5;
364        const BAND_COL_WIDTH: usize = 5;
365        const START_COL_WIDTH: usize = 10;
366        const END_COL_WIDTH: usize = 10;
367
368        header(writer, 1, "IARU/ITU Frequency Allocations")?;
369        blank_line(writer)?;
370
371        let table = Table::new(vec![
372            ("Name", NAME_COL_WIDTH).into(),
373            ("Band", BAND_COL_WIDTH).into(),
374            Column::new("Start")
375                .with_width(START_COL_WIDTH)
376                .with_justification(ColumnJustification::Right),
377            Column::new("End")
378                .with_width(END_COL_WIDTH)
379                .with_justification(ColumnJustification::Right),
380            Column::new("Start")
381                .with_width(START_COL_WIDTH)
382                .with_justification(ColumnJustification::Right),
383            Column::new("End")
384                .with_width(END_COL_WIDTH)
385                .with_justification(ColumnJustification::Right),
386            Column::new("Start")
387                .with_width(START_COL_WIDTH)
388                .with_justification(ColumnJustification::Right),
389            Column::new("End")
390                .with_width(END_COL_WIDTH)
391                .with_justification(ColumnJustification::Right),
392        ])
393        .with_super_labels(vec![
394            "",
395            "",
396            "Region 1",
397            "",
398            "Region 2",
399            "",
400            "Region. 3",
401            "",
402        ]);
403
404        table.headers(writer)?;
405
406        for band in &[
407            Self::Band2200M,
408            Self::Band630M,
409            Self::Band160M,
410            Self::Band80M,
411            Self::Band60M,
412            Self::Band40M,
413            Self::Band30M,
414            Self::Band20M,
415            Self::Band17M,
416            Self::Band15M,
417            Self::Band12M,
418            Self::Band10M,
419            Self::Band6M,
420            Self::Band2M,
421            Self::Band1_25M,
422            Self::Band70Cm,
423            Self::Band33Cm,
424            Self::Band23Cm,
425            Self::Band13Cm,
426            Self::Band9Cm,
427            Self::Band5Cm,
428            Self::Band3Cm,
429            Self::Band1_2Cm,
430            Self::Band6Mm,
431            Self::Band4Mm,
432            Self::Band2_5Mm,
433            Self::Band2Mm,
434            Self::Band1Mm,
435        ] {
436            let region_1 = band.range(Region::One);
437            let region_2 = band.range(Region::Two);
438            let region_3 = band.range(Region::Three);
439
440            table.data_row(
441                writer,
442                &[
443                    band.to_string(),
444                    band.band().to_string(),
445                    region_1
446                        .as_ref()
447                        .map(|r| r.start().to_string())
448                        .unwrap_or("-".to_string()),
449                    region_1
450                        .as_ref()
451                        .map(|r| r.end().to_string())
452                        .unwrap_or("-".to_string()),
453                    region_2
454                        .as_ref()
455                        .map(|r| r.start().to_string())
456                        .unwrap_or("-".to_string()),
457                    region_2
458                        .as_ref()
459                        .map(|r| r.end().to_string())
460                        .unwrap_or("-".to_string()),
461                    region_3
462                        .as_ref()
463                        .map(|r| r.start().to_string())
464                        .unwrap_or("-".to_string()),
465                    region_3
466                        .as_ref()
467                        .map(|r| r.end().to_string())
468                        .unwrap_or("-".to_string()),
469                ],
470            )?;
471        }
472        blank_line(writer)?;
473
474        plain_text(writer, "For more information, see:")?;
475        blank_line(writer)?;
476
477        bulleted_list_item(
478            writer,
479            1,
480            format!(
481                "{}, IARU 2020.",
482                link_to_string(
483                    "Amateur and Amateur-satellite Service Spectrum",
484                    "https://www.iaru.org/wp-content/uploads/2020/01/Amateur-Services-Spectrum-2020_.pdf",
485                )
486            ),
487        )?;
488        bulleted_list_item(
489            writer,
490            1,
491            format!(
492                "{}, IARU.",
493                link_to_string(
494                    "Regions",
495                    "https://www.iaru.org/about-us/organisation-and-history/regions/",
496                )
497            ),
498        )?;
499        Ok(())
500    }
501}
502
503// ------------------------------------------------------------------------------------------------
504// Unit Tests
505// ------------------------------------------------------------------------------------------------
506
507#[cfg(test)]
508mod test {
509    use super::FrequencyAllocation;
510    use rfham_core::frequencies::{kilohertz, megahertz};
511
512    #[test]
513    fn test_write_markdown_band_plan() {
514        FrequencyAllocation::write_markdown(&mut std::io::stdout()).unwrap();
515    }
516
517    #[test]
518    fn test_total_range() {
519        assert_eq!(
520            "3.5 MHz - 4 MHz".to_string(),
521            FrequencyAllocation::Band80M.total_range().to_string()
522        );
523    }
524
525    #[test]
526    fn test_frequency_classifier() {
527        assert_eq!(None, FrequencyAllocation::classify(kilohertz(130.0)));
528        assert_eq!(
529            Some(FrequencyAllocation::Band2200M),
530            FrequencyAllocation::classify(kilohertz(136.0))
531        );
532        assert_eq!(
533            Some(FrequencyAllocation::Band1_25M),
534            // National calling frequency for FM Simplex
535            FrequencyAllocation::classify(megahertz(223.500))
536        );
537    }
538}