forked from hans/Nominatim
python: implement reverse lookup function
The implementation follows for most part the PHP code but introduces an additional layer parameter with which the kind of places to be returned can be restricted. This replaces the hard-coded exclusion lists.
This commit is contained in:
@@ -42,6 +42,9 @@ class APITester:
|
||||
if isinstance(name, str):
|
||||
name = {'name': name}
|
||||
|
||||
centroid = kw.get('centroid', (23.0, 34.0))
|
||||
geometry = kw.get('geometry', 'POINT(%f %f)' % centroid)
|
||||
|
||||
self.add_data('placex',
|
||||
{'place_id': kw.get('place_id', 1000),
|
||||
'osm_type': kw.get('osm_type', 'W'),
|
||||
@@ -61,10 +64,11 @@ class APITester:
|
||||
'rank_search': kw.get('rank_search', 30),
|
||||
'rank_address': kw.get('rank_address', 30),
|
||||
'importance': kw.get('importance'),
|
||||
'centroid': 'SRID=4326;POINT(%f %f)' % kw.get('centroid', (23.0, 34.0)),
|
||||
'centroid': 'SRID=4326;POINT(%f %f)' % centroid,
|
||||
'indexed_status': kw.get('indexed_status', 0),
|
||||
'indexed_date': kw.get('indexed_date',
|
||||
dt.datetime(2022, 12, 7, 14, 14, 46, 0)),
|
||||
'geometry': 'SRID=4326;' + kw.get('geometry', 'POINT(23 34)')})
|
||||
'geometry': 'SRID=4326;' + geometry})
|
||||
|
||||
|
||||
def add_address_placex(self, object_id, **kw):
|
||||
@@ -118,6 +122,13 @@ class APITester:
|
||||
'geometry': 'SRID=4326;' + kw.get('geometry', 'POINT(23 34)')})
|
||||
|
||||
|
||||
def add_country(self, country_code, geometry):
|
||||
self.add_data('country_grid',
|
||||
{'country_code': country_code,
|
||||
'area': 0.1,
|
||||
'geometry': 'SRID=4326;' + geometry})
|
||||
|
||||
|
||||
async def exec_async(self, sql, *args, **kwargs):
|
||||
async with self.api._async_api.begin() as conn:
|
||||
return await conn.execute(sql, *args, **kwargs)
|
||||
@@ -136,8 +147,9 @@ def apiobj(temp_db_with_extensions, temp_db_conn, monkeypatch):
|
||||
testapi = APITester()
|
||||
testapi.async_to_sync(testapi.create_tables())
|
||||
|
||||
SQLPreprocessor(temp_db_conn, testapi.api.config)\
|
||||
.run_sql_file(temp_db_conn, 'functions/address_lookup.sql')
|
||||
proc = SQLPreprocessor(temp_db_conn, testapi.api.config)
|
||||
proc.run_sql_file(temp_db_conn, 'functions/address_lookup.sql')
|
||||
proc.run_sql_file(temp_db_conn, 'functions/ranking.sql')
|
||||
|
||||
loglib.set_log_output('text')
|
||||
yield testapi
|
||||
|
||||
311
test/python/api/test_api_reverse.py
Normal file
311
test/python/api/test_api_reverse.py
Normal file
@@ -0,0 +1,311 @@
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
# This file is part of Nominatim. (https://nominatim.org)
|
||||
#
|
||||
# Copyright (C) 2023 by the Nominatim developer community.
|
||||
# For a full list of authors see the git log.
|
||||
"""
|
||||
Tests for reverse API call.
|
||||
|
||||
These tests make sure that all Python code is correct and executable.
|
||||
Functional tests can be found in the BDD test suite.
|
||||
"""
|
||||
import json
|
||||
|
||||
import pytest
|
||||
|
||||
import nominatim.api as napi
|
||||
|
||||
def test_reverse_rank_30(apiobj):
|
||||
apiobj.add_placex(place_id=223, class_='place', type='house',
|
||||
housenumber='1',
|
||||
centroid=(1.3, 0.7),
|
||||
geometry='POINT(1.3 0.7)')
|
||||
|
||||
result = apiobj.api.reverse((1.3, 0.7))
|
||||
|
||||
assert result is not None
|
||||
assert result.place_id == 223
|
||||
|
||||
|
||||
@pytest.mark.parametrize('country', ['de', 'us'])
|
||||
def test_reverse_street(apiobj, country):
|
||||
apiobj.add_placex(place_id=990, class_='highway', type='service',
|
||||
rank_search=27, rank_address=27,
|
||||
name = {'name': 'My Street'},
|
||||
centroid=(10.0, 10.0),
|
||||
country_code=country,
|
||||
geometry='LINESTRING(9.995 10, 10.005 10)')
|
||||
|
||||
assert apiobj.api.reverse((9.995, 10)).place_id == 990
|
||||
|
||||
|
||||
def test_reverse_ignore_unindexed(apiobj):
|
||||
apiobj.add_placex(place_id=223, class_='place', type='house',
|
||||
housenumber='1',
|
||||
indexed_status=2,
|
||||
centroid=(1.3, 0.7),
|
||||
geometry='POINT(1.3 0.7)')
|
||||
|
||||
result = apiobj.api.reverse((1.3, 0.7))
|
||||
|
||||
assert result is None
|
||||
|
||||
|
||||
@pytest.mark.parametrize('y,layer,place_id', [(0.7, napi.DataLayer.ADDRESS, 223),
|
||||
(0.70001, napi.DataLayer.POI, 224),
|
||||
(0.7, napi.DataLayer.ADDRESS | napi.DataLayer.POI, 224),
|
||||
(0.70001, napi.DataLayer.ADDRESS | napi.DataLayer.POI, 223),
|
||||
(0.7, napi.DataLayer.MANMADE, 225),
|
||||
(0.7, napi.DataLayer.RAILWAY, 226),
|
||||
(0.7, napi.DataLayer.NATURAL, 227),
|
||||
(0.70003, napi.DataLayer.MANMADE | napi.DataLayer.RAILWAY, 225),
|
||||
(0.70003, napi.DataLayer.MANMADE | napi.DataLayer.NATURAL, 225)])
|
||||
def test_reverse_rank_30_layers(apiobj, y, layer, place_id):
|
||||
apiobj.add_placex(place_id=223, class_='place', type='house',
|
||||
housenumber='1',
|
||||
rank_address=30,
|
||||
rank_search=30,
|
||||
centroid=(1.3, 0.70001))
|
||||
apiobj.add_placex(place_id=224, class_='amenity', type='toilet',
|
||||
rank_address=30,
|
||||
rank_search=30,
|
||||
centroid=(1.3, 0.7))
|
||||
apiobj.add_placex(place_id=225, class_='man_made', type='tower',
|
||||
rank_address=0,
|
||||
rank_search=30,
|
||||
centroid=(1.3, 0.70003))
|
||||
apiobj.add_placex(place_id=226, class_='railway', type='station',
|
||||
rank_address=0,
|
||||
rank_search=30,
|
||||
centroid=(1.3, 0.70004))
|
||||
apiobj.add_placex(place_id=227, class_='natural', type='cave',
|
||||
rank_address=0,
|
||||
rank_search=30,
|
||||
centroid=(1.3, 0.70005))
|
||||
|
||||
assert apiobj.api.reverse((1.3, y), layer=layer).place_id == place_id
|
||||
|
||||
|
||||
def test_reverse_poi_layer_with_no_pois(apiobj):
|
||||
apiobj.add_placex(place_id=223, class_='place', type='house',
|
||||
housenumber='1',
|
||||
rank_address=30,
|
||||
rank_search=30,
|
||||
centroid=(1.3, 0.70001))
|
||||
|
||||
assert apiobj.api.reverse((1.3, 0.70001), max_rank=29,
|
||||
layer=napi.DataLayer.POI) is None
|
||||
|
||||
|
||||
def test_reverse_housenumber_on_street(apiobj):
|
||||
apiobj.add_placex(place_id=990, class_='highway', type='service',
|
||||
rank_search=27, rank_address=27,
|
||||
name = {'name': 'My Street'},
|
||||
centroid=(10.0, 10.0),
|
||||
geometry='LINESTRING(9.995 10, 10.005 10)')
|
||||
apiobj.add_placex(place_id=991, class_='place', type='house',
|
||||
parent_place_id=990,
|
||||
rank_search=30, rank_address=30,
|
||||
housenumber='23',
|
||||
centroid=(10.0, 10.00001))
|
||||
|
||||
assert apiobj.api.reverse((10.0, 10.0), max_rank=30).place_id == 991
|
||||
assert apiobj.api.reverse((10.0, 10.0), max_rank=27).place_id == 990
|
||||
assert apiobj.api.reverse((10.0, 10.00001), max_rank=30).place_id == 991
|
||||
|
||||
|
||||
def test_reverse_housenumber_interpolation(apiobj):
|
||||
apiobj.add_placex(place_id=990, class_='highway', type='service',
|
||||
rank_search=27, rank_address=27,
|
||||
name = {'name': 'My Street'},
|
||||
centroid=(10.0, 10.0),
|
||||
geometry='LINESTRING(9.995 10, 10.005 10)')
|
||||
apiobj.add_placex(place_id=991, class_='place', type='house',
|
||||
parent_place_id=990,
|
||||
rank_search=30, rank_address=30,
|
||||
housenumber='23',
|
||||
centroid=(10.0, 10.00002))
|
||||
apiobj.add_osmline(place_id=992,
|
||||
parent_place_id=990,
|
||||
startnumber=1, endnumber=3, step=1,
|
||||
centroid=(10.0, 10.00001),
|
||||
geometry='LINESTRING(9.995 10.00001, 10.005 10.00001)')
|
||||
|
||||
assert apiobj.api.reverse((10.0, 10.0)).place_id == 992
|
||||
|
||||
|
||||
def test_reverse_tiger_number(apiobj):
|
||||
apiobj.add_placex(place_id=990, class_='highway', type='service',
|
||||
rank_search=27, rank_address=27,
|
||||
name = {'name': 'My Street'},
|
||||
centroid=(10.0, 10.0),
|
||||
country_code='us',
|
||||
geometry='LINESTRING(9.995 10, 10.005 10)')
|
||||
apiobj.add_tiger(place_id=992,
|
||||
parent_place_id=990,
|
||||
startnumber=1, endnumber=3, step=1,
|
||||
centroid=(10.0, 10.00001),
|
||||
geometry='LINESTRING(9.995 10.00001, 10.005 10.00001)')
|
||||
|
||||
assert apiobj.api.reverse((10.0, 10.0)).place_id == 992
|
||||
assert apiobj.api.reverse((10.0, 10.00001)).place_id == 992
|
||||
|
||||
|
||||
def test_reverse_low_zoom_address(apiobj):
|
||||
apiobj.add_placex(place_id=1001, class_='place', type='house',
|
||||
housenumber='1',
|
||||
rank_address=30,
|
||||
rank_search=30,
|
||||
centroid=(59.3, 80.70001))
|
||||
apiobj.add_placex(place_id=1002, class_='place', type='town',
|
||||
name={'name': 'Town'},
|
||||
rank_address=16,
|
||||
rank_search=16,
|
||||
centroid=(59.3, 80.70001),
|
||||
geometry="""POLYGON((59.3 80.70001, 59.3001 80.70001,
|
||||
59.3001 80.70101, 59.3 80.70101, 59.3 80.70001))""")
|
||||
|
||||
assert apiobj.api.reverse((59.30005, 80.7005)).place_id == 1001
|
||||
assert apiobj.api.reverse((59.30005, 80.7005), max_rank=18).place_id == 1002
|
||||
|
||||
|
||||
def test_reverse_place_node_in_area(apiobj):
|
||||
apiobj.add_placex(place_id=1002, class_='place', type='town',
|
||||
name={'name': 'Town Area'},
|
||||
rank_address=16,
|
||||
rank_search=16,
|
||||
centroid=(59.3, 80.70001),
|
||||
geometry="""POLYGON((59.3 80.70001, 59.3001 80.70001,
|
||||
59.3001 80.70101, 59.3 80.70101, 59.3 80.70001))""")
|
||||
apiobj.add_placex(place_id=1003, class_='place', type='suburb',
|
||||
name={'name': 'Suburb Point'},
|
||||
osm_type='N',
|
||||
rank_address=18,
|
||||
rank_search=18,
|
||||
centroid=(59.30004, 80.70055))
|
||||
|
||||
assert apiobj.api.reverse((59.30004, 80.70055)).place_id == 1003
|
||||
|
||||
|
||||
@pytest.mark.parametrize('layer,place_id', [(napi.DataLayer.MANMADE, 225),
|
||||
(napi.DataLayer.RAILWAY, 226),
|
||||
(napi.DataLayer.NATURAL, 227),
|
||||
(napi.DataLayer.MANMADE | napi.DataLayer.RAILWAY, 225),
|
||||
(napi.DataLayer.MANMADE | napi.DataLayer.NATURAL, 225)])
|
||||
def test_reverse_larger_area_layers(apiobj, layer, place_id):
|
||||
apiobj.add_placex(place_id=225, class_='man_made', type='dam',
|
||||
name={'name': 'Dam'},
|
||||
rank_address=0,
|
||||
rank_search=25,
|
||||
centroid=(1.3, 0.70003))
|
||||
apiobj.add_placex(place_id=226, class_='railway', type='yard',
|
||||
name={'name': 'Dam'},
|
||||
rank_address=0,
|
||||
rank_search=20,
|
||||
centroid=(1.3, 0.70004))
|
||||
apiobj.add_placex(place_id=227, class_='natural', type='spring',
|
||||
name={'name': 'Dam'},
|
||||
rank_address=0,
|
||||
rank_search=16,
|
||||
centroid=(1.3, 0.70005))
|
||||
|
||||
assert apiobj.api.reverse((1.3, 0.7), layer=layer).place_id == place_id
|
||||
|
||||
|
||||
def test_reverse_country_lookup_no_objects(apiobj):
|
||||
apiobj.add_country('xx', 'POLYGON((0 0, 0 1, 1 1, 1 0, 0 0))')
|
||||
|
||||
assert apiobj.api.reverse((0.5, 0.5)) is None
|
||||
|
||||
|
||||
@pytest.mark.parametrize('rank', [4, 30])
|
||||
def test_reverse_country_lookup_country_only(apiobj, rank):
|
||||
apiobj.add_country('xx', 'POLYGON((0 0, 0 1, 1 1, 1 0, 0 0))')
|
||||
apiobj.add_placex(place_id=225, class_='place', type='country',
|
||||
name={'name': 'My Country'},
|
||||
rank_address=4,
|
||||
rank_search=4,
|
||||
country_code='xx',
|
||||
centroid=(0.7, 0.7))
|
||||
|
||||
assert apiobj.api.reverse((0.5, 0.5), max_rank=rank).place_id == 225
|
||||
|
||||
|
||||
def test_reverse_country_lookup_place_node_inside(apiobj):
|
||||
apiobj.add_country('xx', 'POLYGON((0 0, 0 1, 1 1, 1 0, 0 0))')
|
||||
apiobj.add_placex(place_id=225, class_='place', type='state',
|
||||
osm_type='N',
|
||||
name={'name': 'My State'},
|
||||
rank_address=6,
|
||||
rank_search=6,
|
||||
country_code='xx',
|
||||
centroid=(0.5, 0.505))
|
||||
|
||||
assert apiobj.api.reverse((0.5, 0.5)).place_id == 225
|
||||
|
||||
|
||||
@pytest.mark.parametrize('gtype', list(napi.GeometryFormat))
|
||||
def test_reverse_geometry_output_placex(apiobj, gtype):
|
||||
apiobj.add_country('xx', 'POLYGON((0 0, 0 1, 1 1, 1 0, 0 0))')
|
||||
apiobj.add_placex(place_id=1001, class_='place', type='house',
|
||||
housenumber='1',
|
||||
rank_address=30,
|
||||
rank_search=30,
|
||||
centroid=(59.3, 80.70001))
|
||||
apiobj.add_placex(place_id=1003, class_='place', type='suburb',
|
||||
name={'name': 'Suburb Point'},
|
||||
osm_type='N',
|
||||
rank_address=18,
|
||||
rank_search=18,
|
||||
country_code='xx',
|
||||
centroid=(0.5, 0.5))
|
||||
|
||||
details = napi.LookupDetails(geometry_output=gtype)
|
||||
|
||||
assert apiobj.api.reverse((59.3, 80.70001), details=details).place_id == 1001
|
||||
assert apiobj.api.reverse((0.5, 0.5), details=details).place_id == 1003
|
||||
|
||||
|
||||
def test_reverse_simplified_geometry(apiobj):
|
||||
apiobj.add_placex(place_id=1001, class_='place', type='house',
|
||||
housenumber='1',
|
||||
rank_address=30,
|
||||
rank_search=30,
|
||||
centroid=(59.3, 80.70001))
|
||||
|
||||
details = napi.LookupDetails(geometry_output=napi.GeometryFormat.GEOJSON,
|
||||
geometry_simplification=0.1)
|
||||
assert apiobj.api.reverse((59.3, 80.70001), details=details).place_id == 1001
|
||||
|
||||
|
||||
def test_reverse_interpolation_geometry(apiobj):
|
||||
apiobj.add_osmline(place_id=992,
|
||||
parent_place_id=990,
|
||||
startnumber=1, endnumber=3, step=1,
|
||||
centroid=(10.0, 10.00001),
|
||||
geometry='LINESTRING(9.995 10.00001, 10.005 10.00001)')
|
||||
|
||||
details = napi.LookupDetails(geometry_output=napi.GeometryFormat.TEXT)
|
||||
assert apiobj.api.reverse((10.0, 10.0), details=details)\
|
||||
.geometry['text'] == 'POINT(10 10.00001)'
|
||||
|
||||
|
||||
def test_reverse_tiger_geometry(apiobj):
|
||||
apiobj.add_placex(place_id=990, class_='highway', type='service',
|
||||
rank_search=27, rank_address=27,
|
||||
name = {'name': 'My Street'},
|
||||
centroid=(10.0, 10.0),
|
||||
country_code='us',
|
||||
geometry='LINESTRING(9.995 10, 10.005 10)')
|
||||
apiobj.add_tiger(place_id=992,
|
||||
parent_place_id=990,
|
||||
startnumber=1, endnumber=3, step=1,
|
||||
centroid=(10.0, 10.00001),
|
||||
geometry='LINESTRING(9.995 10.00001, 10.005 10.00001)')
|
||||
|
||||
details = napi.LookupDetails(geometry_output=napi.GeometryFormat.GEOJSON)
|
||||
output = apiobj.api.reverse((10.0, 10.0), details=details).geometry['geojson']
|
||||
|
||||
assert json.loads(output) == {'coordinates': [10, 10.00001], 'type': 'Point'}
|
||||
|
||||
Reference in New Issue
Block a user