bgwu666 发表于 2024-9-30 22:28

基于C++的业余无线电呼号查询工具

本帖最后由 bgwu666 于 2024-9-30 22:30 编辑

## 前言

自从接触业余无线电后,就会对见到的每个呼号都产生好奇心,总想查一查这些HAM呼号的QTH。网络上的QTH查询工具需要呼号所有者自行上传位置,而没上传的自然就查不到,于是萌生了自己写一个查询工具的想法,查到具体省份即可。

该工具依据*业余无线电台呼号管理办法*,基于C++所写,为初学练手项目,如有不足,恳请批评指正:(https://github.com/Goal75KG/CallSignQuery)。

## 功能

- 根据呼号解析出国家信息(暂时只能解析中国)
- 查询电台种类(个人业余电台、信标台、卫星电台等)
- 确定呼号所属省份
- 处理特殊情况的电台种类

## 示例

输入呼号:`BA8MAA`

输出结果:

```
呼号: BA8MAA
国家: 中国
电台种类: 个人业余电台(一级)
省份: 贵州
```

## 实现过程

**映射呼号每部分规则**

```
// 加载国家前缀规则
std::map<std::string, std::string> loadCountryPrefixRules() {
    std::map<std::string, std::string> countryPrefixRules;
    countryPrefixRules["B"] = "中国";
    return countryPrefixRules;
   
// 加载电台种类规则
std::map<char, std::string> loadStationTypeRules() {
    std::map<char, std::string> stationTypeRules;
    stationTypeRules['A'] = "个人业余电台(一级)";
    stationTypeRules['D'] = "个人业余电台(二级)";
    stationTypeRules['E'] = "个人业余电台(二级)";
    stationTypeRules['G'] = "个人业余电台(三级、四级、五级(收信台))";
    stationTypeRules['H'] = "个人业余电台(三级、四级、五级(收信台))";
    stationTypeRules['I'] = "个人业余电台(三级、四级、五级(收信台))";
    stationTypeRules['J'] = "业余信标台";
    stationTypeRules['L'] = "外籍及香港、澳门、台湾地区人员在中国内地设置的业余电台";
    stationTypeRules['R'] = "业余中继台";
    stationTypeRules['T'] = "特设业余电台";
    stationTypeRules['Y'] = "集体业余电台";
    return stationTypeRules;
}
   
// 加载电台种类规则
std::map<char, std::string> loadStationTypeRules() {
    std::map<char, std::string> stationTypeRules;
    stationTypeRules['A'] = "个人业余电台(一级)";
    stationTypeRules['D'] = "个人业余电台(二级)";
    stationTypeRules['E'] = "个人业余电台(二级)";
    stationTypeRules['G'] = "个人业余电台(三级、四级、五级(收信台))";
    stationTypeRules['H'] = "个人业余电台(三级、四级、五级(收信台))";
    stationTypeRules['I'] = "个人业余电台(三级、四级、五级(收信台))";
    stationTypeRules['J'] = "业余信标台";
    stationTypeRules['L'] = "外籍及香港、澳门、台湾地区人员在中国内地设置的业余电台";
    stationTypeRules['R'] = "业余中继台";
    stationTypeRules['T'] = "特设业余电台";
    stationTypeRules['Y'] = "集体业余电台";
    return stationTypeRules;
}
```

**判断对应后缀是否在范围内**

```
bool isWithinRange(const std::string &suffix, const std::string &rangeStart, const std::string &rangeEnd) {
    return suffix >= rangeStart && suffix <= rangeEnd;
}
```

**呼号匹配**

```
void queryCallSign(const std::string &callSign) {
    // 加载各类规则
    auto countryRules = loadCountryPrefixRules();
    auto stationTypeRules = loadStationTypeRules();
    auto regionProvinceRules = loadRegionProvinceRules();

    // 确保呼号至少有5位
    if (callSign.length() < 5) {
      std::cout << "呼号格式不正确" << std::endl;
      return;
    }

    // 解析国家前缀
    std::string country = countryRules;

    // 解析电台种类
    char stationType = callSign;
    std::string station = stationTypeRules;

    // 解析地区分区
    char region = callSign;

    // 解析后缀
    std::string suffix = callSign.substr(3, 2); // 假设后缀长度为2位

    // 查找该地区的省份范围
    std::string provinceName = "未知省份";
    if (regionProvinceRules.find(region) != regionProvinceRules.end()) {
      for (const auto &province: regionProvinceRules) {
            if (isWithinRange(suffix, province.second.first, province.second.second)) {
                provinceName = province.first;
                break;
            }
      }
    }
```

**处理特殊情况(业余信标台、卫星业余电台、特设业余电台)**

```
if (stationType == 'J') {
      char suffix1 = callSign;
      if (suffix1 == 'H' || suffix1 == 'V' || suffix1 == 'U') {
            station = "HF、VHF、UHV频段信标台";
      } else if (suffix1 == 'D') {
            station = "业余无线电测向专用信标台";
      } else if (suffix1 == 'S' && region == '1') {
            station = "卫星业余电台";
      } else if (suffix1 == 'T') {
            station = "特设业余电台";
      }
    }
```

**结果输出**

```
    if (!country.empty() && !station.empty() && provinceName != "未知省份") {
      std::cout << "呼号: " << callSign << std::endl;
      std::cout << "国家: " << country << std::endl;
      std::cout << "电台种类: " << station << std::endl;
      std::cout << "省份: " << provinceName << std::endl;
    } else {
      std::cout << "未找到呼号对应的信息" << std::endl;
    }
}
```

**主程序**

```
int main() {
    std::string callSign;
    std::cout << "请输入要查询的呼号: ";
    std::cin >> callSign;
    std::cout << "\n查询结果如下:\n" << std::endl;

    // 查询呼号信息
    queryCallSign(callSign);
    system("pause");
    return 0;
}
```

## 源代码

```
#include <iostream>
#include <map>
#include <string>

// 加载国家前缀规则
std::map<std::string, std::string> loadCountryPrefixRules() {
    std::map<std::string, std::string> countryPrefixRules;
    countryPrefixRules["B"] = "中国";
    return countryPrefixRules;
}

// 加载电台种类规则
std::map<char, std::string> loadStationTypeRules() {
    std::map<char, std::string> stationTypeRules;
    stationTypeRules['A'] = "个人业余电台(一级)";
    stationTypeRules['D'] = "个人业余电台(二级)";
    stationTypeRules['E'] = "个人业余电台(二级)";
    stationTypeRules['G'] = "个人业余电台(三级、四级、五级(收信台))";
    stationTypeRules['H'] = "个人业余电台(三级、四级、五级(收信台))";
    stationTypeRules['I'] = "个人业余电台(三级、四级、五级(收信台))";
    stationTypeRules['J'] = "业余信标台";
    stationTypeRules['L'] = "外籍及香港、澳门、台湾地区人员在中国内地设置的业余电台";
    stationTypeRules['R'] = "业余中继台";
    stationTypeRules['T'] = "特设业余电台";
    stationTypeRules['Y'] = "集体业余电台";
    return stationTypeRules;
}

// 加载大区和省份范围规则
std::map<char, std::map<std::string, std::pair<std::string, std::string> > > loadRegionProvinceRules() {
    std::map<char, std::map<std::string, std::pair<std::string, std::string> > > regionProvinceRules;

    // 第1区:北京
    regionProvinceRules['1'] = {
      {"北京", {"AA", "XZZ"}}
    };

    // 第2区:黑龙江、辽宁、吉林
    regionProvinceRules['2'] = {
      {"黑龙江", {"AA", "HZZ"}},
      {"吉林", {"IA", "PZZ"}},
      {"辽宁", {"QA", "XZZ"}}
    };

    // 第3区:河北、内蒙古、山西、天津
    regionProvinceRules['3'] = {
      {"天津", {"AA", "FZZ"}},
      {"内蒙古", {"GA", "LZZ"}},
      {"河北", {"MA", "RZZ"}},
      {"山西", {"SA", "XZZ"}}
    };

    // 第4区:山东、江苏、上海
    regionProvinceRules['4'] = {
      {"上海", {"AA", "HZZ"}},
      {"山东", {"IA", "PZZ"}},
      {"江苏", {"QA", "XZZ"}}
    };

    // 第5区:浙江、江西、福建
    regionProvinceRules['5'] = {
      {"浙江", {"AA", "HZZ"}},
      {"江西", {"IA", "PZZ"}},
      {"福建", {"QA", "XZZ"}}
    };

    // 第6区:河南、湖北、安徽
    regionProvinceRules['6'] = {
      {"安徽", {"AA", "HZZ"}},
      {"河南", {"IA", "PZZ"}},
      {"湖北", {"QA", "XZZ"}}
    };

    // 第7区:湖南、广东、广西、海南
    regionProvinceRules['7'] = {
      {"湖南", {"AA", "HZZ"}},
      {"广东", {"IA", "PZZ"}},
      {"广西", {"QA", "XZZ"}},
      {"海南", {"YA", "ZZZ"}}
    };

    // 第8区:四川、贵州、云南、重庆
    regionProvinceRules['8'] = {
      {"四川", {"AA", "FZZ"}},
      {"重庆", {"GA", "LZZ"}},
      {"贵州", {"MA", "RZZ"}},
      {"云南", {"SA", "XZZ"}}
    };

    // 第9区:陕西、甘肃、宁夏、青海
    regionProvinceRules['9'] = {
      {"陕西", {"AA", "FZZ"}},
      {"甘肃", {"GA", "LZZ"}},
      {"宁夏", {"MA", "RZZ"}},
      {"青海", {"SA", "XZZ"}}
    };

    // 第0区:新疆、西藏
    regionProvinceRules['0'] = {
      {"新疆", {"AA", "FZZ"}},
      {"西藏", {"GA", "LZZ"}}
    };

    return regionProvinceRules;
}

// 判断后缀是否在范围内
bool isWithinRange(const std::string &suffix, const std::string &rangeStart, const std::string &rangeEnd) {
    return suffix >= rangeStart && suffix <= rangeEnd;
}

// 查询呼号
void queryCallSign(const std::string &callSign) {
    // 加载各类规则
    auto countryRules = loadCountryPrefixRules();
    auto stationTypeRules = loadStationTypeRules();
    auto regionProvinceRules = loadRegionProvinceRules();

    // 确保呼号至少有5位
    if (callSign.length() < 5) {
      std::cout << "呼号格式不正确" << std::endl;
      return;
    }

    // 解析国家前缀
    std::string country = countryRules;

    // 解析电台种类
    char stationType = callSign;
    std::string station = stationTypeRules;

    // 解析地区分区
    char region = callSign;

    // 解析后缀
    std::string suffix = callSign.substr(3, 2); // 假设后缀长度为2位

    // 查找该地区的省份范围
    std::string provinceName = "未知省份";
    if (regionProvinceRules.find(region) != regionProvinceRules.end()) {
      for (const auto &province: regionProvinceRules) {
            if (isWithinRange(suffix, province.second.first, province.second.second)) {
                provinceName = province.first;
                break;
            }
      }
    }
    // 处理特殊情况(业余信标台、卫星业余电台、特设业余电台)
    if (stationType == 'J') {
      char suffix1 = callSign;
      if (suffix1 == 'H' || suffix1 == 'V' || suffix1 == 'U') {
            station = "HF、VHF、UHV频段信标台";
      } else if (suffix1 == 'D') {
            station = "业余无线电测向专用信标台";
      } else if (suffix1 == 'S' && region == '1') {
            station = "卫星业余电台";
      } else if (suffix1 == 'T') {
            station = "特设业余电台";
      }
    }
    // 输出结果
    if (!country.empty() && !station.empty() && provinceName != "未知省份") {
      std::cout << "呼号: " << callSign << std::endl;
      std::cout << "国家: " << country << std::endl;
      std::cout << "电台种类: " << station << std::endl;
      std::cout << "省份: " << provinceName << std::endl;
    } else {
      std::cout << "未找到呼号对应的信息" << std::endl;
    }
}

int main() {
    std::string callSign;
    std::cout << "请输入要查询的呼号: ";
    std::cin >> callSign;
    std::cout << "\n查询结果如下:\n" << std::endl;

    // 查询呼号信息
    queryCallSign(callSign);
    system("pause");
    return 0;
}
```

## 尾声

**后续计划:**

- [ ] 支持全球呼号
- [ ] 添加GUI

有更多想法?欢迎讨论&#128079;

smilenet 发表于 2024-10-4 00:02

谢谢分享,切实解决生活中实际问题的作品。

bgwu666 发表于 2024-10-2 13:43

arthas19 发表于 2024-10-2 02:56
谢谢分享,代码靓丽;

现在如果是做GUI,是用QT么,还是用VS的?


感谢发表意见,目前还没着手研究GUI,所以暂时无法确定使用何种GUI框架。至于映射表写在代码中是因为中国呼号命名规则几乎不会变动,写死是一种比较简单的方式。后续在添加全球呼号支持时,一定是会把映射表单独写到文件中,通过加载映射文件来解析呼号的。

吾爱蛋蛋 发表于 2024-10-1 00:48

谢谢分享

kim945 发表于 2024-10-1 01:19

这个厉害啊,明年考初级证,准备弄个小电台玩
单位领导说,中级证之后可以让我拿单位的练练

7845 发表于 2024-10-1 04:46

先插个眼待后面需要取

hunkguo 发表于 2024-10-1 08:24

简单明了,学习c++有用。

bgwu666 发表于 2024-10-1 10:25

kim945 发表于 2024-10-1 01:19
这个厉害啊,明年考初级证,准备弄个小电台玩
单位领导说,中级证之后可以让我拿单位的练练

今年先把初级先考了吧,中级需要初级证持有一段时间才能再考

tp206555 发表于 2024-10-1 13:01

没有证 来学习一下

arthas19 发表于 2024-10-2 02:56


谢谢分享,代码靓丽;

现在如果是做GUI,是用QT么,还是用VS的?

不如不忙着搞GUI,映射表如果不写在代码里面,写在外面,会不会更灵活一些

unix2005 发表于 2024-10-3 17:05

感谢分享,现在能做c/c++的好像越来越少了!
页: [1] 2
查看完整版本: 基于C++的业余无线电呼号查询工具