check if a band index was passed in the query argument extract the lhs band index from the band transform class and the rhs band index from the input tuple postgis band indices are 1based so the band index needs to be increased to be consistent with the gdalraster band indices getdbpreplookup is called by processrhs from super class if rhs is some query don t touch it unlike builtinlookup the gis getrhsop implementation should return an object spatialoperator with an assql method to allow for more complex computations where the lhs part can be mixed in geometry operators the overlapsleft operator returns true if a s bounding box overlaps or is to the left of b s bounding box the overlapsright operator returns true if a s bounding box overlaps or is to the right of b s bounding box the overlapsbelow operator returns true if a s bounding box overlaps or is below b s bounding box the overlapsabove operator returns true if a s bounding box overlaps or is above b s bounding box the left operator returns true if a s bounding box is strictly to the left of b s bounding box the right operator returns true if a s bounding box is strictly to the right of b s bounding box the strictlybelow operator returns true if a s bounding box is strictly below b s bounding box the strictlyabove operator returns true if a s bounding box is strictly above b s bounding box the operator is the same as operator it tests actual geometric equality of two features so if a and b are the same feature vertexbyvertex the operator returns true the bbcontains operator returns true if a s bounding box completely contains by b s bounding box the bboverlaps operator returns true if a s bounding box overlaps b s bounding box the contained operator returns true if a s bounding box is completely contained by b s bounding box geometry functions check the pattern argument check if the second parameter is a band index check if a band index was passed in the query argument extract the lhs band index from the band transform class and the rhs band index from the input tuple postgis band indices are 1 based so the band index needs to be increased to be consistent with the gdalraster band indices get_db_prep_lookup is called by process_rhs from super class if rhs is some query don t touch it unlike builtinlookup the gis get_rhs_op implementation should return an object spatialoperator with an as_sql method to allow for more complex computations where the lhs part can be mixed in geometry operators the overlaps_left operator returns true if a s bounding box overlaps or is to the left of b s bounding box the overlaps_right operator returns true if a s bounding box overlaps or is to the right of b s bounding box the overlaps_below operator returns true if a s bounding box overlaps or is below b s bounding box the overlaps_above operator returns true if a s bounding box overlaps or is above b s bounding box the left operator returns true if a s bounding box is strictly to the left of b s bounding box the right operator returns true if a s bounding box is strictly to the right of b s bounding box the strictly_below operator returns true if a s bounding box is strictly below b s bounding box the strictly_above operator returns true if a s bounding box is strictly above b s bounding box the operator is the same as operator it tests actual geometric equality of two features so if a and b are the same feature vertex by vertex the operator returns true the bbcontains operator returns true if a s bounding box completely contains by b s bounding box the bboverlaps operator returns true if a s bounding box overlaps b s bounding box the contained operator returns true if a s bounding box is completely contained by b s bounding box geometry functions check the pattern argument check if the second parameter is a band index
from django.contrib.gis.db.models.fields import BaseSpatialField
from django.contrib.gis.measure import Distance
from django.db import NotSupportedError
from django.db.models import Expression, Lookup, Transform
from django.db.models.sql.query import Query
from django.utils.regex_helper import _lazy_re_compile


class RasterBandTransform(Transform):
    def as_sql(self, compiler, connection):
        return compiler.compile(self.lhs)


