From: Sarah Hoffmann Date: Wed, 24 May 2023 16:12:34 +0000 (+0200) Subject: make localisation of results explicit X-Git-Tag: v4.3.0~74^2~12 X-Git-Url: https://git.openstreetmap.org./nominatim.git/commitdiff_plain/f335e78d1eb54737202aa7a1bafaece19b6659a6 make localisation of results explicit Localisation was previously done as part of the formatting but might also be useful on its own when working with the results directly. --- diff --git a/nominatim/api/results.py b/nominatim/api/results.py index 5981cb3e..1e77d0be 100644 --- a/nominatim/api/results.py +++ b/nominatim/api/results.py @@ -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 diff --git a/nominatim/api/v1/format.py b/nominatim/api/v1/format.py index 2e1caa99..7492e48d 100644 --- a/nominatim/api/v1/format.py +++ b/nominatim/api/v1/format.py @@ -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)\ diff --git a/nominatim/api/v1/format_json.py b/nominatim/api/v1/format_json.py index c82681e9..0907c955 100644 --- a/nominatim/api/v1/format_json.py +++ b/nominatim/api/v1/format_json.py @@ -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, diff --git a/nominatim/api/v1/format_xml.py b/nominatim/api/v1/format_xml.py index 1fd0675a..c6ea17c0 100644 --- a/nominatim/api/v1/format_xml.py +++ b/nominatim/api/v1/format_xml.py @@ -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) diff --git a/nominatim/api/v1/server_glue.py b/nominatim/api/v1/server_glue.py index ccf8f7d1..1dcfdbc6 100644 --- a/nominatim/api/v1/server_glue.py +++ b/nominatim/api/v1/server_glue.py @@ -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) diff --git a/nominatim/clicmd/api.py b/nominatim/clicmd/api.py index fef6bdf6..02725255 100644 --- a/nominatim/clicmd/api.py +++ b/nominatim/clicmd/api.py @@ -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) diff --git a/test/python/api/test_result_formatting_v1.py b/test/python/api/test_result_formatting_v1.py index e0fcc025..0c54667e 100644 --- a/test/python/api/test_result_formatting_v1.py +++ b/test/python/api/test_result_formatting_v1.py @@ -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', {}) diff --git a/test/python/api/test_result_formatting_v1_reverse.py b/test/python/api/test_result_formatting_v1_reverse.py index 6e94cf10..d9d43953 100644 --- a/test/python/api/test_result_formatting_v1_reverse.py +++ b/test/python/api/test_result_formatting_v1_reverse.py @@ -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})