Skip to content

Commit 196de9e

Browse files
authored
Merge pull request #3796 from anqixxx/locale-refactor
Localize() + Results refactor
2 parents b7d77b9 + 6b627df commit 196de9e

10 files changed

Lines changed: 112 additions & 66 deletions

File tree

src/nominatim_api/localization.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
"""
1010
from typing import Mapping, List, Optional
1111
from .config import Configuration
12+
from .results import AddressLines, BaseResultT
1213

1314
import re
1415

@@ -96,3 +97,24 @@ def from_accept_languages(langstr: str) -> 'Locales':
9697
languages.append(parts[0])
9798

9899
return Locales(languages)
100+
101+
def localize(self, lines: AddressLines) -> None:
102+
""" Sets the local name of address parts according to the chosen
103+
locale.
104+
105+
Only address parts that are marked as isaddress are localized.
106+
107+
AddressLines should be modified in place.
108+
"""
109+
for line in lines:
110+
if line.isaddress and line.names:
111+
line.local_name = self.display_name(line.names)
112+
113+
def localize_results(self, results: List[BaseResultT]) -> None:
114+
""" Set the local name of results according to the chosen
115+
locale.
116+
"""
117+
for result in results:
118+
result.locale_name = self.display_name(result.names)
119+
if result.address_rows:
120+
self.localize(result.address_rows)

src/nominatim_api/results.py

Lines changed: 45 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,10 @@
1111
internal use only. That's why they are implemented as free-standing functions
1212
instead of member functions.
1313
"""
14-
from typing import Optional, Tuple, Dict, Sequence, TypeVar, Type, List, cast, Callable
14+
from typing import (
15+
Optional, Tuple, Dict, Sequence, TypeVar, Type, List,
16+
cast, Callable
17+
)
1518
import enum
1619
import dataclasses
1720
import datetime as dt
@@ -23,7 +26,6 @@
2326
from .types import Point, Bbox, LookupDetails
2427
from .connection import SearchConnection
2528
from .logging import log
26-
from .localization import Locales
2729

2830
# This file defines complex result data classes.
2931