class GISLookup(Lookup):
    sql_template = None
    transform_func = None
    distance = False
    band_rhs = None
    band_lhs = None

    def __init__(self, lhs, rhs):
        rhs, *self.rhs_params = rhs if isinstance(rhs, (list, tuple)) else [rhs]
        super().__init__(lhs, rhs)
        self.template_params = {}
        self.process_rhs_params()

    def process_rhs_params(self):
        if self.rhs_params:
            
            if len(self.rhs_params) == (2 if self.lookup_name == "relate" else 1):
                self.process_band_indices()
            elif len(self.rhs_params) > 1:
                raise ValueError("Tuple too long for lookup %s." % self.lookup_name)
        elif isinstance(self.lhs, RasterBandTransform):
            self.process_band_indices(only_lhs=True)

    def process_band_indices(self, only_lhs=False):
        
        
        
        if only_lhs:
            self.band_rhs = 1
            self.band_lhs = self.lhs.band_index + 1
            return

        if isinstance(self.lhs, RasterBandTransform):
            self.band_lhs = self.lhs.band_index + 1
        else:
            self.band_lhs = 1

        self.band_rhs, *self.rhs_params = self.rhs_params

    def get_db_prep_lookup(self, value, connection):
        
        return ("%s", [connection.ops.Adapter(value)])

    def process_rhs(self, compiler, connection):
        if isinstance(self.rhs, Query):
            
            return super().process_rhs(compiler, connection)
        if isinstance(self.rhs, Expression):
            self.rhs = self.rhs.resolve_expression(compiler.query)
        rhs, rhs_params = super().process_rhs(compiler, connection)
        placeholder = connection.ops.get_geom_placeholder(
            self.lhs.output_field, self.rhs, compiler
        )
        return placeholder % rhs, rhs_params

    def get_rhs_op(self, connection, rhs):
        
        
        
        return connection.ops.gis_operators[self.lookup_name]

    def as_sql(self, compiler, connection):
        lhs_sql, lhs_params = self.process_lhs(compiler, connection)
        rhs_sql, rhs_params = self.process_rhs(compiler, connection)
        sql_params = (*lhs_params, *rhs_params)

        template_params = {
            "lhs": lhs_sql,
            "rhs": rhs_sql,
            "value": "%s",
            **self.template_params,
        }
        rhs_op = self.get_rhs_op(connection, rhs_sql)
        return rhs_op.as_sql(connection, self, template_params, sql_params)







@BaseSpatialField.register_lookup
class OverlapsLeftLookup(GISLookup):
    

    lookup_name = "overlaps_left"


@BaseSpatialField.register_lookup
class OverlapsRightLookup(GISLookup):
    

    lookup_name = "overlaps_right"


@BaseSpatialField.register_lookup
class OverlapsBelowLookup(GISLookup):
    

    lookup_name = "overlaps_below"


@BaseSpatialField.register_lookup
class OverlapsAboveLookup(GISLookup):
    

    lookup_name = "overlaps_above"


@BaseSpatialField.register_lookup
class LeftLookup(GISLookup):
    

    lookup_name = "left"


@BaseSpatialField.register_lookup
class RightLookup(GISLookup):
    

    lookup_name = "right"


@BaseSpatialField.register_lookup
class StrictlyBelowLookup(GISLookup):
    

    lookup_name = "strictly_below"


@BaseSpatialField.register_lookup
class StrictlyAboveLookup(GISLookup):
    

    lookup_name = "strictly_above"


@BaseSpatialField.register_lookup
class SameAsLookup(GISLookup):
    

    lookup_name = "same_as"


BaseSpatialField.register_lookup(SameAsLookup, "exact")


@BaseSpatialField.register_lookup
class BBContainsLookup(GISLookup):
    

    lookup_name = "bbcontains"


@BaseSpatialField.register_lookup
class BBOverlapsLookup(GISLookup):
    

    lookup_name = "bboverlaps"


@BaseSpatialField.register_lookup
class ContainedLookup(GISLookup):
    

    lookup_name = "contained"







@BaseSpatialField.register_lookup
class ContainsLookup(GISLookup):
    lookup_name = "contains"


@BaseSpatialField.register_lookup
class ContainsProperlyLookup(GISLookup):
    lookup_name = "contains_properly"


@BaseSpatialField.register_lookup
class CoveredByLookup(GISLookup):
    lookup_name = "coveredby"


@BaseSpatialField.register_lookup
class CoversLookup(GISLookup):
    lookup_name = "covers"


@BaseSpatialField.register_lookup
class CrossesLookup(GISLookup):
    lookup_name = "crosses"


@BaseSpatialField.register_lookup
class DisjointLookup(GISLookup):
    lookup_name = "disjoint"


@BaseSpatialField.register_lookup
class EqualsLookup(GISLookup):
    lookup_name = "equals"


@BaseSpatialField.register_lookup
class IntersectsLookup(GISLookup):
    lookup_name = "intersects"


@BaseSpatialField.register_lookup
class OverlapsLookup(GISLookup):
    lookup_name = "overlaps"


