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