使用 C++ 脚本批量处理name:zh相关标签,解决地点名称无法在某些软件内显示的问题
Posted by lpf452 on 14 September 2025 in Chinese (China) (中文(中国大陆)). Last updated on 1 October 2025.作为一名贡献者,我一直致力于提升本地 OSM 数据的细节和可用性。但是最近我发现一个令人困扰的问题:在我绘制过的区域,很多地点的中文名称在某些软件/服务中 (比如 OsmAPP、JawgMaps 和 MapTiler 的瓦片等)无法正确显示(回退为拼音),或是优先显示了英文名 (name:en),导致看起来怪怪的,明明有 name 但就是不用。
主要原因这些软件的神必渲染规则通常会优先寻找符合用户语言的 name:[lang] 标签。虽然我们加了 name 标签,但如果缺少了明确的 name:zh 或 name:zh-Hans 标签,渲染器可能就会“不知所措”,转而去寻找 name:en 或干脆直接显示拼音。
手动为成千上万个要素添加这些标签显然是不现实的,又不能靠复制粘贴,一圈下来人可能都麻了。我决定靠自动化,也就是写一个脚本来解决这个问题。
技术选型与脚本逻辑
对于这种讲究高性能的操作,C++ 肯定是首选,我还用了两个强大的开源库:
- pugixml: 一个以轻量和高性能著称的 C++ XML 解析库,用来极速读写庞大的
.osm文件。 - OpenCC: 社区公认的中文简繁转换标准库,用于生成
name:zh-Hant标签。
我编写的脚本核心逻辑如下:
- 读取与解析: 使用
pugixml加载从 Overpass API 查询的的本地.osm数据文件; - 遍历要素: 循环遍历文件中的每一个
nodeway和relation; - 定位目标: 检查元素是否含有
k="name"的tag; - 生成标签: 如果找到
name标签,则执行以下操作:- 复制
name标签的值,创建新的<tag k="name:zh" v="..."/>; - 再次复制
name标签的值,创建新的<tag k="name:zh-Hans" v="..."/>; - 调用 OpenCC 库(使用
s2twp.json),将name的值从简体中文转换为台湾地区通行的繁体中文,并创建<tag k="name:zh-Hant" v="..."/>;
- 复制
- 生成变更文件: 将所有被修改的要素(保留其原始
version号)写入一个全新的.osc(osmChange) 文件中,以便上传。
这里我贴一个 AI 生成的代码 (懒得写),各位可以参考一下:
#include <iostream>
#include <fstream>
#include <string>
#include <cstring>
#include <memory>
#include <vector>
#include "pugixml.hpp"
#include <opencc/SimpleConverter.hpp>
#include <opencc/Exception.hpp>
int main(int argc, char* argv[]) {
if (argc != 3) {
std::cerr << "Usage: " << argv[0] << " <input.osm> <output.osc>" << std::endl;
return 1;
}
const char* input_file = argv[1];
const char* output_file = argv[2];
opencc::SimpleConverter converter("s2twp.json");
pugi::xml_document doc_in;
pugi::xml_parse_result result = doc_in.load_file(input_file);
if (!result) {
std::cerr << "Error parsing input file: " << result.description() << " at offset " << result.offset << std::endl;
return 1;
}
std::cout << "Successfully parsed input file: " << input_file << std::endl;
pugi::xml_node osm_node = doc_in.child("osm");
if (!osm_node) {
std::cerr << "Error: <osm> root tag not found." << std::endl;
return 1;
}
std::vector<pugi::xml_node> modified_elements;
for (pugi::xml_node element : osm_node.children()) {
if (strcmp(element.name(), "node") != 0 &&
strcmp(element.name(), "way") != 0 &&
strcmp(element.name(), "relation") != 0) {
continue;
}
bool has_name_tag = false;
std::string name_value;
for (const auto& tag : element.children("tag")) {
if (strcmp(tag.attribute("k").as_string(), "name") == 0) {
has_name_tag = true;
name_value = tag.attribute("v").as_string();
break;
}
}
if (has_name_tag) {
pugi::xml_node tag_zh = element.append_child("tag");
tag_zh.append_attribute("k") = "name:zh";
tag_zh.append_attribute("v") = name_value.c_str();
pugi::xml_node tag_zh_hans = element.append_child("tag");
tag_zh_hans.append_attribute("k") = "name:zh-Hans";
tag_zh_hans.append_attribute("v") = name_value.c_str();
try {
std::string name_hant = converter.Convert(name_value);
pugi::xml_node tag_zh_hant = element.append_child("tag");
tag_zh_hant.append_attribute("k") = "name:zh-Hant";
tag_zh_hant.append_attribute("v") = name_hant.c_str();
} catch (const opencc::Exception& e) {
std::cerr << "Warning: OpenCC conversion failed for value '" << name_value << "'. Error: " << e.what() << std::endl;
}
modified_elements.push_back(element);
}
}
std::cout << "Found and processed " << modified_elements.size() << " elements with 'name' tag." << std::endl;
if (!modified_elements.empty()) {
pugi::xml_document doc_out;
auto declarationNode = doc_out.append_child(pugi::node_declaration);
declarationNode.append_attribute("version") = "1.0";
declarationNode.append_attribute("encoding") = "UTF-8";
pugi::xml_node osm_change_node = doc_out.append_child("osmChange");
osm_change_node.append_attribute("version") = "0.6";
osm_change_node.append_attribute("generator") = "osm_name_tool_cpp";
pugi::xml_node modify_node = osm_change_node.append_child("modify");
for (const auto& el : modified_elements) {
modify_node.append_copy(el);
}
if (doc_out.save_file(output_file, " ")) {
std::cout << "Successfully generated osmChange file: " << output_file << std::endl;
} else {
std::cerr << "Error writing to output file: " << output_file << std::endl;
return 1;
}
} else {
std::cout << "No elements with 'name' tag found. Output file was not created." << std::endl;
}
return 0;
}
结果
脚本不到 1s 的时间内高效地完成了任务。在大竹县区域内 (我从 Overpass 查询出来大小是 30 多M),它为 1790 个要素(包括 599 个节点,1110 条道路和 81 个关系)添加了缺失的中文语言标签。
我已将生成的 .osc 文件通过 Vespucci 上传,相关变更位于这个变更集中。