@BaseSpatialField.register_lookup
class RelateLookup(GISLookup):
    lookup_name = "relate"
    sql_template = "%(func)s(%(lhs)s, %(rhs)s, %%s)"
    pattern_regex = _lazy_re_compile(r"^[012TF*]{9}$")

    def process_rhs(self, compiler, connection):
        
        pattern = self.rhs_params[0]
        backend_op = connection.ops.gis_operators[self.lookup_name]
        if hasattr(backend_op, "check_relate_argument"):
            backend_op.check_relate_argument(pattern)
        elif not isinstance(pattern, str) or not self.pattern_regex.match(pattern):
            raise ValueError('Invalid intersection matrix pattern "%s".' % pattern)
        sql, params = super().process_rhs(compiler, connection)
        return sql, params + [pattern]


@BaseSpatialField.register_lookup
class TouchesLookup(GISLookup):
    lookup_name = "touches"


@BaseSpatialField.register_lookup
class WithinLookup(GISLookup):
    lookup_name = "within"


class DistanceLookupBase(GISLookup):
    distance = True
    sql_template = "%(func)s(%(lhs)s, %(rhs)s) %(op)s %(value)s"

    def process_rhs_params(self):
        if not 1 <= len(self.rhs_params) <= 3:
            raise ValueError(
                "2, 3, or 4-element tuple required for '%s' lookup." % self.lookup_name
            )
        elif len(self.rhs_params) == 3 and self.rhs_params[2] != "spheroid":
            raise ValueError(
                "For 4-element tuples the last argument must be the 'spheroid' "
                "directive."
            )

        
        if len(self.rhs_params) > 1 and self.rhs_params[1] != "spheroid":
            self.process_band_indices()

    def process_distance(self, compiler, connection):
        dist_param = self.rhs_params[0]
        return (
            compiler.compile(dist_param.resolve_expression(compiler.query))
            if hasattr(dist_param, "resolve_expression")
            else (
                "%s",
                connection.ops.get_distance(
                    self.lhs.output_field, self.rhs_params, self.lookup_name
                ),
            )
        )


@BaseSpatialField.register_lookup
class DWithinLookup(DistanceLookupBase):
    lookup_name = "dwithin"
    sql_template = "%(func)s(%(lhs)s, %(rhs)s, %(value)s)"

    def process_distance(self, compiler, connection):
        dist_param = self.rhs_params[0]
        if (
            not connection.features.supports_dwithin_distance_expr
            and hasattr(dist_param, "resolve_expression")
            and not isinstance(dist_param, Distance)
        ):
            raise NotSupportedError(
                "This backend does not support expressions for specifying "
                "distance in the dwithin lookup."
            )
        return super().process_distance(compiler, connection)

    def process_rhs(self, compiler, connection):
        dist_sql, dist_params = self.process_distance(compiler, connection)
        self.template_params["value"] = dist_sql
        rhs_sql, params = super().process_rhs(compiler, connection)
        return rhs_sql, params + dist_params


class DistanceLookupFromFunction(DistanceLookupBase):
    def as_sql(self, compiler, connection):
        spheroid = (
            len(self.rhs_params) == 2 and self.rhs_params[-1] == "spheroid"
        ) or None
        distance_expr = connection.ops.distance_expr_for_lookup(
            self.lhs, self.rhs, spheroid=spheroid
        )
        sql, params = compiler.compile(distance_expr.resolve_expression(compiler.query))
        dist_sql, dist_params = self.process_distance(compiler, connection)
        return (
            "%(func)s %(op)s %(dist)s" % {"func": sql, "op": self.op, "dist": dist_sql},
            params + dist_params,
        )


@BaseSpatialField.register_lookup
class DistanceGTLookup(DistanceLookupFromFunction):
    lookup_name = "distance_gt"
    op = ">"


@BaseSpatialField.register_lookup
class DistanceGTELookup(DistanceLookupFromFunction):
    lookup_name = "distance_gte"
    op = ">="


@BaseSpatialField.register_lookup
class DistanceLTLookup(DistanceLookupFromFunction):
    lookup_name = "distance_lt"
    op = "<"


@BaseSpatialField.register_lookup
class DistanceLTELookup(DistanceLookupFromFunction):
    lookup_name = "distance_lte"
    op = "<="