# Source code for geoalchemy2.comparator

    """This module defines a ``Comparator`` class for use with geometry and geography objects.

    This is where spatial operators, like ``&&``, ``&<``, are defined.
    Spatial operators very often apply to the bounding boxes of geometries. For
    example, ``geom1 && geom2`` indicates if geom1's bounding box intersects
    geom2's.

    Examples:
    ---------
    Select the objects whose bounding boxes are to the left of the
    bounding box of ``POLYGON((-5 45,5 45,5 -45,-5 -45,-5 45))``::

        select([table]).where(table.c.geom.to_left(
            'POLYGON((-5 45,5 45,5 -45,-5 -45,-5 45))'))

    The ``<<`` and ``>>`` operators are a bit specific, because they have
    corresponding Python operator (``__lshift__`` and ``__rshift__``). The
    above ``SELECT`` expression can thus be rewritten like this::

        select([table]).where(
            table.c.geom << 'POLYGON((-5 45,5 45,5 -45,-5 -45,-5 45))')

    Operators can also be used when using the ORM. For example::

        Session.query(Cls).filter(
            Cls.geom << 'POLYGON((-5 45,5 45,5 -45,-5 -45,-5 45))')

    Now some other examples with the ``<#>`` operator.

    Select the ten objects that are the closest to ``POINT(0 0)`` (typical
    closed neighbors problem)::

        select([table]).order_by(table.c.geom.distance_box('POINT(0 0)')).limit(10)

    Using the ORM::

        Session.query(Cls).order_by(Cls.geom.distance_box('POINT(0 0)')).limit(10)
    """

    from typing import Any

    from sqlalchemy import types as sqltypes
    from sqlalchemy.dialects.postgresql import DOUBLE_PRECISION
    from sqlalchemy.sql.elements import ColumnElement
    from sqlalchemy.sql.functions import _FunctionGenerator
    from sqlalchemy.sql.operators import custom_op
    from sqlalchemy.types import UserDefinedType

    from geoalchemy2.elements import WKBElement
    from geoalchemy2.elements import WKTElement

    INTERSECTS: custom_op = custom_op("&&")
    INTERSECTS_ND: custom_op = custom_op("&&&")
    OVERLAPS_OR_TO_LEFT: custom_op = custom_op("&<")
    OVERLAPS_OR_TO_RIGHT: custom_op = custom_op("&>")
    OVERLAPS_OR_BELOW: custom_op = custom_op("&<|")
    TO_LEFT: custom_op = custom_op("<<")
    BELOW: custom_op = custom_op("<<|")
    TO_RIGHT: custom_op = custom_op(">>")
    CONTAINED: custom_op = custom_op("@")
    OVERLAPS_OR_ABOVE: custom_op = custom_op("|&>")
    ABOVE: custom_op = custom_op("|>>")
    CONTAINS: custom_op = custom_op("~")
    SAME: custom_op = custom_op("~=")
    DISTANCE_CENTROID: custom_op = custom_op("<->")
    DISTANCE_BOX: custom_op = custom_op("<#>")

    _COMPARATOR_INPUT_TYPE = str | WKBElement | WKTElement

    class BaseComparator(UserDefinedType.Comparator):
        """A custom comparator base class.

        It adds the ability to call spatial functions on columns that use this kind of comparator.
        It also defines functions that map to operators supported by ``Geometry``, ``Geography``
        and ``Raster`` columns.

        This comparator is used by the :class:`geoalchemy2.types.Raster`.
        """

        key = None

        def __getattr__(self, name: str) -> Any:
            # Function names that don't start with "ST_" are rejected.
            # This is not to mess up with SQLAlchemy's use of
            # hasattr/getattr on Column objects.

            if not name.lower().startswith("st_"):
                raise AttributeError(f"Attribute {name} not found")

            # We create our own _FunctionGenerator here, and use it in place of
            # SQLAlchemy's "func" object. This is to be able to "bind" the
            # function to the SQL expression. See also GenericFunction.

            func_ = _FunctionGenerator(expr=self.expr)
            return getattr(func_, name)

        def intersects(self, other: _COMPARATOR_INPUT_TYPE) -> ColumnElement:
            """The ``&&`` operator. A's BBOX intersects B's."""
            return self.operate(INTERSECTS, other, result_type=sqltypes.Boolean)

        def overlaps_or_to_left(self, other: _COMPARATOR_INPUT_TYPE) -> ColumnElement:
            """The ``&<`` operator. A's BBOX overlaps or is to the left of B's."""
            return self.operate(OVERLAPS_OR_TO_LEFT, other, result_type=sqltypes.Boolean)

        def overlaps_or_to_right(self, other: _COMPARATOR_INPUT_TYPE) -> ColumnElement:
            """The ``&>`` operator. A's BBOX overlaps or is to the right of B's."""
            return self.operate(OVERLAPS_OR_TO_RIGHT, other, result_type=sqltypes.Boolean)

    class Comparator(BaseComparator):
        """A custom comparator class.

        Used in :class:`geoalchemy2.types.Geometry` and :class:`geoalchemy2.types.Geography`.

        This is where spatial operators like ``<<`` and ``<->`` are defined.
        """

        def overlaps_or_below(self, other: _COMPARATOR_INPUT_TYPE) -> ColumnElement:
            """The ``&<|`` operator.

            A's BBOX overlaps or is below B's.
            """
            return self.operate(OVERLAPS_OR_BELOW, other, result_type=sqltypes.Boolean)

        def to_left(self, other: _COMPARATOR_INPUT_TYPE) -> ColumnElement:
            """The ``<<`` operator.

            A's BBOX is strictly to the left of B's.
            """
            return self.operate(TO_LEFT, other, result_type=sqltypes.Boolean)

        def __lshift__(self, other: _COMPARATOR_INPUT_TYPE) -> ColumnElement:
            """The ``<<`` operator.

            A's BBOX is strictly to the left of B's.

            Same as ``to_left``, so::

                table.c.geom << 'POINT(1 2)'

            is the same as::

                table.c.geom.to_left('POINT(1 2)')
            """
            return self.to_left(other)

        def below(self, other: _COMPARATOR_INPUT_TYPE) -> ColumnElement:
            """The ``<<|`` operator.

            A's BBOX is strictly below B's.
            """
            return self.operate(BELOW, other, result_type=sqltypes.Boolean)

        def to_right(self, other: _COMPARATOR_INPUT_TYPE) -> ColumnElement:
            """The ``>>`` operator.

            A's BBOX is strictly to the right of B's.
            """
            return self.operate(TO_RIGHT, other, result_type=sqltypes.Boolean)

        def __rshift__(self, other: _COMPARATOR_INPUT_TYPE) -> ColumnElement:
            """The ``>>`` operator.

            A's BBOX is strictly to the left of B's.

            Same as `to_`right``, so::

                table.c.geom >> 'POINT(1 2)'

            is the same as::

                table.c.geom.to_right('POINT(1 2)')
            """
            return self.to_right(other)

        def contained(self, other: _COMPARATOR_INPUT_TYPE) -> ColumnElement:
            """The ``@`` operator.

            A's BBOX is contained by B's.
            """
            return self.operate(CONTAINED, other, result_type=sqltypes.Boolean)

        def overlaps_or_above(self, other: _COMPARATOR_INPUT_TYPE) -> ColumnElement:
            """The ``|&>`` operator.

            A's BBOX overlaps or is above B's.
            """
            return self.operate(OVERLAPS_OR_ABOVE, other, result_type=sqltypes.Boolean)

        def above(self, other: _COMPARATOR_INPUT_TYPE) -> ColumnElement:
            """The ``|>>`` operator.

            A's BBOX is strictly above B's.
            """
            return self.operate(ABOVE, other, result_type=sqltypes.Boolean)

        def contains(self, other: _COMPARATOR_INPUT_TYPE, **kw) -> ColumnElement:
            """The ``~`` operator.

            A's BBOX contains B's.
            """
            return self.operate(CONTAINS, other, result_type=sqltypes.Boolean)

        def same(self, other: _COMPARATOR_INPUT_TYPE) -> ColumnElement:
            """The ``~=`` operator.

            A's BBOX is the same as B's.
            """
            return self.operate(SAME, other, result_type=sqltypes.Boolean)

        def distance_centroid(self, other: _COMPARATOR_INPUT_TYPE) -> ColumnElement:
            """The ``<->`` operator.

            The distance between two points.
            """
            return self.operate(DISTANCE_CENTROID, other, result_type=DOUBLE_PRECISION)

        def distance_box(self, other: _COMPARATOR_INPUT_TYPE) -> ColumnElement:
            """The ``<#>`` operator.

            The distance between bounding box of two geometries.
            """
            return self.operate(DISTANCE_BOX, other, result_type=DOUBLE_PRECISION)

        def intersects_nd(self, other: _COMPARATOR_INPUT_TYPE) -> ColumnElement:
            """The ``&&&`` operator.

            This operator returns TRUE if the n-D bounding box of geometry A
            intersects the n-D bounding box of geometry B.
            """
            return self.operate(INTERSECTS_ND, other, result_type=sqltypes.Boolean)