]> git.openstreetmap.org Git - nominatim.git/blob - test/python/api/test_api_details.py
Merge pull request #3260 from lonvia/improve-catgeory-search
[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) 2023 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 @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',
26                      housenumber='4',
27                      postcode='34425', wikipedia='en:Faa',
28                      rank_search=27, rank_address=26,
29                      importance=0.01,
30                      centroid=(23, 34),
31                      indexed_date=import_date,
32                      geometry='LINESTRING(23 34, 23.1 34, 23.1 34.1, 23 34)')
33
34     api = frontend(apiobj, options={'details'})
35     result = api.details(idobj)
36
37     assert result is not None
38
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))
42
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
48
49     assert result.names == {'name': 'Road'}
50     assert result.address == {'city': 'Barrow'}
51     assert result.extratags == {'surface': 'paved'}
52
53     assert result.housenumber == '4'
54     assert result.postcode == '34425'
55     assert result.wikipedia == 'en:Faa'
56
57     assert result.rank_search == 27
58     assert result.rank_address == 26
59     assert result.importance == pytest.approx(0.01)
60
61     assert result.country_code == 'gb'
62     assert result.indexed_date == import_date.replace(tzinfo=dt.timezone.utc)
63
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
69
70     assert result.geometry == {'type': 'ST_LineString'}
71
72
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',
77                      admin_level=15,
78                      rank_search=27, rank_address=26,
79                      centroid=(23, 34),
80                      indexed_date=import_date,
81                      geometry='LINESTRING(23 34, 23.1 34, 23.1 34.1, 23 34)')
82
83     api = frontend(apiobj, options={'details'})
84     result = api.details(napi.PlaceID(332))
85
86     assert result is not None
87
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))
91
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
97
98     assert result.names is None
99     assert result.address is None
100     assert result.extratags is None
101
102     assert result.housenumber is None
103     assert result.postcode is None
104     assert result.wikipedia is None
105
106     assert result.rank_search == 27
107     assert result.rank_address == 26
108     assert result.importance is None
109
110     assert result.country_code is None
111     assert result.indexed_date == import_date.replace(tzinfo=dt.timezone.utc)
112
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
118
119     assert result.geometry == {'type': 'ST_LineString'}
120
121
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)')
125
126     api = frontend(apiobj, options={'details'})
127     result = api.details(napi.PlaceID(332), geometry_output=napi.GeometryFormat.GEOJSON)
128
129     assert result.geometry == {'geojson': '{"type":"LineString","coordinates":[[23,34],[23.1,34]]}'}
130
131
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',
135                      country_code='pl',
136                      rank_search=27, rank_address=26)
137     apiobj.add_address_placex(332, fromarea=False, isaddress=False,
138                               distance=0.0034,
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',
146                               country_code='pl',
147                               rank_search=17, rank_address=16)
148
149     api = frontend(apiobj, options={'details'})
150     result = api.details(napi.PlaceID(332), address_details=True)
151
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)
176            ]
177
178
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)
184
185     api = frontend(apiobj, options={'details'})
186     result = api.details(napi.PlaceID(332), linked_places=True)
187
188     assert result.linked_rows == []
189
190
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)
204
205     api = frontend(apiobj, options={'details'})
206     result = api.details(napi.PlaceID(332), linked_places=True)
207
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),
219     ]
220
221
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)
227
228     api = frontend(apiobj, options={'details'})
229     result = api.details(napi.PlaceID(332), parented_places=True)
230
231     assert result.parented_rows == []
232
233
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)
247
248     api = frontend(apiobj, options={'details'})
249     result = api.details(napi.PlaceID(332), parented_places=True)
250
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),
257     ]
258
259
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,
264                        parent_place_id=12,
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)')
270
271     api = frontend(apiobj, options={'details'})
272     result = api.details(idobj)
273
274     assert result is not None
275
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))
279
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
285
286     assert result.names is None
287     assert result.address == {'city': 'Big'}
288     assert result.extratags == {'startnumber': '1', 'endnumber': '4', 'step': '1'}
289
290     assert result.housenumber is None
291     assert result.postcode == '34425'
292     assert result.wikipedia is None
293
294     assert result.rank_search == 30
295     assert result.rank_address == 30
296     assert result.importance is None
297
298     assert result.country_code == 'gb'
299     assert result.indexed_date == import_date.replace(tzinfo=dt.timezone.utc)
300
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
306
307     assert result.geometry == {'type': 'ST_LineString'}
308
309
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)
317
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
328
329
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,
333                        parent_place_id=332)
334     apiobj.add_placex(place_id=332, osm_type='W', osm_id=4,
335                      class_='highway', type='residential',  name='Street',
336                      country_code='pl',
337                      rank_search=27, rank_address=26)
338     apiobj.add_address_placex(332, fromarea=False, isaddress=False,
339                               distance=0.0034,
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',
347                               country_code='pl',
348                               rank_search=17, rank_address=16)
349
350     api = frontend(apiobj, options={'details'})
351     result = api.details(napi.PlaceID(9000), address_details=True)
352
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)
377            ]
378
379
380 def test_lookup_in_tiger(apiobj, frontend):
381     apiobj.add_tiger(place_id=4924,
382                      parent_place_id=12,
383                      startnumber=1, endnumber=4, step=1,
384                      postcode='34425',
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)')
390
391     api = frontend(apiobj, options={'details'})
392     result = api.details(napi.PlaceID(4924))
393
394     assert result is not None
395
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))
399
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
405
406     assert result.names is None
407     assert result.address is None
408     assert result.extratags == {'startnumber': '1', 'endnumber': '4', 'step': '1'}
409
410     assert result.housenumber is None
411     assert result.postcode == '34425'
412     assert result.wikipedia is None
413
414     assert result.rank_search == 30
415     assert result.rank_address == 30
416     assert result.importance is None
417
418     assert result.country_code == 'us'
419     assert result.indexed_date is None
420
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
426
427     assert result.geometry == {'type': 'ST_LineString'}
428
429
430 def test_lookup_tiger_with_address_details(apiobj, frontend):
431     apiobj.add_tiger(place_id=9000,
432                      startnumber=2, endnumber=4, step=1,
433                      parent_place_id=332)
434     apiobj.add_placex(place_id=332, osm_type='W', osm_id=4,
435                      class_='highway', type='residential',  name='Street',
436                      country_code='us',
437                      rank_search=27, rank_address=26)
438     apiobj.add_address_placex(332, fromarea=False, isaddress=False,
439                               distance=0.0034,
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',
447                               country_code='us',
448                               rank_search=17, rank_address=16)
449
450     api = frontend(apiobj, options={'details'})
451     result = api.details(napi.PlaceID(9000), address_details=True)
452
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)
477            ]
478
479
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,
483                         parent_place_id=152,
484                         postcode='34 425',
485                         country_code='gb',
486                         rank_search=20, rank_address=22,
487                         indexed_date=import_date,
488                         geometry='POINT(-9.45 5.6)')
489
490     api = frontend(apiobj, options={'details'})
491     result = api.details(napi.PlaceID(554))
492
493     assert result is not None
494
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))
498
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
504
505     assert result.names == {'ref': '34 425'}
506     assert result.address is None
507     assert result.extratags is None
508
509     assert result.housenumber is None
510     assert result.postcode is None
511     assert result.wikipedia is None
512
513     assert result.rank_search == 20
514     assert result.rank_address == 22
515     assert result.importance is None
516
517     assert result.country_code == 'gb'
518     assert result.indexed_date == import_date.replace(tzinfo=dt.timezone.utc)
519
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
525
526     assert result.geometry == {'type': 'ST_Point'}
527
528
529 def test_lookup_postcode_with_address_details(apiobj, frontend):
530     apiobj.add_postcode(place_id=9000,
531                         parent_place_id=332,
532                         postcode='34 425',
533                         country_code='gb',
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',
542                               country_code='gb',
543                               rank_search=17, rank_address=16)
544
545     api = frontend(apiobj, options={'details'})
546     result = api.details(napi.PlaceID(9000), address_details=True)
547
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)
572            ]
573
574 @pytest.mark.parametrize('objid', [napi.PlaceID(1736),
575                                    napi.OsmID('W', 55),
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')
580
581     api = frontend(apiobj, options={'details'})
582     assert api.details(objid) is None
583
584
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)
590
591     api = frontend(apiobj, options={'details'})
592     with pytest.raises(ValueError):
593         api.details(napi.PlaceID(332), geometry_output=gtype)