Nominatim中文检索词的检索流程(端到端)


Nominatim 通过 ICU 音译(Transliteration) 机制处理中文,核心思路是:将中文转为拼音(Latin 形式),然后用拼音 token 在数据库中进行匹配

一、导入阶段(Indexing)—— 中文名称如何入库

以 OSM 标签 name:zh=天安门 为例:

OSM name:zh=天安门
    │
    ▼
PlaceSanitizer → tag-analyzer-by-language
    ├─ 检测后缀 'zh' → 不在 whitelist 中,不创建特定 analyzer
    │  (但 use-defaults: all 会为中国境内要素创建 analyzer:zh 变体)
    └─ 输出清洗后的名称
    │
    ▼
ICUNameAnalyzer.process_place()
    ├─ get_analyzer('zh') → 无 'zh' 分析器 → 回退到 generic 分析器
    │
    ├─ generic.get_canonical_id("天安门")
    │   → self.norm.transliterate("天安门")
    │   → 规范化规则处理(CJK 保留,因为 ICU 的 [:alnum:] 是 Unicode 感知的)
    │   → 返回 "天安门"(保持不变)
    │
    ├─ generic.compute_variants("天安门")
    │   ├─ _generate_word_variants("天安门") → 无替换规则匹配 → 原样返回
    │   └─ self.to_ascii.transliterate("天安门")
    │       → 应用音译规则:
    │         ① :: Latin ()        → "tian an men"  (ICU 拉丁音译,CJK→拼音)
    │         ② extended-unicode-to-asccii.yaml → 无匹配
    │         ③ :: Ascii ()        → "tian an men"
    │         ④ [^a-z0-9[:Space:]] > → 移除非法字符
    │         ⑤ 最终结果: ["tian an men"]
    │
    ├─ 写入数据库 word 表:
    │   - word_token = "tian" (完整词)
    │   - word_token = "an"   (完整词)
    │   - word_token = "men"  (完整词)
    │   - word_id 关联到原始名称 "天安门"
    │
    └─ token_info.name_categories 记录:
        {"name:zh": "{word_id_123, word_id_456, ...}"}
    │
    ▼
search_name 表:
    name_vector: [{word_id_天}, {word_id_安}, {word_id_门}]
    token_info: {"names": "{...}", "name_categories": {"name:zh": "{...}"}}

二、查询阶段(Searching)—— 用户输入中文如何匹配

以用户搜索 "天安门" 为例:

用户输入 "天安门"
    │
    ▼
QueryPreprocessor
    └─ split_japanese_phrases → 不匹配,原样通过
    │
    ▼
ICUQueryAnalyzer.analyze_query()
    │
    ├─ ① normalize_text("天安门")
    │   → self.normalizer.transliterate("天安门")
    │   → 应用规范化规则:
    │     ① :: lower()           → "天安门" (CJK 无大小写)
    │     ② :: Hans-Hant         → 简繁统一(如天→天)
    │     ③ [^[:alnum:]...] >    → 保留 CJK(ICU 的 [:alnum:] 含 Unicode 字母)
    │     ④ 最终: "天安门"(保留完整)
    │
    ├─ ② split_query("天安门")
    │   → re.split('([ :-])', "天安门")
    │   → 无分隔符 → 整个字符串作为一个 word
    │   → self.transliterator.transliterate("天安门")
    │     · :: Latin ()         → "tian an men" (ICU 拉丁音译器,CJK→拼音)
    │     · :: Ascii ()         → "tian an men"
    │     · NFD → lower → [^a-z0-9[:Space:]] > → "tian an men "
    │     → 返回 "tian an men"
    │   → split_transliteration("tian an men", "天安门")
    │     · 按空格拆分成: ["tian", "an", "men"]
    │     · 与原始字对齐:
    │       ("tian", "天"), ("an", "安"), ("men", "门")
    │   → 每个子词作为一个查询节点:
    │     Node(btype=BREAK_TOKEN, term_lookup="tian", term_normalized="天")
    │     Node(btype=BREAK_TOKEN, term_lookup="an",   term_normalized="安")
    │     Node(btype=BREAK_TOKEN, term_lookup="men",  term_normalized="门")
    │
    ├─ ③ lookup_in_db(["tian", "an", "men"])
    │   → SELECT word_id FROM word WHERE word_token IN ('tian','an','men')
    │   → 匹配到导入时创建的词条
    │
    └─ ④ 构建搜索,执行 SQL 检索
       → 生成的 token 与 search_name.name_vector 进行匹配
       → 找到包含这些 token 的地点
    │
    ▼
