1 # SPDX-License-Identifier: GPL-3.0-or-later
 
   3 # This file is part of Nominatim. (https://nominatim.org)
 
   5 # Copyright (C) 2023 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 def test_reverse_rank_30(apiobj):
 
  20     apiobj.add_placex(place_id=223, class_='place', type='house',
 
  23                       geometry='POINT(1.3 0.7)')
 
  25     result = apiobj.api.reverse((1.3, 0.7))
 
  27     assert result is not None
 
  28     assert result.place_id == 223
 
  31 @pytest.mark.parametrize('country', ['de', 'us'])
 
  32 def test_reverse_street(apiobj, country):
 
  33     apiobj.add_placex(place_id=990, class_='highway', type='service',
 
  34                       rank_search=27, rank_address=27,
 
  35                       name = {'name': 'My Street'},
 
  36                       centroid=(10.0, 10.0),
 
  38                       geometry='LINESTRING(9.995 10, 10.005 10)')
 
  40     assert apiobj.api.reverse((9.995, 10)).place_id == 990
 
  43 def test_reverse_ignore_unindexed(apiobj):
 
  44     apiobj.add_placex(place_id=223, class_='place', type='house',
 
  48                       geometry='POINT(1.3 0.7)')
 
  50     result = apiobj.api.reverse((1.3, 0.7))
 
  55 @pytest.mark.parametrize('y,layer,place_id', [(0.7, napi.DataLayer.ADDRESS, 223),
 
  56                                               (0.70001, napi.DataLayer.POI, 224),
 
  57                                               (0.7, napi.DataLayer.ADDRESS | napi.DataLayer.POI, 224),
 
  58                                               (0.70001, napi.DataLayer.ADDRESS | napi.DataLayer.POI, 223),
 
  59                                               (0.7, napi.DataLayer.MANMADE, 225),
 
  60                                               (0.7, napi.DataLayer.RAILWAY, 226),
 
  61                                               (0.7, napi.DataLayer.NATURAL, 227),
 
  62                                               (0.70003, napi.DataLayer.MANMADE | napi.DataLayer.RAILWAY, 225),
 
  63                                               (0.70003, napi.DataLayer.MANMADE | napi.DataLayer.NATURAL, 225),
 
  64                                               (5, napi.DataLayer.ADDRESS, 229)])
 
  65 def test_reverse_rank_30_layers(apiobj, y, layer, place_id):
 
  66     apiobj.add_placex(place_id=223, class_='place', type='house',
 
  70                       centroid=(1.3, 0.70001))
 
  71     apiobj.add_placex(place_id=224, class_='amenity', type='toilet',
 
  75     apiobj.add_placex(place_id=225, class_='man_made', type='tower',
 
  78                       centroid=(1.3, 0.70003))
 
  79     apiobj.add_placex(place_id=226, class_='railway', type='station',
 
  82                       centroid=(1.3, 0.70004))
 
  83     apiobj.add_placex(place_id=227, class_='natural', type='cave',
 
  86                       centroid=(1.3, 0.70005))
 
  87     apiobj.add_placex(place_id=229, class_='place', type='house',
 
  88                       name={'addr:housename': 'Old Cottage'},
 
  93     assert apiobj.api.reverse((1.3, y), layers=layer).place_id == place_id
 
  96 def test_reverse_poi_layer_with_no_pois(apiobj):
 
  97     apiobj.add_placex(place_id=223, class_='place', type='house',
 
 101                       centroid=(1.3, 0.70001))
 
 103     assert apiobj.api.reverse((1.3, 0.70001), max_rank=29,
 
 104                               layers=napi.DataLayer.POI) is None
 
 107 def test_reverse_housenumber_on_street(apiobj):
 
 108     apiobj.add_placex(place_id=990, class_='highway', type='service',
 
 109                       rank_search=27, rank_address=27,
 
 110                       name = {'name': 'My Street'},
 
 111                       centroid=(10.0, 10.0),
 
 112                       geometry='LINESTRING(9.995 10, 10.005 10)')
 
 113     apiobj.add_placex(place_id=991, class_='place', type='house',
 
 115                       rank_search=30, rank_address=30,
 
 117                       centroid=(10.0, 10.00001))
 
 119     assert apiobj.api.reverse((10.0, 10.0), max_rank=30).place_id == 991
 
 120     assert apiobj.api.reverse((10.0, 10.0), max_rank=27).place_id == 990
 
 121     assert apiobj.api.reverse((10.0, 10.00001), max_rank=30).place_id == 991
 
 124 def test_reverse_housenumber_interpolation(apiobj):
 
 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',
 
 132                       rank_search=30, rank_address=30,
 
 134                       centroid=(10.0, 10.00002))
 
 135     apiobj.add_osmline(place_id=992,
 
 137                        startnumber=1, endnumber=3, step=1,
 
 138                        centroid=(10.0, 10.00001),
 
 139                        geometry='LINESTRING(9.995 10.00001, 10.005 10.00001)')
 
 141     assert apiobj.api.reverse((10.0, 10.0)).place_id == 992
 
 144 def test_reverse_housenumber_point_interpolation(apiobj):
 
 145     apiobj.add_placex(place_id=990, class_='highway', type='service',
 
 146                       rank_search=27, rank_address=27,
 
 147                       name = {'name': 'My Street'},
 
 148                       centroid=(10.0, 10.0),
 
 149                       geometry='LINESTRING(9.995 10, 10.005 10)')
 
 150     apiobj.add_osmline(place_id=992,
 
 152                        startnumber=42, endnumber=42, step=1,
 
 153                        centroid=(10.0, 10.00001),
 
 154                        geometry='POINT(10.0 10.00001)')
 
 156     res = apiobj.api.reverse((10.0, 10.0))
 
 157     assert res.place_id == 992
 
 158     assert res.housenumber == '42'
 
 161 def test_reverse_tiger_number(apiobj):
 
 162     apiobj.add_placex(place_id=990, class_='highway', type='service',
 
 163                       rank_search=27, rank_address=27,
 
 164                       name = {'name': 'My Street'},
 
 165                       centroid=(10.0, 10.0),
 
 167                       geometry='LINESTRING(9.995 10, 10.005 10)')
 
 168     apiobj.add_tiger(place_id=992,
 
 170                      startnumber=1, endnumber=3, step=1,
 
 171                      centroid=(10.0, 10.00001),
 
 172                      geometry='LINESTRING(9.995 10.00001, 10.005 10.00001)')
 
 174     assert apiobj.api.reverse((10.0, 10.0)).place_id == 992
 
 175     assert apiobj.api.reverse((10.0, 10.00001)).place_id == 992
 
 178 def test_reverse_point_tiger(apiobj):
 
 179     apiobj.add_placex(place_id=990, class_='highway', type='service',
 
 180                       rank_search=27, rank_address=27,
 
 181                       name = {'name': 'My Street'},
 
 182                       centroid=(10.0, 10.0),
 
 184                       geometry='LINESTRING(9.995 10, 10.005 10)')
 
 185     apiobj.add_tiger(place_id=992,
 
 187                      startnumber=1, endnumber=1, step=1,
 
 188                      centroid=(10.0, 10.00001),
 
 189                      geometry='POINT(10.0 10.00001)')
 
 191     res = apiobj.api.reverse((10.0, 10.0))
 
 192     assert res.place_id == 992
 
 193     assert res.housenumber == '1'
 
 196 def test_reverse_low_zoom_address(apiobj):
 
 197     apiobj.add_placex(place_id=1001, class_='place', type='house',
 
 201                       centroid=(59.3, 80.70001))
 
 202     apiobj.add_placex(place_id=1002, class_='place', type='town',
 
 203                       name={'name': 'Town'},
 
 206                       centroid=(59.3, 80.70001),
 
 207                       geometry="""POLYGON((59.3 80.70001, 59.3001 80.70001,
 
 208                                         59.3001 80.70101, 59.3 80.70101, 59.3 80.70001))""")
 
 210     assert apiobj.api.reverse((59.30005, 80.7005)).place_id == 1001
 
 211     assert apiobj.api.reverse((59.30005, 80.7005), max_rank=18).place_id == 1002
 
 214 def test_reverse_place_node_in_area(apiobj):
 
 215     apiobj.add_placex(place_id=1002, class_='place', type='town',
 
 216                       name={'name': 'Town Area'},
 
 219                       centroid=(59.3, 80.70001),
 
 220                       geometry="""POLYGON((59.3 80.70001, 59.3001 80.70001,
 
 221                                         59.3001 80.70101, 59.3 80.70101, 59.3 80.70001))""")
 
 222     apiobj.add_placex(place_id=1003, class_='place', type='suburb',
 
 223                       name={'name': 'Suburb Point'},
 
 227                       centroid=(59.30004, 80.70055))
 
 229     assert apiobj.api.reverse((59.30004, 80.70055)).place_id == 1003
 
 232 @pytest.mark.parametrize('layer,place_id', [(napi.DataLayer.MANMADE, 225),
 
 233                                             (napi.DataLayer.RAILWAY, 226),
 
 234                                             (napi.DataLayer.NATURAL, 227),
 
 235                                             (napi.DataLayer.MANMADE | napi.DataLayer.RAILWAY, 225),
 
 236                                             (napi.DataLayer.MANMADE | napi.DataLayer.NATURAL, 225)])
 
 237 def test_reverse_larger_area_layers(apiobj, layer, place_id):
 
 238     apiobj.add_placex(place_id=225, class_='man_made', type='dam',
 
 239                       name={'name': 'Dam'},
 
 242                       centroid=(1.3, 0.70003))
 
 243     apiobj.add_placex(place_id=226, class_='railway', type='yard',
 
 244                       name={'name': 'Dam'},
 
 247                       centroid=(1.3, 0.70004))
 
 248     apiobj.add_placex(place_id=227, class_='natural', type='spring',
 
 249                       name={'name': 'Dam'},
 
 252                       centroid=(1.3, 0.70005))
 
 254     assert apiobj.api.reverse((1.3, 0.7), layers=layer).place_id == place_id
 
 257 def test_reverse_country_lookup_no_objects(apiobj):
 
 258     apiobj.add_country('xx', 'POLYGON((0 0, 0 1, 1 1, 1 0, 0 0))')
 
 260     assert apiobj.api.reverse((0.5, 0.5)) is None
 
 263 @pytest.mark.parametrize('rank', [4, 30])
 
 264 def test_reverse_country_lookup_country_only(apiobj, rank):
 
 265     apiobj.add_country('xx', 'POLYGON((0 0, 0 1, 1 1, 1 0, 0 0))')
 
 266     apiobj.add_placex(place_id=225, class_='place', type='country',
 
 267                       name={'name': 'My Country'},
 
 273     assert apiobj.api.reverse((0.5, 0.5), max_rank=rank).place_id == 225
 
 276 def test_reverse_country_lookup_place_node_inside(apiobj):
 
 277     apiobj.add_country('xx', 'POLYGON((0 0, 0 1, 1 1, 1 0, 0 0))')
 
 278     apiobj.add_placex(place_id=225, class_='place', type='state',
 
 280                       name={'name': 'My State'},
 
 284                       centroid=(0.5, 0.505))
 
 286     assert apiobj.api.reverse((0.5, 0.5)).place_id == 225
 
 289 @pytest.mark.parametrize('gtype', list(napi.GeometryFormat))
 
 290 def test_reverse_geometry_output_placex(apiobj, gtype):
 
 291     apiobj.add_country('xx', 'POLYGON((0 0, 0 1, 1 1, 1 0, 0 0))')
 
 292     apiobj.add_placex(place_id=1001, class_='place', type='house',
 
 296                       centroid=(59.3, 80.70001))
 
 297     apiobj.add_placex(place_id=1003, class_='place', type='suburb',
 
 298                       name={'name': 'Suburb Point'},
 
 305     assert apiobj.api.reverse((59.3, 80.70001), geometry_output=gtype).place_id == 1001
 
 306     assert apiobj.api.reverse((0.5, 0.5), geometry_output=gtype).place_id == 1003
 
 309 def test_reverse_simplified_geometry(apiobj):
 
 310     apiobj.add_placex(place_id=1001, class_='place', type='house',
 
 314                       centroid=(59.3, 80.70001))
 
 316     details = dict(geometry_output=napi.GeometryFormat.GEOJSON,
 
 317                    geometry_simplification=0.1)
 
 318     assert apiobj.api.reverse((59.3, 80.70001), **details).place_id == 1001
 
 321 def test_reverse_interpolation_geometry(apiobj):
 
 322     apiobj.add_osmline(place_id=992,
 
 324                        startnumber=1, endnumber=3, step=1,
 
 325                        centroid=(10.0, 10.00001),
 
 326                        geometry='LINESTRING(9.995 10.00001, 10.005 10.00001)')
 
 328     assert apiobj.api.reverse((10.0, 10.0), geometry_output=napi.GeometryFormat.TEXT)\
 
 329                      .geometry['text'] == 'POINT(10 10.00001)'
 
 332 def test_reverse_tiger_geometry(apiobj):
 
 333     apiobj.add_placex(place_id=990, class_='highway', type='service',
 
 334                       rank_search=27, rank_address=27,
 
 335                       name = {'name': 'My Street'},
 
 336                       centroid=(10.0, 10.0),
 
 338                       geometry='LINESTRING(9.995 10, 10.005 10)')
 
 339     apiobj.add_tiger(place_id=992,
 
 341                      startnumber=1, endnumber=3, step=1,
 
 342                      centroid=(10.0, 10.00001),
 
 343                      geometry='LINESTRING(9.995 10.00001, 10.005 10.00001)')
 
 345     output = apiobj.api.reverse((10.0, 10.0),
 
 346                                 geometry_output=napi.GeometryFormat.GEOJSON).geometry['geojson']
 
 348     assert json.loads(output) == {'coordinates': [10, 10.00001], 'type': 'Point'}