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 Custom types for SQLAlchemy.
 
  10 from typing import Callable, Any, cast
 
  13 import sqlalchemy as sa
 
  14 from sqlalchemy import types
 
  16 from nominatim.typing import SaColumn, SaBind
 
  20 class Geometry(types.UserDefinedType): # type: ignore[type-arg]
 
  21     """ Simplified type decorator for PostGIS geometry. This type
 
  22         only supports geometries in 4326 projection.
 
  26     def __init__(self, subtype: str = 'Geometry'):
 
  27         self.subtype = subtype
 
  30     def get_col_spec(self) -> str:
 
  31         return f'GEOMETRY({self.subtype}, 4326)'
 
  34     def bind_processor(self, dialect: 'sa.Dialect') -> Callable[[Any], str]:
 
  35         def process(value: Any) -> str:
 
  36             if isinstance(value, str):
 
  39             return cast(str, value.to_wkt())
 
  43     def result_processor(self, dialect: 'sa.Dialect', coltype: object) -> Callable[[Any], str]:
 
  44         def process(value: Any) -> str:
 
  45             assert isinstance(value, str)
 
  50     def bind_expression(self, bindvalue: SaBind) -> SaColumn:
 
  51         return sa.func.ST_GeomFromText(bindvalue, sa.text('4326'), type_=self)
 
  54     class comparator_factory(types.UserDefinedType.Comparator): # type: ignore[type-arg]
 
  56         def intersects(self, other: SaColumn) -> 'sa.Operators':
 
  57             return self.op('&&')(other)
 
  59         def is_line_like(self) -> SaColumn:
 
  60             return sa.func.ST_GeometryType(self, type_=sa.String).in_(('ST_LineString',
 
  61                                                                        'ST_MultiLineString'))
 
  63         def is_area(self) -> SaColumn:
 
  64             return sa.func.ST_GeometryType(self, type_=sa.String).in_(('ST_Polygon',
 
  68         def ST_DWithin(self, other: SaColumn, distance: SaColumn) -> SaColumn:
 
  69             return sa.func.ST_DWithin(self, other, distance, type_=sa.Boolean)
 
  72         def ST_DWithin_no_index(self, other: SaColumn, distance: SaColumn) -> SaColumn:
 
  73             return sa.func.ST_DWithin(sa.func.coalesce(sa.null(), self),
 
  74                                       other, distance, type_=sa.Boolean)
 
  77         def ST_Intersects_no_index(self, other: SaColumn) -> 'sa.Operators':
 
  78             return sa.func.coalesce(sa.null(), self).op('&&')(other)
 
  81         def ST_Distance(self, other: SaColumn) -> SaColumn:
 
  82             return sa.func.ST_Distance(self, other, type_=sa.Float)
 
  85         def ST_Contains(self, other: SaColumn) -> SaColumn:
 
  86             return sa.func.ST_Contains(self, other, type_=sa.Boolean)
 
  89         def ST_CoveredBy(self, other: SaColumn) -> SaColumn:
 
  90             return sa.func.ST_CoveredBy(self, other, type_=sa.Boolean)
 
  93         def ST_ClosestPoint(self, other: SaColumn) -> SaColumn:
 
  94             return sa.func.ST_ClosestPoint(self, other, type_=Geometry)
 
  97         def ST_Buffer(self, other: SaColumn) -> SaColumn:
 
  98             return sa.func.ST_Buffer(self, other, type_=Geometry)
 
 101         def ST_Expand(self, other: SaColumn) -> SaColumn:
 
 102             return sa.func.ST_Expand(self, other, type_=Geometry)
 
 105         def ST_Collect(self) -> SaColumn:
 
 106             return sa.func.ST_Collect(self, type_=Geometry)
 
 109         def ST_Centroid(self) -> SaColumn:
 
 110             return sa.func.ST_Centroid(self, type_=Geometry)
 
 113         def ST_LineInterpolatePoint(self, other: SaColumn) -> SaColumn:
 
 114             return sa.func.ST_LineInterpolatePoint(self, other, type_=Geometry)
 
 117         def ST_LineLocatePoint(self, other: SaColumn) -> SaColumn:
 
 118             return sa.func.ST_LineLocatePoint(self, other, type_=sa.Float)