结果处理
    ├─ add_result_details() → 补充地址详情
    ├─ 如果 output_withevidence=True:
    │   → compute_match_types()
    │   → 从 token_info.name_categories 查出匹配的类别是 "name:zh"
    │   → result.match_type = "name:zh"
    │   → result.matched_name_tag = "name:zh"
    │   → result.matched_name = "天安门"
    └─ rerank → sort → 返回

三、关键设计要点

环节说明
CJK 字符保留ICU 的 [:alnum:]Unicode 感知的,包含 CJK 表意文字(Category Lo),不会在规范化阶段被移除
简繁统一:: Hans-Hant 规则将简体/繁体中文统一,搜索 “天安門” 也会命中 “天安门”
拼音桥接:: Latin () 将 CJK 转为拼音,这是中文与英文搜索能共用同一套 token 系统的关键
无中文分词没有单独的中文分词器(如 jieba),依赖 ICU Latin 音译输出的空格来分割词语
无中文分析器36 种语言有变体规则(variants),中文没有;中文名称走 generic 分析器
类别追踪name_categories 记录 name:zh 类别,withevidence 功能可以利用它报告匹配的标签类型

四、局限性

问题影响
无中文分词“南京市长江大桥” 可能被音译为 “nan jing shi chang jiang da qiao”,但无法区分”南京/市长/江大桥” vs “南京市/长江大桥” 的语义差异
同音字歧义拼音到 token 的转换会丢失声调信息,”山西” (Shanxi) 和 “陕西” (Shaanxi) 可能混淆
多音字“重庆” → “zhong qing” 而非 “chong qing”,因为 ICU Latin 音译器对多音字的处理不一定准确
无中文变体没有 zh 的 variants 配置,意味着没有中文同义词/缩写扩展(如 “京”↔”北京”)

五、总结流程图

flowchart TD
    subgraph 导入侧
        A1["name:zh=天安门"] --> A2["generic.get_canonical_id()"]
        A2 --> A3["保留 CJK"]
        A3 --> A4["compute_variants()→::Latin→拼音"]
        A4 --> A5["word 表: tian/an/men"]
        A4 --> A6["name_categories: name:zh→{id}"]
    end

    subgraph 查询侧
        B1["用户输入 '天安门'"] --> B2["normalize_text()"]
        B2 --> B3["保留 CJK"]
        B3 --> B4["split_query()→::Latin→'tian an men'"]
        B4 --> B5["split_transliteration→(tian/an/men)"]
        B5 --> B6["lookup_in_db('tian','an','men')"]
        B6 --> B7["匹配 word 表→取 word_id"]
    end

    A5 --> B6
    B7 --> C1["SQL: name_vector 匹配→返回结果"]
    C1 --> C2["compute_match_types→match_type='name:zh'"]

核心机制:导入时与查询时走完全相同的音译管道normalize → :: Latin → ASCII → 分词),确保拼音 token 两侧一致,从而实现中文搜索。这本质上是一个拼音全文检索方案,而非真正的语义中文搜索。


发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

探索未来出版

九录科技愿意通过最前沿的技术和深厚的行业理解,为您的数字业务提供架构简单但很灵活的从创作到发布的全方位支持。

本站内容部分由AI生成,仅供参考,具体业务可随时电话/微信咨询(18610359982)。