]> git.openstreetmap.org Git - nominatim.git/blob - src/nominatim_db/tokenizer/token_analysis/housenumbers.py
look up all places at once
[nominatim.git] / src / nominatim_db / tokenizer / token_analysis / housenumbers.py
1 # SPDX-License-Identifier: GPL-3.0-or-later
2 #
3 # This file is part of Nominatim. (https://nominatim.org)
4 #
5 # Copyright (C) 2024 by the Nominatim developer community.
6 # For a full list of authors see the git log.
7 """
8 Specialized processor for housenumbers. Analyses common housenumber patterns
9 and creates variants for them.
10 """
11 from typing import Any, List, cast
12 import re
13
14 from ...data.place_name import PlaceName
15 from .generic_mutation import MutationVariantGenerator
16
17 RE_NON_DIGIT = re.compile('[^0-9]')
18 RE_DIGIT_ALPHA = re.compile(r'(\d)\s*([^\d\s␣])')
19 RE_ALPHA_DIGIT = re.compile(r'([^\s\d␣])\s*(\d)')
20 RE_NAMED_PART = re.compile(r'[a-z]{4}')
21
22 # Configuration section
23
24
25 def configure(*_: Any) -> None:
26     """ All behaviour is currently hard-coded.
27     """
28     return None
29
30 # Analysis section
31
32
33 def create(normalizer: Any, transliterator: Any, config: None) -> 'HousenumberTokenAnalysis':
34     """ Create a new token analysis instance for this module.
35     """
36     return HousenumberTokenAnalysis(normalizer, transliterator)
37
38
39 class HousenumberTokenAnalysis:
40     """ Detects common housenumber patterns and normalizes them.
41     """
42     def __init__(self, norm: Any, trans: Any) -> None:
43         self.norm = norm
44         self.trans = trans
45
46         self.mutator = MutationVariantGenerator('␣', (' ', ''))
47
48     def get_canonical_id(self, name: PlaceName) -> str:
49         """ Return the normalized form of the housenumber.
50         """
51         # shortcut for number-only numbers, which make up 90% of the data.
52         if RE_NON_DIGIT.search(name.name) is None:
53             return name.name
54
55         norm = cast(str, self.trans.transliterate(self.norm.transliterate(name.name)))
56         # If there is a significant non-numeric part, use as is.
57         if RE_NAMED_PART.search(norm) is None:
58             # Otherwise add optional spaces between digits and letters.
59             (norm_opt, cnt1) = RE_DIGIT_ALPHA.subn(r'\1␣\2', norm)
60             (norm_opt, cnt2) = RE_ALPHA_DIGIT.subn(r'\1␣\2', norm_opt)
61             # Avoid creating too many variants per number.
62             if cnt1 + cnt2 <= 4:
63                 return norm_opt
64
65         return norm
66
67     def compute_variants(self, norm_name: str) -> List[str]:
68         """ Compute the spelling variants for the given normalized housenumber.
69
70             Generates variants for optional spaces (marked with '␣').
71         """
72         return list(self.mutator.generate([norm_name]))