1 # SPDX-License-Identifier: GPL-3.0-or-later
3 # This file is part of Nominatim. (https://nominatim.org)
5 # Copyright (C) 2025 by the Nominatim developer community.
6 # For a full list of authors see the git log.
8 Tests for details API call.
14 import nominatim_api as napi
17 @pytest.mark.parametrize('idobj', (napi.PlaceID(332), napi.OsmID('W', 4),
18 napi.OsmID('W', 4, 'highway')))
19 def test_lookup_in_placex(apiobj, frontend, idobj):
20 import_date = dt.datetime(2022, 12, 7, 14, 14, 46, 0)
21 apiobj.add_placex(place_id=332, osm_type='W', osm_id=4,
22 class_='highway', type='residential',
23 name={'name': 'Road'}, address={'city': 'Barrow'},
24 extratags={'surface': 'paved'},
25 parent_place_id=34, linked_place_id=55,
26 admin_level=15, country_code='gb',
28 postcode='34425', wikipedia='en:Faa',
29 rank_search=27, rank_address=26,
32 indexed_date=import_date,
33 geometry='LINESTRING(23 34, 23.1 34, 23.1 34.1, 23 34)')
35 api = frontend(apiobj, options={'details'})
36 result = api.details(idobj)
37 napi.Locales().localize_results([result])
39 assert result is not None
41 assert result.source_table.name == 'PLACEX'
42 assert result.category == ('highway', 'residential')
43 assert result.centroid == (pytest.approx(23.0), pytest.approx(34.0))
45 assert result.place_id == 332
46 assert result.parent_place_id == 34
47 assert result.linked_place_id == 55
48 assert result.osm_object == ('W', 4)
49 assert result.admin_level == 15
51 assert result.names == {'name': 'Road'}
52 assert result.address == {'city': 'Barrow'}
53 assert result.extratags == {'surface': 'paved'}
55 assert result.housenumber == '4'
56 assert result.postcode == '34425'
57 assert result.wikipedia == 'en:Faa'
59 assert result.rank_search == 27
60 assert result.rank_address == 26
61 assert result.importance == pytest.approx(0.01)
63 assert result.country_code == 'gb'
64 assert result.indexed_date == import_date.replace(tzinfo=dt.timezone.utc)
66 assert result.address_rows is None
67 assert result.linked_rows is None
68 assert result.parented_rows is None
69 assert result.name_keywords is None
70 assert result.address_keywords is None
72 assert result.geometry == {'type': 'ST_LineString'}
75 def test_lookup_in_placex_minimal_info(apiobj, frontend):
76 import_date = dt.datetime(2022, 12, 7, 14, 14, 46, 0)
77 apiobj.add_placex(place_id=332, osm_type='W', osm_id=4,
78 class_='highway', type='residential',
80 rank_search=27, rank_address=26,
82 indexed_date=import_date,
83 geometry='LINESTRING(23 34, 23.1 34, 23.1 34.1, 23 34)')
85 api = frontend(apiobj, options={'details'})
86 result = api.details(napi.PlaceID(332))
87 napi.Locales().localize_results([result])
89 assert result is not None
91 assert result.source_table.name == 'PLACEX'
92 assert result.category == ('highway', 'residential')
93 assert result.centroid == (pytest.approx(23.0), pytest.approx(34.0))
95 assert result.place_id == 332
96 assert result.parent_place_id is None
97 assert result.linked_place_id is None
98 assert result.osm_object == ('W', 4)
99 assert result.admin_level == 15
101 assert result.names is None
102 assert result.address is None
103 assert result.extratags is None
105 assert result.housenumber is None
106 assert result.postcode is None
107 assert result.wikipedia is None
109 assert result.rank_search == 27
110 assert result.rank_address == 26
111 assert result.importance is None
113 assert result.country_code is None
114 assert result.indexed_date == import_date.replace(tzinfo=dt.timezone.utc)
116 assert result.address_rows is None
117 assert result.linked_rows is None
118 assert result.parented_rows is None
119 assert result.name_keywords is None
120 assert result.address_keywords is None
122 assert result.geometry == {'type': 'ST_LineString'}
125 def test_lookup_in_placex_with_geometry(apiobj, frontend):
126 apiobj.add_placex(place_id=332,
127 geometry='LINESTRING(23 34, 23.1 34)')
129 api = frontend(apiobj, options={'details'})
130 result = api.details(napi.PlaceID(332), geometry_output=napi.GeometryFormat.GEOJSON)
132 assert result.geometry == {'geojson': '{"type":"LineString","coordinates":[[23,34],[23.1,34]]}'}
135 def test_lookup_placex_with_address_details(apiobj, frontend):
136 apiobj.add_placex(place_id=332, osm_type='W', osm_id=4,
137 class_='highway', type='residential', name='Street',
139 rank_search=27, rank_address=26)
140 apiobj.add_address_placex(332, fromarea=False, isaddress=False,
142 place_id=1000, osm_type='N', osm_id=3333,
143 class_='place', type='suburb', name='Smallplace',
144 country_code='pl', admin_level=13,
145 rank_search=24, rank_address=23)
146 apiobj.add_address_placex(332, fromarea=True, isaddress=True,
147 place_id=1001, osm_type='N', osm_id=3334,
148 class_='place', type='city', name='Bigplace',
150 rank_search=17, rank_address=16)
152 api = frontend(apiobj, options={'details'})
153 result = api.details(napi.PlaceID(332), address_details=True)
154 napi.Locales().localize_results([result])
156 assert result.address_rows == [
157 napi.AddressLine(place_id=332, osm_object=('W', 4),
158 category=('highway', 'residential'),
159 names={'name': 'Street'}, extratags={},
160 admin_level=15, fromarea=True, isaddress=True,
161 rank_address=26, distance=0.0,
162 local_name='Street'),
163 napi.AddressLine(place_id=1000, osm_object=('N', 3333),
164 category=('place', 'suburb'),
165 names={'name': 'Smallplace'}, extratags={},
166 admin_level=13, fromarea=False, isaddress=True,
167 rank_address=23, distance=0.0034,
168 local_name='Smallplace'),
169 napi.AddressLine(place_id=1001, osm_object=('N', 3334),
170 category=('place', 'city'),
171 names={'name': 'Bigplace'}, extratags={},
172 admin_level=15, fromarea=True, isaddress=True,
173 rank_address=16, distance=0.0,
174 local_name='Bigplace'),
175 napi.AddressLine(place_id=None, osm_object=None,
176 category=('place', 'country_code'),
177 names={'ref': 'pl'}, extratags={},
178 admin_level=None, fromarea=True, isaddress=False,
179 rank_address=4, distance=0.0)
183 def test_lookup_place_with_linked_places_none_existing(apiobj, frontend):
184 apiobj.add_placex(place_id=332, osm_type='W', osm_id=4,
185 class_='highway', type='residential', name='Street',
186 country_code='pl', linked_place_id=45,
187 rank_search=27, rank_address=26)
189 api = frontend(apiobj, options={'details'})
190 result = api.details(napi.PlaceID(332), linked_places=True)
192 assert result.linked_rows == []
195 def test_lookup_place_with_linked_places_existing(apiobj, frontend):
196 apiobj.add_placex(place_id=332, osm_type='W', osm_id=4,
197 class_='highway', type='residential', name='Street',
198 country_code='pl', linked_place_id=45,
199 rank_search=27, rank_address=26)
200 apiobj.add_placex(place_id=1001, osm_type='W', osm_id=5,
201 class_='highway', type='residential', name='Street',
202 country_code='pl', linked_place_id=332,
203 rank_search=27, rank_address=26)
204 apiobj.add_placex(place_id=1002, osm_type='W', osm_id=6,
205 class_='highway', type='residential', name='Street',
206 country_code='pl', linked_place_id=332,
207 rank_search=27, rank_address=26)
209 api = frontend(apiobj, options={'details'})
210 result = api.details(napi.PlaceID(332), linked_places=True)
212 assert result.linked_rows == [
213 napi.AddressLine(place_id=1001, osm_object=('W', 5),
214 category=('highway', 'residential'),
215 names={'name': 'Street'}, extratags={},
216 admin_level=15, fromarea=False, isaddress=True,
217 rank_address=26, distance=0.0),
218 napi.AddressLine(place_id=1002, osm_object=('W', 6),
219 category=('highway', 'residential'),
220 names={'name': 'Street'}, extratags={},
221 admin_level=15, fromarea=False, isaddress=True,
222 rank_address=26, distance=0.0),
226 def test_lookup_place_with_parented_places_not_existing(apiobj, frontend):
227 apiobj.add_placex(place_id=332, osm_type='W', osm_id=4,
228 class_='highway', type='residential', name='Street',
229 country_code='pl', parent_place_id=45,
230 rank_search=27, rank_address=26)
232 api = frontend(apiobj, options={'details'})
233 result = api.details(napi.PlaceID(332), parented_places=True)
235 assert result.parented_rows == []
238 def test_lookup_place_with_parented_places_existing(apiobj, frontend):
239 apiobj.add_placex(place_id=332, osm_type='W', osm_id=4,
240 class_='highway', type='residential', name='Street',
241 country_code='pl', parent_place_id=45,
242 rank_search=27, rank_address=26)
243 apiobj.add_placex(place_id=1001, osm_type='N', osm_id=5,
244 class_='place', type='house', housenumber='23',
245 country_code='pl', parent_place_id=332,
246 rank_search=30, rank_address=30)
247 apiobj.add_placex(place_id=1002, osm_type='W', osm_id=6,
248 class_='highway', type='residential', name='Street',
249 country_code='pl', parent_place_id=332,
250 rank_search=27, rank_address=26)
252 api = frontend(apiobj, options={'details'})
253 result = api.details(napi.PlaceID(332), parented_places=True)
255 assert result.parented_rows == [
256 napi.AddressLine(place_id=1001, osm_object=('N', 5),
257 category=('place', 'house'),
258 names={'housenumber': '23'}, extratags={},
259 admin_level=15, fromarea=False, isaddress=True,
260 rank_address=30, distance=0.0),
264 @pytest.mark.parametrize('idobj', (napi.PlaceID(4924), napi.OsmID('W', 9928)))
265 def test_lookup_in_osmline(apiobj, frontend, idobj):
266 import_date = dt.datetime(2022, 12, 7, 14, 14, 46, 0)
267 apiobj.add_osmline(place_id=4924, osm_id=9928,
269 startnumber=1, endnumber=4, step=1,
270 country_code='gb', postcode='34425',
271 address={'city': 'Big'},
272 indexed_date=import_date,
273 geometry='LINESTRING(23 34, 23 35)')
275 api = frontend(apiobj, options={'details'})
276 result = api.details(idobj)
278 assert result is not None
280 assert result.source_table.name == 'OSMLINE'
281 assert result.category == ('place', 'houses')
282 assert result.centroid == (pytest.approx(23.0), pytest.approx(34.5))
284 assert result.place_id == 4924
285 assert result.parent_place_id == 12
286 assert result.linked_place_id is None
287 assert result.osm_object == ('W', 9928)
288 assert result.admin_level == 15
290 assert result.names is None
291 assert result.address == {'city': 'Big'}
292 assert result.extratags == {'startnumber': '1', 'endnumber': '4', 'step': '1'}
294 assert result.housenumber is None
295 assert result.postcode == '34425'
296 assert result.wikipedia is None
298 assert result.rank_search == 30
299 assert result.rank_address == 30
300 assert result.importance is None
302 assert result.country_code == 'gb'
303 assert result.indexed_date == import_date.replace(tzinfo=dt.timezone.utc)
305 assert result.address_rows is None
306 assert result.linked_rows is None
307 assert result.parented_rows is None
308 assert result.name_keywords is None
309 assert result.address_keywords is None
311 assert result.geometry == {'type': 'ST_LineString'}
314 def test_lookup_in_osmline_split_interpolation(apiobj, frontend):
315 apiobj.add_osmline(place_id=1000, osm_id=9,
316 startnumber=2, endnumber=4, step=1)
317 apiobj.add_osmline(place_id=1001, osm_id=9,
318 startnumber=6, endnumber=9, step=1)
319 apiobj.add_osmline(place_id=1002, osm_id=9,
320 startnumber=11, endnumber=20, step=1)
322 api = frontend(apiobj, options={'details'})
323 for i in range(1, 6):
324 result = api.details(napi.OsmID('W', 9, str(i)))
325 assert result.place_id == 1000
326 for i in range(7, 11):
327 result = api.details(napi.OsmID('W', 9, str(i)))
328 assert result.place_id == 1001
329 for i in range(12, 22):
330 result = api.details(napi.OsmID('W', 9, str(i)))
331 assert result.place_id == 1002
334 def test_lookup_osmline_with_address_details(apiobj, frontend):
335 apiobj.add_osmline(place_id=9000, osm_id=9,
336 startnumber=2, endnumber=4, step=1,
338 apiobj.add_placex(place_id=332, osm_type='W', osm_id=4,
339 class_='highway', type='residential', name='Street',
341 rank_search=27, rank_address=26)
342 apiobj.add_address_placex(332, fromarea=False, isaddress=False,
344 place_id=1000, osm_type='N', osm_id=3333,
345 class_='place', type='suburb', name='Smallplace',
346 country_code='pl', admin_level=13,
347 rank_search=24, rank_address=23)
348 apiobj.add_address_placex(332, fromarea=True, isaddress=True,
349 place_id=1001, osm_type='N', osm_id=3334,
350 class_='place', type='city', name='Bigplace',
352 rank_search=17, rank_address=16)
354 api = frontend(apiobj, options={'details'})
355 result = api.details(napi.PlaceID(9000), address_details=True)
356 napi.Locales().localize_results([result])
358 assert result.address_rows == [
359 napi.AddressLine(place_id=332, osm_object=('W', 4),
360 category=('highway', 'residential'),
361 names={'name': 'Street'}, extratags={},
362 admin_level=15, fromarea=True, isaddress=True,
363 rank_address=26, distance=0.0,
364 local_name='Street'),
365 napi.AddressLine(place_id=1000, osm_object=('N', 3333),
366 category=('place', 'suburb'),
367 names={'name': 'Smallplace'}, extratags={},
368 admin_level=13, fromarea=False, isaddress=True,
369 rank_address=23, distance=0.0034,
370 local_name='Smallplace'),
371 napi.AddressLine(place_id=1001, osm_object=('N', 3334),
372 category=('place', 'city'),
373 names={'name': 'Bigplace'}, extratags={},
374 admin_level=15, fromarea=True, isaddress=True,
375 rank_address=16, distance=0.0,
376 local_name='Bigplace'),
377 napi.AddressLine(place_id=None, osm_object=None,
378 category=('place', 'country_code'),
379 names={'ref': 'pl'}, extratags={},
380 admin_level=None, fromarea=True, isaddress=False,
381 rank_address=4, distance=0.0)
385 def test_lookup_in_tiger(apiobj, frontend):
386 apiobj.add_tiger(place_id=4924,
388 startnumber=1, endnumber=4, step=1,
390 geometry='LINESTRING(23 34, 23 35)')
391 apiobj.add_placex(place_id=12,
392 category=('highway', 'residential'),
393 osm_type='W', osm_id=6601223,
394 geometry='LINESTRING(23 34, 23 35)')
396 api = frontend(apiobj, options={'details'})
397 result = api.details(napi.PlaceID(4924))
399 assert result is not None
401 assert result.source_table.name == 'TIGER'
402 assert result.category == ('place', 'houses')
403 assert result.centroid == (pytest.approx(23.0), pytest.approx(34.5))
405 assert result.place_id == 4924
406 assert result.parent_place_id == 12
407 assert result.linked_place_id is None
408 assert result.osm_object == ('W', 6601223)
409 assert result.admin_level == 15
411 assert result.names is None
412 assert result.address is None
413 assert result.extratags == {'startnumber': '1', 'endnumber': '4', 'step': '1'}
415 assert result.housenumber is None
416 assert result.postcode == '34425'
417 assert result.wikipedia is None
419 assert result.rank_search == 30
420 assert result.rank_address == 30
421 assert result.importance is None
423 assert result.country_code == 'us'
424 assert result.indexed_date is None
426 assert result.address_rows is None
427 assert result.linked_rows is None
428 assert result.parented_rows is None
429 assert result.name_keywords is None
430 assert result.address_keywords is None
432 assert result.geometry == {'type': 'ST_LineString'}
435 def test_lookup_tiger_with_address_details(apiobj, frontend):
436 apiobj.add_tiger(place_id=9000,
437 startnumber=2, endnumber=4, step=1,
439 apiobj.add_placex(place_id=332, osm_type='W', osm_id=4,
440 class_='highway', type='residential', name='Street',
442 rank_search=27, rank_address=26)
443 apiobj.add_address_placex(332, fromarea=False, isaddress=False,
445 place_id=1000, osm_type='N', osm_id=3333,
446 class_='place', type='suburb', name='Smallplace',
447 country_code='us', admin_level=13,
448 rank_search=24, rank_address=23)
449 apiobj.add_address_placex(332, fromarea=True, isaddress=True,
450 place_id=1001, osm_type='N', osm_id=3334,
451 class_='place', type='city', name='Bigplace',
453 rank_search=17, rank_address=16)
455 api = frontend(apiobj, options={'details'})
456 result = api.details(napi.PlaceID(9000), address_details=True)
457 napi.Locales().localize_results([result])
459 assert result.address_rows == [
460 napi.AddressLine(place_id=332, osm_object=('W', 4),
461 category=('highway', 'residential'),
462 names={'name': 'Street'}, extratags={},
463 admin_level=15, fromarea=True, isaddress=True,
464 rank_address=26, distance=0.0,
465 local_name='Street'),
466 napi.AddressLine(place_id=1000, osm_object=('N', 3333),
467 category=('place', 'suburb'),
468 names={'name': 'Smallplace'}, extratags={},
469 admin_level=13, fromarea=False, isaddress=True,
470 rank_address=23, distance=0.0034,
471 local_name='Smallplace'),
472 napi.AddressLine(place_id=1001, osm_object=('N', 3334),
473 category=('place', 'city'),
474 names={'name': 'Bigplace'}, extratags={},
475 admin_level=15, fromarea=True, isaddress=True,
476 rank_address=16, distance=0.0,
477 local_name='Bigplace'),
478 napi.AddressLine(place_id=None, osm_object=None,
479 category=('place', 'country_code'),
480 names={'ref': 'us'}, extratags={},
481 admin_level=None, fromarea=True, isaddress=False,
482 rank_address=4, distance=0.0)
486 def test_lookup_in_postcode(apiobj, frontend):
487 import_date = dt.datetime(2022, 12, 7, 14, 14, 46, 0)
488 apiobj.add_postcode(place_id=554,
493 indexed_date=import_date,
494 centroid='POINT(-9.45 5.6)',
495 geometry='POLYGON((-9.5 5.5, -9.5 5.7, -9.4 5.6, -9.5 5.5))')
497 api = frontend(apiobj, options={'details'})
498 result = api.details(napi.PlaceID(554))
500 assert result is not None
502 assert result.source_table.name == 'POSTCODE'
503 assert result.category == ('place', 'postcode')
504 assert result.centroid == (pytest.approx(-9.45), pytest.approx(5.6))
506 assert result.place_id == 554
507 assert result.parent_place_id == 152
508 assert result.linked_place_id is None
509 assert result.osm_object is None
510 assert result.admin_level == 15
512 assert result.names == {'ref': '34 425'}
513 assert result.address is None
514 assert result.extratags is None
516 assert result.housenumber is None
517 assert result.postcode is None
518 assert result.wikipedia is None
520 assert result.rank_search == 20
521 assert result.rank_address == 5
522 assert result.importance is None
524 assert result.country_code == 'gb'
525 assert result.indexed_date == import_date.replace(tzinfo=dt.timezone.utc)
527 assert result.address_rows is None
528 assert result.linked_rows is None
529 assert result.parented_rows is None
530 assert result.name_keywords is None
531 assert result.address_keywords is None
533 assert result.geometry == {'type': 'ST_Polygon'}
536 @pytest.mark.parametrize('lookup', [napi.PlaceID(9000),
537 napi.OsmID('R', 12)])
538 def test_lookup_postcode_with_address_details(apiobj, frontend, lookup):
539 apiobj.add_postcode(place_id=9000, osm_id=12,
544 apiobj.add_placex(place_id=332, osm_type='N', osm_id=3333,
545 class_='place', type='suburb', name='Smallplace',
546 country_code='gb', admin_level=13,
547 rank_search=24, rank_address=23)
548 apiobj.add_address_placex(332, fromarea=True, isaddress=True,
549 place_id=1001, osm_type='N', osm_id=3334,
550 class_='place', type='city', name='Bigplace',
552 rank_search=17, rank_address=16)
554 api = frontend(apiobj, options={'details'})
555 result = api.details(lookup, address_details=True)
556 napi.Locales().localize_results([result])
558 assert result.address_rows == [
559 napi.AddressLine(place_id=9000, osm_object=('R', 12),
560 category=('boundary', 'postal_code'),
561 names={'ref': '34 425'}, extratags={},
562 admin_level=15, fromarea=True, isaddress=True,
563 rank_address=5, distance=0.0,
564 local_name='34 425'),
565 napi.AddressLine(place_id=332, osm_object=('N', 3333),
566 category=('place', 'suburb'),
567 names={'name': 'Smallplace'}, extratags={},
568 admin_level=13, fromarea=True, isaddress=True,
569 rank_address=23, distance=0.0,
570 local_name='Smallplace'),
571 napi.AddressLine(place_id=1001, osm_object=('N', 3334),
572 category=('place', 'city'),
573 names={'name': 'Bigplace'}, extratags={},
574 admin_level=15, fromarea=True, isaddress=True,
575 rank_address=16, distance=0.0,
576 local_name='Bigplace'),
577 napi.AddressLine(place_id=None, osm_object=None,
578 category=('place', 'country_code'),
579 names={'ref': 'gb'}, extratags={},
580 admin_level=None, fromarea=True, isaddress=False,
581 rank_address=4, distance=0.0)
585 @pytest.mark.parametrize('objid', [napi.PlaceID(1736),
587 napi.OsmID('N', 55, 'amenity')])
588 def test_lookup_missing_object(apiobj, frontend, objid):
589 apiobj.add_placex(place_id=1, osm_type='N', osm_id=55,
590 class_='place', type='suburb')
592 api = frontend(apiobj, options={'details'})
593 assert api.details(objid) is None
596 @pytest.mark.parametrize('gtype', (napi.GeometryFormat.KML,
597 napi.GeometryFormat.SVG,
598 napi.GeometryFormat.TEXT))
599 def test_lookup_unsupported_geometry(apiobj, frontend, gtype):
600 apiobj.add_placex(place_id=332)
602 api = frontend(apiobj, options={'details'})
603 with pytest.raises(ValueError):
604 api.details(napi.PlaceID(332), geometry_output=gtype)