比较
这里有一组用脚本编辑之前与之后的对比,可供参考 (图中所展示的是 OsmAPP,“一个通用的 OpenStreetMap 应用程序”):
| 编辑之前 | 编辑之后 |
|---|---|
![]() |
![]() |
用脚本带来的潜在问题与风险
- 非中文名称: 脚本目前比较初级,它没有检查
name标签本身是否为英文或其他非中文字符(例如name="KFC")。虽然在我熟悉的区域内这种情况几乎没有,但不能完全排除错误处理的可能性。 - 简繁转换错误: OpenCC 虽然强大,但不是万能的。某些特定地名的转换可能会出错。
欢迎审查与反馈
我进行这次编辑的初衷是为了让 OSM 数据在更广泛的应用中发挥价值,提升最终用户的地图体验。
我非常欢迎,并恳请社区的各位 mapper 帮助审查这个变更集,如果你发现了任何不当的修改、错误的数据或有更好的处理建议,请不要犹豫,直接在变更集上留 comment 或通过 OSM 站内信联系我。
对于任何指出的问题,我都会积极跟进,进行修正甚至是靠 osm-revert 回滚。谢谢。


Discussion
Comment from 真中あお on 11 December 2025 at 17:28
我很赞同该批量修正一下缺失name:zh的问题! 不过,从流程上来讲,使用程序批量编辑时应当先讨论取得共识,再执行。 鉴于用户日记很少有人互动,我觉得在OSM中国社群的电报群(id编辑器上每次提交编辑后会显示加群链接)或QQ群(群号290278518)可能会比较容易找到人完成讨论~