]> git.openstreetmap.org Git - nominatim.git/commitdiff
make localisation of results explicit
authorSarah Hoffmann <lonvia@denofr.de>
Wed, 24 May 2023 16:12:34 +0000 (18:12 +0200)
committerSarah Hoffmann <lonvia@denofr.de>
Wed, 24 May 2023 16:12:34 +0000 (18:12 +0200)
Localisation was previously done as part of the formatting but might
also be useful on its own when working with the results directly.

nominatim/api/results.py
nominatim/api/v1/format.py
nominatim/api/v1/format_json.py
nominatim/api/v1/format_xml.py
nominatim/api/v1/server_glue.py
nominatim/clicmd/api.py
test/python/api/test_result_formatting_v1.py
test/python/api/test_result_formatting_v1_reverse.py

index 5981cb3ecc8f753a55a3186abbe9617ed8d1eabb..1e77d0be5aba5b6b6c9ba7118380956adba90d7d 100644 (file)
@@ -103,6 +103,9 @@ class BaseResult:
     place_id : Optional[int] = None
     osm_object: Optional[Tuple[str, int]] = None
 
+    locale_name: Optional[str] = None
+    display_name: Optional[str] = None
+
     names: Optional[Dict[str, str]] = None
     address: Optional[Dict[str, str]] = None
     extratags: Optional[Dict[str, str]] = None
@@ -147,6 +150,18 @@ class BaseResult:
         return self.importance or (0.7500001 - (self.rank_search/40.0))
 
 
+    def localize(self, locales: Locales) -> None:
+        """ Fill the locale_name and the display_name field for the
+            place and, if available, its address information.
+        """
+        self.locale_name = locales.display_name(self.names)
+        if self.address_rows:
+            self.display_name = ', '.join(self.address_rows.localize(locales))
+        else:
+            self.display_name = self.locale_name
+
+
+
 BaseResultT = TypeVar('BaseResultT', bound=BaseResult)
 
 @dataclasses.dataclass
