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,
84 geometry='LINESTRING(23 34, 23.1 34, 23.1 34.1, 23 34)')
86 api = frontend(apiobj, options={'details'})
87 result = api.details(napi.PlaceID(332))
88 napi.Locales().localize_results([result])
90 assert result is not None
92 assert result.source_table.name == 'PLACEX'
93 assert result.category == ('highway', 'residential')
94 assert result.centroid == (pytest.approx(23.0), pytest.approx(34.0))
96 assert result.place_id == 332
97 assert result.parent_place_id is None
98 assert result.linked_place_id is None
99 assert result.osm_object == ('W', 4)
100 assert result.admin_level == 15
102 assert result.names is None
103 assert result.address is None
104 assert result.extratags is None
106 assert result.housenumber is None
107 assert result.postcode is None
108 assert result.wikipedia is None
110 assert result.rank_search == 27
111 assert result.rank_address == 26
112 assert result.importance == pytest.approx(0.8)
114 assert result.country_code is None
115 assert result.indexed_date == import_date.replace(tzinfo=dt.timezone.utc)
117 assert result.address_rows is None
118 assert result.linked_rows is None
119 assert result.parented_rows is None
120 assert result.name_keywords is None
121 assert result.address_keywords is None
123 assert result.geometry == {'type': 'ST_LineString'}
126 def test_lookup_in_placex_with_geometry(apiobj, frontend):
127 apiobj.add_placex(place_id=332,
128 geometry='LINESTRING(23 34, 23.1 34)')
130 api = frontend(apiobj, options={'details'})
131 result = api.details(napi.PlaceID(332), geometry_output=napi.GeometryFormat.GEOJSON)
133 assert result.geometry == {'geojson': '{"type":"LineString","coordinates":[[23,34],[23.1,34]]}'}
136 def test_lookup_placex_with_address_details(apiobj, frontend):
137 apiobj.add_placex(place_id=332, osm_type='W', osm_id=4,
138 class_='highway', type='residential', name='Street',
140 rank_search=27, rank_address=26)
141 apiobj.add_address_placex(332, fromarea=False, isaddress=False,
143 place_id=1000, osm_type='N', osm_id=3333,
144 class_='place', type='suburb', name='Smallplace',
145 country_code='pl', admin_level=13,
146 rank_search=24, rank_address=23)
147 apiobj.add_address_placex(332, fromarea=True, isaddress=True,
148 place_id=1001, osm_type='N', osm_id=3334,
149 class_='place', type='city', name='Bigplace',
151 rank_search=17, rank_address=16)
153 api = frontend(apiobj, options={'details'})
154 result = api.details(napi.PlaceID(332), address_details=True)
155 napi.Locales().localize_results([result])
157 assert result.address_rows == [
158 napi.AddressLine(place_id=332, osm_object=('W', 4),
159 category=('highway', 'residential'),
160 names={'name': 'Street'}, extratags={},
161 admin_level=15, fromarea=True, isaddress=True,
162 rank_address=26, distance=0.0,
163 local_name='Street'),
164 napi.AddressLine(place_id=1000, osm_object=('N', 3333),
165 category=('place', 'suburb'),
166 names={'name': 'Smallplace'}, extratags={},
167 admin_level=13, fromarea=False, isaddress=True,
168 rank_address=23, distance=0.0034,
169 local_name='Smallplace'),
170 napi.AddressLine(place_id=1001, osm_object=('N', 3334),
171 category=('place', 'city'),
172 names={'name': 'Bigplace'}, extratags={},
173 admin_level=15, fromarea=True, isaddress=True,
174 rank_address=16, distance=0.0,
175 local_name='Bigplace'),
176 napi.AddressLine(place_id=None, osm_object=None,
177 category=('place', 'country_code'),
178 names={'ref': 'pl'}, extratags={},
179 admin_level=None, fromarea=True, isaddress=False,
180 rank_address=4, distance=0.0)
184 def test_lookup_place_with_linked_places_none_existing(apiobj, frontend):
185 apiobj.add_placex(place_id=332, osm_type='W', osm_id=4,
186 class_='highway', type='residential', name='Street',
187 country_code='pl', linked_place_id=45,
188 rank_search=27, rank_address=26)
190 api = frontend(apiobj, options={'details'})
191 result = api.details(napi.PlaceID(332), linked_places=True)
193 assert result.linked_rows == []
196 def test_lookup_place_with_linked_places_existing(apiobj, frontend):
197 apiobj.add_placex(place_id=332, osm_type='W', osm_id=4,
198 class_='highway', type='residential', name='Street',
199 country_code='pl', linked_place_id=45,
200 rank_search=27, rank_address=26)
201 apiobj.add_placex(place_id=1001, osm_type='W', osm_id=5,
202 class_='highway', type='residential', name='Street',
203 country_code='pl', linked_place_id=332,
204 rank_search=27, rank_address=26)
205 apiobj.add_placex(place_id=1002, osm_type='W', osm_id=6,
206 class_='highway', type='residential', name='Street',
207 country_code='pl', linked_place_id=332,
208 rank_search=27, rank_address=26)
210 api = frontend(apiobj, options={'details'})
211 result = api.details(napi.PlaceID(332), linked_places=True)
213 assert result.linked_rows == [
214 napi.AddressLine(place_id=1001, osm_object=('W', 5),
215 category=('highway', 'residential'),
216 names={'name': 'Street'}, extratags={},
217 admin_level=15, fromarea=False, isaddress=True,
218 rank_address=26, distance=0.0),
219 napi.AddressLine(place_id=1002, osm_object=('W', 6),
220 category=('highway', 'residential'),
221 names={'name': 'Street'}, extratags={},
222 admin_level=15, fromarea=False, isaddress=True,
223 rank_address=26, distance=0.0),
227 def test_lookup_place_with_parented_places_not_existing(apiobj, frontend):
228 apiobj.add_placex(place_id=332, osm_type='W', osm_id=4,
229 class_='highway', type='residential', name='Street',
230 country_code='pl', parent_place_id=45,
231 rank_search=27, rank_address=26)
233 api = frontend(apiobj, options={'details'})
234 result = api.details(napi.PlaceID(332), parented_places=True)
236 assert result.parented_rows == []
239 def test_lookup_place_with_parented_places_existing(apiobj, frontend):
240 apiobj.add_placex(place_id=332, osm_type='W', osm_id=4,
241 class_='highway', type='residential', name='Street',
242 country_code='pl', parent_place_id=45,
243 rank_search=27, rank_address=26)
244 apiobj.add_placex(place_id=1001, osm_type='N', osm_id=5,
245 class_='place', type='house', housenumber='23',
246 country_code='pl', parent_place_id=332,
247 rank_search=30, rank_address=30)
248 apiobj.add_placex(place_id=1002, osm_type='W', osm_id=6,
249 class_='highway', type='residential', name='Street',
250 country_code='pl', parent_place_id=332,
251 rank_search=27, rank_address=26)
253 api = frontend(apiobj, options={'details'})
254 result = api.details(napi.PlaceID(332), parented_places=True)
256 assert result.parented_rows == [
257 napi.AddressLine(place_id=1001, osm_object=('N', 5),
258 category=('place', 'house'),
259 names={'housenumber': '23'}, extratags={},
260 admin_level=15, fromarea=False, isaddress=True,
261 rank_address=30, distance=0.0),
265 @pytest.mark.parametrize('idobj', (napi.PlaceID(4924), napi.OsmID('W', 9928)))
266 def test_lookup_in_osmline(apiobj, frontend, idobj):
267 import_date = dt.datetime(2022, 12, 7, 14, 14, 46, 0)
268 apiobj.add_osmline(place_id=4924, osm_id=9928,
270 startnumber=1, endnumber=4, step=1,
271 country_code='gb', postcode='34425',
272 address={'city': 'Big'},
273 indexed_date=import_date,
274 geometry='LINESTRING(23 34, 23 35)')
276 api = frontend(apiobj, options={'details'})
277 result = api.details(idobj)
279 assert result is not None
281 assert result.source_table.name == 'OSMLINE'
282 assert result.category == ('place', 'houses')
283 assert result.centroid == (pytest.approx(23.0), pytest.approx(34.5))
285 assert result.place_id == 4924
286 assert result.parent_place_id == 12
287 assert result.linked_place_id is None
288 assert result.osm_object == ('W', 9928)
289 assert result.admin_level == 15
291 assert result.names is None
292 assert result.address == {'city': 'Big'}
293 assert result.extratags == {'startnumber': '1', 'endnumber': '4', 'step': '1'}
295 assert result.housenumber is None
296 assert result.postcode == '34425'
297 assert result.wikipedia is None
299 assert result.rank_search == 30
300 assert result.rank_address == 30
301 assert result.importance is None
303 assert result.country_code == 'gb'
304 assert result.indexed_date == import_date.replace(tzinfo=dt.timezone.utc)
306 assert result.address_rows is None
307 assert result.linked_rows is None
308 assert result.parented_rows is None
309 assert result.name_keywords is None
310 assert result.address_keywords is None
312 assert result.geometry == {'type': 'ST_LineString'}
315 def test_lookup_in_osmline_split_interpolation(apiobj, frontend):
316 apiobj.add_osmline(place_id=1000, osm_id=9,
317 startnumber=2, endnumber=4, step=1)
318 apiobj.add_osmline(place_id=1001, osm_id=9,
319 startnumber=6, endnumber=9, step=1)
320 apiobj.add_osmline(place_id=1002, osm_id=9,
321 startnumber=11, endnumber=20, step=1)
323 api = frontend(apiobj, options={'details'})
324 for i in range(1, 6):
325 result = api.details(napi.OsmID('W', 9, str(i)))
326 assert result.place_id == 1000
327 for i in range(7, 11):
328 result = api.details(napi.OsmID('W', 9, str(i)))
329 assert result.place_id == 1001
330 for i in range(12, 22):
331 result = api.details(napi.OsmID('W', 9, str(i)))
332 assert result.place_id == 1002
335 def test_lookup_osmline_with_address_details(apiobj, frontend):
336 apiobj.add_osmline(place_id=9000, osm_id=9,
337 startnumber=2, endnumber=4, step=1,
339 apiobj.add_placex(place_id=332, osm_type='W', osm_id=4,
340 class_='highway', type='residential', name='Street',
342 rank_search=27, rank_address=26)
343 apiobj.add_address_placex(332, fromarea=False, isaddress=False,
345 place_id=1000, osm_type='N', osm_id=3333,
346 class_='place', type='suburb', name='Smallplace',
347 country_code='pl', admin_level=13,
348 rank_search=24, rank_address=23)
349 apiobj.add_address_placex(332, fromarea=True, isaddress=True,
350 place_id=1001, osm_type='N', osm_id=3334,
351 class_='place', type='city', name='Bigplace',
353 rank_search=17, rank_address=16)
355 api = frontend(apiobj, options={'details'})
356 result = api.details(napi.PlaceID(9000), address_details=True)
357 napi.Locales().localize_results([result])
359 assert result.address_rows == [
360 napi.AddressLine(place_id=332, osm_object=('W', 4),
361 category=('highway', 'residential'),
362 names={'name': 'Street'}, extratags={},
363 admin_level=15, fromarea=True, isaddress=True,
364 rank_address=26, distance=0.0,
365 local_name='Street'),
366 napi.AddressLine(place_id=1000, osm_object=('N', 3333),
367 category=('place', 'suburb'),
368 names={'name': 'Smallplace'}, extratags={},
369 admin_level=13, fromarea=False, isaddress=True,
370 rank_address=23, distance=0.0034,
371 local_name='Smallplace'),
372 napi.AddressLine(place_id=1001, osm_object=('N', 3334),
373 category=('place', 'city'),
374 names={'name': 'Bigplace'}, extratags={},
375 admin_level=15, fromarea=True, isaddress=True,
376 rank_address=16, distance=0.0,
377 local_name='Bigplace'),
378 napi.AddressLine(place_id=None, osm_object=None,
379 category=('place', 'country_code'),
380 names={'ref': 'pl'}, extratags={},
381 admin_level=None, fromarea=True, isaddress=False,
382 rank_address=4, distance=0.0)
386 def test_lookup_in_tiger(apiobj, frontend):
387 apiobj.add_tiger(place_id=4924,
389 startnumber=1, endnumber=4, step=1,
391 geometry='LINESTRING(23 34, 23 35)')
392 apiobj.add_placex(place_id=12,
393 category=('highway', 'residential'),
394 osm_type='W', osm_id=6601223,
395 geometry='LINESTRING(23 34, 23 35)')
397 api = frontend(apiobj, options={'details'})
398 result = api.details(napi.PlaceID(4924))
400 assert result is not None
402 assert result.source_table.name == 'TIGER'
403 assert result.category == ('place', 'houses')
404 assert result.centroid == (pytest.approx(23.0), pytest.approx(34.5))
406 assert result.place_id == 4924
407 assert result.parent_place_id == 12
408 assert result.linked_place_id is None
409 assert result.osm_object == ('W', 6601223)
410 assert result.admin_level == 15
412 assert result.names is None
413 assert result.address is None
414 assert result.extratags == {'startnumber': '1', 'endnumber': '4', 'step': '1'}
416 assert result.housenumber is None
417 assert result.postcode == '34425'
418 assert result.wikipedia is None
420 assert result.rank_search == 30
421 assert result.rank_address == 30
422 assert result.importance is None
424 assert result.country_code == 'us'
425 assert result.indexed_date is None
427 assert result.address_rows is None
428 assert result.linked_rows is None
429 assert result.parented_rows is None
430 assert result.name_keywords is None
431 assert result.address_keywords is None
433 assert result.geometry == {'type': 'ST_LineString'}
436 def test_lookup_tiger_with_address_details(apiobj, frontend):
437 apiobj.add_tiger(place_id=9000,
438 startnumber=2, endnumber=4, step=1,
440 apiobj.add_placex(place_id=332, osm_type='W', osm_id=4,
441 class_='highway', type='residential', name='Street',
443 rank_search=27, rank_address=26)
444 apiobj.add_address_placex(332, fromarea=False, isaddress=False,
446 place_id=1000, osm_type='N', osm_id=3333,
447 class_='place', type='suburb', name='Smallplace',
448 country_code='us', admin_level=13,
449 rank_search=24, rank_address=23)
450 apiobj.add_address_placex(332, fromarea=True, isaddress=True,
451 place_id=1001, osm_type='N', osm_id=3334,
452 class_='place', type='city', name='Bigplace',
454 rank_search=17, rank_address=16)
456 api = frontend(apiobj, options={'details'})
457 result = api.details(napi.PlaceID(9000), address_details=True)
458 napi.Locales().localize_results([result])
460 assert result.address_rows == [
461 napi.AddressLine(place_id=332, osm_object=('W', 4),
462 category=('highway', 'residential'),
463 names={'name': 'Street'}, extratags={},
464 admin_level=15, fromarea=True, isaddress=True,
465 rank_address=26, distance=0.0,
466 local_name='Street'),
467 napi.AddressLine(place_id=1000, osm_object=('N', 3333),
468 category=('place', 'suburb'),
469 names={'name': 'Smallplace'}, extratags={},
470 admin_level=13, fromarea=False, isaddress=True,
471 rank_address=23, distance=0.0034,
472 local_name='Smallplace'),
473 napi.AddressLine(place_id=1001, osm_object=('N', 3334),
474 category=('place', 'city'),
475 names={'name': 'Bigplace'}, extratags={},
476 admin_level=15, fromarea=True, isaddress=True,
477 rank_address=16, distance=0.0,
478 local_name='Bigplace'),
479 napi.AddressLine(place_id=None, osm_object=None,
480 category=('place', 'country_code'),
481 names={'ref': 'us'}, extratags={},
482 admin_level=None, fromarea=True, isaddress=False,
483 rank_address=4, distance=0.0)
487 def test_lookup_in_postcode(apiobj, frontend):
488 import_date = dt.datetime(2022, 12, 7, 14, 14, 46, 0)
489 apiobj.add_postcode(place_id=554,
494 indexed_date=import_date,
495 centroid='POINT(-9.45 5.6)',
496 geometry='POLYGON((-9.5 5.5, -9.5 5.7, -9.4 5.6, -9.5 5.5))')
498 api = frontend(apiobj, options={'details'})
499 result = api.details(napi.PlaceID(554))
501 assert result is not None
503 assert result.source_table.name == 'POSTCODE'
504 assert result.category == ('place', 'postcode')
505 assert result.centroid == (pytest.approx(-9.45), pytest.approx(5.6))
507 assert result.place_id == 554
508 assert result.parent_place_id == 152
509 assert result.linked_place_id is None
510 assert result.osm_object is None
511 assert result.admin_level == 15
513 assert result.names == {'ref': '34 425'}
514 assert result.address is None
515 assert result.extratags is None
517 assert result.housenumber is None
518 assert result.postcode is None
519 assert result.wikipedia is None
521 assert result.rank_search == 20
522 assert result.rank_address == 5
523 assert result.importance is None
525 assert result.country_code == 'gb'
526 assert result.indexed_date == import_date.replace(tzinfo=dt.timezone.utc)
528 assert result.address_rows is None
529 assert result.linked_rows is None
530 assert result.parented_rows is None
531 assert result.name_keywords is None
532 assert result.address_keywords is None
534 assert result.geometry == {'type': 'ST_Polygon'}
537 @pytest.mark.parametrize('lookup', [napi.PlaceID(9000),
538 napi.OsmID('R', 12)])
539 def test_lookup_postcode_with_address_details(apiobj, frontend, lookup):
540 apiobj.add_postcode(place_id=9000, osm_id=12,
545 apiobj.add_placex(place_id=332, osm_type='N', osm_id=3333,
546 class_='place', type='suburb', name='Smallplace',
547 country_code='gb', admin_level=13,
548 rank_search=24, rank_address=23)
549 apiobj.add_address_placex(332, fromarea=True, isaddress=True,
550 place_id=1001, osm_type='N', osm_id=3334,
551 class_='place', type='city', name='Bigplace',
553 rank_search=17, rank_address=16)
555 api = frontend(apiobj, options={'details'})
556 result = api.details(lookup, address_details=True)
557 napi.Locales().localize_results([result])
559 assert result.address_rows == [
560 napi.AddressLine(place_id=9000, osm_object=('R', 12),
561 category=('boundary', 'postal_code'),
562 names={'ref': '34 425'}, extratags={},
563 admin_level=15, fromarea=True, isaddress=True,
564 rank_address=5, distance=0.0,
565 local_name='34 425'),
566 napi.AddressLine(place_id=332, osm_object=('N', 3333),
567 category=('place', 'suburb'),
568 names={'name': 'Smallplace'}, extratags={},
569 admin_level=13, fromarea=True, isaddress=True,
570 rank_address=23, distance=0.0,
571 local_name='Smallplace'),
572 napi.AddressLine(place_id=1001, osm_object=('N', 3334),
573 category=('place', 'city'),
574 names={'name': 'Bigplace'}, extratags={},
575 admin_level=15, fromarea=True, isaddress=True,
576 rank_address=16, distance=0.0,
577 local_name='Bigplace'),
578 napi.AddressLine(place_id=None, osm_object=None,
579 category=('place', 'country_code'),
580 names={'ref': 'gb'}, extratags={},
581 admin_level=None, fromarea=True, isaddress=False,
582 rank_address=4, distance=0.0)
586 @pytest.mark.parametrize('objid', [napi.PlaceID(1736),
588 napi.OsmID('N', 55, 'amenity')])
589 def test_lookup_missing_object(apiobj, frontend, objid):
590 apiobj.add_placex(place_id=1, osm_type='N', osm_id=55,
591 class_='place', type='suburb')
593 api = frontend(apiobj, options={'details'})
594 assert api.details(objid) is None
597 @pytest.mark.parametrize('gtype', (napi.GeometryFormat.KML,
598 napi.GeometryFormat.SVG,
599 napi.GeometryFormat.TEXT))
600 def test_lookup_unsupported_geometry(apiobj, frontend, gtype):
601 apiobj.add_placex(place_id=332)
603 api = frontend(apiobj, options={'details'})
604 with pytest.raises(ValueError):
605 api.details(napi.PlaceID(332), geometry_output=gtype)