@@ -130,27 +132,21 @@ class AddressLine:
130132
[Localization](Result-Handling.md#localization) below.
131133
"""
132134

133-
134-
class AddressLines(List[AddressLine]):
135-
""" Sequence of address lines order in descending order by their rank.
136-
"""
137-
138-
def localize(self, locales: Locales) -> List[str]:
139-
""" Set the local name of address parts according to the chosen
140-
locale. Return the list of local names without duplicates.
141-
142-
Only address parts that are marked as isaddress are localized
143-
and returned.
135+
@property
136+
def display_name(self) -> Optional[str]:
137+
""" Dynamically compute the display name for the Address Line component
144138
"""
145-
label_parts: List[str] = []
139+
if self.local_name:
140+
return self.local_name
141+
elif 'name' in self.names:
142+
return self.names['name']
143+
elif self.names:
144+
return next(iter(self.names.values()), None)
145+
return None
146146

147-
for line in self:
148-
if line.isaddress and line.names:
149-
line.local_name = locales.display_name(line.names)
150-
if not label_parts or label_parts[-1] != line.local_name:
151-
label_parts.append(line.local_name)
152147

153-
return label_parts
148+
class AddressLines(List[AddressLine]):
149+
""" A wrapper around a list of AddressLine objects."""
154150

155151

156152
@dataclasses.dataclass
@@ -189,7 +185,6 @@ class BaseResult:
189185
admin_level: int = 15
190186

191187
locale_name: Optional[str] = None
192-
display_name: Optional[str] = None
193188

194189
names: Optional[Dict[str, str]] = None
195190
address: Optional[Dict[str, str]] = None
@@ -225,23 +220,42 @@ def lon(self) -> float:
225220
"""
226221
return self.centroid[0]
227222

223+
@property
224+
def display_name(self) -> Optional[str]:
225+
""" Dynamically compute the display name for the result place
226+
and, if available, its address information..
227+
"""
228+
if self.address_rows: # if this is true we need additional processing
229+
label_parts: List[str] = []
230+
231+
for line in self.address_rows: # assume locale_name is set by external formatter
232+
if line.isaddress and line.names:
233+
address_name = line.display_name
234+
235+
if address_name and (not label_parts or label_parts[-1] != address_name):
236+
label_parts.append(address_name)
237+
238+
if label_parts:
239+
return ', '.join(label_parts)
240+
241+
# Now adding additional information for reranking
242+
if self.locale_name:
243+
return self.locale_name
244+
elif self.names and 'name' in self.names:
245+
return self.names['name']
246+
elif self.names:
247+
return next(iter(self.names.values()))
248+
elif self.housenumber:
249+
return self.housenumber
250+
return None
251+
228252
def calculated_importance(self) -> float:
229253
""" Get a valid importance value. This is either the stored importance
230254
of the value or an artificial value computed from the place's
231255
search rank.
232256
"""
233257
return self.importance or (0.40001 - (self.rank_search/75.0))
234258

235-
def localize(self, locales: Locales) -> None:
236-
""" Fill the locale_name and the display_name field for the
237-
place and, if available, its address information.
238-
"""
239-
self.locale_name = locales.display_name(self.names)
240-
if self.address_rows:
241-
self.display_name = ', '.join(self.address_rows.localize(locales))
242-
else:
243-
self.display_name = self.locale_name
244-
245259

246260
BaseResultT = TypeVar('BaseResultT', bound=BaseResult)
247261

@@ -456,8 +470,6 @@ async def add_result_details(conn: SearchConnection, results: List[BaseResultT],
456470
log().comment('Query keywords')
457471
for result in results:
458472
await complete_keywords(conn, result)
459-
for result in results:
460-
result.localize(details.locales)
461473

462474

463475
def _result_row_to_address_row(row: SaRow, isaddress: Optional[bool] = None) -> AddressLine:

src/nominatim_api/types.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
from binascii import unhexlify
1818

1919
from .errors import UsageError
20-
from .localization import Locales
2120

2221

2322
@dataclasses.dataclass
@@ -410,9 +409,6 @@ class LookupDetails:
410409
0.0 means the original geometry is kept. The higher the value, the
411410
more the geometry gets simplified.
412411
"""
413-
locales: Locales = Locales()
414-
""" Preferred languages for localization of results.
415-
"""
416412

417413
@classmethod
418414
def from_kwargs(cls: Type[TParam], kwargs: Dict[str, Any]) -> TParam:

src/nominatim_api/v1/server_glue.py

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -156,8 +156,6 @@ async def details_endpoint(api: NominatimAPIAsync, params: ASGIAdaptor) -> Any:
156156

157157
debug = setup_debugging(params)
158158

159-
locales = Locales.from_accept_languages(get_accepted_languages(params))
160-
161159
result = await api.details(place,
162160
address_details=params.get_bool('addressdetails', False),
163161
linked_places=params.get_bool('linkedplaces', True),
@@ -166,7 +164,6 @@ async def details_endpoint(api: NominatimAPIAsync, params: ASGIAdaptor) -> Any:
166164
geometry_output=(GeometryFormat.GEOJSON
167165
if params.get_bool('polygon_geojson', False)
168166
else GeometryFormat.NONE),
169-
locales=locales
170167
)
171168

172169
if debug:
@@ -175,6 +172,9 @@ async def details_endpoint(api: NominatimAPIAsync, params: ASGIAdaptor) -> Any:
175172
if result is None:
176173
params.raise_error('No place with that OSM ID found.', status=404)
177174

175+
locales = Locales.from_accept_languages(get_accepted_languages(params))
176+
locales.localize_results([result])
177+
178178
output = params.formatting().format_result(
179179
result, fmt,
180180
{'locales': locales,
@@ -194,7 +194,6 @@ async def reverse_endpoint(api: NominatimAPIAsync, params: ASGIAdaptor) -> Any:
194194
details = parse_geometry_details(params, fmt)
195195
details['max_rank'] = helpers.zoom_to_rank(params.get_int('zoom', 18))
196196
details['layers'] = get_layers(params)
197-
details['locales'] = Locales.from_accept_languages(get_accepted_languages(params))
198197

199198
result = await api.reverse(coord, **details)
200199

@@ -210,6 +209,10 @@ async def reverse_endpoint(api: NominatimAPIAsync, params: ASGIAdaptor) -> Any:
210209
else:
211210
query = ''
212211

212+
if result:
213+
Locales.from_accept_languages(get_accepted_languages(params)).localize_results(
214+
[result])
215+
213216
fmt_options = {'query': query,
214217
'extratags': params.get_bool('extratags', False),
215218
'namedetails': params.get_bool('namedetails', False),
@@ -227,7 +230,6 @@ async def lookup_endpoint(api: NominatimAPIAsync, params: ASGIAdaptor) -> Any:
227230
fmt = parse_format(params, SearchResults, 'xml')
228231
debug = setup_debugging(params)
229232
details = parse_geometry_details(params, fmt)
230-
details['locales'] = Locales.from_accept_languages(get_accepted_languages(params))
231233

232234
places = []
233235
for oid in (params.get('osm_ids') or '').split(','):
@@ -246,6 +248,8 @@ async def lookup_endpoint(api: NominatimAPIAsync, params: ASGIAdaptor) -> Any:
246248
if debug:
247249
return build_response(params, loglib.get_and_disable(), num_results=len(results))
248250

251+
Locales.from_accept_languages(get_accepted_languages(params)).localize_results(results)
252+
249253
fmt_options = {'extratags': params.get_bool('extratags', False),
250254
'namedetails': params.get_bool('namedetails', False),
251255
'addressdetails': params.get_bool('addressdetails', True)}
@@ -310,8 +314,6 @@ async def search_endpoint(api: NominatimAPIAsync, params: ASGIAdaptor) -> Any:
310314
else:
311315
details['layers'] = get_layers(params)
312316

313-
details['locales'] = Locales.from_accept_languages(get_accepted_languages(params))
314-
315317
# unstructured query parameters
316318
query = params.get('q', None)
317319
# structured query parameters
@@ -336,6 +338,8 @@ async def search_endpoint(api: NominatimAPIAsync, params: ASGIAdaptor) -> Any:
336338
except UsageError as err:
337339
params.raise_error(str(err))
338340

341+
Locales.from_accept_languages(get_accepted_languages(params)).localize_results(results)
342+
339343
if details['dedupe'] and len(results) > 1:
340344
results = helpers.deduplicate_results(results, max_results)
341345

src/nominatim_db/clicmd/api.py

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,6 @@ def run(self, args: NominatimArgs) -> int:
196196
'excluded': args.exclude_place_ids,
197197
'viewbox': args.viewbox,
198198
'bounded_viewbox': args.bounded,
199-
'locales': _get_locales(args, api.config.DEFAULT_LANGUAGE)
200199
}
201200

202201
if args.query:
@@ -213,6 +212,9 @@ def run(self, args: NominatimArgs) -> int:
213212
except napi.UsageError as ex:
214213
raise UsageError(ex) from ex
215214

215+
locales = _get_locales(args, api.config.DEFAULT_LANGUAGE)
216+
locales.localize_results(results)
217+
216218
if args.dedupe and len(results) > 1:
217219
results = deduplicate_results(results, args.limit)
218220

@@ -277,11 +279,14 @@ def run(self, args: NominatimArgs) -> int:
277279
layers=layers,
278280
address_details=True, # needed for display name
279281
geometry_output=_get_geometry_output(args),
280-
geometry_simplification=args.polygon_threshold,
281-
locales=_get_locales(args, api.config.DEFAULT_LANGUAGE))
282+
geometry_simplification=args.polygon_threshold)
282283
except napi.UsageError as ex:
283284
raise UsageError(ex) from ex
284285

286+
if result is not None:
287+
locales = _get_locales(args, api.config.DEFAULT_LANGUAGE)
288+
locales.localize_results([result])
289+
285290
if args.format == 'debug':
286291
print(loglib.get_and_disable())
287292
return 0
@@ -339,11 +344,13 @@ def run(self, args: NominatimArgs) -> int:
339344
results = api.lookup(places,
340345
address_details=True, # needed for display name
341346
geometry_output=_get_geometry_output(args),
342-
geometry_simplification=args.polygon_threshold or 0.0,
343-
locales=_get_locales(args, api.config.DEFAULT_LANGUAGE))
347+
geometry_simplification=args.polygon_threshold or 0.0)
344348
except napi.UsageError as ex:
345349
raise UsageError(ex) from ex
346350

