1 # SPDX-License-Identifier: GPL-3.0-or-later
3 # This file is part of Nominatim. (https://nominatim.org)
5 # Copyright (C) 2024 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
16 @pytest.mark.parametrize('idobj', (napi.PlaceID(332), napi.OsmID('W', 4),
17 napi.OsmID('W', 4, 'highway')))
18 def test_lookup_in_placex(apiobj, frontend, idobj):
19 import_date = dt.datetime(2022, 12, 7, 14, 14, 46, 0)
20 apiobj.add_placex(place_id=332, osm_type='W', osm_id=4,
21 class_='highway', type='residential',
22 name={'name': 'Road'}, address={'city': 'Barrow'},
23 extratags={'surface': 'paved'},
24 parent_place_id=34, linked_place_id=55,
25 admin_level=15, country_code='gb',
27 postcode='34425', wikipedia='en:Faa',
28 rank_search=27, rank_address=26,
31 indexed_date=import_date,
32 geometry='LINESTRING(23 34, 23.1 34, 23.1 34.1, 23 34)')
34 api = frontend(apiobj, options={'details'})
35 result = api.details(idobj)
37 assert result is not None
39 assert result.source_table.name == 'PLACEX'
40 assert result.category == ('highway', 'residential')
41 assert result.centroid == (pytest.approx(23.0), pytest.approx(34.0))
43 assert result.place_id == 332
44 assert result.parent_place_id == 34
45 assert result.linked_place_id == 55
46 assert result.osm_object == ('W', 4)
47 assert result.admin_level == 15
49 assert result.names == {'name': 'Road'}
50 assert result.address == {'city': 'Barrow'}
51 assert result.extratags == {'surface': 'paved'}
53 assert result.housenumber == '4'
54 assert result.postcode == '34425'
55 assert result.wikipedia == 'en:Faa'
57 assert result.rank_search == 27
58 assert result.rank_address == 26
59 assert result.importance == pytest.approx(0.01)
61 assert result.country_code == 'gb'
62 assert result.indexed_date == import_date.replace(tzinfo=dt.timezone.utc)
64 assert result.address_rows is None
65 assert result.linked_rows is None
66 assert result.parented_rows is None
67 assert result.name_keywords is None
68 assert result.address_keywords is None
70 assert result.geometry == {'type': 'ST_LineString'}
73 def test_lookup_in_placex_minimal_info(apiobj, frontend):
74 import_date = dt.datetime(2022, 12, 7, 14, 14, 46, 0)
75 apiobj.add_placex(place_id=332, osm_type='W', osm_id=4,
76 class_='highway', type='residential',
78 rank_search=27, rank_address=26,
80 indexed_date=import_date,
81 geometry='LINESTRING(23 34, 23.1 34, 23.1 34.1, 23 34)')
83 api = frontend(apiobj, options={'details'})
84 result = api.details(napi.PlaceID(332))
86 assert result is not None
88 assert result.source_table.name == 'PLACEX'
89 assert result.category == ('highway', 'residential')
90 assert result.centroid == (pytest.approx(23.0), pytest.approx(34.0))
92 assert result.place_id == 332
93 assert result.parent_place_id is None
94 assert result.linked_place_id is None
95 assert result.osm_object == ('W', 4)
96 assert result.admin_level == 15
98 assert result.names is None
99 assert result.address is None
100 assert result.extratags is None
102 assert result.housenumber is None
103 assert result.postcode is None
104 assert result.wikipedia is None
106 assert result.rank_search == 27
107 assert result.rank_address == 26
108 assert result.importance is None
110 assert result.country_code is None
111 assert result.indexed_date == import_date.replace(tzinfo=dt.timezone.utc)
113 assert result.address_rows is None
114 assert result.linked_rows is None
115 assert result.parented_rows is None
116 assert result.name_keywords is None
117 assert result.address_keywords is None
119 assert result.geometry == {'type': 'ST_LineString'}
122 def test_lookup_in_placex_with_geometry(apiobj, frontend):
123 apiobj.add_placex(place_id=332,
124 geometry='LINESTRING(23 34, 23.1 34)')
126 api = frontend(apiobj, options={'details'})
127 result = api.details(napi.PlaceID(332), geometry_output=napi.GeometryFormat.GEOJSON)
129 assert result.geometry == {'geojson': '{"type":"LineString","coordinates":[[23,34],[23.1,34]]}'}
132 def test_lookup_placex_with_address_details(apiobj, frontend):
133 apiobj.add_placex(place_id=332, osm_type='W', osm_id=4,
134 class_='highway', type='residential', name='Street',
136 rank_search=27, rank_address=26)
137 apiobj.add_address_placex(332, fromarea=False, isaddress=False,
139 place_id=1000, osm_type='N', osm_id=3333,
140 class_='place', type='suburb', name='Smallplace',
141 country_code='pl', admin_level=13,
142 rank_search=24, rank_address=23)
143 apiobj.add_address_placex(332, fromarea=True, isaddress=True,
144 place_id=1001, osm_type='N', osm_id=3334,
145 class_='place', type='city', name='Bigplace',
147 rank_search=17, rank_address=16)
149 api = frontend(apiobj, options={'details'})
150 result = api.details(napi.PlaceID(332), address_details=True)
152 assert result.address_rows == [
153 napi.AddressLine(place_id=332, osm_object=('W', 4),
154 category=('highway', 'residential'),
155 names={'name': 'Street'}, extratags={},
156 admin_level=15, fromarea=True, isaddress=True,
157 rank_address=26, distance=0.0,
158 local_name='Street'),
159 napi.AddressLine(place_id=1000, osm_object=('N', 3333),
160 category=('place', 'suburb'),
161 names={'name': 'Smallplace'}, extratags={},
162 admin_level=13, fromarea=False, isaddress=True,
163 rank_address=23, distance=0.0034,
164 local_name='Smallplace'),
165 napi.AddressLine(place_id=1001, osm_object=('N', 3334),
166 category=('place', 'city'),
167 names={'name': 'Bigplace'}, extratags={},
168 admin_level=15, fromarea=True, isaddress=True,
169 rank_address=16, distance=0.0,
170 local_name='Bigplace'),
171 napi.AddressLine(place_id=None, osm_object=None,
172 category=('place', 'country_code'),
173 names={'ref': 'pl'}, extratags={},
174 admin_level=None, fromarea=True, isaddress=False,
175 rank_address=4, distance=0.0)
179 def test_lookup_place_with_linked_places_none_existing(apiobj, frontend):
180 apiobj.add_placex(place_id=332, osm_type='W', osm_id=4,
181 class_='highway', type='residential', name='Street',
182 country_code='pl', linked_place_id=45,
183 rank_search=27, rank_address=26)
185 api = frontend(apiobj, options={'details'})
186 result = api.details(napi.PlaceID(332), linked_places=True)
188 assert result.linked_rows == []
191 def test_lookup_place_with_linked_places_existing(apiobj, frontend):
192 apiobj.add_placex(place_id=332, osm_type='W', osm_id=4,
193 class_='highway', type='residential', name='Street',
194 country_code='pl', linked_place_id=45,
195 rank_search=27, rank_address=26)
196 apiobj.add_placex(place_id=1001, osm_type='W', osm_id=5,
197 class_='highway', type='residential', name='Street',
198 country_code='pl', linked_place_id=332,
199 rank_search=27, rank_address=26)
200 apiobj.add_placex(place_id=1002, osm_type='W', osm_id=6,
201 class_='highway', type='residential', name='Street',
202 country_code='pl', linked_place_id=332,
203 rank_search=27, rank_address=26)
205 api = frontend(apiobj, options={'details'})
206 result = api.details(napi.PlaceID(332), linked_places=True)
208 assert result.linked_rows == [
209 napi.AddressLine(place_id=1001, osm_object=('W', 5),
210 category=('highway', 'residential'),
211 names={'name': 'Street'}, extratags={},
212 admin_level=15, fromarea=False, isaddress=True,
213 rank_address=26, distance=0.0),
214 napi.AddressLine(place_id=1002, osm_object=('W', 6),
215 category=('highway', 'residential'),
216 names={'name': 'Street'}, extratags={},
217 admin_level=15, fromarea=False, isaddress=True,
218 rank_address=26, distance=0.0),
222 def test_lookup_place_with_parented_places_not_existing(apiobj, frontend):
223 apiobj.add_placex(place_id=332, osm_type='W', osm_id=4,
224 class_='highway', type='residential', name='Street',
225 country_code='pl', parent_place_id=45,
226 rank_search=27, rank_address=26)
228 api = frontend(apiobj, options={'details'})
229 result = api.details(napi.PlaceID(332), parented_places=True)
231 assert result.parented_rows == []
234 def test_lookup_place_with_parented_places_existing(apiobj, frontend):
235 apiobj.add_placex(place_id=332, osm_type='W', osm_id=4,
236 class_='highway', type='residential', name='Street',
237 country_code='pl', parent_place_id=45,
238 rank_search=27, rank_address=26)
239 apiobj.add_placex(place_id=1001, osm_type='N', osm_id=5,
240 class_='place', type='house', housenumber='23',
241 country_code='pl', parent_place_id=332,
242 rank_search=30, rank_address=30)
243 apiobj.add_placex(place_id=1002, osm_type='W', osm_id=6,
244 class_='highway', type='residential', name='Street',
245 country_code='pl', parent_place_id=332,
246 rank_search=27, rank_address=26)
248 api = frontend(apiobj, options={'details'})
249 result = api.details(napi.PlaceID(332), parented_places=True)
251 assert result.parented_rows == [
252 napi.AddressLine(place_id=1001, osm_object=('N', 5),
253 category=('place', 'house'),
254 names={'housenumber': '23'}, extratags={},
255 admin_level=15, fromarea=False, isaddress=True,
256 rank_address=30, distance=0.0),
260 @pytest.mark.parametrize('idobj', (napi.PlaceID(4924), napi.OsmID('W', 9928)))
261 def test_lookup_in_osmline(apiobj, frontend, idobj):
262 import_date = dt.datetime(2022, 12, 7, 14, 14, 46, 0)
263 apiobj.add_osmline(place_id=4924, osm_id=9928,
265 startnumber=1, endnumber=4, step=1,
266 country_code='gb', postcode='34425',
267 address={'city': 'Big'},
268 indexed_date=import_date,
269 geometry='LINESTRING(23 34, 23 35)')
271 api = frontend(apiobj, options={'details'})
272 result = api.details(idobj)
274 assert result is not None
276 assert result.source_table.name == 'OSMLINE'
277 assert result.category == ('place', 'houses')
278 assert result.centroid == (pytest.approx(23.0), pytest.approx(34.5))
280 assert result.place_id == 4924
281 assert result.parent_place_id == 12
282 assert result.linked_place_id is None
283 assert result.osm_object == ('W', 9928)
284 assert result.admin_level == 15
286 assert result.names is None
287 assert result.address == {'city': 'Big'}
288 assert result.extratags == {'startnumber': '1', 'endnumber': '4', 'step': '1'}
290 assert result.housenumber is None
291 assert result.postcode == '34425'
292 assert result.wikipedia is None
294 assert result.rank_search == 30
295 assert result.rank_address == 30
296 assert result.importance is None
298 assert result.country_code == 'gb'
299 assert result.indexed_date == import_date.replace(tzinfo=dt.timezone.utc)
301 assert result.address_rows is None
302 assert result.linked_rows is None
303 assert result.parented_rows is None
304 assert result.name_keywords is None
305 assert result.address_keywords is None
307 assert result.geometry == {'type': 'ST_LineString'}
310 def test_lookup_in_osmline_split_interpolation(apiobj, frontend):
311 apiobj.add_osmline(place_id=1000, osm_id=9,
312 startnumber=2, endnumber=4, step=1)
313 apiobj.add_osmline(place_id=1001, osm_id=9,
314 startnumber=6, endnumber=9, step=1)
315 apiobj.add_osmline(place_id=1002, osm_id=9,
316 startnumber=11, endnumber=20, step=1)
318 api = frontend(apiobj, options={'details'})
319 for i in range(1, 6):
320 result = api.details(napi.OsmID('W', 9, str(i)))
321 assert result.place_id == 1000
322 for i in range(7, 11):
323 result = api.details(napi.OsmID('W', 9, str(i)))
324 assert result.place_id == 1001
325 for i in range(12, 22):
326 result = api.details(napi.OsmID('W', 9, str(i)))
327 assert result.place_id == 1002
330 def test_lookup_osmline_with_address_details(apiobj, frontend):
331 apiobj.add_osmline(place_id=9000, osm_id=9,
332 startnumber=2, endnumber=4, step=1,
334 apiobj.add_placex(place_id=332, osm_type='W', osm_id=4,
335 class_='highway', type='residential', name='Street',
337 rank_search=27, rank_address=26)
338 apiobj.add_address_placex(332, fromarea=False, isaddress=False,
340 place_id=1000, osm_type='N', osm_id=3333,
341 class_='place', type='suburb', name='Smallplace',
342 country_code='pl', admin_level=13,
343 rank_search=24, rank_address=23)
344 apiobj.add_address_placex(332, fromarea=True, isaddress=True,
345 place_id=1001, osm_type='N', osm_id=3334,
346 class_='place', type='city', name='Bigplace',
348 rank_search=17, rank_address=16)
350 api = frontend(apiobj, options={'details'})
351 result = api.details(napi.PlaceID(9000), address_details=True)
353 assert result.address_rows == [
354 napi.AddressLine(place_id=332, osm_object=('W', 4),
355 category=('highway', 'residential'),
356 names={'name': 'Street'}, extratags={},
357 admin_level=15, fromarea=True, isaddress=True,
358 rank_address=26, distance=0.0,
359 local_name='Street'),
360 napi.AddressLine(place_id=1000, osm_object=('N', 3333),
361 category=('place', 'suburb'),
362 names={'name': 'Smallplace'}, extratags={},
363 admin_level=13, fromarea=False, isaddress=True,
364 rank_address=23, distance=0.0034,
365 local_name='Smallplace'),
366 napi.AddressLine(place_id=1001, osm_object=('N', 3334),
367 category=('place', 'city'),
368 names={'name': 'Bigplace'}, extratags={},
369 admin_level=15, fromarea=True, isaddress=True,
370 rank_address=16, distance=0.0,
371 local_name='Bigplace'),
372 napi.AddressLine(place_id=None, osm_object=None,
373 category=('place', 'country_code'),
374 names={'ref': 'pl'}, extratags={},
375 admin_level=None, fromarea=True, isaddress=False,
376 rank_address=4, distance=0.0)
380 def test_lookup_in_tiger(apiobj, frontend):
381 apiobj.add_tiger(place_id=4924,
383 startnumber=1, endnumber=4, step=1,
385 geometry='LINESTRING(23 34, 23 35)')
386 apiobj.add_placex(place_id=12,
387 category=('highway', 'residential'),
388 osm_type='W', osm_id=6601223,
389 geometry='LINESTRING(23 34, 23 35)')
391 api = frontend(apiobj, options={'details'})
392 result = api.details(napi.PlaceID(4924))
394 assert result is not None
396 assert result.source_table.name == 'TIGER'
397 assert result.category == ('place', 'houses')
398 assert result.centroid == (pytest.approx(23.0), pytest.approx(34.5))
400 assert result.place_id == 4924
401 assert result.parent_place_id == 12
402 assert result.linked_place_id is None
403 assert result.osm_object == ('W', 6601223)
404 assert result.admin_level == 15
406 assert result.names is None
407 assert result.address is None
408 assert result.extratags == {'startnumber': '1', 'endnumber': '4', 'step': '1'}
410 assert result.housenumber is None
411 assert result.postcode == '34425'
412 assert result.wikipedia is None
414 assert result.rank_search == 30
415 assert result.rank_address == 30
416 assert result.importance is None
418 assert result.country_code == 'us'
419 assert result.indexed_date is None
421 assert result.address_rows is None
422 assert result.linked_rows is None
423 assert result.parented_rows is None
424 assert result.name_keywords is None
425 assert result.address_keywords is None
427 assert result.geometry == {'type': 'ST_LineString'}
430 def test_lookup_tiger_with_address_details(apiobj, frontend):
431 apiobj.add_tiger(place_id=9000,
432 startnumber=2, endnumber=4, step=1,
434 apiobj.add_placex(place_id=332, osm_type='W', osm_id=4,
435 class_='highway', type='residential', name='Street',
437 rank_search=27, rank_address=26)
438 apiobj.add_address_placex(332, fromarea=False, isaddress=False,
440 place_id=1000, osm_type='N', osm_id=3333,
441 class_='place', type='suburb', name='Smallplace',
442 country_code='us', admin_level=13,
443 rank_search=24, rank_address=23)
444 apiobj.add_address_placex(332, fromarea=True, isaddress=True,
445 place_id=1001, osm_type='N', osm_id=3334,
446 class_='place', type='city', name='Bigplace',
448 rank_search=17, rank_address=16)
450 api = frontend(apiobj, options={'details'})
451 result = api.details(napi.PlaceID(9000), address_details=True)
453 assert result.address_rows == [
454 napi.AddressLine(place_id=332, osm_object=('W', 4),
455 category=('highway', 'residential'),
456 names={'name': 'Street'}, extratags={},
457 admin_level=15, fromarea=True, isaddress=True,
458 rank_address=26, distance=0.0,
459 local_name='Street'),
460 napi.AddressLine(place_id=1000, osm_object=('N', 3333),
461 category=('place', 'suburb'),
462 names={'name': 'Smallplace'}, extratags={},
463 admin_level=13, fromarea=False, isaddress=True,
464 rank_address=23, distance=0.0034,
465 local_name='Smallplace'),
466 napi.AddressLine(place_id=1001, osm_object=('N', 3334),
467 category=('place', 'city'),
468 names={'name': 'Bigplace'}, extratags={},
469 admin_level=15, fromarea=True, isaddress=True,
470 rank_address=16, distance=0.0,
471 local_name='Bigplace'),
472 napi.AddressLine(place_id=None, osm_object=None,
473 category=('place', 'country_code'),
474 names={'ref': 'us'}, extratags={},
475 admin_level=None, fromarea=True, isaddress=False,
476 rank_address=4, distance=0.0)
480 def test_lookup_in_postcode(apiobj, frontend):
481 import_date = dt.datetime(2022, 12, 7, 14, 14, 46, 0)
482 apiobj.add_postcode(place_id=554,
486 rank_search=20, rank_address=22,
487 indexed_date=import_date,
488 geometry='POINT(-9.45 5.6)')
490 api = frontend(apiobj, options={'details'})
491 result = api.details(napi.PlaceID(554))
493 assert result is not None
495 assert result.source_table.name == 'POSTCODE'
496 assert result.category == ('place', 'postcode')
497 assert result.centroid == (pytest.approx(-9.45), pytest.approx(5.6))
499 assert result.place_id == 554
500 assert result.parent_place_id == 152
501 assert result.linked_place_id is None
502 assert result.osm_object is None
503 assert result.admin_level == 15
505 assert result.names == {'ref': '34 425'}
506 assert result.address is None
507 assert result.extratags is None
509 assert result.housenumber is None
510 assert result.postcode is None
511 assert result.wikipedia is None
513 assert result.rank_search == 20
514 assert result.rank_address == 22
515 assert result.importance is None
517 assert result.country_code == 'gb'
518 assert result.indexed_date == import_date.replace(tzinfo=dt.timezone.utc)
520 assert result.address_rows is None
521 assert result.linked_rows is None
522 assert result.parented_rows is None
523 assert result.name_keywords is None
524 assert result.address_keywords is None
526 assert result.geometry == {'type': 'ST_Point'}
529 def test_lookup_postcode_with_address_details(apiobj, frontend):
530 apiobj.add_postcode(place_id=9000,
534 rank_search=25, rank_address=25)
535 apiobj.add_placex(place_id=332, osm_type='N', osm_id=3333,
536 class_='place', type='suburb', name='Smallplace',
537 country_code='gb', admin_level=13,
538 rank_search=24, rank_address=23)
539 apiobj.add_address_placex(332, fromarea=True, isaddress=True,
540 place_id=1001, osm_type='N', osm_id=3334,
541 class_='place', type='city', name='Bigplace',
543 rank_search=17, rank_address=16)
545 api = frontend(apiobj, options={'details'})
546 result = api.details(napi.PlaceID(9000), address_details=True)
548 assert result.address_rows == [
549 napi.AddressLine(place_id=9000, osm_object=None,
550 category=('place', 'postcode'),
551 names={'ref': '34 425'}, extratags={},
552 admin_level=15, fromarea=True, isaddress=True,
553 rank_address=25, distance=0.0,
554 local_name='34 425'),
555 napi.AddressLine(place_id=332, osm_object=('N', 3333),
556 category=('place', 'suburb'),
557 names={'name': 'Smallplace'}, extratags={},
558 admin_level=13, fromarea=True, isaddress=True,
559 rank_address=23, distance=0.0,
560 local_name='Smallplace'),
561 napi.AddressLine(place_id=1001, osm_object=('N', 3334),
562 category=('place', 'city'),
563 names={'name': 'Bigplace'}, extratags={},
564 admin_level=15, fromarea=True, isaddress=True,
565 rank_address=16, distance=0.0,
566 local_name='Bigplace'),
567 napi.AddressLine(place_id=None, osm_object=None,
568 category=('place', 'country_code'),
569 names={'ref': 'gb'}, extratags={},
570 admin_level=None, fromarea=True, isaddress=False,
571 rank_address=4, distance=0.0)
574 @pytest.mark.parametrize('objid', [napi.PlaceID(1736),
576 napi.OsmID('N', 55, 'amenity')])
577 def test_lookup_missing_object(apiobj, frontend, objid):
578 apiobj.add_placex(place_id=1, osm_type='N', osm_id=55,
579 class_='place', type='suburb')
581 api = frontend(apiobj, options={'details'})
582 assert api.details(objid) is None
585 @pytest.mark.parametrize('gtype', (napi.GeometryFormat.KML,
586 napi.GeometryFormat.SVG,
587 napi.GeometryFormat.TEXT))
588 def test_lookup_unsupported_geometry(apiobj, frontend, gtype):
589 apiobj.add_placex(place_id=332)
591 api = frontend(apiobj, options={'details'})
592 with pytest.raises(ValueError):
593 api.details(napi.PlaceID(332), geometry_output=gtype)