]> git.openstreetmap.org Git - nominatim.git/blob - test/python/api/test_api_reverse.py
enable flake for Python tests
[nominatim.git] / test / python / api / test_api_reverse.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 reverse API call.
9
10 These tests make sure that all Python code is correct and executable.
11 Functional tests can be found in the BDD test suite.
12 """
13 import json
14
15 import pytest
16
17 import nominatim_api as napi
18
19 API_OPTIONS = {'reverse'}
20
21
22 def test_reverse_rank_30(apiobj, frontend):
23     apiobj.add_placex(place_id=223, class_='place', type='house',
24                       housenumber='1',
25                       centroid=(1.3, 0.7),
26                       geometry='POINT(1.3 0.7)')
27
28     api = frontend(apiobj, options=API_OPTIONS)
29     result = api.reverse((1.3, 0.7))
30
31     assert result is not None
32     assert result.place_id == 223
33
34
35 @pytest.mark.parametrize('country', ['de', 'us'])
36 def test_reverse_street(apiobj, frontend, country):
37     apiobj.add_placex(place_id=990, class_='highway', type='service',
38                       rank_search=27, rank_address=27,
39                       name={'name': 'My Street'},
40                       centroid=(10.0, 10.0),
41                       country_code=country,
42                       geometry='LINESTRING(9.995 10, 10.005 10)')
43
44     api = frontend(apiobj, options=API_OPTIONS)
45     assert api.reverse((9.995, 10)).place_id == 990
46
47
48 def test_reverse_ignore_unindexed(apiobj, frontend):
49     apiobj.add_placex(place_id=223, class_='place', type='house',
50                       housenumber='1',
51                       indexed_status=2,
52                       centroid=(1.3, 0.7),
53                       geometry='POINT(1.3 0.7)')
54
55     api = frontend(apiobj, options=API_OPTIONS)
56     result = api.reverse((1.3, 0.7))
57
58     assert result is None
59
60
61 @pytest.mark.parametrize('y,layer,place_id',
62                          [(0.7, napi.DataLayer.ADDRESS, 223),
63                           (0.70001, napi.DataLayer.POI, 224),
64                           (0.7, napi.DataLayer.ADDRESS | napi.DataLayer.POI, 224),
65                           (0.70001, napi.DataLayer.ADDRESS | napi.DataLayer.POI, 223),
66                           (0.7, napi.DataLayer.MANMADE, 225),
67                           (0.7, napi.DataLayer.RAILWAY, 226),
68                           (0.7, napi.DataLayer.NATURAL, 227),
69                           (0.70003, napi.DataLayer.MANMADE | napi.DataLayer.RAILWAY, 225),
70                           (0.70003, napi.DataLayer.MANMADE | napi.DataLayer.NATURAL, 225),
71                           (5, napi.DataLayer.ADDRESS, 229)])
72 def test_reverse_rank_30_layers(apiobj, frontend, y, layer, place_id):
73     apiobj.add_placex(place_id=223, osm_type='N', class_='place', type='house',
74                       housenumber='1',
75                       rank_address=30,
76                       rank_search=30,
77                       centroid=(1.3, 0.70001))
78     apiobj.add_placex(place_id=224, osm_type='N', class_='amenity', type='toilet',
79                       rank_address=30,
80                       rank_search=30,
81                       centroid=(1.3, 0.7))
82     apiobj.add_placex(place_id=225, osm_type='N', class_='man_made', type='tower',
83                       rank_address=0,
84                       rank_search=30,
85                       centroid=(1.3, 0.70003))
86     apiobj.add_placex(place_id=226, osm_type='N', class_='railway', type='station',
87                       rank_address=0,
88                       rank_search=30,
89                       centroid=(1.3, 0.70004))
90     apiobj.add_placex(place_id=227, osm_type='N', class_='natural', type='cave',
91                       rank_address=0,
92                       rank_search=30,
93                       centroid=(1.3, 0.70005))
94     apiobj.add_placex(place_id=229, class_='place', type='house',
95                       name={'addr:housename': 'Old Cottage'},
96                       rank_address=30,
97                       rank_search=30,
98                       centroid=(1.3, 5))
99
100     api = frontend(apiobj, options=API_OPTIONS)
101     assert api.reverse((1.3, y), layers=layer).place_id == place_id
102
103
104 def test_reverse_poi_layer_with_no_pois(apiobj, frontend):
105     apiobj.add_placex(place_id=223, class_='place', type='house',
106                       housenumber='1',
107                       rank_address=30,
108                       rank_search=30,
109                       centroid=(1.3, 0.70001))
110
111     api = frontend(apiobj, options=API_OPTIONS)
112     assert api.reverse((1.3, 0.70001), max_rank=29,
113                        layers=napi.DataLayer.POI) is None
114
115
116 @pytest.mark.parametrize('with_geom', [True, False])
117 def test_reverse_housenumber_on_street(apiobj, frontend, with_geom):
118     apiobj.add_placex(place_id=990, class_='highway', type='service',
119                       rank_search=27, rank_address=27,
120                       name={'name': 'My Street'},
121                       centroid=(10.0, 10.0),
122                       geometry='LINESTRING(9.995 10, 10.005 10)')
123     apiobj.add_placex(place_id=991, class_='place', type='house',
124                       parent_place_id=990,
125                       rank_search=30, rank_address=30,
126                       housenumber='23',
127                       centroid=(10.0, 10.00001))
128     apiobj.add_placex(place_id=1990, class_='highway', type='service',
129                       rank_search=27, rank_address=27,
130                       name={'name': 'Other Street'},
131                       centroid=(10.0, 1.0),
132                       geometry='LINESTRING(9.995 1, 10.005 1)')
133     apiobj.add_placex(place_id=1991, class_='place', type='house',
134                       parent_place_id=1990,
135                       rank_search=30, rank_address=30,
136                       housenumber='23',
137                       centroid=(10.0, 1.00001))
138
139     params = {'geometry_output': napi.GeometryFormat.TEXT} if with_geom else {}
140
141     api = frontend(apiobj, options=API_OPTIONS)
142     assert api.reverse((10.0, 10.0), max_rank=30, **params).place_id == 991
143     assert api.reverse((10.0, 10.0), max_rank=27).place_id == 990
144     assert api.reverse((10.0, 10.00001), max_rank=30).place_id == 991
145     assert api.reverse((10.0, 1.0), **params).place_id == 1991
146
147
148 @pytest.mark.parametrize('with_geom', [True, False])
149 def test_reverse_housenumber_interpolation(apiobj, frontend, with_geom):
150     apiobj.add_placex(place_id=990, class_='highway', type='service',
151                       rank_search=27, rank_address=27,
152                       name={'name': 'My Street'},
153                       centroid=(10.0, 10.0),
154                       geometry='LINESTRING(9.995 10, 10.005 10)')
155     apiobj.add_placex(place_id=991, class_='place', type='house',
156                       parent_place_id=990,
157                       rank_search=30, rank_address=30,
158                       housenumber='23',
159                       centroid=(10.0, 10.00002))
160     apiobj.add_osmline(place_id=992,
161                        parent_place_id=990,
162                        startnumber=1, endnumber=3, step=1,
163                        centroid=(10.0, 10.00001),
164                        geometry='LINESTRING(9.995 10.00001, 10.005 10.00001)')
165     apiobj.add_placex(place_id=1990, class_='highway', type='service',
166                       rank_search=27, rank_address=27,
167                       name={'name': 'Other Street'},
168                       centroid=(10.0, 20.0),
169                       geometry='LINESTRING(9.995 20, 10.005 20)')
170     apiobj.add_osmline(place_id=1992,
171                        parent_place_id=1990,
172                        startnumber=1, endnumber=3, step=1,
173                        centroid=(10.0, 20.00001),
174                        geometry='LINESTRING(9.995 20.00001, 10.005 20.00001)')
175
176     params = {'geometry_output': napi.GeometryFormat.TEXT} if with_geom else {}
177
178     api = frontend(apiobj, options=API_OPTIONS)
179     assert api.reverse((10.0, 10.0), **params).place_id == 992
180     assert api.reverse((10.0, 20.0), **params).place_id == 1992
181
182
183 def test_reverse_housenumber_point_interpolation(apiobj, frontend):
184     apiobj.add_placex(place_id=990, class_='highway', type='service',
185                       rank_search=27, rank_address=27,
186                       name={'name': 'My Street'},
187                       centroid=(10.0, 10.0),
188                       geometry='LINESTRING(9.995 10, 10.005 10)')
189     apiobj.add_osmline(place_id=992,
190                        parent_place_id=990,
191                        startnumber=42, endnumber=42, step=1,
192                        centroid=(10.0, 10.00001),
193                        geometry='POINT(10.0 10.00001)')
194
195     api = frontend(apiobj, options=API_OPTIONS)
196     res = api.reverse((10.0, 10.0))
197     assert res.place_id == 992
198     assert res.housenumber == '42'
199
200
201 def test_reverse_tiger_number(apiobj, frontend):
202     apiobj.add_placex(place_id=990, class_='highway', type='service',
203                       rank_search=27, rank_address=27,
204                       name={'name': 'My Street'},
205                       centroid=(10.0, 10.0),
206                       country_code='us',
207                       geometry='LINESTRING(9.995 10, 10.005 10)')
208     apiobj.add_tiger(place_id=992,
209                      parent_place_id=990,
210                      startnumber=1, endnumber=3, step=1,
211                      centroid=(10.0, 10.00001),
212                      geometry='LINESTRING(9.995 10.00001, 10.005 10.00001)')
213
214     api = frontend(apiobj, options=API_OPTIONS)
215     assert api.reverse((10.0, 10.0)).place_id == 992
216     assert api.reverse((10.0, 10.00001)).place_id == 992
217
218
219 def test_reverse_point_tiger(apiobj, frontend):
220     apiobj.add_placex(place_id=990, class_='highway', type='service',
221                       rank_search=27, rank_address=27,
222                       name={'name': 'My Street'},
223                       centroid=(10.0, 10.0),
224                       country_code='us',
225                       geometry='LINESTRING(9.995 10, 10.005 10)')
226     apiobj.add_tiger(place_id=992,
227                      parent_place_id=990,
228                      startnumber=1, endnumber=1, step=1,
229                      centroid=(10.0, 10.00001),
230                      geometry='POINT(10.0 10.00001)')
231
232     api = frontend(apiobj, options=API_OPTIONS)
233     res = api.reverse((10.0, 10.0))
234     assert res.place_id == 992
235     assert res.housenumber == '1'
236
237
238 def test_reverse_low_zoom_address(apiobj, frontend):
239     apiobj.add_placex(place_id=1001, class_='place', type='house',
240                       housenumber='1',
241                       rank_address=30,
242                       rank_search=30,
243                       centroid=(59.3, 80.70001))
244     apiobj.add_placex(place_id=1002, class_='place', type='town',
245                       name={'name': 'Town'},
246                       rank_address=16,
247                       rank_search=16,
248                       centroid=(59.3, 80.70001),
249                       geometry="""POLYGON((59.3 80.70001, 59.3001 80.70001,
250                                         59.3001 80.70101, 59.3 80.70101, 59.3 80.70001))""")
251
252     api = frontend(apiobj, options=API_OPTIONS)
253     assert api.reverse((59.30005, 80.7005)).place_id == 1001
254     assert api.reverse((59.30005, 80.7005), max_rank=18).place_id == 1002
255
256
257 def test_reverse_place_node_in_area(apiobj, frontend):
258     apiobj.add_placex(place_id=1002, class_='place', type='town',
259                       name={'name': 'Town Area'},
260                       rank_address=16,
261                       rank_search=16,
262                       centroid=(59.3, 80.70001),
263                       geometry="""POLYGON((59.3 80.70001, 59.3001 80.70001,
264                                         59.3001 80.70101, 59.3 80.70101, 59.3 80.70001))""")
265     apiobj.add_placex(place_id=1003, class_='place', type='suburb',
266                       name={'name': 'Suburb Point'},
267                       osm_type='N',
268                       rank_address=18,
269                       rank_search=18,
270                       centroid=(59.30004, 80.70055))
271
272     api = frontend(apiobj, options=API_OPTIONS)
273     assert api.reverse((59.30004, 80.70055)).place_id == 1003
274
275
276 @pytest.mark.parametrize('layer,place_id', [(napi.DataLayer.MANMADE, 225),
277                                             (napi.DataLayer.RAILWAY, 226),
278                                             (napi.DataLayer.NATURAL, 227),
279                                             (napi.DataLayer.MANMADE | napi.DataLayer.RAILWAY, 225),
280                                             (napi.DataLayer.MANMADE | napi.DataLayer.NATURAL, 225)])
281 def test_reverse_larger_area_layers(apiobj, frontend, layer, place_id):
282     apiobj.add_placex(place_id=225, class_='man_made', type='dam',
283                       name={'name': 'Dam'},
284                       rank_address=0,
285                       rank_search=25,
286                       centroid=(1.3, 0.70003))
287     apiobj.add_placex(place_id=226, class_='railway', type='yard',
288                       name={'name': 'Dam'},
289                       rank_address=0,
290                       rank_search=20,
291                       centroid=(1.3, 0.70004))
292     apiobj.add_placex(place_id=227, class_='natural', type='spring',
293                       name={'name': 'Dam'},
294                       rank_address=0,
295                       rank_search=16,
296                       centroid=(1.3, 0.70005))
297
298     api = frontend(apiobj, options=API_OPTIONS)
299     assert api.reverse((1.3, 0.7), layers=layer).place_id == place_id
300
301
302 def test_reverse_country_lookup_no_objects(apiobj, frontend):
303     apiobj.add_country('xx', 'POLYGON((0 0, 0 1, 1 1, 1 0, 0 0))')
304
305     api = frontend(apiobj, options=API_OPTIONS)
306     assert api.reverse((0.5, 0.5)) is None
307
308
309 @pytest.mark.parametrize('rank', [4, 30])
310 @pytest.mark.parametrize('with_geom', [True, False])
311 def test_reverse_country_lookup_country_only(apiobj, frontend, rank, with_geom):
312     apiobj.add_country('xx', 'POLYGON((0 0, 0 1, 1 1, 1 0, 0 0))')
313     apiobj.add_country('yy', 'POLYGON((10 0, 10 1, 11 1, 11 0, 10 0))')
314     apiobj.add_placex(place_id=225, class_='place', type='country',
315                       name={'name': 'My Country'},
316                       rank_address=4,
317                       rank_search=4,
318                       country_code='xx',
319                       centroid=(0.7, 0.7))
320
321     params = {'max_rank': rank}
322     if with_geom:
323         params['geometry_output'] = napi.GeometryFormat.TEXT
324
325     api = frontend(apiobj, options=API_OPTIONS)
326     assert api.reverse((0.5, 0.5), **params).place_id == 225
327     assert api.reverse((10.5, 0.5), **params) is None
328
329
330 @pytest.mark.parametrize('with_geom', [True, False])
331 def test_reverse_country_lookup_place_node_inside(apiobj, frontend, with_geom):
332     apiobj.add_country('xx', 'POLYGON((0 0, 0 1, 1 1, 1 0, 0 0))')
333     apiobj.add_country('yy', 'POLYGON((10 0, 10 1, 11 1, 11 0, 10 0))')
334     apiobj.add_placex(place_id=225, class_='place', type='state',
335                       osm_type='N',
336                       name={'name': 'My State'},
337                       rank_address=6,
338                       rank_search=6,
339                       country_code='xx',
340                       centroid=(0.5, 0.505))
341     apiobj.add_placex(place_id=425, class_='place', type='state',
342                       osm_type='N',
343                       name={'name': 'Other State'},
344                       rank_address=6,
345                       rank_search=6,
346                       country_code='yy',
347                       centroid=(10.5, 0.505))
348
349     params = {'geometry_output': napi.GeometryFormat.KML} if with_geom else {}
350
351     api = frontend(apiobj, options=API_OPTIONS)
352     assert api.reverse((0.5, 0.5), **params).place_id == 225
353     assert api.reverse((10.5, 0.5), **params).place_id == 425
354
355
356 @pytest.mark.parametrize('gtype', list(napi.GeometryFormat))
357 def test_reverse_geometry_output_placex(apiobj, frontend, gtype):
358     apiobj.add_country('xx', 'POLYGON((0 0, 0 1, 1 1, 1 0, 0 0))')
359     apiobj.add_placex(place_id=1001, class_='place', type='house',
360                       housenumber='1',
361                       rank_address=30,
362                       rank_search=30,
363                       centroid=(59.3, 80.70001))
364     apiobj.add_placex(place_id=1003, class_='place', type='suburb',
365                       name={'name': 'Suburb Point'},
366                       osm_type='N',
367                       rank_address=18,
368                       rank_search=18,
369                       country_code='xx',
370                       centroid=(0.5, 0.5))
371
372     api = frontend(apiobj, options=API_OPTIONS)
373     assert api.reverse((59.3, 80.70001), geometry_output=gtype).place_id == 1001
374     assert api.reverse((0.5, 0.5), geometry_output=gtype).place_id == 1003
375
376
377 def test_reverse_simplified_geometry(apiobj, frontend):
378     apiobj.add_placex(place_id=1001, class_='place', type='house',
379                       housenumber='1',
380                       rank_address=30,
381                       rank_search=30,
382                       centroid=(59.3, 80.70001))
383
384     api = frontend(apiobj, options=API_OPTIONS)
385     details = dict(geometry_output=napi.GeometryFormat.GEOJSON,
386                    geometry_simplification=0.1)
387     assert api.reverse((59.3, 80.70001), **details).place_id == 1001
388
389
390 def test_reverse_interpolation_geometry(apiobj, frontend):
391     apiobj.add_osmline(place_id=992,
392                        parent_place_id=990,
393                        startnumber=1, endnumber=3, step=1,
394                        centroid=(10.0, 10.00001),
395                        geometry='LINESTRING(9.995 10.00001, 10.005 10.00001)')
396
397     api = frontend(apiobj, options=API_OPTIONS)
398     result = api.reverse((10.0, 10.0), geometry_output=napi.GeometryFormat.TEXT)
399
400     assert result.geometry['text'] == 'POINT(10 10.00001)'
401
402
403 def test_reverse_tiger_geometry(apiobj, frontend):
404     apiobj.add_placex(place_id=990, class_='highway', type='service',
405                       rank_search=27, rank_address=27,
406                       name={'name': 'My Street'},
407                       centroid=(10.0, 10.0),
408                       country_code='us',
409                       geometry='LINESTRING(9.995 10, 10.005 10)')
410     apiobj.add_tiger(place_id=992,
411                      parent_place_id=990,
412                      startnumber=1, endnumber=3, step=1,
413                      centroid=(10.0, 10.00001),
414                      geometry='LINESTRING(9.995 10.00001, 10.005 10.00001)')
415     apiobj.add_placex(place_id=1000, class_='highway', type='service',
416                       rank_search=27, rank_address=27,
417                       name={'name': 'My Street'},
418                       centroid=(11.0, 11.0),
419                       country_code='us',
420                       geometry='LINESTRING(10.995 11, 11.005 11)')
421     apiobj.add_tiger(place_id=1001,
422                      parent_place_id=1000,
423                      startnumber=1, endnumber=3, step=1,
424                      centroid=(11.0, 11.00001),
425                      geometry='LINESTRING(10.995 11.00001, 11.005 11.00001)')
426
427     api = frontend(apiobj, options=API_OPTIONS)
428
429     params = {'geometry_output': napi.GeometryFormat.GEOJSON}
430
431     output = api.reverse((10.0, 10.0), **params)
432     assert json.loads(output.geometry['geojson']) \
433         == {'coordinates': [10, 10.00001], 'type': 'Point'}
434
435     output = api.reverse((11.0, 11.0), **params)
436     assert json.loads(output.geometry['geojson']) \
437         == {'coordinates': [11, 11.00001], 'type': 'Point'}