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)
38 assert result is not None
40 assert result.source_table.name == 'PLACEX'
41 assert result.category == ('highway', 'residential')
42 assert result.centroid == (pytest.approx(23.0), pytest.approx(34.0))
44 assert result.place_id == 332
45 assert result.parent_place_id == 34
46 assert result.linked_place_id == 55
47 assert result.osm_object == ('W', 4)
48 assert result.admin_level == 15
50 assert result.names == {'name': 'Road'}
51 assert result.address == {'city': 'Barrow'}
52 assert result.extratags == {'surface': 'paved'}
54 assert result.housenumber == '4'
55 assert result.postcode == '34425'
56 assert result.wikipedia == 'en:Faa'
58 assert result.rank_search == 27
59 assert result.rank_address == 26
60 assert result.importance == pytest.approx(0.01)
62 assert result.country_code == 'gb'
63 assert result.indexed_date == import_date.replace(tzinfo=dt.timezone.utc)
65 assert result.address_rows is None
66 assert result.linked_rows is None
67 assert result.parented_rows is None
68 assert result.name_keywords is None
69 assert result.address_keywords is None
71 assert result.geometry == {'type': 'ST_LineString'}
74 def test_lookup_in_placex_minimal_info(apiobj, frontend):
75 import_date = dt.datetime(2022, 12, 7, 14, 14, 46, 0)
76 apiobj.add_placex(place_id=332, osm_type='W', osm_id=4,
77 class_='highway', type='residential',
79 rank_search=27, rank_address=26,
81 indexed_date=import_date,
82 geometry='LINESTRING(23 34, 23.1 34, 23.1 34.1, 23 34)')
84 api = frontend(apiobj, options={'details'})
85 result = api.details(napi.PlaceID(332))
87 assert result is not None
89 assert result.source_table.name == 'PLACEX'
90 assert result.category == ('highway', 'residential')
91 assert result.centroid == (pytest.approx(23.0), pytest.approx(34.0))
93 assert result.place_id == 332
94 assert result.parent_place_id is None
95 assert result.linked_place_id is None
96 assert result.osm_object == ('W', 4)
97 assert result.admin_level == 15
99 assert result.names is None
100 assert result.address is None
101 assert result.extratags is None
103 assert result.housenumber is None
104 assert result.postcode is None
105 assert result.wikipedia is None
107 assert result.rank_search == 27
108 assert result.rank_address == 26
109 assert result.importance is None
111 assert result.country_code is None
112 assert result.indexed_date == import_date.replace(tzinfo=dt.timezone.utc)
114 assert result.address_rows is None
115 assert result.linked_rows is None
116 assert result.parented_rows is None
117 assert result.name_keywords is None
118 assert result.address_keywords is None
120 assert result.geometry == {'type': 'ST_LineString'}
123 def test_lookup_in_placex_with_geometry(apiobj, frontend):
124 apiobj.add_placex(place_id=332,
125 geometry='LINESTRING(23 34, 23.1 34)')
127 api = frontend(apiobj, options={'details'})
128 result = api.details(napi.PlaceID(332), geometry_output=napi.GeometryFormat.GEOJSON)
130 assert result.geometry == {'geojson': '{"type":"LineString","coordinates":[[23,34],[23.1,34]]}'}
133 def test_lookup_placex_with_address_details(apiobj, frontend):
134 apiobj.add_placex(place_id=332, osm_type='W', osm_id=4,
135 class_='highway', type='residential', name='Street',
137 rank_search=27, rank_address=26)
138 apiobj.add_address_placex(332, fromarea=False, isaddress=False,
140 place_id=1000, osm_type='N', osm_id=3333,
141 class_='place', type='suburb', name='Smallplace',
142 country_code='pl', admin_level=13,
143 rank_search=24, rank_address=23)
144 apiobj.add_address_placex(332, fromarea=True, isaddress=True,
145 place_id=1001, osm_type='N', osm_id=3334,
146 class_='place', type='city', name='Bigplace',
148 rank_search=17, rank_address=16)
150 api = frontend(apiobj, options={'details'})
151 result = api.details(napi.PlaceID(332), address_details=True)
153 assert result.address_rows == [
154 napi.AddressLine(place_id=332, osm_object=('W', 4),
155 category=('highway', 'residential'),
156 names={'name': 'Street'}, extratags={},
157 admin_level=15, fromarea=True, isaddress=True,
158 rank_address=26, distance=0.0,
159 local_name='Street'),
160 napi.AddressLine(place_id=1000, osm_object=('N', 3333),
161 category=('place', 'suburb'),
162 names={'name': 'Smallplace'}, extratags={},
163 admin_level=13, fromarea=False, isaddress=True,
164 rank_address=23, distance=0.0034,
165 local_name='Smallplace'),
166 napi.AddressLine(place_id=1001, osm_object=('N', 3334),
167 category=('place', 'city'),
168 names={'name': 'Bigplace'}, extratags={},
169 admin_level=15, fromarea=True, isaddress=True,
170 rank_address=16, distance=0.0,
171 local_name='Bigplace'),
172 napi.AddressLine(place_id=None, osm_object=None,
173 category=('place', 'country_code'),
174 names={'ref': 'pl'}, extratags={},
175 admin_level=None, fromarea=True, isaddress=False,
176 rank_address=4, distance=0.0)
180 def test_lookup_place_with_linked_places_none_existing(apiobj, frontend):
181 apiobj.add_placex(place_id=332, osm_type='W', osm_id=4,
182 class_='highway', type='residential', name='Street',
183 country_code='pl', linked_place_id=45,
184 rank_search=27, rank_address=26)
186 api = frontend(apiobj, options={'details'})
187 result = api.details(napi.PlaceID(332), linked_places=True)
189 assert result.linked_rows == []
192 def test_lookup_place_with_linked_places_existing(apiobj, frontend):
193 apiobj.add_placex(place_id=332, osm_type='W', osm_id=4,
194 class_='highway', type='residential', name='Street',
195 country_code='pl', linked_place_id=45,
196 rank_search=27, rank_address=26)
197 apiobj.add_placex(place_id=1001, osm_type='W', osm_id=5,
198 class_='highway', type='residential', name='Street',
199 country_code='pl', linked_place_id=332,
200 rank_search=27, rank_address=26)
201 apiobj.add_placex(place_id=1002, osm_type='W', osm_id=6,
202 class_='highway', type='residential', name='Street',
203 country_code='pl', linked_place_id=332,
204 rank_search=27, rank_address=26)
206 api = frontend(apiobj, options={'details'})
207 result = api.details(napi.PlaceID(332), linked_places=True)
209 assert result.linked_rows == [
210 napi.AddressLine(place_id=1001, osm_object=('W', 5),
211 category=('highway', 'residential'),
212 names={'name': 'Street'}, extratags={},
213 admin_level=15, fromarea=False, isaddress=True,
214 rank_address=26, distance=0.0),
215 napi.AddressLine(place_id=1002, osm_object=('W', 6),
216 category=('highway', 'residential'),
217 names={'name': 'Street'}, extratags={},
218 admin_level=15, fromarea=False, isaddress=True,
219 rank_address=26, distance=0.0),
223 def test_lookup_place_with_parented_places_not_existing(apiobj, frontend):
224 apiobj.add_placex(place_id=332, osm_type='W', osm_id=4,
225 class_='highway', type='residential', name='Street',
226 country_code='pl', parent_place_id=45,
227 rank_search=27, rank_address=26)
229 api = frontend(apiobj, options={'details'})
230 result = api.details(napi.PlaceID(332), parented_places=True)
232 assert result.parented_rows == []
235 def test_lookup_place_with_parented_places_existing(apiobj, frontend):
236 apiobj.add_placex(place_id=332, osm_type='W', osm_id=4,
237 class_='highway', type='residential', name='Street',
238 country_code='pl', parent_place_id=45,
239 rank_search=27, rank_address=26)
240 apiobj.add_placex(place_id=1001, osm_type='N', osm_id=5,
241 class_='place', type='house', housenumber='23',
242 country_code='pl', parent_place_id=332,
243 rank_search=30, rank_address=30)
244 apiobj.add_placex(place_id=1002, osm_type='W', osm_id=6,
245 class_='highway', type='residential', name='Street',
246 country_code='pl', parent_place_id=332,
247 rank_search=27, rank_address=26)
249 api = frontend(apiobj, options={'details'})
250 result = api.details(napi.PlaceID(332), parented_places=True)
252 assert result.parented_rows == [
253 napi.AddressLine(place_id=1001, osm_object=('N', 5),
254 category=('place', 'house'),
255 names={'housenumber': '23'}, extratags={},
256 admin_level=15, fromarea=False, isaddress=True,
257 rank_address=30, distance=0.0),
261 @pytest.mark.parametrize('idobj', (napi.PlaceID(4924), napi.OsmID('W', 9928)))
262 def test_lookup_in_osmline(apiobj, frontend, idobj):
263 import_date = dt.datetime(2022, 12, 7, 14, 14, 46, 0)
264 apiobj.add_osmline(place_id=4924, osm_id=9928,
266 startnumber=1, endnumber=4, step=1,
267 country_code='gb', postcode='34425',
268 address={'city': 'Big'},
269 indexed_date=import_date,
270 geometry='LINESTRING(23 34, 23 35)')
272 api = frontend(apiobj, options={'details'})
273 result = api.details(idobj)
275 assert result is not None
277 assert result.source_table.name == 'OSMLINE'
278 assert result.category == ('place', 'houses')
279 assert result.centroid == (pytest.approx(23.0), pytest.approx(34.5))
281 assert result.place_id == 4924
282 assert result.parent_place_id == 12
283 assert result.linked_place_id is None
284 assert result.osm_object == ('W', 9928)
285 assert result.admin_level == 15
287 assert result.names is None
288 assert result.address == {'city': 'Big'}
289 assert result.extratags == {'startnumber': '1', 'endnumber': '4', 'step': '1'}
291 assert result.housenumber is None
292 assert result.postcode == '34425'
293 assert result.wikipedia is None
295 assert result.rank_search == 30
296 assert result.rank_address == 30
297 assert result.importance is None
299 assert result.country_code == 'gb'
300 assert result.indexed_date == import_date.replace(tzinfo=dt.timezone.utc)
302 assert result.address_rows is None
303 assert result.linked_rows is None
304 assert result.parented_rows is None
305 assert result.name_keywords is None
306 assert result.address_keywords is None
308 assert result.geometry == {'type': 'ST_LineString'}
311 def test_lookup_in_osmline_split_interpolation(apiobj, frontend):
312 apiobj.add_osmline(place_id=1000, osm_id=9,
313 startnumber=2, endnumber=4, step=1)
314 apiobj.add_osmline(place_id=1001, osm_id=9,
315 startnumber=6, endnumber=9, step=1)
316 apiobj.add_osmline(place_id=1002, osm_id=9,
317 startnumber=11, endnumber=20, step=1)
319 api = frontend(apiobj, options={'details'})
320 for i in range(1, 6):
321 result = api.details(napi.OsmID('W', 9, str(i)))
322 assert result.place_id == 1000
323 for i in range(7, 11):
324 result = api.details(napi.OsmID('W', 9, str(i)))
325 assert result.place_id == 1001
326 for i in range(12, 22):
327 result = api.details(napi.OsmID('W', 9, str(i)))
328 assert result.place_id == 1002
331 def test_lookup_osmline_with_address_details(apiobj, frontend):
332 apiobj.add_osmline(place_id=9000, osm_id=9,
333 startnumber=2, endnumber=4, step=1,
335 apiobj.add_placex(place_id=332, osm_type='W', osm_id=4,
336 class_='highway', type='residential', name='Street',
338 rank_search=27, rank_address=26)
339 apiobj.add_address_placex(332, fromarea=False, isaddress=False,
341 place_id=1000, osm_type='N', osm_id=3333,
342 class_='place', type='suburb', name='Smallplace',
343 country_code='pl', admin_level=13,
344 rank_search=24, rank_address=23)
345 apiobj.add_address_placex(332, fromarea=True, isaddress=True,
346 place_id=1001, osm_type='N', osm_id=3334,
347 class_='place', type='city', name='Bigplace',
349 rank_search=17, rank_address=16)
351 api = frontend(apiobj, options={'details'})
352 result = api.details(napi.PlaceID(9000), address_details=True)
354 assert result.address_rows == [
355 napi.AddressLine(place_id=332, osm_object=('W', 4),
356 category=('highway', 'residential'),
357 names={'name': 'Street'}, extratags={},
358 admin_level=15, fromarea=True, isaddress=True,
359 rank_address=26, distance=0.0,
360 local_name='Street'),
361 napi.AddressLine(place_id=1000, osm_object=('N', 3333),
362 category=('place', 'suburb'),
363 names={'name': 'Smallplace'}, extratags={},
364 admin_level=13, fromarea=False, isaddress=True,
365 rank_address=23, distance=0.0034,
366 local_name='Smallplace'),
367 napi.AddressLine(place_id=1001, osm_object=('N', 3334),
368 category=('place', 'city'),
369 names={'name': 'Bigplace'}, extratags={},
370 admin_level=15, fromarea=True, isaddress=True,
371 rank_address=16, distance=0.0,
372 local_name='Bigplace'),
373 napi.AddressLine(place_id=None, osm_object=None,
374 category=('place', 'country_code'),
375 names={'ref': 'pl'}, extratags={},
376 admin_level=None, fromarea=True, isaddress=False,
377 rank_address=4, distance=0.0)
381 def test_lookup_in_tiger(apiobj, frontend):
382 apiobj.add_tiger(place_id=4924,
384 startnumber=1, endnumber=4, step=1,
386 geometry='LINESTRING(23 34, 23 35)')
387 apiobj.add_placex(place_id=12,
388 category=('highway', 'residential'),
389 osm_type='W', osm_id=6601223,
390 geometry='LINESTRING(23 34, 23 35)')
392 api = frontend(apiobj, options={'details'})
393 result = api.details(napi.PlaceID(4924))
395 assert result is not None
397 assert result.source_table.name == 'TIGER'
398 assert result.category == ('place', 'houses')
399 assert result.centroid == (pytest.approx(23.0), pytest.approx(34.5))
401 assert result.place_id == 4924
402 assert result.parent_place_id == 12
403 assert result.linked_place_id is None
404 assert result.osm_object == ('W', 6601223)
405 assert result.admin_level == 15
407 assert result.names is None
408 assert result.address is None
409 assert result.extratags == {'startnumber': '1', 'endnumber': '4', 'step': '1'}
411 assert result.housenumber is None
412 assert result.postcode == '34425'
413 assert result.wikipedia is None
415 assert result.rank_search == 30
416 assert result.rank_address == 30
417 assert result.importance is None
419 assert result.country_code == 'us'
420 assert result.indexed_date is None
422 assert result.address_rows is None
423 assert result.linked_rows is None
424 assert result.parented_rows is None
425 assert result.name_keywords is None
426 assert result.address_keywords is None
428 assert result.geometry == {'type': 'ST_LineString'}
431 def test_lookup_tiger_with_address_details(apiobj, frontend):
432 apiobj.add_tiger(place_id=9000,
433 startnumber=2, endnumber=4, step=1,
435 apiobj.add_placex(place_id=332, osm_type='W', osm_id=4,
436 class_='highway', type='residential', name='Street',
438 rank_search=27, rank_address=26)
439 apiobj.add_address_placex(332, fromarea=False, isaddress=False,
441 place_id=1000, osm_type='N', osm_id=3333,
442 class_='place', type='suburb', name='Smallplace',
443 country_code='us', admin_level=13,
444 rank_search=24, rank_address=23)
445 apiobj.add_address_placex(332, fromarea=True, isaddress=True,
446 place_id=1001, osm_type='N', osm_id=3334,
447 class_='place', type='city', name='Bigplace',
449 rank_search=17, rank_address=16)
451 api = frontend(apiobj, options={'details'})
452 result = api.details(napi.PlaceID(9000), address_details=True)
454 assert result.address_rows == [
455 napi.AddressLine(place_id=332, osm_object=('W', 4),
456 category=('highway', 'residential'),
457 names={'name': 'Street'}, extratags={},
458 admin_level=15, fromarea=True, isaddress=True,
459 rank_address=26, distance=0.0,
460 local_name='Street'),
461 napi.AddressLine(place_id=1000, osm_object=('N', 3333),
462 category=('place', 'suburb'),
463 names={'name': 'Smallplace'}, extratags={},
464 admin_level=13, fromarea=False, isaddress=True,
465 rank_address=23, distance=0.0034,
466 local_name='Smallplace'),
467 napi.AddressLine(place_id=1001, osm_object=('N', 3334),
468 category=('place', 'city'),
469 names={'name': 'Bigplace'}, extratags={},
470 admin_level=15, fromarea=True, isaddress=True,
471 rank_address=16, distance=0.0,
472 local_name='Bigplace'),
473 napi.AddressLine(place_id=None, osm_object=None,
474 category=('place', 'country_code'),
475 names={'ref': 'us'}, extratags={},
476 admin_level=None, fromarea=True, isaddress=False,
477 rank_address=4, distance=0.0)
481 def test_lookup_in_postcode(apiobj, frontend):
482 import_date = dt.datetime(2022, 12, 7, 14, 14, 46, 0)
483 apiobj.add_postcode(place_id=554,
487 rank_search=20, rank_address=22,
488 indexed_date=import_date,
489 geometry='POINT(-9.45 5.6)')
491 api = frontend(apiobj, options={'details'})
492 result = api.details(napi.PlaceID(554))
494 assert result is not None
496 assert result.source_table.name == 'POSTCODE'
497 assert result.category == ('place', 'postcode')
498 assert result.centroid == (pytest.approx(-9.45), pytest.approx(5.6))
500 assert result.place_id == 554
501 assert result.parent_place_id == 152
502 assert result.linked_place_id is None
503 assert result.osm_object is None
504 assert result.admin_level == 15
506 assert result.names == {'ref': '34 425'}
507 assert result.address is None
508 assert result.extratags is None
510 assert result.housenumber is None
511 assert result.postcode is None
512 assert result.wikipedia is None
514 assert result.rank_search == 20
515 assert result.rank_address == 22
516 assert result.importance is None
518 assert result.country_code == 'gb'
519 assert result.indexed_date == import_date.replace(tzinfo=dt.timezone.utc)
521 assert result.address_rows is None
522 assert result.linked_rows is None
523 assert result.parented_rows is None
524 assert result.name_keywords is None
525 assert result.address_keywords is None
527 assert result.geometry == {'type': 'ST_Point'}
530 def test_lookup_postcode_with_address_details(apiobj, frontend):
531 apiobj.add_postcode(place_id=9000,
535 rank_search=25, rank_address=25)
536 apiobj.add_placex(place_id=332, osm_type='N', osm_id=3333,
537 class_='place', type='suburb', name='Smallplace',
538 country_code='gb', admin_level=13,
539 rank_search=24, rank_address=23)
540 apiobj.add_address_placex(332, fromarea=True, isaddress=True,
541 place_id=1001, osm_type='N', osm_id=3334,
542 class_='place', type='city', name='Bigplace',
544 rank_search=17, rank_address=16)
546 api = frontend(apiobj, options={'details'})
547 result = api.details(napi.PlaceID(9000), address_details=True)
549 assert result.address_rows == [
550 napi.AddressLine(place_id=9000, osm_object=None,
551 category=('place', 'postcode'),
552 names={'ref': '34 425'}, extratags={},
553 admin_level=15, fromarea=True, isaddress=True,
554 rank_address=25, distance=0.0,
555 local_name='34 425'),
556 napi.AddressLine(place_id=332, osm_object=('N', 3333),
557 category=('place', 'suburb'),
558 names={'name': 'Smallplace'}, extratags={},
559 admin_level=13, fromarea=True, isaddress=True,
560 rank_address=23, distance=0.0,
561 local_name='Smallplace'),
562 napi.AddressLine(place_id=1001, osm_object=('N', 3334),
563 category=('place', 'city'),
564 names={'name': 'Bigplace'}, extratags={},
565 admin_level=15, fromarea=True, isaddress=True,
566 rank_address=16, distance=0.0,
567 local_name='Bigplace'),
568 napi.AddressLine(place_id=None, osm_object=None,
569 category=('place', 'country_code'),
570 names={'ref': 'gb'}, extratags={},
571 admin_level=None, fromarea=True, isaddress=False,
572 rank_address=4, distance=0.0)
576 @pytest.mark.parametrize('objid', [napi.PlaceID(1736),
578 napi.OsmID('N', 55, 'amenity')])
579 def test_lookup_missing_object(apiobj, frontend, objid):
580 apiobj.add_placex(place_id=1, osm_type='N', osm_id=55,
581 class_='place', type='suburb')
583 api = frontend(apiobj, options={'details'})
584 assert api.details(objid) is None
587 @pytest.mark.parametrize('gtype', (napi.GeometryFormat.KML,
588 napi.GeometryFormat.SVG,
589 napi.GeometryFormat.TEXT))
590 def test_lookup_unsupported_geometry(apiobj, frontend, gtype):
591 apiobj.add_placex(place_id=332)
593 api = frontend(apiobj, options={'details'})
594 with pytest.raises(ValueError):
595 api.details(napi.PlaceID(332), geometry_output=gtype)