^-^
Jingbin的技术博客
基于C++的业余无线电呼号查询工具
最近更新:2025-07-20   |   字数总计:2.1k   |   阅读估时:9分钟   |   阅读量:
  1. 前言
  2. 功能
  3. 示例
  4. 实现过程
  5. 源代码
  6. 尾声

前言

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

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

功能

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

示例

输入呼号:BA8MAA

输出结果:

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

实现过程

映射呼号每部分规则

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
// 加载国家前缀规则
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;
}

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

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

呼号匹配

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
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[callSign.substr(0, 1)];

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

// 解析地区分区
char region = callSign[2];

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
if (stationType == 'J') {
char suffix1 = callSign[3];
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 = "特设业余电台";
}
}

结果输出

1
2
3
4
5
6
7
8
9
    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;
}
}

主程序

1
2
3
4
5
6
7
8
9
10
11
int main() {
std::string callSign;
std::cout << "请输入要查询的呼号: ";
std::cin >> callSign;
std::cout << "\n查询结果如下:\n" << std::endl;

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

源代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
#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[callSign.substr(0, 1)];

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

// 解析地区分区
char region = callSign[2];

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

// 查找该地区的省份范围
std::string provinceName = "未知省份";
if (regionProvinceRules.find(region) != regionProvinceRules.end()) {
for (const auto &province: regionProvinceRules[region]) {
if (isWithinRange(suffix, province.second.first, province.second.second)) {
provinceName = province.first;
break;
}
}
}
// 处理特殊情况(业余信标台、卫星业余电台、特设业余电台)
if (stationType == 'J') {
char suffix1 = callSign[3];
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

有更多想法?欢迎讨论👏