1 # SPDX-License-Identifier: GPL-3.0-or-later
3 # This file is part of Nominatim. (https://nominatim.org)
5 # Copyright (C) 2024 by the Nominatim developer community.
6 # For a full list of authors see the git log.
8 Tests for reverse API call.
10 These tests make sure that all Python code is correct and executable.
11 Functional tests can be found in the BDD test suite.
17 import nominatim_api as napi
19 API_OPTIONS = {'reverse'}
21 def test_reverse_rank_30(apiobj, frontend):
22 apiobj.add_placex(place_id=223, class_='place', type='house',
25 geometry='POINT(1.3 0.7)')
27 api = frontend(apiobj, options=API_OPTIONS)
28 result = api.reverse((1.3, 0.7))
30 assert result is not None
31 assert result.place_id == 223
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),
41 geometry='LINESTRING(9.995 10, 10.005 10)')
43 api = frontend(apiobj, options=API_OPTIONS)
44 assert api.reverse((9.995, 10)).place_id == 990
47 def test_reverse_ignore_unindexed(apiobj, frontend):
48 apiobj.add_placex(place_id=223, class_='place', type='house',
52 geometry='POINT(1.3 0.7)')
54 api = frontend(apiobj, options=API_OPTIONS)
55 result = api.reverse((1.3, 0.7))
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, osm_type='N', class_='place', type='house',
75 centroid=(1.3, 0.70001))
76 apiobj.add_placex(place_id=224, osm_type='N', class_='amenity', type='toilet',
80 apiobj.add_placex(place_id=225, osm_type='N', class_='man_made', type='tower',
83 centroid=(1.3, 0.70003))
84 apiobj.add_placex(place_id=226, osm_type='N', class_='railway', type='station',
87 centroid=(1.3, 0.70004))
88 apiobj.add_placex(place_id=227, osm_type='N', class_='natural', type='cave',
91 centroid=(1.3, 0.70005))
92 apiobj.add_placex(place_id=229, class_='place', type='house',
93 name={'addr:housename': 'Old Cottage'},
98 api = frontend(apiobj, options=API_OPTIONS)
99 assert api.reverse((1.3, y), layers=layer).place_id == place_id
102 def test_reverse_poi_layer_with_no_pois(apiobj, frontend):
103 apiobj.add_placex(place_id=223, class_='place', type='house',
107 centroid=(1.3, 0.70001))
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
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',
123 rank_search=30, rank_address=30,
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,
135 centroid=(10.0, 1.00001))
137 params = {'geometry_output': napi.GeometryFormat.TEXT} if with_geom else {}
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
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',
155 rank_search=30, rank_address=30,
157 centroid=(10.0, 10.00002))
158 apiobj.add_osmline(place_id=992,
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)')
174 params = {'geometry_output': napi.GeometryFormat.TEXT} if with_geom else {}
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
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,
189 startnumber=42, endnumber=42, step=1,
190 centroid=(10.0, 10.00001),
191 geometry='POINT(10.0 10.00001)')
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'
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),
205 geometry='LINESTRING(9.995 10, 10.005 10)')
206 apiobj.add_tiger(place_id=992,
208 startnumber=1, endnumber=3, step=1,
209 centroid=(10.0, 10.00001),
210 geometry='LINESTRING(9.995 10.00001, 10.005 10.00001)')
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
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),
223 geometry='LINESTRING(9.995 10, 10.005 10)')
224 apiobj.add_tiger(place_id=992,
226 startnumber=1, endnumber=1, step=1,
227 centroid=(10.0, 10.00001),
228 geometry='POINT(10.0 10.00001)')
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'
236 def test_reverse_low_zoom_address(apiobj, frontend):
237 apiobj.add_placex(place_id=1001, class_='place', type='house',
241 centroid=(59.3, 80.70001))
242 apiobj.add_placex(place_id=1002, class_='place', type='town',
243 name={'name': 'Town'},
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))""")
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
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'},
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'},
268 centroid=(59.30004, 80.70055))
270 api = frontend(apiobj, options=API_OPTIONS)
271 assert api.reverse((59.30004, 80.70055)).place_id == 1003
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'},
284 centroid=(1.3, 0.70003))
285 apiobj.add_placex(place_id=226, class_='railway', type='yard',
286 name={'name': 'Dam'},
289 centroid=(1.3, 0.70004))
290 apiobj.add_placex(place_id=227, class_='natural', type='spring',
291 name={'name': 'Dam'},
294 centroid=(1.3, 0.70005))
296 api = frontend(apiobj, options=API_OPTIONS)
297 assert api.reverse((1.3, 0.7), layers=layer).place_id == place_id
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))')
303 api = frontend(apiobj, options=API_OPTIONS)
304 assert api.reverse((0.5, 0.5)) is None
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'},
319 params = {'max_rank': rank}
321 params['geometry_output'] = napi.GeometryFormat.TEXT
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
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',
334 name={'name': 'My State'},
338 centroid=(0.5, 0.505))
339 apiobj.add_placex(place_id=425, class_='place', type='state',
341 name={'name': 'Other State'},
345 centroid=(10.5, 0.505))
347 params = {'geometry_output': napi.GeometryFormat.KML} if with_geom else {}
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
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',
361 centroid=(59.3, 80.70001))
362 apiobj.add_placex(place_id=1003, class_='place', type='suburb',
363 name={'name': 'Suburb Point'},
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
375 def test_reverse_simplified_geometry(apiobj, frontend):
376 apiobj.add_placex(place_id=1001, class_='place', type='house',
380 centroid=(59.3, 80.70001))
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
388 def test_reverse_interpolation_geometry(apiobj, frontend):
389 apiobj.add_osmline(place_id=992,
391 startnumber=1, endnumber=3, step=1,
392 centroid=(10.0, 10.00001),
393 geometry='LINESTRING(9.995 10.00001, 10.005 10.00001)')
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)'
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),
406 geometry='LINESTRING(9.995 10, 10.005 10)')
407 apiobj.add_tiger(place_id=992,
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),
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)')
424 api = frontend(apiobj, options=API_OPTIONS)
426 params = {'geometry_output': napi.GeometryFormat.GEOJSON}
428 output = api.reverse((10.0, 10.0), **params)
429 assert json.loads(output.geometry['geojson']) == {'coordinates': [10, 10.00001], 'type': 'Point'}
431 output = api.reverse((11.0, 11.0), **params)
432 assert json.loads(output.geometry['geojson']) == {'coordinates': [11, 11.00001], 'type': 'Point'}