mirror of
https://github.com/osm-search/Nominatim.git
synced 2026-02-16 15:47:58 +00:00
add a WKB decoder for the Point class
This allows to return point geometries from the database and makes the SQL a bit simpler.
This commit is contained in:
@@ -46,8 +46,7 @@ async def find_in_placex(conn: SearchConnection, place: ntyp.PlaceRef,
|
|||||||
t.c.importance, t.c.wikipedia, t.c.indexed_date,
|
t.c.importance, t.c.wikipedia, t.c.indexed_date,
|
||||||
t.c.parent_place_id, t.c.rank_address, t.c.rank_search,
|
t.c.parent_place_id, t.c.rank_address, t.c.rank_search,
|
||||||
t.c.linked_place_id,
|
t.c.linked_place_id,
|
||||||
sa.func.ST_X(t.c.centroid).label('x'),
|
t.c.centroid,
|
||||||
sa.func.ST_Y(t.c.centroid).label('y'),
|
|
||||||
_select_column_geometry(t.c.geometry, details.geometry_output))
|
_select_column_geometry(t.c.geometry, details.geometry_output))
|
||||||
|
|
||||||
if isinstance(place, ntyp.PlaceID):
|
if isinstance(place, ntyp.PlaceID):
|
||||||
@@ -76,8 +75,7 @@ async def find_in_osmline(conn: SearchConnection, place: ntyp.PlaceRef,
|
|||||||
sql = sa.select(t.c.place_id, t.c.osm_id, t.c.parent_place_id,
|
sql = sa.select(t.c.place_id, t.c.osm_id, t.c.parent_place_id,
|
||||||
t.c.indexed_date, t.c.startnumber, t.c.endnumber,
|
t.c.indexed_date, t.c.startnumber, t.c.endnumber,
|
||||||
t.c.step, t.c.address, t.c.postcode, t.c.country_code,
|
t.c.step, t.c.address, t.c.postcode, t.c.country_code,
|
||||||
sa.func.ST_X(sa.func.ST_Centroid(t.c.linegeo)).label('x'),
|
t.c.linegeo.ST_Centroid().label('centroid'),
|
||||||
sa.func.ST_Y(sa.func.ST_Centroid(t.c.linegeo)).label('y'),
|
|
||||||
_select_column_geometry(t.c.linegeo, details.geometry_output))
|
_select_column_geometry(t.c.linegeo, details.geometry_output))
|
||||||
|
|
||||||
if isinstance(place, ntyp.PlaceID):
|
if isinstance(place, ntyp.PlaceID):
|
||||||
@@ -106,8 +104,7 @@ async def find_in_tiger(conn: SearchConnection, place: ntyp.PlaceRef,
|
|||||||
sql = sa.select(t.c.place_id, t.c.parent_place_id,
|
sql = sa.select(t.c.place_id, t.c.parent_place_id,
|
||||||
t.c.startnumber, t.c.endnumber, t.c.step,
|
t.c.startnumber, t.c.endnumber, t.c.step,
|
||||||
t.c.postcode,
|
t.c.postcode,
|
||||||
sa.func.ST_X(sa.func.ST_Centroid(t.c.linegeo)).label('x'),
|
t.c.linegeo.ST_Centroid().label('centroid'),
|
||||||
sa.func.ST_Y(sa.func.ST_Centroid(t.c.linegeo)).label('y'),
|
|
||||||
_select_column_geometry(t.c.linegeo, details.geometry_output))
|
_select_column_geometry(t.c.linegeo, details.geometry_output))
|
||||||
|
|
||||||
if isinstance(place, ntyp.PlaceID):
|
if isinstance(place, ntyp.PlaceID):
|
||||||
@@ -128,8 +125,7 @@ async def find_in_postcode(conn: SearchConnection, place: ntyp.PlaceRef,
|
|||||||
sql = sa.select(t.c.place_id, t.c.parent_place_id,
|
sql = sa.select(t.c.place_id, t.c.parent_place_id,
|
||||||
t.c.rank_search, t.c.rank_address,
|
t.c.rank_search, t.c.rank_address,
|
||||||
t.c.indexed_date, t.c.postcode, t.c.country_code,
|
t.c.indexed_date, t.c.postcode, t.c.country_code,
|
||||||
sa.func.ST_X(t.c.geometry).label('x'),
|
t.c.geometry.label('centroid'),
|
||||||
sa.func.ST_Y(t.c.geometry).label('y'),
|
|
||||||
_select_column_geometry(t.c.geometry, details.geometry_output))
|
_select_column_geometry(t.c.geometry, details.geometry_output))
|
||||||
|
|
||||||
if isinstance(place, ntyp.PlaceID):
|
if isinstance(place, ntyp.PlaceID):
|
||||||
|
|||||||
@@ -132,13 +132,6 @@ class SearchResult:
|
|||||||
return self.importance or (0.7500001 - (self.rank_search/40.0))
|
return self.importance or (0.7500001 - (self.rank_search/40.0))
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=consider-using-f-string
|
|
||||||
def centroid_as_geojson(self) -> str:
|
|
||||||
""" Get the centroid in GeoJSON format.
|
|
||||||
"""
|
|
||||||
return '{"type": "Point","coordinates": [%f, %f]}' % self.centroid
|
|
||||||
|
|
||||||
|
|
||||||
def _filter_geometries(row: SaRow) -> Dict[str, str]:
|
def _filter_geometries(row: SaRow) -> Dict[str, str]:
|
||||||
return {k[9:]: v for k, v in row._mapping.items() # pylint: disable=W0212
|
return {k[9:]: v for k, v in row._mapping.items() # pylint: disable=W0212
|
||||||
if k.startswith('geometry_')}
|
if k.startswith('geometry_')}
|
||||||
@@ -166,7 +159,7 @@ def create_from_placex_row(row: SaRow) -> SearchResult:
|
|||||||
importance=row.importance,
|
importance=row.importance,
|
||||||
country_code=row.country_code,
|
country_code=row.country_code,
|
||||||
indexed_date=getattr(row, 'indexed_date'),
|
indexed_date=getattr(row, 'indexed_date'),
|
||||||
centroid=Point(row.x, row.y),
|
centroid=Point.from_wkb(row.centroid.data),
|
||||||
geometry=_filter_geometries(row))
|
geometry=_filter_geometries(row))
|
||||||
|
|
||||||
|
|
||||||
@@ -186,7 +179,7 @@ def create_from_osmline_row(row: SaRow) -> SearchResult:
|
|||||||
'step': str(row.step)},
|
'step': str(row.step)},
|
||||||
country_code=row.country_code,
|
country_code=row.country_code,
|
||||||
indexed_date=getattr(row, 'indexed_date'),
|
indexed_date=getattr(row, 'indexed_date'),
|
||||||
centroid=Point(row.x, row.y),
|
centroid=Point.from_wkb(row.centroid.data),
|
||||||
geometry=_filter_geometries(row))
|
geometry=_filter_geometries(row))
|
||||||
|
|
||||||
|
|
||||||
@@ -203,7 +196,7 @@ def create_from_tiger_row(row: SaRow) -> SearchResult:
|
|||||||
'endnumber': str(row.endnumber),
|
'endnumber': str(row.endnumber),
|
||||||
'step': str(row.step)},
|
'step': str(row.step)},
|
||||||
country_code='us',
|
country_code='us',
|
||||||
centroid=Point(row.x, row.y),
|
centroid=Point.from_wkb(row.centroid.data),
|
||||||
geometry=_filter_geometries(row))
|
geometry=_filter_geometries(row))
|
||||||
|
|
||||||
|
|
||||||
@@ -219,7 +212,7 @@ def create_from_postcode_row(row: SaRow) -> SearchResult:
|
|||||||
rank_search=row.rank_search,
|
rank_search=row.rank_search,
|
||||||
rank_address=row.rank_address,
|
rank_address=row.rank_address,
|
||||||
country_code=row.country_code,
|
country_code=row.country_code,
|
||||||
centroid=Point(row.x, row.y),
|
centroid=Point.from_wkb(row.centroid.data),
|
||||||
indexed_date=row.indexed_date,
|
indexed_date=row.indexed_date,
|
||||||
geometry=_filter_geometries(row))
|
geometry=_filter_geometries(row))
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ Complex datatypes used by the Nominatim API.
|
|||||||
from typing import Optional, Union, NamedTuple
|
from typing import Optional, Union, NamedTuple
|
||||||
import dataclasses
|
import dataclasses
|
||||||
import enum
|
import enum
|
||||||
|
from struct import unpack
|
||||||
|
|
||||||
@dataclasses.dataclass
|
@dataclasses.dataclass
|
||||||
class PlaceID:
|
class PlaceID:
|
||||||
@@ -55,6 +56,33 @@ class Point(NamedTuple):
|
|||||||
return self.x
|
return self.x
|
||||||
|
|
||||||
|
|
||||||
|
def to_geojson(self) -> str:
|
||||||
|
""" Return the point in GeoJSON format.
|
||||||
|
"""
|
||||||
|
return f'{{"type": "Point","coordinates": [{self.x}, {self.y}]}}'
|
||||||
|
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def from_wkb(wkb: bytes) -> 'Point':
|
||||||
|
""" Create a point from EWKB as returned from the database.
|
||||||
|
"""
|
||||||
|
if len(wkb) != 25:
|
||||||
|
raise ValueError("Point wkb has unexpected length")
|
||||||
|
if wkb[0] == 0:
|
||||||
|
gtype, srid, x, y = unpack('>iidd', wkb[1:])
|
||||||
|
elif wkb[0] == 1:
|
||||||
|
gtype, srid, x, y = unpack('<iidd', wkb[1:])
|
||||||
|
else:
|
||||||
|
raise ValueError("WKB has unknown endian value.")
|
||||||
|
|
||||||
|
if gtype != 0x20000001:
|
||||||
|
raise ValueError("WKB must be a point geometry.")
|
||||||
|
if srid != 4326:
|
||||||
|
raise ValueError("Only WGS84 WKB supported.")
|
||||||
|
|
||||||
|
return Point(x, y)
|
||||||
|
|
||||||
|
|
||||||
class GeometryFormat(enum.Flag):
|
class GeometryFormat(enum.Flag):
|
||||||
""" Geometry output formats supported by Nominatim.
|
""" Geometry output formats supported by Nominatim.
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ def _add_parent_rows_grouped(writer: JsonWriter, rows: napi.AddressLines,
|
|||||||
def _format_search_json(result: napi.SearchResult, options: Mapping[str, Any]) -> str:
|
def _format_search_json(result: napi.SearchResult, options: Mapping[str, Any]) -> str:
|
||||||
locales = options.get('locales', napi.Locales())
|
locales = options.get('locales', napi.Locales())
|
||||||
geom = result.geometry.get('geojson')
|
geom = result.geometry.get('geojson')
|
||||||
centroid = result.centroid_as_geojson()
|
centroid = result.centroid.to_geojson()
|
||||||
|
|
||||||
out = JsonWriter()
|
out = JsonWriter()
|
||||||
out.start_object()\
|
out.start_object()\
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ class TestCliDetailsCall:
|
|||||||
@pytest.fixture(autouse=True)
|
@pytest.fixture(autouse=True)
|
||||||
def setup_status_mock(self, monkeypatch):
|
def setup_status_mock(self, monkeypatch):
|
||||||
result = napi.SearchResult(napi.SourceTable.PLACEX, ('place', 'thing'),
|
result = napi.SearchResult(napi.SourceTable.PLACEX, ('place', 'thing'),
|
||||||
(1.0, -3.0))
|
napi.Point(1.0, -3.0))
|
||||||
|
|
||||||
monkeypatch.setattr(napi.NominatimAPI, 'lookup',
|
monkeypatch.setattr(napi.NominatimAPI, 'lookup',
|
||||||
lambda *args: result)
|
lambda *args: result)
|
||||||
@@ -90,7 +90,7 @@ class TestCliDetailsCall:
|
|||||||
('--relation', '1'),
|
('--relation', '1'),
|
||||||
('--place_id', '10001')])
|
('--place_id', '10001')])
|
||||||
|
|
||||||
def test_status_json_format(self, cli_call, tmp_path, capsys, params):
|
def test_details_json_format(self, cli_call, tmp_path, capsys, params):
|
||||||
result = cli_call('details', '--project-dir', str(tmp_path), *params)
|
result = cli_call('details', '--project-dir', str(tmp_path), *params)
|
||||||
|
|
||||||
assert result == 0
|
assert result == 0
|
||||||
|
|||||||
Reference in New Issue
Block a user