Merge pull request #2981 from lonvia/add-point-wkb-decoder

Python frontend: add a WKB decoder for the Point class
This commit is contained in:
Sarah Hoffmann 2023-02-17 08:40:14 +01:00 committed by GitHub
commit 3405dbf90e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 39 additions and 22 deletions

View File

@ -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.parent_place_id, t.c.rank_address, t.c.rank_search,
t.c.linked_place_id,
sa.func.ST_X(t.c.centroid).label('x'),
sa.func.ST_Y(t.c.centroid).label('y'),
t.c.centroid,
_select_column_geometry(t.c.geometry, details.geometry_output))
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,
t.c.indexed_date, t.c.startnumber, t.c.endnumber,
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'),
sa.func.ST_Y(sa.func.ST_Centroid(t.c.linegeo)).label('y'),
t.c.linegeo.ST_Centroid().label('centroid'),
_select_column_geometry(t.c.linegeo, details.geometry_output))
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,
t.c.startnumber, t.c.endnumber, t.c.step,
t.c.postcode,
sa.func.ST_X(sa.func.ST_Centroid(t.c.linegeo)).label('x'),
sa.func.ST_Y(sa.func.ST_Centroid(t.c.linegeo)).label('y'),
t.c.linegeo.ST_Centroid().label('centroid'),
_select_column_geometry(t.c.linegeo, details.geometry_output))
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,
t.c.rank_search, t.c.rank_address,
t.c.indexed_date, t.c.postcode, t.c.country_code,
sa.func.ST_X(t.c.geometry).label('x'),
sa.func.ST_Y(t.c.geometry).label('y'),
t.c.geometry.label('centroid'),
_select_column_geometry(t.c.geometry, details.geometry_output))
if isinstance(place, ntyp.PlaceID):

View File

@ -132,13 +132,6 @@ class SearchResult:
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]:
return {k[9:]: v for k, v in row._mapping.items() # pylint: disable=W0212
if k.startswith('geometry_')}
@ -166,7 +159,7 @@ def create_from_placex_row(row: SaRow) -> SearchResult:
importance=row.importance,
country_code=row.country_code,
indexed_date=getattr(row, 'indexed_date'),
centroid=Point(row.x, row.y),
centroid=Point.from_wkb(row.centroid.data),
geometry=_filter_geometries(row))
@ -186,7 +179,7 @@ def create_from_osmline_row(row: SaRow) -> SearchResult:
'step': str(row.step)},
country_code=row.country_code,
indexed_date=getattr(row, 'indexed_date'),
centroid=Point(row.x, row.y),
centroid=Point.from_wkb(row.centroid.data),
geometry=_filter_geometries(row))
@ -203,7 +196,7 @@ def create_from_tiger_row(row: SaRow) -> SearchResult:
'endnumber': str(row.endnumber),
'step': str(row.step)},
country_code='us',
centroid=Point(row.x, row.y),
centroid=Point.from_wkb(row.centroid.data),
geometry=_filter_geometries(row))
@ -219,7 +212,7 @@ def create_from_postcode_row(row: SaRow) -> SearchResult:
rank_search=row.rank_search,
rank_address=row.rank_address,
country_code=row.country_code,
centroid=Point(row.x, row.y),
centroid=Point.from_wkb(row.centroid.data),
indexed_date=row.indexed_date,
geometry=_filter_geometries(row))

View File

@ -10,6 +10,7 @@ Complex datatypes used by the Nominatim API.
from typing import Optional, Union, NamedTuple
import dataclasses
import enum
from struct import unpack
@dataclasses.dataclass
class PlaceID:
@ -55,6 +56,33 @@ class Point(NamedTuple):
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):
""" Geometry output formats supported by Nominatim.
"""

View File

@ -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:
locales = options.get('locales', napi.Locales())
geom = result.geometry.get('geojson')
centroid = result.centroid_as_geojson()
centroid = result.centroid.to_geojson()
out = JsonWriter()
out.start_object()\

View File

@ -80,7 +80,7 @@ class TestCliDetailsCall:
@pytest.fixture(autouse=True)
def setup_status_mock(self, monkeypatch):
result = napi.SearchResult(napi.SourceTable.PLACEX, ('place', 'thing'),
(1.0, -3.0))
napi.Point(1.0, -3.0))
monkeypatch.setattr(napi.NominatimAPI, 'lookup',
lambda *args: result)
@ -90,7 +90,7 @@ class TestCliDetailsCall:
('--relation', '1'),
('--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)
assert result == 0