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