]> git.openstreetmap.org Git - nominatim.git/commitdiff
output names as setting
authorastridx <info@astrid-guenther.de>
Fri, 14 Mar 2025 21:03:36 +0000 (22:03 +0100)
committerastridx <info@astrid-guenther.de>
Mon, 31 Mar 2025 14:55:05 +0000 (16:55 +0200)
docs/customize/Settings.md
settings/env.defaults
src/nominatim_api/localization.py
test/python/api/test_localization.py

index 94726ca7638ee33899e3d878ded7b5837e8b24a4..edf2241b41bdc5dbaeece29b1e36938c8cc0d03b 100644 (file)
@@ -602,6 +602,43 @@ results gathered so far.
 Note that under high load you may observe that users receive different results
 than usual without seeing an error. This may cause some confusion.
 
+#### NOMINATIM_OUTPUT_NAMES
+
+| Summary            |                                                     |
+| --------------     | --------------------------------------------------- |
+| **Description:**   | Specifies order of name tags |
+| **Format:**        | string: comma-separated list of tag names |
+| **Default:**       | name:XX,name,brand,official_name:XX,short_name:XX,official_name,short_name,ref |
+
+Specifies the order in which different name tags are used.
+The values in this list determine the preferred order of name variants,
+including language-specific names (in OSM: the name tag with and without any language suffix).
+
+Comma-separated list, where :XX stands for language suffix
+(e.g. name:en) and no :XX stands for general tags (e.g. name).
+
+See also [NOMINATIM_DEFAULT_LANGUAGE](#nominatim_default_language).
+
+!!! note
+    If NOMINATIM_OUTPUT_NAMES = `name:XX,name,short_name:XX,short_name` the search follows
+
+        ```
+        'name', 'short_name'
+        ```
+
+    if we have no preferred language order for showing search results.
+
+    For languages ['en', 'es'] the search follows
+
+        ```
+        'name:en', 'name:es',
+        'name',
+        'short_name:en', 'short_name:es',
+        'short_name'
+        ```
+
+    For those familiar with the internal implementation, the `_place_*` expansion is added, but to simplify, it is not included in this example.
+
 ### Logging Settings
 
 #### NOMINATIM_LOG_DB
index b8c666677ff04616252e0ecf38ba3505ce3a668b..3ebb288f515b9fdaba6b3d50c53f4d75db747f19 100644 (file)
@@ -192,6 +192,13 @@ NOMINATIM_REQUEST_TIMEOUT=60
 # to geocode" instead.
 NOMINATIM_SEARCH_WITHIN_COUNTRIES=False
 
+# Specifies the order in which different name tags are used.
+# The values in this list determine the preferred order of name variants,
+# including language-specific names.
+# Comma-separated list, where :XX stands for language-specific tags
+# (e.g. name:en) and no :XX stands for general tags (e.g. name).
+NOMINATIM_OUTPUT_NAMES=name:XX,name,brand,official_name:XX,short_name:XX,official_name,short_name,ref
+
 ### Log settings
 #
 # The following options allow to enable logging of API requests.
index bbf9225bb22fdac08fd6217dd84b14959aa21604..3414286e6c63d24163f1f630199caa4ac48cb67e 100644 (file)
@@ -8,6 +8,7 @@
 Helper functions for localizing names of results.
 """
 from typing import Mapping, List, Optional
+from .config import Configuration
 
 import re
 
@@ -20,14 +21,18 @@ class Locales:
     """
 
     def __init__(self, langs: Optional[List[str]] = None):
+        self.config = Configuration(None)
         self.languages = langs or []
         self.name_tags: List[str] = []
 
-        # Build the list of supported tags. It is currently hard-coded.
-        self._add_lang_tags('name')
-        self._add_tags('name', 'brand')
-        self._add_lang_tags('official_name', 'short_name')
-        self._add_tags('official_name', 'short_name', 'ref')
+        parts = self.config.OUTPUT_NAMES.split(',')
+
+        for part in parts:
+            part = part.strip()
+            if part.endswith(":XX"):
+                self._add_lang_tags(part[:-3])
+            else:
+                self._add_tags(part)
 
     def __bool__(self) -> bool:
         return len(self.languages) > 0
index 0a30cdc110b1eac1d1ec825952c6982380162f64..c3e02596b42bf3a7d174d12d0d922b874e85edc0 100644 (file)
@@ -27,6 +27,62 @@ def test_display_name_none_localized():
     assert loc.display_name({'ref': '34', 'name:de': 'DE'}) == '34'
 
 
+def test_output_names_none_localized():
+    loc = Locales()
+
+    expected_tags = [
+        'name', '_place_name', 'brand', '_place_brand', 'official_name', '_place_official_name',
+        'short_name', '_place_short_name', 'ref', '_place_ref'
+    ]
+
+    assert loc.name_tags == expected_tags, f'Expected {expected_tags}, but got {loc.name_tags}'
+
+
+def test_output_names_none_localized_and_custom_output_names(monkeypatch):
+    monkeypatch.setenv(
+        'NOMINATIM_OUTPUT_NAMES',
+        'name:XX,entrance:XX,name,brand,test_tag,'
+        'official_name:XX,short_name:XX,alt_name:XX'
+    )
+    loc = Locales()
+
+    expected_tags = [
+        'name', '_place_name', 'brand', '_place_brand', 'test_tag', '_place_test_tag'
+    ]
+
+    assert loc.name_tags == expected_tags, f'Expected {expected_tags}, but got {loc.name_tags}'
+
+
+def test_output_names_none_localized_and_custom_output_names_more_than_two_changes(monkeypatch):
+    monkeypatch.setenv(
+        'NOMINATIM_OUTPUT_NAMES',
+        'name:XX,brand,test_tag:XX,official_name,short_name:XX,'
+        'alt_name,another_tag_with:XX,another_tag_withoutXX'
+    )
+    loc = Locales()
+
+    expected_tags = [
+        'brand', '_place_brand', 'official_name', '_place_official_name', 'alt_name',
+        '_place_alt_name', 'another_tag_withoutXX', '_place_another_tag_withoutXX'
+    ]
+
+    assert loc.name_tags == expected_tags, f'Expected {expected_tags}, but got {loc.name_tags}'
+
+
+def test_output_names_none_localized_and_custom_output_names_including_space(monkeypatch):
+    monkeypatch.setenv(
+        'NOMINATIM_OUTPUT_NAMES',
+        'name:XX,name ,short_name:XX, short_name'
+    )
+    loc = Locales()
+
+    expected_tags = [
+        'name', '_place_name', 'short_name', '_place_short_name'
+    ]
+
+    assert loc.name_tags == expected_tags, f'Expected {expected_tags}, but got {loc.name_tags}'
+
+
 def test_display_name_localized():
     loc = Locales(['en', 'de'])
 
@@ -35,6 +91,146 @@ def test_display_name_localized():
     assert loc.display_name({'ref': '34', 'name:de': 'DE'}) == 'DE'
 
 
+def test_output_names_localized():
+    loc = Locales(['en', 'es'])
+
+    expected_tags = [
+        'name:en', '_place_name:en', 'name:es', '_place_name:es', 'name', '_place_name', 'brand',
+        '_place_brand', 'official_name:en', '_place_official_name:en', 'official_name:es',
+        '_place_official_name:es', 'short_name:en', '_place_short_name:en', 'short_name:es',
+        '_place_short_name:es', 'official_name', '_place_official_name', 'short_name',
+        '_place_short_name', 'ref', '_place_ref'
+    ]
+
+    assert loc.name_tags == expected_tags, f'Expected {expected_tags}, but got {loc.name_tags}'
+
+
+def test_output_names_localized_and_custom_output_names_including_space(monkeypatch):
+    monkeypatch.setenv(
+        'NOMINATIM_OUTPUT_NAMES',
+        'name:XX,name ,short_name:XX, short_name'
+    )
+    loc = Locales(['en', 'es'])
+
+    expected_tags = [
+        'name:en', '_place_name:en', 'name:es', '_place_name:es',
+        'name', '_place_name',
+        'short_name:en', '_place_short_name:en', 'short_name:es', '_place_short_name:es',
+        'short_name', '_place_short_name'
+    ]
+
+    assert loc.name_tags == expected_tags, f'Expected {expected_tags}, but got {loc.name_tags}'
+
+
+def test_output_names_localized_and_custom_output_names(monkeypatch):
+    monkeypatch.setenv(
+        'NOMINATIM_OUTPUT_NAMES',
+        'name:XX,entrance:XX,name,brand,test_tag,official_name:XX,short_name:XX,alt_name:XX'
+    )
+    loc = Locales(['en', 'es'])
+
+    expected_tags = [
+        'name:en', '_place_name:en', 'name:es', '_place_name:es', 'entrance:en',
+        '_place_entrance:en', 'entrance:es', '_place_entrance:es', 'name', '_place_name',
+        'brand', '_place_brand', 'test_tag', '_place_test_tag', 'official_name:en',
+        '_place_official_name:en', 'official_name:es', '_place_official_name:es',
+        'short_name:en', '_place_short_name:en', 'short_name:es', '_place_short_name:es',
+        'alt_name:en', '_place_alt_name:en', 'alt_name:es', '_place_alt_name:es'
+    ]
+
+    assert loc.name_tags == expected_tags, f'Expected {expected_tags}, but got {loc.name_tags}'
+
+
+def test_output_names_localized_and_custom_output_names_start_with_tag_that_has_no_XX(monkeypatch):
+    monkeypatch.setenv(
+        'NOMINATIM_OUTPUT_NAMES',
+        'name,brand,test_tag,official_name:XX,short_name:XX,alt_name:XX'
+    )
+    loc = Locales(['en', 'es'])
+
+    expected_tags = [
+        'name', '_place_name', 'brand', '_place_brand', 'test_tag', '_place_test_tag',
+        'official_name:en', '_place_official_name:en', 'official_name:es',
+        '_place_official_name:es', 'short_name:en', '_place_short_name:en', 'short_name:es',
+        '_place_short_name:es', 'alt_name:en', '_place_alt_name:en', 'alt_name:es',
+        '_place_alt_name:es'
+    ]
+
+    assert loc.name_tags == expected_tags, f'Expected {expected_tags}, but got {loc.name_tags}'
+
+
+def test_output_names_localized_and_custom_output_names_no_named_tags(monkeypatch):
+    monkeypatch.setenv(
+        'NOMINATIM_OUTPUT_NAMES',
+        'name,brand,test_tag'
+    )
+    loc = Locales(['en', 'es'])
+
+    expected_tags = [
+        'name', '_place_name', 'brand', '_place_brand', 'test_tag', '_place_test_tag'
+    ]
+
+    assert loc.name_tags == expected_tags, f'Expected {expected_tags}, but got {loc.name_tags}'
+
+
+def test_output_names_localized_and_custom_output_names_only_named_tags(monkeypatch):
+    monkeypatch.setenv(
+        'NOMINATIM_OUTPUT_NAMES',
+        'name:XX,entrance:XX,official_name:XX,short_name:XX,alt_name:XX'
+    )
+    loc = Locales(['en', 'es'])
+
+    expected_tags = [
+        'name:en', '_place_name:en', 'name:es', '_place_name:es', 'entrance:en',
+        '_place_entrance:en', 'entrance:es', '_place_entrance:es', 'official_name:en',
+        '_place_official_name:en', 'official_name:es', '_place_official_name:es',
+        'short_name:en', '_place_short_name:en', 'short_name:es', '_place_short_name:es',
+        'alt_name:en', '_place_alt_name:en', 'alt_name:es', '_place_alt_name:es'
+    ]
+
+    assert loc.name_tags == expected_tags, f'Expected {expected_tags}, but got {loc.name_tags}'
+
+
+def test_output_names_localized_and_custom_output_names_more_than_two_changes(monkeypatch):
+    monkeypatch.setenv(
+        'NOMINATIM_OUTPUT_NAMES',
+        'name:XX,brand,test_tag:XX,official_name,short_name:XX,'
+        'alt_name,another_tag_with:XX,another_tag_withoutXX'
+    )
+    loc = Locales(['en', 'es'])
+
+    expected_tags = [
+        'name:en', '_place_name:en', 'name:es', '_place_name:es', 'brand', '_place_brand',
+        'test_tag:en', '_place_test_tag:en', 'test_tag:es', '_place_test_tag:es', 'official_name',
+        '_place_official_name', 'short_name:en', '_place_short_name:en', 'short_name:es',
+        '_place_short_name:es', 'alt_name', '_place_alt_name', 'another_tag_with:en',
+        '_place_another_tag_with:en', 'another_tag_with:es', '_place_another_tag_with:es',
+        'another_tag_withoutXX', '_place_another_tag_withoutXX'
+    ]
+
+    assert loc.name_tags == expected_tags, f'Expected {expected_tags}, but got {loc.name_tags}'
+
+
+def test_output_names_localized_and_custom_output_names_XX_in_the_middle(monkeypatch):
+    monkeypatch.setenv(
+        'NOMINATIM_OUTPUT_NAMES',
+        'name:XX,br:XXand,test_tag:XX,official_name,sh:XXort_name:XX,'
+        'alt_name,another_tag_with:XX,another_tag_withoutXX'
+    )
+    loc = Locales(['en', 'es'])
+
+    expected_tags = [
+        'name:en', '_place_name:en', 'name:es', '_place_name:es', 'br:XXand', '_place_br:XXand',
+        'test_tag:en', '_place_test_tag:en', 'test_tag:es', '_place_test_tag:es', 'official_name',
+        '_place_official_name', 'sh:XXort_name:en', '_place_sh:XXort_name:en', 'sh:XXort_name:es',
+        '_place_sh:XXort_name:es', 'alt_name', '_place_alt_name', 'another_tag_with:en',
+        '_place_another_tag_with:en', 'another_tag_with:es', '_place_another_tag_with:es',
+        'another_tag_withoutXX', '_place_another_tag_withoutXX'
+    ]
+
+    assert loc.name_tags == expected_tags, f'Expected {expected_tags}, but got {loc.name_tags}'
+
+
 def test_display_name_preference():
     loc = Locales(['en', 'de'])