]> git.openstreetmap.org Git - nominatim.git/blob - test/python/api/test_api_details.py
Merge remote-tracking branch 'upstream/master'
[nominatim.git] / test / python / api / test_api_details.py
1 # SPDX-License-Identifier: GPL-3.0-or-later
2 #
3 # This file is part of Nominatim. (https://nominatim.org)
4 #
5 # Copyright (C) 2025 by the Nominatim developer community.
6 # For a full list of authors see the git log.
7 """
8 Tests for details API call.
9 """
10 import datetime as dt
11
12 import pytest
13
14 import nominatim_api as napi
15
16
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',
27                       housenumber='4',
28                       postcode='34425', wikipedia='en:Faa',
29                       rank_search=27, rank_address=26,
30                       importance=0.01,
31                       centroid=(23, 34),
32                       indexed_date=import_date,
33                       geometry='LINESTRING(23 34, 23.1 34, 23.1 34.1, 23 34)')
34
35     api = frontend(apiobj, options={'details'})
36     result = api.details(idobj)
37
38     assert result is not None
39
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))
43
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
49
50     assert result.names == {'name': 'Road'}
51     assert result.address == {'city': 'Barrow'}
52     assert result.extratags == {'surface': 'paved'}
53
54     assert result.housenumber == '4'
55     assert result.postcode == '34425'
56     assert result.wikipedia == 'en:Faa'
57
58     assert result.rank_search == 27
59     assert result.rank_address == 26
60     assert result.importance == pytest.approx(0.01)
61
62     assert result.country_code == 'gb'
63     assert result.indexed_date == import_date.replace(tzinfo=dt.timezone.utc)
64
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
70
71     assert result.geometry == {'type': 'ST_LineString'}
72
73
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',
78                       admin_level=15,
79                       rank_search=27, rank_address=26,
80                       centroid=(23, 34),
81                       indexed_date=import_date,
82                       geometry='LINESTRING(23 34, 23.1 34, 23.1 34.1, 23 34)')
83
84     api = frontend(apiobj, options={'details'})
85     result = api.details(napi.PlaceID(332))
86
87     assert result is not None
88
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))
92
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
98
99     assert result.names is None
100     assert result.address is None
101     assert result.extratags is None
102
103     assert result.housenumber is None
104     assert result.postcode is None
105     assert result.wikipedia is None
106
107     assert result.rank_search == 27
108     assert result.rank_address == 26
109     assert result.importance is None
110
111     assert result.country_code is None
112     assert result.indexed_date == import_date.replace(tzinfo=dt.timezone.utc)
113
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
119
120     assert result.geometry == {'type': 'ST_LineString'}
121
122
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)')
126
127     api = frontend(apiobj, options={'details'})
128     result = api.details(napi.PlaceID(332), geometry_output=napi.GeometryFormat.GEOJSON)
129
130     assert result.geometry == {'geojson': '{"type":"LineString","coordinates":[[23,34],[23.1,34]]}'}
131
132
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',
136                       country_code='pl',
137                       rank_search=27, rank_address=26)
138     apiobj.add_address_placex(332, fromarea=False, isaddress=False,
139                               distance=0.0034,
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',
147                               country_code='pl',
148                               rank_search=17, rank_address=16)
149
150     api = frontend(apiobj, options={'details'})
151     result = api.details(napi.PlaceID(332), address_details=True)
152
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)
177            ]
178
179
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)
185
186     api = frontend(apiobj, options={'details'})
187     result = api.details(napi.PlaceID(332), linked_places=True)
188
189     assert result.linked_rows == []
190
191
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)
205
206     api = frontend(apiobj, options={'details'})
207     result = api.details(napi.PlaceID(332), linked_places=True)
208
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),
220     ]
221
222
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)
228
229     api = frontend(apiobj, options={'details'})
230     result = api.details(napi.PlaceID(332), parented_places=True)
231
232     assert result.parented_rows == []
233
234
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)
248
249     api = frontend(apiobj, options={'details'})
250     result = api.details(napi.PlaceID(332), parented_places=True)
251
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),
258     ]
259
260
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,
265                        parent_place_id=12,
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)')
271
272     api = frontend(apiobj, options={'details'})
273     result = api.details(idobj)
274
275     assert result is not None
276
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))
280
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
286
287     assert result.names is None
288     assert result.address == {'city': 'Big'}
289     assert result.extratags == {'startnumber': '1', 'endnumber': '4', 'step': '1'}
290
291     assert result.housenumber is None
292     assert result.postcode == '34425'
293     assert result.wikipedia is None
294
295     assert result.rank_search == 30
296     assert result.rank_address == 30
297     assert result.importance is None
298
299     assert result.country_code == 'gb'
300     assert result.indexed_date == import_date.replace(tzinfo=dt.timezone.utc)
301
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
307
308     assert result.geometry == {'type': 'ST_LineString'}
309
310
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)
318
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
329
330
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,
334                        parent_place_id=332)
335     apiobj.add_placex(place_id=332, osm_type='W', osm_id=4,
336                       class_='highway', type='residential',  name='Street',
337                       country_code='pl',
338                       rank_search=27, rank_address=26)
339     apiobj.add_address_placex(332, fromarea=False, isaddress=False,
340                               distance=0.0034,
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',
348                               country_code='pl',
349                               rank_search=17, rank_address=16)
350
351     api = frontend(apiobj, options={'details'})
352     result = api.details(napi.PlaceID(9000), address_details=True)
353
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)
378            ]
379
380
381 def test_lookup_in_tiger(apiobj, frontend):
382     apiobj.add_tiger(place_id=4924,
383                      parent_place_id=12,
384                      startnumber=1, endnumber=4, step=1,
385                      postcode='34425',
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)')
391
392     api = frontend(apiobj, options={'details'})
393     result = api.details(napi.PlaceID(4924))
394
395     assert result is not None
396
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))
400
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
406
407     assert result.names is None
408     assert result.address is None
409     assert result.extratags == {'startnumber': '1', 'endnumber': '4', 'step': '1'}
410
411     assert result.housenumber is None
412     assert result.postcode == '34425'
413     assert result.wikipedia is None
414
415     assert result.rank_search == 30
416     assert result.rank_address == 30
417     assert result.importance is None
418
419     assert result.country_code == 'us'
420     assert result.indexed_date is None
421
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
427
428     assert result.geometry == {'type': 'ST_LineString'}
429
430
431 def test_lookup_tiger_with_address_details(apiobj, frontend):
432     apiobj.add_tiger(place_id=9000,
433                      startnumber=2, endnumber=4, step=1,
434                      parent_place_id=332)
435     apiobj.add_placex(place_id=332, osm_type='W', osm_id=4,
436                       class_='highway', type='residential',  name='Street',
437                       country_code='us',
438                       rank_search=27, rank_address=26)
439     apiobj.add_address_placex(332, fromarea=False, isaddress=False,
440                               distance=0.0034,
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',
448                               country_code='us',
449                               rank_search=17, rank_address=16)
450
451     api = frontend(apiobj, options={'details'})
452     result = api.details(napi.PlaceID(9000), address_details=True)
453
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)
478            ]
479
480
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,
484                         parent_place_id=152,
485                         postcode='34 425',
486                         country_code='gb',
487                         rank_search=20, rank_address=22,
488                         indexed_date=import_date,
489                         geometry='POINT(-9.45 5.6)')
490
491     api = frontend(apiobj, options={'details'})
492     result = api.details(napi.PlaceID(554))
493
494     assert result is not None
495
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))
499
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
505
506     assert result.names == {'ref': '34 425'}
507     assert result.address is None
508     assert result.extratags is None
509
510     assert result.housenumber is None
511     assert result.postcode is None
512     assert result.wikipedia is None
513
514     assert result.rank_search == 20
515     assert result.rank_address == 22
516     assert result.importance is None
517
518     assert result.country_code == 'gb'
519     assert result.indexed_date == import_date.replace(tzinfo=dt.timezone.utc)
520
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
526
527     assert result.geometry == {'type': 'ST_Point'}
528
529
530 def test_lookup_postcode_with_address_details(apiobj, frontend):
531     apiobj.add_postcode(place_id=9000,
532                         parent_place_id=332,
533                         postcode='34 425',
534                         country_code='gb',
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',
543                               country_code='gb',
544                               rank_search=17, rank_address=16)
545
546     api = frontend(apiobj, options={'details'})
547     result = api.details(napi.PlaceID(9000), address_details=True)
548
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)
573            ]
574
575
576 @pytest.mark.parametrize('objid', [napi.PlaceID(1736),
577                                    napi.OsmID('W', 55),
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')
582
583     api = frontend(apiobj, options={'details'})
584     assert api.details(objid) is None
585
586
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)
592
593     api = frontend(apiobj, options={'details'})
594     with pytest.raises(ValueError):
595         api.details(napi.PlaceID(332), geometry_output=gtype)