]> git.openstreetmap.org Git - nominatim.git/commitdiff
add a WKB decoder for the Point class
authorSarah Hoffmann <lonvia@denofr.de>
Thu, 16 Feb 2023 14:35:54 +0000 (15:35 +0100)
committerSarah Hoffmann <lonvia@denofr.de>
Thu, 16 Feb 2023 16:29:56 +0000 (17:29 +0100)
This allows to return point geometries from the database and makes
the SQL a bit simpler.

nominatim/api/lookup.py
nominatim/api/results.py
nominatim/api/types.py
nominatim/api/v1/format.py
test/python/cli/test_cmd_api.py

index 27706ff306e0aaaaf7866e4233060401339cc407..c42bf0c203671debca108cec297643a46c4d50b9 100644 (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):
index 23cb47f40f1b8a63237a1517ff332434087c44e0..10f03393416b33b36f2969e012c0e678d064998a 100644 (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))
 
index 89b8111107fff712908fe70cad48d61404263fd0..9dc3ff2e6341cfcdbbc34f28e2c10b280797ea34 100644 (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.
     """
index 7c8ba80897ff3cec093d355a71881638291de8bb..3f26f903c5675f28502cef19641aa86f34f88dbc 100644 (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()\
index 966059c480ddf627cc3989c9a835dea8f40d0995..0b5dccfb63d36d5075a47e167d4a16e273af6259 100644 (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