351+
locales = _get_locales(args, api.config.DEFAULT_LANGUAGE)
352+
locales.localize_results(results)
353+
347354
if args.format == 'debug':
348355
print(loglib.get_and_disable())
349356
return 0
@@ -425,27 +432,28 @@ def run(self, args: NominatimArgs) -> int:
425432

426433
try:
427434
with napi.NominatimAPI(args.project_dir) as api:
428-
locales = _get_locales(args, api.config.DEFAULT_LANGUAGE)
429435
result = api.details(place,
430436
address_details=args.addressdetails,
431437
linked_places=args.linkedplaces,
432438
parented_places=args.hierarchy,
433439
keywords=args.keywords,
434440
geometry_output=(napi.GeometryFormat.GEOJSON
435441
if args.polygon_geojson
436-
else napi.GeometryFormat.NONE),
437-
locales=locales)
442+
else napi.GeometryFormat.NONE))
438443
except napi.UsageError as ex:
439444
raise UsageError(ex) from ex
440445

446+
if result is not None:
447+
locales = _get_locales(args, api.config.DEFAULT_LANGUAGE)
448+
locales.localize_results([result])
449+
441450
if args.format == 'debug':
442451
print(loglib.get_and_disable())
443452
return 0
444453

445454
if result:
446455
_print_output(formatter, result, args.format or 'json',
447-
{'locales': locales,
448-
'group_hierarchy': args.group_hierarchy})
456+
{'group_hierarchy': args.group_hierarchy})
449457
return 0
450458

451459
LOG.error("Object not found in database.")

src/nominatim_db/clicmd/export.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -151,9 +151,11 @@ async def dump_results(conn: napi.SearchConnection,
151151
results: List[ReverseResult],
152152
writer: 'csv.DictWriter[str]',
153153
lang: Optional[str]) -> None:
154-
locale = napi.Locales([lang] if lang else None)
155154
await add_result_details(conn, results,
156-
LookupDetails(address_details=True, locales=locale))
155+
LookupDetails(address_details=True))
156+
157+
locale = napi.Locales([lang] if lang else None)
158+
locale.localize_results(results)
157159

158160
for result in results:
159161
data = {'placeid': result.place_id,

0 commit comments

Comments
 (0)