index 2e1caa991a91e0ca09eff6c458ed42b5ae8ff99b..7492e48d5c053ed24aaf67e4592c19b06281886c 100644 (file)
@@ -111,16 +111,16 @@ def _format_details_json(result: napi.DetailedResult, options: Mapping[str, Any]
     out.keyval('category', result.category[0])\
          .keyval('type', result.category[1])\
          .keyval('admin_level', result.admin_level)\
-         .keyval('localname', locales.display_name(result.names))\
-         .keyval_not_none('names', result.names or None)\
-         .keyval_not_none('addresstags', result.address or None)\
+         .keyval('localname', result.locale_name or '')\
+         .keyval('names', result.names or {})\
+         .keyval('addresstags', result.address or {})\
          .keyval_not_none('housenumber', result.housenumber)\
          .keyval_not_none('calculated_postcode', result.postcode)\
          .keyval_not_none('country_code', result.country_code)\
          .keyval_not_none('indexed_date', result.indexed_date, lambda v: v.isoformat())\
          .keyval_not_none('importance', result.importance)\
          .keyval('calculated_importance', result.calculated_importance())\
-         .keyval_not_none('extratags', result.extratags or None)\
+         .keyval('extratags', result.extratags or {})\
          .keyval_not_none('calculated_wikipedia', result.wikipedia)\
          .keyval('rank_address', result.rank_address)\
          .keyval('rank_search', result.rank_search)\
index c82681e91f7078fc7446644fa800a08a97c28637..0907c95526b25d4d9b47238dad008d2ad1bc54fa 100644 (file)
@@ -68,8 +68,6 @@ def format_base_json(results: Union[napi.ReverseResults, napi.SearchResults],
                      class_label: str) -> str:
     """ Return the result list as a simple json string in custom Nominatim format.
     """
-    locales = options.get('locales', napi.Locales())
-
     out = JsonWriter()
 
     if simple:
@@ -79,8 +77,6 @@ def format_base_json(results: Union[napi.ReverseResults, napi.SearchResults],
         out.start_array()
 
     for result in results:
-        label_parts = result.address_rows.localize(locales) if result.address_rows else []
-
         out.start_object()\
              .keyval_not_none('place_id', result.place_id)\
              .keyval('licence', cl.OSM_ATTRIBUTION)\
@@ -96,8 +92,8 @@ def format_base_json(results: Union[napi.ReverseResults, napi.SearchResults],
              .keyval('addresstype', cl.get_label_tag(result.category, result.extratags,
                                                      result.rank_address,
                                                      result.country_code))\
-             .keyval('name', locales.display_name(result.names))\
-             .keyval('display_name', ', '.join(label_parts))
+             .keyval('name', result.locale_name or '')\
+             .keyval('display_name', result.display_name or '')
 
 
         if options.get('icon_base_url', None):
@@ -151,8 +147,6 @@ def format_base_geojson(results: Union[napi.ReverseResults, napi.SearchResults],
     if not results and simple:
         return '{"error":"Unable to geocode"}'
 
-    locales = options.get('locales', napi.Locales())
-
     out = JsonWriter()
 
     out.start_object()\
@@ -161,11 +155,6 @@ def format_base_geojson(results: Union[napi.ReverseResults, napi.SearchResults],
          .key('features').start_array()
 
     for result in results:
-        if result.address_rows:
-            label_parts = result.address_rows.localize(locales)
-        else:
-            label_parts = []
-
         out.start_object()\
              .keyval('type', 'Feature')\
              .key('properties').start_object()
@@ -181,8 +170,8 @@ def format_base_geojson(results: Union[napi.ReverseResults, napi.SearchResults],
            .keyval('addresstype', cl.get_label_tag(result.category, result.extratags,
                                                    result.rank_address,
                                                    result.country_code))\
-           .keyval('name', locales.display_name(result.names))\
-           .keyval('display_name', ', '.join(label_parts))
+           .keyval('name', result.locale_name or '')\
+           .keyval('display_name', result.display_name or '')
 
         if options.get('addressdetails', False):
             out.key('address').start_object()
@@ -219,8 +208,6 @@ def format_base_geocodejson(results: Union[napi.ReverseResults, napi.SearchResul
     if not results and simple:
         return '{"error":"Unable to geocode"}'
 
-    locales = options.get('locales', napi.Locales())
-
     out = JsonWriter()
 
     out.start_object()\
@@ -234,11 +221,6 @@ def format_base_geocodejson(results: Union[napi.ReverseResults, napi.SearchResul
          .key('features').start_array()
 
     for result in results:
-        if result.address_rows:
-            label_parts = result.address_rows.localize(locales)
-        else:
-            label_parts = []
-
         out.start_object()\
              .keyval('type', 'Feature')\
              .key('properties').start_object()\
@@ -252,8 +234,8 @@ def format_base_geocodejson(results: Union[napi.ReverseResults, napi.SearchResul
            .keyval('osm_value', result.category[1])\
            .keyval('type', GEOCODEJSON_RANKS[max(3, min(28, result.rank_address))])\
            .keyval_not_none('accuracy', getattr(result, 'distance', None), transform=int)\
-           .keyval('label', ', '.join(label_parts))\
-           .keyval_not_none('name', result.names, transform=locales.display_name)\
+           .keyval('label', result.display_name or '')\
+           .keyval_not_none('name', result.locale_name or None)\
 
         if options.get('addressdetails', False):
             _write_geocodejson_address(out, result.address_rows, result.place_id,
index 1fd0675a36d04f88026ebebdc6730a720605a37d..c6ea17c01f9a4559bfb7ca1529a4212059daac65 100644 (file)
@@ -37,13 +37,7 @@ def _write_xml_address(root: ET.Element, address: napi.AddressLines,
 
 
 def _create_base_entry(result: Union[napi.ReverseResult, napi.SearchResult],
-                       root: ET.Element, simple: bool,
-                       locales: napi.Locales) -> ET.Element:
-    if result.address_rows:
-        label_parts = result.address_rows.localize(locales)
-    else:
-        label_parts = []
-
+                       root: ET.Element, simple: bool) -> ET.Element:
     place = ET.SubElement(root, 'result' if simple else 'place')
     if result.place_id is not None:
         place.set('place_id', str(result.place_id))
@@ -54,9 +48,9 @@ def _create_base_entry(result: Union[napi.ReverseResult, napi.SearchResult],
         place.set('osm_id', str(result.osm_object[1]))
     if result.names and 'ref' in result.names:
         place.set('ref', result.names['ref'])
-    elif label_parts:
+    elif result.locale_name:
         # bug reproduced from PHP
-        place.set('ref', label_parts[0])
+        place.set('ref', result.locale_name)
     place.set('lat', f"{result.centroid.lat:.7f}")
     place.set('lon', f"{result.centroid.lon:.7f}")
 
@@ -78,9 +72,9 @@ def _create_base_entry(result: Union[napi.ReverseResult, napi.SearchResult],
             place.set('geojson', result.geometry['geojson'])
 
     if simple:
-        place.text = ', '.join(label_parts)
+        place.text = result.display_name or ''
     else:
-        place.set('display_name', ', '.join(label_parts))
+        place.set('display_name', result.display_name or '')
         place.set('class', result.category[0])
         place.set('type', result.category[1])
         place.set('importance', str(result.calculated_importance()))
@@ -95,8 +89,6 @@ def format_base_xml(results: Union[napi.ReverseResults, napi.SearchResults],
     """ Format the result into an XML response. With 'simple' exactly one
         result will be output, otherwise a list.
     """
-    locales = options.get('locales', napi.Locales())
-
     root = ET.Element(xml_root_tag)
     root.set('timestamp', dt.datetime.utcnow().strftime('%a, %d %b %Y %H:%M:%S +00:00'))
     root.set('attribution', cl.OSM_ATTRIBUTION)
@@ -107,7 +99,7 @@ def format_base_xml(results: Union[napi.ReverseResults, napi.SearchResults],
         ET.SubElement(root, 'error').text = 'Unable to geocode'
 
     for result in results:
-        place = _create_base_entry(result, root, simple, locales)
+        place = _create_base_entry(result, root, simple)
 
         if not simple and options.get('icon_base_url', None):
             icon = cl.ICONS.get(result.category)
index ccf8f7d104b33b7672d2e9241ddc563c3f003401..1dcfdbc6e02fd4af68d3ba3cf11535e6a484ae3b 100644 (file)
@@ -305,6 +305,8 @@ async def details_endpoint(api: napi.NominatimAPIAsync, params: ASGIAdaptor) ->
     if result is None:
         params.raise_error('No place with that OSM ID found.', status=404)
 
+    result.localize(locales)
+
     output = formatting.format_result(result, fmt,
                  {'locales': locales,
                   'group_hierarchy': params.get_bool('group_hierarchy', False),
@@ -330,11 +332,13 @@ async def reverse_endpoint(api: napi.NominatimAPIAsync, params: ASGIAdaptor) ->
     if debug:
         return params.build_response(loglib.get_and_disable())
 
-    fmt_options = {'locales': locales,
-                   'extratags': params.get_bool('extratags', False),
+    fmt_options = {'extratags': params.get_bool('extratags', False),
                    'namedetails': params.get_bool('namedetails', False),
                    'addressdetails': params.get_bool('addressdetails', True)}
 
+    if result:
+        result.localize(locales)
+
     output = formatting.format_result(napi.ReverseResults([result] if result else []),
                                       fmt, fmt_options)
 
@@ -363,11 +367,13 @@ async def lookup_endpoint(api: napi.NominatimAPIAsync, params: ASGIAdaptor) -> A
     if debug:
         return params.build_response(loglib.get_and_disable())
 
-    fmt_options = {'locales': locales,
-                   'extratags': params.get_bool('extratags', False),
+    fmt_options = {'extratags': params.get_bool('extratags', False),
                    'namedetails': params.get_bool('namedetails', False),
                    'addressdetails': params.get_bool('addressdetails', True)}
 
+    for result in results:
+        result.localize(locales)
+
     output = formatting.format_result(results, fmt, fmt_options)
 
     return params.build_response(output)
index fef6bdf6ae1d9a3075e0997d5062778081d423c0..02725255d1d290f802e2032030adf906b3f44b92 100644 (file)
@@ -179,11 +179,11 @@ class APIReverse:
             return 0
 
         if result:
+            result.localize(args.get_locales(api.config.DEFAULT_LANGUAGE))
             output = api_output.format_result(
                         napi.ReverseResults([result]),
                         args.format,
-                        {'locales': args.get_locales(api.config.DEFAULT_LANGUAGE),
-                         'extratags': args.extratags,
+                        {'extratags': args.extratags,
                          'namedetails': args.namedetails,
                          'addressdetails': args.addressdetails})
             if args.format != 'xml':
@@ -236,11 +236,13 @@ class APILookup:
                              geometry_output=args.get_geometry_output(),
                              geometry_simplification=args.polygon_threshold or 0.0)
 
+        for result in results:
+            result.localize(args.get_locales(api.config.DEFAULT_LANGUAGE))
+
         output = api_output.format_result(
                     results,
                     args.format,
-                    {'locales': args.get_locales(api.config.DEFAULT_LANGUAGE),
-                     'extratags': args.extratags,
+                    {'extratags': args.extratags,
                      'namedetails': args.namedetails,
                      'addressdetails': args.addressdetails})
         if args.format != 'xml':
@@ -320,10 +322,13 @@ class APIDetails:
 
 
         if result:
+            locales = args.get_locales(api.config.DEFAULT_LANGUAGE)
+            result.localize(locales)
+
             output = api_output.format_result(
                         result,
                         'json',
-                        {'locales': args.get_locales(api.config.DEFAULT_LANGUAGE),
+                        {'locales': locales,
                          'group_hierarchy': args.group_hierarchy})
             # reformat the result, so it is pretty-printed
             json.dump(json.loads(output), sys.stdout, indent=4, ensure_ascii=False)
index e0fcc02578612d02ff8e547c73e7ddef890161ad..0c54667ead80b86180ff8e1d654fec0ae17bc42e 100644 (file)
@@ -75,11 +75,14 @@ def test_search_details_minimal():
            {'category': 'place',
             'type': 'thing',
             'admin_level': 15,
+            'names': {},
             'localname': '',
             'calculated_importance': pytest.approx(0.0000001),
             'rank_address': 30,
             'rank_search': 30,
             'isarea': False,
+            'addresstags': {},
+            'extratags': {},
             'centroid': {'type': 'Point', 'coordinates': [1.0, 2.0]},
             'geometry': {'type': 'Point', 'coordinates': [1.0, 2.0]},
            }
@@ -108,6 +111,7 @@ def test_search_details_full():
                   country_code='ll',
                   indexed_date = import_date
                   )
+    search.localize(napi.Locales())
 
     result = api_impl.format_result(search, 'json', {})
 
index 6e94cf10b0a17f9f297d0d738eb1f2a1c1aad7ae..d9d43953c3ed13607a05b30cac8254e53bc8586f 100644 (file)
@@ -101,6 +101,7 @@ def test_format_reverse_with_address(fmt):
                                                     rank_address=10,
                                                     distance=0.0)
                                  ]))
+    reverse.localize(napi.Locales())
 
     raw = api_impl.format_result(napi.ReverseResults([reverse]), fmt,
                                  {'addressdetails': True})
@@ -164,6 +165,8 @@ def test_format_reverse_geocodejson_special_parts():
                                                     distance=0.0)
                                  ]))
 
+    reverse.localize(napi.Locales())
+
     raw = api_impl.format_result(napi.ReverseResults([reverse]), 'geocodejson',
                                  {'addressdetails': True})