Source code for geoalchemy2.types

"""This module defines the Column types.

The :class:`geoalchemy2.types.Geometry`, :class:`geoalchemy2.types.Geography`, and
:class:`geoalchemy2.types.Raster` classes are used when defining geometry, geography and raster
columns/properties in models.
"""

import warnings
from typing import Any
from typing import Dict
from typing import Optional

from sqlalchemy.dialects import postgresql
from sqlalchemy.dialects.postgresql.base import ischema_names as _postgresql_ischema_names
from sqlalchemy.dialects.sqlite.base import ischema_names as _sqlite_ischema_names
from sqlalchemy.ext.compiler import compiles
from sqlalchemy.sql import func
from sqlalchemy.types import Float
from sqlalchemy.types import Integer
from sqlalchemy.types import UserDefinedType

try:
    # SQLAlchemy >= 2
    from sqlalchemy.sql._typing import _TypeEngineArgument
except ImportError:
    # SQLAlchemy < 2
    _TypeEngineArgument = Any  # type: ignore

from geoalchemy2.comparator import BaseComparator
from geoalchemy2.comparator import Comparator
from geoalchemy2.elements import CompositeElement
from geoalchemy2.elements import RasterElement
from geoalchemy2.elements import WKBElement
from geoalchemy2.exc import ArgumentError
from geoalchemy2.types import dialects


[docs] def select_dialect(dialect_name): """Select the dialect from its name.""" known_dialects = { "geopackage": dialects.geopackage, "mysql": dialects.mysql, "mariadb": dialects.mysql, "postgresql": dialects.postgresql, "sqlite": dialects.sqlite, } return known_dialects.get(dialect_name, dialects.common)
[docs] class _GISType(UserDefinedType): """The base class for spatial types. This class defines ``bind_expression`` and ``column_expression`` methods that wrap column expressions in ``ST_GeomFromEWKT``, ``ST_GeogFromText``, or ``ST_AsEWKB`` calls. This class also defines ``result_processor`` and ``bind_processor`` methods. The function returned by ``result_processor`` converts WKB values received from the database to :class:`geoalchemy2.elements.WKBElement` objects. The function returned by ``bind_processor`` converts :class:`geoalchemy2.elements.WKTElement` objects to EWKT strings. Args: geometry_type: The geometry type. Possible values are: * ``"GEOMETRY"``, * ``"POINT"``, * ``"LINESTRING"``, * ``"POLYGON"``, * ``"MULTIPOINT"``, * ``"MULTILINESTRING"``, * ``"MULTIPOLYGON"``, * ``"GEOMETRYCOLLECTION"``, * ``"CURVE"``, * ``None``. The latter is actually not supported with :class:`geoalchemy2.types.Geography`. When set to ``None`` then no "geometry type" constraints will be attached to the geometry type declaration. Default is ``"GEOMETRY"``. srid: The SRID for this column. E.g. 4326. Default is ``-1``. dimension: The dimension of the geometry. Default is ``2``. spatial_index: Indicate if a spatial index should be created. Default is ``True``. use_N_D_index: Use the N-D index instead of the standard 2-D index. use_typmod: By default PostgreSQL type modifiers are used to create the geometry column. To use check constraints instead set ``use_typmod`` to ``False``. By default this option is not included in the call to ``AddGeometryColumn``. Note that this option is only available for PostGIS 2.x. """ name: Optional[str] = None """ Name used for defining the main geo type (geometry or geography) in CREATE TABLE statements. Set in subclasses. """ from_text: Optional[str] = None """ The name of "from text" function for this type. Set in subclasses. """ as_binary: Optional[str] = None """ The name of the "as binary" function for this type. Set in subclasses. """ comparator_factory: Any = Comparator """ This is the way by which spatial operators are defined for geometry/geography columns. """ cache_ok = False """ Disable cache for this type. """ def __init__( self, geometry_type: Optional[str] = "GEOMETRY", srid=-1, dimension=2, spatial_index=True, use_N_D_index=False, use_typmod: Optional[bool] = None, from_text: Optional[str] = None, name: Optional[str] = None, nullable=True, _spatial_index_reflected=None, ) -> None: geometry_type, srid = self.check_ctor_args( geometry_type, srid, dimension, use_typmod, nullable ) self.geometry_type = geometry_type self.srid = srid if name is not None: self.name = name if from_text is not None: self.from_text = from_text self.dimension = dimension self.spatial_index = spatial_index self.use_N_D_index = use_N_D_index self.use_typmod = use_typmod self.extended: Optional[bool] = self.as_binary == "ST_AsEWKB" self.nullable = nullable self._spatial_index_reflected = _spatial_index_reflected def get_col_spec(self): if not self.geometry_type: return self.name return "%s(%s,%d)" % (self.name, self.geometry_type, self.srid)
[docs] def column_expression(self, col): """Specific column_expression that automatically adds a conversion function.""" return getattr(func, self.as_binary)(col, type_=self)
[docs] def result_processor(self, dialect, coltype): """Specific result_processor that automatically process spatial elements.""" def process(value): if value is not None: kwargs = {} if self.srid > 0: kwargs["srid"] = self.srid if self.extended is not None and dialect.name not in ["mysql", "mariadb"]: kwargs["extended"] = self.extended return self.ElementType(value, **kwargs) return process
[docs] def bind_expression(self, bindvalue): """Specific bind_expression that automatically adds a conversion function.""" return getattr(func, self.from_text)(bindvalue, type_=self)
[docs] def bind_processor(self, dialect): """Specific bind_processor that automatically process spatial elements.""" def process(bindvalue): return select_dialect(dialect.name).bind_processor_process(self, bindvalue) return process
@staticmethod def check_ctor_args(geometry_type, srid, dimension, use_typmod, nullable): try: # passing default SRID if it is NULL from DB srid = int(srid if srid is not None else -1) except (ValueError, TypeError): raise ArgumentError("srid must be convertible to an integer") if geometry_type: geometry_type = geometry_type.upper() elif srid > 0: warnings.warn("srid not enforced when geometry_type is None") if use_typmod is not None and not nullable: raise ArgumentError( 'The "nullable" and "use_typmod" arguments can not be used together' ) return geometry_type, srid
@compiles(_GISType, "mariadb") @compiles(_GISType, "mysql") def get_col_spec(self, *args, **kwargs): if self.geometry_type is not None: spec = "%s" % self.geometry_type else: spec = "GEOMETRY" if not self.nullable or self.spatial_index: spec += " NOT NULL" if self.srid > 0: spec += " SRID %d" % self.srid return spec
[docs] class Geometry(_GISType): """The Geometry type. Creating a geometry column is done like this:: Column(Geometry(geometry_type='POINT', srid=4326)) See :class:`geoalchemy2.types._GISType` for the list of arguments that can be passed to the constructor. If ``srid`` is set then the ``WKBElement`` objects resulting from queries will have that SRID, and, when constructing the ``WKBElement`` objects, the SRID won't be read from the data returned by the database. If ``srid`` is not set (meaning it's ``-1``) then the SRID set in ``WKBElement`` objects will be read from the data returned by the database. """ name = "geometry" """ Type name used for defining geometry columns in ``CREATE TABLE``. """ from_text = "ST_GeomFromEWKT" """ The "from text" geometry constructor. Used by the parent class' ``bind_expression`` method. """ as_binary = "ST_AsEWKB" """ The "as binary" function to use. Used by the parent class' ``column_expression`` method. """ ElementType = WKBElement """ The element class to use. Used by the parent class' ``result_processor`` method. """ cache_ok = False """ Disable cache for this type. """
[docs] class Geography(_GISType): """The Geography type. Creating a geography column is done like this:: Column(Geography(geometry_type='POINT', srid=4326)) See :class:`geoalchemy2.types._GISType` for the list of arguments that can be passed to the constructor. """ name = "geography" """ Type name used for defining geography columns in ``CREATE TABLE``. """ from_text = "ST_GeogFromText" """ The ``FromText`` geography constructor. Used by the parent class' ``bind_expression`` method. """ as_binary = "ST_AsBinary" """ The "as binary" function to use. Used by the parent class' ``column_expression`` method. """ ElementType = WKBElement """ The element class to use. Used by the parent class' ``result_processor`` method. """ cache_ok = False """ Disable cache for this type. """
[docs] class Raster(_GISType): """The Raster column type. Creating a raster column is done like this:: Column(Raster) This class defines the ``result_processor`` method, so that raster values received from the database are converted to :class:`geoalchemy2.elements.RasterElement` objects. Args: spatial_index: Indicate if a spatial index should be created. Default is ``True``. """ comparator_factory = BaseComparator """ This is the way by which spatial operators and functions are defined for raster columns. """ name = "raster" """ Type name used for defining raster columns in ``CREATE TABLE``. """ from_text = "raster" """ The "from text" raster constructor. Used by the parent class' ``bind_expression`` method. """ as_binary = "raster" """ The "as binary" function to use. Used by the parent class' ``column_expression`` method. """ ElementType = RasterElement """ The element class to use. Used by the parent class' ``result_processor`` method. """ cache_ok = False """ Disable cache for this type. """ def __init__(self, spatial_index=True, from_text=None, name=None, nullable=True) -> None: # Enforce default values super(Raster, self).__init__( geometry_type=None, srid=-1, dimension=2, spatial_index=spatial_index, use_N_D_index=False, use_typmod=False, from_text=from_text, name=name, nullable=nullable, ) self.extended = None @staticmethod def check_ctor_args(*args, **kwargs): return None, -1
class _DummyGeometry(Geometry): """A dummy type only used with SQLite.""" def get_col_spec(self): return self.geometry_type or "GEOMETRY"
[docs] class CompositeType(UserDefinedType): """A composite type used by some spatial functions. A wrapper for :class:`geoalchemy2.elements.CompositeElement`, that can be used as the return type in PostgreSQL functions that return composite values. This is used as the base class of :class:`geoalchemy2.types.GeometryDump`. """ typemap: Dict[str, _TypeEngineArgument] = {} """ Dictionary used for defining the content types and their corresponding keys. Set in subclasses. """
[docs] class comparator_factory(UserDefinedType.Comparator): def __getattr__(self, key): try: type_ = self.type.typemap[key] except KeyError: raise AttributeError("Type '%s' doesn't have an attribute: '%s'" % (self.type, key)) return CompositeElement(self.expr, key, type_)
[docs] class GeometryDump(CompositeType): """The return type for functions like ``ST_Dump``. The type consists in a path and a geom field. You should normally never use this class directly. """ typemap = {"path": postgresql.ARRAY(Integer), "geom": Geometry} """ Dictionary defining the contents of a ``geometry_dump``. """ cache_ok = True """ Enable cache for this type. """
# Register Geometry, Geography and Raster to SQLAlchemy's reflection subsystems. _postgresql_ischema_names["geometry"] = Geometry _postgresql_ischema_names["geography"] = Geography _postgresql_ischema_names["raster"] = Raster _sqlite_ischema_names["GEOMETRY"] = Geometry _sqlite_ischema_names["POINT"] = Geometry _sqlite_ischema_names["LINESTRING"] = Geometry _sqlite_ischema_names["POLYGON"] = Geometry _sqlite_ischema_names["MULTIPOINT"] = Geometry _sqlite_ischema_names["MULTILINESTRING"] = Geometry _sqlite_ischema_names["MULTIPOLYGON"] = Geometry _sqlite_ischema_names["CURVE"] = Geometry _sqlite_ischema_names["GEOMETRYCOLLECTION"] = Geometry _sqlite_ischema_names["RASTER"] = Raster
[docs] class SummaryStats(CompositeType): """Define the composite type returned by the function ST_SummaryStatsAgg.""" typemap = { "count": Integer, "sum": Float, "mean": Float, "stddev": Float, "min": Float, "max": Float, } cache_ok = True """ Enable cache for this type. """
__all__ = [ "_GISType", "CompositeType", "Geography", "Geometry", "GeometryDump", "Raster", "SummaryStats", "dialects", "select_dialect", ] def __dir__(): return __all__