Ajoutez des fichiers projet.
This commit is contained in:
@@ -0,0 +1,18 @@
|
||||
from django.db.models import * # NOQA isort:skip
|
||||
from django.db.models import __all__ as models_all # isort:skip
|
||||
import django.contrib.gis.db.models.functions # NOQA
|
||||
import django.contrib.gis.db.models.lookups # NOQA
|
||||
from django.contrib.gis.db.models.aggregates import * # NOQA
|
||||
from django.contrib.gis.db.models.aggregates import __all__ as aggregates_all
|
||||
from django.contrib.gis.db.models.fields import (
|
||||
GeometryCollectionField, GeometryField, LineStringField,
|
||||
MultiLineStringField, MultiPointField, MultiPolygonField, PointField,
|
||||
PolygonField, RasterField,
|
||||
)
|
||||
|
||||
__all__ = models_all + aggregates_all
|
||||
__all__ += [
|
||||
'GeometryCollectionField', 'GeometryField', 'LineStringField',
|
||||
'MultiLineStringField', 'MultiPointField', 'MultiPolygonField', 'PointField',
|
||||
'PolygonField', 'RasterField',
|
||||
]
|
@@ -0,0 +1,83 @@
|
||||
from django.contrib.gis.db.models.fields import (
|
||||
ExtentField, GeometryCollectionField, GeometryField, LineStringField,
|
||||
)
|
||||
from django.db.models import Aggregate, Value
|
||||
from django.utils.functional import cached_property
|
||||
|
||||
__all__ = ['Collect', 'Extent', 'Extent3D', 'MakeLine', 'Union']
|
||||
|
||||
|
||||
class GeoAggregate(Aggregate):
|
||||
function = None
|
||||
is_extent = False
|
||||
|
||||
@cached_property
|
||||
def output_field(self):
|
||||
return self.output_field_class(self.source_expressions[0].output_field.srid)
|
||||
|
||||
def as_sql(self, compiler, connection, function=None, **extra_context):
|
||||
# this will be called again in parent, but it's needed now - before
|
||||
# we get the spatial_aggregate_name
|
||||
connection.ops.check_expression_support(self)
|
||||
return super().as_sql(
|
||||
compiler,
|
||||
connection,
|
||||
function=function or connection.ops.spatial_aggregate_name(self.name),
|
||||
**extra_context
|
||||
)
|
||||
|
||||
def as_oracle(self, compiler, connection, **extra_context):
|
||||
if not self.is_extent:
|
||||
tolerance = self.extra.get('tolerance') or getattr(self, 'tolerance', 0.05)
|
||||
clone = self.copy()
|
||||
clone.set_source_expressions([
|
||||
*self.get_source_expressions(),
|
||||
Value(tolerance),
|
||||
])
|
||||
template = '%(function)s(SDOAGGRTYPE(%(expressions)s))'
|
||||
return clone.as_sql(compiler, connection, template=template, **extra_context)
|
||||
return self.as_sql(compiler, connection, **extra_context)
|
||||
|
||||
def resolve_expression(self, query=None, allow_joins=True, reuse=None, summarize=False, for_save=False):
|
||||
c = super().resolve_expression(query, allow_joins, reuse, summarize, for_save)
|
||||
for expr in c.get_source_expressions():
|
||||
if not hasattr(expr.field, 'geom_type'):
|
||||
raise ValueError('Geospatial aggregates only allowed on geometry fields.')
|
||||
return c
|
||||
|
||||
|
||||
class Collect(GeoAggregate):
|
||||
name = 'Collect'
|
||||
output_field_class = GeometryCollectionField
|
||||
|
||||
|
||||
class Extent(GeoAggregate):
|
||||
name = 'Extent'
|
||||
is_extent = '2D'
|
||||
|
||||
def __init__(self, expression, **extra):
|
||||
super().__init__(expression, output_field=ExtentField(), **extra)
|
||||
|
||||
def convert_value(self, value, expression, connection):
|
||||
return connection.ops.convert_extent(value)
|
||||
|
||||
|
||||
class Extent3D(GeoAggregate):
|
||||
name = 'Extent3D'
|
||||
is_extent = '3D'
|
||||
|
||||
def __init__(self, expression, **extra):
|
||||
super().__init__(expression, output_field=ExtentField(), **extra)
|
||||
|
||||
def convert_value(self, value, expression, connection):
|
||||
return connection.ops.convert_extent3d(value)
|
||||
|
||||
|
||||
class MakeLine(GeoAggregate):
|
||||
name = 'MakeLine'
|
||||
output_field_class = LineStringField
|
||||
|
||||
|
||||
class Union(GeoAggregate):
|
||||
name = 'Union'
|
||||
output_field_class = GeometryField
|
387
venv/Lib/site-packages/django/contrib/gis/db/models/fields.py
Normal file
387
venv/Lib/site-packages/django/contrib/gis/db/models/fields.py
Normal file
@@ -0,0 +1,387 @@
|
||||
from collections import defaultdict, namedtuple
|
||||
|
||||
from django.contrib.gis import forms, gdal
|
||||
from django.contrib.gis.db.models.proxy import SpatialProxy
|
||||
from django.contrib.gis.gdal.error import GDALException
|
||||
from django.contrib.gis.geos import (
|
||||
GeometryCollection, GEOSException, GEOSGeometry, LineString,
|
||||
MultiLineString, MultiPoint, MultiPolygon, Point, Polygon,
|
||||
)
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.db.models import Field
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
# Local cache of the spatial_ref_sys table, which holds SRID data for each
|
||||
# spatial database alias. This cache exists so that the database isn't queried
|
||||
# for SRID info each time a distance query is constructed.
|
||||
_srid_cache = defaultdict(dict)
|
||||
|
||||
|
||||
SRIDCacheEntry = namedtuple('SRIDCacheEntry', ['units', 'units_name', 'spheroid', 'geodetic'])
|
||||
|
||||
|
||||
def get_srid_info(srid, connection):
|
||||
"""
|
||||
Return the units, unit name, and spheroid WKT associated with the
|
||||
given SRID from the `spatial_ref_sys` (or equivalent) spatial database
|
||||
table for the given database connection. These results are cached.
|
||||
"""
|
||||
from django.contrib.gis.gdal import SpatialReference
|
||||
global _srid_cache
|
||||
|
||||
try:
|
||||
# The SpatialRefSys model for the spatial backend.
|
||||
SpatialRefSys = connection.ops.spatial_ref_sys()
|
||||
except NotImplementedError:
|
||||
SpatialRefSys = None
|
||||
|
||||
alias, get_srs = (
|
||||
(connection.alias, lambda srid: SpatialRefSys.objects.using(connection.alias).get(srid=srid).srs)
|
||||
if SpatialRefSys else
|
||||
(None, SpatialReference)
|
||||
)
|
||||
if srid not in _srid_cache[alias]:
|
||||
srs = get_srs(srid)
|
||||
units, units_name = srs.units
|
||||
_srid_cache[alias][srid] = SRIDCacheEntry(
|
||||
units=units,
|
||||
units_name=units_name,
|
||||
spheroid='SPHEROID["%s",%s,%s]' % (srs['spheroid'], srs.semi_major, srs.inverse_flattening),
|
||||
geodetic=srs.geographic,
|
||||
)
|
||||
|
||||
return _srid_cache[alias][srid]
|
||||
|
||||
|
||||
class BaseSpatialField(Field):
|
||||
"""
|
||||
The Base GIS Field.
|
||||
|
||||
It's used as a base class for GeometryField and RasterField. Defines
|
||||
properties that are common to all GIS fields such as the characteristics
|
||||
of the spatial reference system of the field.
|
||||
"""
|
||||
description = _("The base GIS field.")
|
||||
empty_strings_allowed = False
|
||||
|
||||
def __init__(self, verbose_name=None, srid=4326, spatial_index=True, **kwargs):
|
||||
"""
|
||||
The initialization function for base spatial fields. Takes the following
|
||||
as keyword arguments:
|
||||
|
||||
srid:
|
||||
The spatial reference system identifier, an OGC standard.
|
||||
Defaults to 4326 (WGS84).
|
||||
|
||||
spatial_index:
|
||||
Indicates whether to create a spatial index. Defaults to True.
|
||||
Set this instead of 'db_index' for geographic fields since index
|
||||
creation is different for geometry columns.
|
||||
"""
|
||||
|
||||
# Setting the index flag with the value of the `spatial_index` keyword.
|
||||
self.spatial_index = spatial_index
|
||||
|
||||
# Setting the SRID and getting the units. Unit information must be
|
||||
# easily available in the field instance for distance queries.
|
||||
self.srid = srid
|
||||
|
||||
# Setting the verbose_name keyword argument with the positional
|
||||
# first parameter, so this works like normal fields.
|
||||
kwargs['verbose_name'] = verbose_name
|
||||
|
||||
super().__init__(**kwargs)
|
||||
|
||||
def deconstruct(self):
|
||||
name, path, args, kwargs = super().deconstruct()
|
||||
# Always include SRID for less fragility; include spatial index if it's
|
||||
# not the default value.
|
||||
kwargs['srid'] = self.srid
|
||||
if self.spatial_index is not True:
|
||||
kwargs['spatial_index'] = self.spatial_index
|
||||
return name, path, args, kwargs
|
||||
|
||||
def db_type(self, connection):
|
||||
return connection.ops.geo_db_type(self)
|
||||
|
||||
def spheroid(self, connection):
|
||||
return get_srid_info(self.srid, connection).spheroid
|
||||
|
||||
def units(self, connection):
|
||||
return get_srid_info(self.srid, connection).units
|
||||
|
||||
def units_name(self, connection):
|
||||
return get_srid_info(self.srid, connection).units_name
|
||||
|
||||
def geodetic(self, connection):
|
||||
"""
|
||||
Return true if this field's SRID corresponds with a coordinate
|
||||
system that uses non-projected units (e.g., latitude/longitude).
|
||||
"""
|
||||
return get_srid_info(self.srid, connection).geodetic
|
||||
|
||||
def get_placeholder(self, value, compiler, connection):
|
||||
"""
|
||||
Return the placeholder for the spatial column for the
|
||||
given value.
|
||||
"""
|
||||
return connection.ops.get_geom_placeholder(self, value, compiler)
|
||||
|
||||
def get_srid(self, obj):
|
||||
"""
|
||||
Return the default SRID for the given geometry or raster, taking into
|
||||
account the SRID set for the field. For example, if the input geometry
|
||||
or raster doesn't have an SRID, then the SRID of the field will be
|
||||
returned.
|
||||
"""
|
||||
srid = obj.srid # SRID of given geometry.
|
||||
if srid is None or self.srid == -1 or (srid == -1 and self.srid != -1):
|
||||
return self.srid
|
||||
else:
|
||||
return srid
|
||||
|
||||
def get_db_prep_value(self, value, connection, *args, **kwargs):
|
||||
if value is None:
|
||||
return None
|
||||
return connection.ops.Adapter(
|
||||
super().get_db_prep_value(value, connection, *args, **kwargs),
|
||||
**(
|
||||
{'geography': True}
|
||||
if self.geography and connection.features.supports_geography
|
||||
else {}
|
||||
)
|
||||
)
|
||||
|
||||
def get_raster_prep_value(self, value, is_candidate):
|
||||
"""
|
||||
Return a GDALRaster if conversion is successful, otherwise return None.
|
||||
"""
|
||||
if isinstance(value, gdal.GDALRaster):
|
||||
return value
|
||||
elif is_candidate:
|
||||
try:
|
||||
return gdal.GDALRaster(value)
|
||||
except GDALException:
|
||||
pass
|
||||
elif isinstance(value, dict):
|
||||
try:
|
||||
return gdal.GDALRaster(value)
|
||||
except GDALException:
|
||||
raise ValueError("Couldn't create spatial object from lookup value '%s'." % value)
|
||||
|
||||
def get_prep_value(self, value):
|
||||
obj = super().get_prep_value(value)
|
||||
if obj is None:
|
||||
return None
|
||||
# When the input is not a geometry or raster, attempt to construct one
|
||||
# from the given string input.
|
||||
if isinstance(obj, GEOSGeometry):
|
||||
pass
|
||||
else:
|
||||
# Check if input is a candidate for conversion to raster or geometry.
|
||||
is_candidate = isinstance(obj, (bytes, str)) or hasattr(obj, '__geo_interface__')
|
||||
# Try to convert the input to raster.
|
||||
raster = self.get_raster_prep_value(obj, is_candidate)
|
||||
|
||||
if raster:
|
||||
obj = raster
|
||||
elif is_candidate:
|
||||
try:
|
||||
obj = GEOSGeometry(obj)
|
||||
except (GEOSException, GDALException):
|
||||
raise ValueError("Couldn't create spatial object from lookup value '%s'." % obj)
|
||||
else:
|
||||
raise ValueError('Cannot use object with type %s for a spatial lookup parameter.' % type(obj).__name__)
|
||||
|
||||
# Assigning the SRID value.
|
||||
obj.srid = self.get_srid(obj)
|
||||
return obj
|
||||
|
||||
|
||||
class GeometryField(BaseSpatialField):
|
||||
"""
|
||||
The base Geometry field -- maps to the OpenGIS Specification Geometry type.
|
||||
"""
|
||||
description = _('The base Geometry field — maps to the OpenGIS Specification Geometry type.')
|
||||
form_class = forms.GeometryField
|
||||
# The OpenGIS Geometry name.
|
||||
geom_type = 'GEOMETRY'
|
||||
geom_class = None
|
||||
|
||||
def __init__(self, verbose_name=None, dim=2, geography=False, *, extent=(-180.0, -90.0, 180.0, 90.0),
|
||||
tolerance=0.05, **kwargs):
|
||||
"""
|
||||
The initialization function for geometry fields. In addition to the
|
||||
parameters from BaseSpatialField, it takes the following as keyword
|
||||
arguments:
|
||||
|
||||
dim:
|
||||
The number of dimensions for this geometry. Defaults to 2.
|
||||
|
||||
extent:
|
||||
Customize the extent, in a 4-tuple of WGS 84 coordinates, for the
|
||||
geometry field entry in the `USER_SDO_GEOM_METADATA` table. Defaults
|
||||
to (-180.0, -90.0, 180.0, 90.0).
|
||||
|
||||
tolerance:
|
||||
Define the tolerance, in meters, to use for the geometry field
|
||||
entry in the `USER_SDO_GEOM_METADATA` table. Defaults to 0.05.
|
||||
"""
|
||||
# Setting the dimension of the geometry field.
|
||||
self.dim = dim
|
||||
|
||||
# Is this a geography rather than a geometry column?
|
||||
self.geography = geography
|
||||
|
||||
# Oracle-specific private attributes for creating the entry in
|
||||
# `USER_SDO_GEOM_METADATA`
|
||||
self._extent = extent
|
||||
self._tolerance = tolerance
|
||||
|
||||
super().__init__(verbose_name=verbose_name, **kwargs)
|
||||
|
||||
def deconstruct(self):
|
||||
name, path, args, kwargs = super().deconstruct()
|
||||
# Include kwargs if they're not the default values.
|
||||
if self.dim != 2:
|
||||
kwargs['dim'] = self.dim
|
||||
if self.geography is not False:
|
||||
kwargs['geography'] = self.geography
|
||||
if self._extent != (-180.0, -90.0, 180.0, 90.0):
|
||||
kwargs['extent'] = self._extent
|
||||
if self._tolerance != 0.05:
|
||||
kwargs['tolerance'] = self._tolerance
|
||||
return name, path, args, kwargs
|
||||
|
||||
def contribute_to_class(self, cls, name, **kwargs):
|
||||
super().contribute_to_class(cls, name, **kwargs)
|
||||
|
||||
# Setup for lazy-instantiated Geometry object.
|
||||
setattr(cls, self.attname, SpatialProxy(self.geom_class or GEOSGeometry, self, load_func=GEOSGeometry))
|
||||
|
||||
def formfield(self, **kwargs):
|
||||
defaults = {
|
||||
'form_class': self.form_class,
|
||||
'geom_type': self.geom_type,
|
||||
'srid': self.srid,
|
||||
**kwargs,
|
||||
}
|
||||
if self.dim > 2 and not getattr(defaults['form_class'].widget, 'supports_3d', False):
|
||||
defaults.setdefault('widget', forms.Textarea)
|
||||
return super().formfield(**defaults)
|
||||
|
||||
def select_format(self, compiler, sql, params):
|
||||
"""
|
||||
Return the selection format string, depending on the requirements
|
||||
of the spatial backend. For example, Oracle and MySQL require custom
|
||||
selection formats in order to retrieve geometries in OGC WKB.
|
||||
"""
|
||||
if not compiler.query.subquery:
|
||||
return compiler.connection.ops.select % sql, params
|
||||
return sql, params
|
||||
|
||||
|
||||
# The OpenGIS Geometry Type Fields
|
||||
class PointField(GeometryField):
|
||||
geom_type = 'POINT'
|
||||
geom_class = Point
|
||||
form_class = forms.PointField
|
||||
description = _("Point")
|
||||
|
||||
|
||||
class LineStringField(GeometryField):
|
||||
geom_type = 'LINESTRING'
|
||||
geom_class = LineString
|
||||
form_class = forms.LineStringField
|
||||
description = _("Line string")
|
||||
|
||||
|
||||
class PolygonField(GeometryField):
|
||||
geom_type = 'POLYGON'
|
||||
geom_class = Polygon
|
||||
form_class = forms.PolygonField
|
||||
description = _("Polygon")
|
||||
|
||||
|
||||
class MultiPointField(GeometryField):
|
||||
geom_type = 'MULTIPOINT'
|
||||
geom_class = MultiPoint
|
||||
form_class = forms.MultiPointField
|
||||
description = _("Multi-point")
|
||||
|
||||
|
||||
class MultiLineStringField(GeometryField):
|
||||
geom_type = 'MULTILINESTRING'
|
||||
geom_class = MultiLineString
|
||||
form_class = forms.MultiLineStringField
|
||||
description = _("Multi-line string")
|
||||
|
||||
|
||||
class MultiPolygonField(GeometryField):
|
||||
geom_type = 'MULTIPOLYGON'
|
||||
geom_class = MultiPolygon
|
||||
form_class = forms.MultiPolygonField
|
||||
description = _("Multi polygon")
|
||||
|
||||
|
||||
class GeometryCollectionField(GeometryField):
|
||||
geom_type = 'GEOMETRYCOLLECTION'
|
||||
geom_class = GeometryCollection
|
||||
form_class = forms.GeometryCollectionField
|
||||
description = _("Geometry collection")
|
||||
|
||||
|
||||
class ExtentField(Field):
|
||||
"Used as a return value from an extent aggregate"
|
||||
|
||||
description = _("Extent Aggregate Field")
|
||||
|
||||
def get_internal_type(self):
|
||||
return "ExtentField"
|
||||
|
||||
def select_format(self, compiler, sql, params):
|
||||
select = compiler.connection.ops.select_extent
|
||||
return select % sql if select else sql, params
|
||||
|
||||
|
||||
class RasterField(BaseSpatialField):
|
||||
"""
|
||||
Raster field for GeoDjango -- evaluates into GDALRaster objects.
|
||||
"""
|
||||
|
||||
description = _("Raster Field")
|
||||
geom_type = 'RASTER'
|
||||
geography = False
|
||||
|
||||
def _check_connection(self, connection):
|
||||
# Make sure raster fields are used only on backends with raster support.
|
||||
if not connection.features.gis_enabled or not connection.features.supports_raster:
|
||||
raise ImproperlyConfigured('Raster fields require backends with raster support.')
|
||||
|
||||
def db_type(self, connection):
|
||||
self._check_connection(connection)
|
||||
return super().db_type(connection)
|
||||
|
||||
def from_db_value(self, value, expression, connection):
|
||||
return connection.ops.parse_raster(value)
|
||||
|
||||
def contribute_to_class(self, cls, name, **kwargs):
|
||||
super().contribute_to_class(cls, name, **kwargs)
|
||||
# Setup for lazy-instantiated Raster object. For large querysets, the
|
||||
# instantiation of all GDALRasters can potentially be expensive. This
|
||||
# delays the instantiation of the objects to the moment of evaluation
|
||||
# of the raster attribute.
|
||||
setattr(cls, self.attname, SpatialProxy(gdal.GDALRaster, self))
|
||||
|
||||
def get_transform(self, name):
|
||||
from django.contrib.gis.db.models.lookups import RasterBandTransform
|
||||
try:
|
||||
band_index = int(name)
|
||||
return type(
|
||||
'SpecificRasterBandTransform',
|
||||
(RasterBandTransform,),
|
||||
{'band_index': band_index}
|
||||
)
|
||||
except ValueError:
|
||||
pass
|
||||
return super().get_transform(name)
|
489
venv/Lib/site-packages/django/contrib/gis/db/models/functions.py
Normal file
489
venv/Lib/site-packages/django/contrib/gis/db/models/functions.py
Normal file
@@ -0,0 +1,489 @@
|
||||
from decimal import Decimal
|
||||
|
||||
from django.contrib.gis.db.models.fields import BaseSpatialField, GeometryField
|
||||
from django.contrib.gis.db.models.sql import AreaField, DistanceField
|
||||
from django.contrib.gis.geos import GEOSGeometry
|
||||
from django.core.exceptions import FieldError
|
||||
from django.db import NotSupportedError
|
||||
from django.db.models import (
|
||||
BinaryField, BooleanField, FloatField, Func, IntegerField, TextField,
|
||||
Transform, Value,
|
||||
)
|
||||
from django.db.models.functions import Cast
|
||||
from django.utils.functional import cached_property
|
||||
|
||||
NUMERIC_TYPES = (int, float, Decimal)
|
||||
|
||||
|
||||
class GeoFuncMixin:
|
||||
function = None
|
||||
geom_param_pos = (0,)
|
||||
|
||||
def __init__(self, *expressions, **extra):
|
||||
super().__init__(*expressions, **extra)
|
||||
|
||||
# Ensure that value expressions are geometric.
|
||||
for pos in self.geom_param_pos:
|
||||
expr = self.source_expressions[pos]
|
||||
if not isinstance(expr, Value):
|
||||
continue
|
||||
try:
|
||||
output_field = expr.output_field
|
||||
except FieldError:
|
||||
output_field = None
|
||||
geom = expr.value
|
||||
if not isinstance(geom, GEOSGeometry) or output_field and not isinstance(output_field, GeometryField):
|
||||
raise TypeError("%s function requires a geometric argument in position %d." % (self.name, pos + 1))
|
||||
if not geom.srid and not output_field:
|
||||
raise ValueError("SRID is required for all geometries.")
|
||||
if not output_field:
|
||||
self.source_expressions[pos] = Value(geom, output_field=GeometryField(srid=geom.srid))
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self.__class__.__name__
|
||||
|
||||
@cached_property
|
||||
def geo_field(self):
|
||||
return self.source_expressions[self.geom_param_pos[0]].field
|
||||
|
||||
def as_sql(self, compiler, connection, function=None, **extra_context):
|
||||
if self.function is None and function is None:
|
||||
function = connection.ops.spatial_function_name(self.name)
|
||||
return super().as_sql(compiler, connection, function=function, **extra_context)
|
||||
|
||||
def resolve_expression(self, *args, **kwargs):
|
||||
res = super().resolve_expression(*args, **kwargs)
|
||||
|
||||
# Ensure that expressions are geometric.
|
||||
source_fields = res.get_source_fields()
|
||||
for pos in self.geom_param_pos:
|
||||
field = source_fields[pos]
|
||||
if not isinstance(field, GeometryField):
|
||||
raise TypeError(
|
||||
"%s function requires a GeometryField in position %s, got %s." % (
|
||||
self.name, pos + 1, type(field).__name__,
|
||||
)
|
||||
)
|
||||
|
||||
base_srid = res.geo_field.srid
|
||||
for pos in self.geom_param_pos[1:]:
|
||||
expr = res.source_expressions[pos]
|
||||
expr_srid = expr.output_field.srid
|
||||
if expr_srid != base_srid:
|
||||
# Automatic SRID conversion so objects are comparable.
|
||||
res.source_expressions[pos] = Transform(expr, base_srid).resolve_expression(*args, **kwargs)
|
||||
return res
|
||||
|
||||
def _handle_param(self, value, param_name='', check_types=None):
|
||||
if not hasattr(value, 'resolve_expression'):
|
||||
if check_types and not isinstance(value, check_types):
|
||||
raise TypeError(
|
||||
"The %s parameter has the wrong type: should be %s." % (
|
||||
param_name, check_types)
|
||||
)
|
||||
return value
|
||||
|
||||
|
||||
class GeoFunc(GeoFuncMixin, Func):
|
||||
pass
|
||||
|
||||
|
||||
class GeomOutputGeoFunc(GeoFunc):
|
||||
@cached_property
|
||||
def output_field(self):
|
||||
return GeometryField(srid=self.geo_field.srid)
|
||||
|
||||
|
||||
class SQLiteDecimalToFloatMixin:
|
||||
"""
|
||||
By default, Decimal values are converted to str by the SQLite backend, which
|
||||
is not acceptable by the GIS functions expecting numeric values.
|
||||
"""
|
||||
def as_sqlite(self, compiler, connection, **extra_context):
|
||||
copy = self.copy()
|
||||
copy.set_source_expressions([
|
||||
Value(float(expr.value)) if hasattr(expr, 'value') and isinstance(expr.value, Decimal)
|
||||
else expr
|
||||
for expr in copy.get_source_expressions()
|
||||
])
|
||||
return copy.as_sql(compiler, connection, **extra_context)
|
||||
|
||||
|
||||
class OracleToleranceMixin:
|
||||
tolerance = 0.05
|
||||
|
||||
def as_oracle(self, compiler, connection, **extra_context):
|
||||
tolerance = Value(self._handle_param(
|
||||
self.extra.get('tolerance', self.tolerance),
|
||||
'tolerance',
|
||||
NUMERIC_TYPES,
|
||||
))
|
||||
clone = self.copy()
|
||||
clone.set_source_expressions([*self.get_source_expressions(), tolerance])
|
||||
return clone.as_sql(compiler, connection, **extra_context)
|
||||
|
||||
|
||||
class Area(OracleToleranceMixin, GeoFunc):
|
||||
arity = 1
|
||||
|
||||
@cached_property
|
||||
def output_field(self):
|
||||
return AreaField(self.geo_field)
|
||||
|
||||
def as_sql(self, compiler, connection, **extra_context):
|
||||
if not connection.features.supports_area_geodetic and self.geo_field.geodetic(connection):
|
||||
raise NotSupportedError('Area on geodetic coordinate systems not supported.')
|
||||
return super().as_sql(compiler, connection, **extra_context)
|
||||
|
||||
def as_sqlite(self, compiler, connection, **extra_context):
|
||||
if self.geo_field.geodetic(connection):
|
||||
extra_context['template'] = '%(function)s(%(expressions)s, %(spheroid)d)'
|
||||
extra_context['spheroid'] = True
|
||||
return self.as_sql(compiler, connection, **extra_context)
|
||||
|
||||
|
||||
class Azimuth(GeoFunc):
|
||||
output_field = FloatField()
|
||||
arity = 2
|
||||
geom_param_pos = (0, 1)
|
||||
|
||||
|
||||
class AsGeoJSON(GeoFunc):
|
||||
output_field = TextField()
|
||||
|
||||
def __init__(self, expression, bbox=False, crs=False, precision=8, **extra):
|
||||
expressions = [expression]
|
||||
if precision is not None:
|
||||
expressions.append(self._handle_param(precision, 'precision', int))
|
||||
options = 0
|
||||
if crs and bbox:
|
||||
options = 3
|
||||
elif bbox:
|
||||
options = 1
|
||||
elif crs:
|
||||
options = 2
|
||||
if options:
|
||||
expressions.append(options)
|
||||
super().__init__(*expressions, **extra)
|
||||
|
||||
def as_oracle(self, compiler, connection, **extra_context):
|
||||
source_expressions = self.get_source_expressions()
|
||||
clone = self.copy()
|
||||
clone.set_source_expressions(source_expressions[:1])
|
||||
return super(AsGeoJSON, clone).as_sql(compiler, connection, **extra_context)
|
||||
|
||||
|
||||
class AsGML(GeoFunc):
|
||||
geom_param_pos = (1,)
|
||||
output_field = TextField()
|
||||
|
||||
def __init__(self, expression, version=2, precision=8, **extra):
|
||||
expressions = [version, expression]
|
||||
if precision is not None:
|
||||
expressions.append(self._handle_param(precision, 'precision', int))
|
||||
super().__init__(*expressions, **extra)
|
||||
|
||||
def as_oracle(self, compiler, connection, **extra_context):
|
||||
source_expressions = self.get_source_expressions()
|
||||
version = source_expressions[0]
|
||||
clone = self.copy()
|
||||
clone.set_source_expressions([source_expressions[1]])
|
||||
extra_context['function'] = 'SDO_UTIL.TO_GML311GEOMETRY' if version.value == 3 else 'SDO_UTIL.TO_GMLGEOMETRY'
|
||||
return super(AsGML, clone).as_sql(compiler, connection, **extra_context)
|
||||
|
||||
|
||||
class AsKML(GeoFunc):
|
||||
output_field = TextField()
|
||||
|
||||
def __init__(self, expression, precision=8, **extra):
|
||||
expressions = [expression]
|
||||
if precision is not None:
|
||||
expressions.append(self._handle_param(precision, 'precision', int))
|
||||
super().__init__(*expressions, **extra)
|
||||
|
||||
|
||||
class AsSVG(GeoFunc):
|
||||
output_field = TextField()
|
||||
|
||||
def __init__(self, expression, relative=False, precision=8, **extra):
|
||||
relative = relative if hasattr(relative, 'resolve_expression') else int(relative)
|
||||
expressions = [
|
||||
expression,
|
||||
relative,
|
||||
self._handle_param(precision, 'precision', int),
|
||||
]
|
||||
super().__init__(*expressions, **extra)
|
||||
|
||||
|
||||
class AsWKB(GeoFunc):
|
||||
output_field = BinaryField()
|
||||
arity = 1
|
||||
|
||||
|
||||
class AsWKT(GeoFunc):
|
||||
output_field = TextField()
|
||||
arity = 1
|
||||
|
||||
|
||||
class BoundingCircle(OracleToleranceMixin, GeomOutputGeoFunc):
|
||||
def __init__(self, expression, num_seg=48, **extra):
|
||||
super().__init__(expression, num_seg, **extra)
|
||||
|
||||
def as_oracle(self, compiler, connection, **extra_context):
|
||||
clone = self.copy()
|
||||
clone.set_source_expressions([self.get_source_expressions()[0]])
|
||||
return super(BoundingCircle, clone).as_oracle(compiler, connection, **extra_context)
|
||||
|
||||
|
||||
class Centroid(OracleToleranceMixin, GeomOutputGeoFunc):
|
||||
arity = 1
|
||||
|
||||
|
||||
class Difference(OracleToleranceMixin, GeomOutputGeoFunc):
|
||||
arity = 2
|
||||
geom_param_pos = (0, 1)
|
||||
|
||||
|
||||
class DistanceResultMixin:
|
||||
@cached_property
|
||||
def output_field(self):
|
||||
return DistanceField(self.geo_field)
|
||||
|
||||
def source_is_geography(self):
|
||||
return self.geo_field.geography and self.geo_field.srid == 4326
|
||||
|
||||
|
||||
class Distance(DistanceResultMixin, OracleToleranceMixin, GeoFunc):
|
||||
geom_param_pos = (0, 1)
|
||||
spheroid = None
|
||||
|
||||
def __init__(self, expr1, expr2, spheroid=None, **extra):
|
||||
expressions = [expr1, expr2]
|
||||
if spheroid is not None:
|
||||
self.spheroid = self._handle_param(spheroid, 'spheroid', bool)
|
||||
super().__init__(*expressions, **extra)
|
||||
|
||||
def as_postgresql(self, compiler, connection, **extra_context):
|
||||
clone = self.copy()
|
||||
function = None
|
||||
expr2 = clone.source_expressions[1]
|
||||
geography = self.source_is_geography()
|
||||
if expr2.output_field.geography != geography:
|
||||
if isinstance(expr2, Value):
|
||||
expr2.output_field.geography = geography
|
||||
else:
|
||||
clone.source_expressions[1] = Cast(
|
||||
expr2,
|
||||
GeometryField(srid=expr2.output_field.srid, geography=geography),
|
||||
)
|
||||
|
||||
if not geography and self.geo_field.geodetic(connection):
|
||||
# Geometry fields with geodetic (lon/lat) coordinates need special distance functions
|
||||
if self.spheroid:
|
||||
# DistanceSpheroid is more accurate and resource intensive than DistanceSphere
|
||||
function = connection.ops.spatial_function_name('DistanceSpheroid')
|
||||
# Replace boolean param by the real spheroid of the base field
|
||||
clone.source_expressions.append(Value(self.geo_field.spheroid(connection)))
|
||||
else:
|
||||
function = connection.ops.spatial_function_name('DistanceSphere')
|
||||
return super(Distance, clone).as_sql(compiler, connection, function=function, **extra_context)
|
||||
|
||||
def as_sqlite(self, compiler, connection, **extra_context):
|
||||
if self.geo_field.geodetic(connection):
|
||||
# SpatiaLite returns NULL instead of zero on geodetic coordinates
|
||||
extra_context['template'] = 'COALESCE(%(function)s(%(expressions)s, %(spheroid)s), 0)'
|
||||
extra_context['spheroid'] = int(bool(self.spheroid))
|
||||
return super().as_sql(compiler, connection, **extra_context)
|
||||
|
||||
|
||||
class Envelope(GeomOutputGeoFunc):
|
||||
arity = 1
|
||||
|
||||
|
||||
class ForcePolygonCW(GeomOutputGeoFunc):
|
||||
arity = 1
|
||||
|
||||
|
||||
class GeoHash(GeoFunc):
|
||||
output_field = TextField()
|
||||
|
||||
def __init__(self, expression, precision=None, **extra):
|
||||
expressions = [expression]
|
||||
if precision is not None:
|
||||
expressions.append(self._handle_param(precision, 'precision', int))
|
||||
super().__init__(*expressions, **extra)
|
||||
|
||||
def as_mysql(self, compiler, connection, **extra_context):
|
||||
clone = self.copy()
|
||||
# If no precision is provided, set it to the maximum.
|
||||
if len(clone.source_expressions) < 2:
|
||||
clone.source_expressions.append(Value(100))
|
||||
return clone.as_sql(compiler, connection, **extra_context)
|
||||
|
||||
|
||||
class GeometryDistance(GeoFunc):
|
||||
output_field = FloatField()
|
||||
arity = 2
|
||||
function = ''
|
||||
arg_joiner = ' <-> '
|
||||
geom_param_pos = (0, 1)
|
||||
|
||||
|
||||
class Intersection(OracleToleranceMixin, GeomOutputGeoFunc):
|
||||
arity = 2
|
||||
geom_param_pos = (0, 1)
|
||||
|
||||
|
||||
@BaseSpatialField.register_lookup
|
||||
class IsValid(OracleToleranceMixin, GeoFuncMixin, Transform):
|
||||
lookup_name = 'isvalid'
|
||||
output_field = BooleanField()
|
||||
|
||||
def as_oracle(self, compiler, connection, **extra_context):
|
||||
sql, params = super().as_oracle(compiler, connection, **extra_context)
|
||||
return "CASE %s WHEN 'TRUE' THEN 1 ELSE 0 END" % sql, params
|
||||
|
||||
|
||||
class Length(DistanceResultMixin, OracleToleranceMixin, GeoFunc):
|
||||
def __init__(self, expr1, spheroid=True, **extra):
|
||||
self.spheroid = spheroid
|
||||
super().__init__(expr1, **extra)
|
||||
|
||||
def as_sql(self, compiler, connection, **extra_context):
|
||||
if self.geo_field.geodetic(connection) and not connection.features.supports_length_geodetic:
|
||||
raise NotSupportedError("This backend doesn't support Length on geodetic fields")
|
||||
return super().as_sql(compiler, connection, **extra_context)
|
||||
|
||||
def as_postgresql(self, compiler, connection, **extra_context):
|
||||
clone = self.copy()
|
||||
function = None
|
||||
if self.source_is_geography():
|
||||
clone.source_expressions.append(Value(self.spheroid))
|
||||
elif self.geo_field.geodetic(connection):
|
||||
# Geometry fields with geodetic (lon/lat) coordinates need length_spheroid
|
||||
function = connection.ops.spatial_function_name('LengthSpheroid')
|
||||
clone.source_expressions.append(Value(self.geo_field.spheroid(connection)))
|
||||
else:
|
||||
dim = min(f.dim for f in self.get_source_fields() if f)
|
||||
if dim > 2:
|
||||
function = connection.ops.length3d
|
||||
return super(Length, clone).as_sql(compiler, connection, function=function, **extra_context)
|
||||
|
||||
def as_sqlite(self, compiler, connection, **extra_context):
|
||||
function = None
|
||||
if self.geo_field.geodetic(connection):
|
||||
function = 'GeodesicLength' if self.spheroid else 'GreatCircleLength'
|
||||
return super().as_sql(compiler, connection, function=function, **extra_context)
|
||||
|
||||
|
||||
class LineLocatePoint(GeoFunc):
|
||||
output_field = FloatField()
|
||||
arity = 2
|
||||
geom_param_pos = (0, 1)
|
||||
|
||||
|
||||
class MakeValid(GeomOutputGeoFunc):
|
||||
pass
|
||||
|
||||
|
||||
class MemSize(GeoFunc):
|
||||
output_field = IntegerField()
|
||||
arity = 1
|
||||
|
||||
|
||||
class NumGeometries(GeoFunc):
|
||||
output_field = IntegerField()
|
||||
arity = 1
|
||||
|
||||
|
||||
class NumPoints(GeoFunc):
|
||||
output_field = IntegerField()
|
||||
arity = 1
|
||||
|
||||
|
||||
class Perimeter(DistanceResultMixin, OracleToleranceMixin, GeoFunc):
|
||||
arity = 1
|
||||
|
||||
def as_postgresql(self, compiler, connection, **extra_context):
|
||||
function = None
|
||||
if self.geo_field.geodetic(connection) and not self.source_is_geography():
|
||||
raise NotSupportedError("ST_Perimeter cannot use a non-projected non-geography field.")
|
||||
dim = min(f.dim for f in self.get_source_fields())
|
||||
if dim > 2:
|
||||
function = connection.ops.perimeter3d
|
||||
return super().as_sql(compiler, connection, function=function, **extra_context)
|
||||
|
||||
def as_sqlite(self, compiler, connection, **extra_context):
|
||||
if self.geo_field.geodetic(connection):
|
||||
raise NotSupportedError("Perimeter cannot use a non-projected field.")
|
||||
return super().as_sql(compiler, connection, **extra_context)
|
||||
|
||||
|
||||
class PointOnSurface(OracleToleranceMixin, GeomOutputGeoFunc):
|
||||
arity = 1
|
||||
|
||||
|
||||
class Reverse(GeoFunc):
|
||||
arity = 1
|
||||
|
||||
|
||||
class Scale(SQLiteDecimalToFloatMixin, GeomOutputGeoFunc):
|
||||
def __init__(self, expression, x, y, z=0.0, **extra):
|
||||
expressions = [
|
||||
expression,
|
||||
self._handle_param(x, 'x', NUMERIC_TYPES),
|
||||
self._handle_param(y, 'y', NUMERIC_TYPES),
|
||||
]
|
||||
if z != 0.0:
|
||||
expressions.append(self._handle_param(z, 'z', NUMERIC_TYPES))
|
||||
super().__init__(*expressions, **extra)
|
||||
|
||||
|
||||
class SnapToGrid(SQLiteDecimalToFloatMixin, GeomOutputGeoFunc):
|
||||
def __init__(self, expression, *args, **extra):
|
||||
nargs = len(args)
|
||||
expressions = [expression]
|
||||
if nargs in (1, 2):
|
||||
expressions.extend(
|
||||
[self._handle_param(arg, '', NUMERIC_TYPES) for arg in args]
|
||||
)
|
||||
elif nargs == 4:
|
||||
# Reverse origin and size param ordering
|
||||
expressions += [
|
||||
*(self._handle_param(arg, '', NUMERIC_TYPES) for arg in args[2:]),
|
||||
*(self._handle_param(arg, '', NUMERIC_TYPES) for arg in args[0:2]),
|
||||
]
|
||||
else:
|
||||
raise ValueError('Must provide 1, 2, or 4 arguments to `SnapToGrid`.')
|
||||
super().__init__(*expressions, **extra)
|
||||
|
||||
|
||||
class SymDifference(OracleToleranceMixin, GeomOutputGeoFunc):
|
||||
arity = 2
|
||||
geom_param_pos = (0, 1)
|
||||
|
||||
|
||||
class Transform(GeomOutputGeoFunc):
|
||||
def __init__(self, expression, srid, **extra):
|
||||
expressions = [
|
||||
expression,
|
||||
self._handle_param(srid, 'srid', int),
|
||||
]
|
||||
if 'output_field' not in extra:
|
||||
extra['output_field'] = GeometryField(srid=srid)
|
||||
super().__init__(*expressions, **extra)
|
||||
|
||||
|
||||
class Translate(Scale):
|
||||
def as_sqlite(self, compiler, connection, **extra_context):
|
||||
clone = self.copy()
|
||||
if len(self.source_expressions) < 4:
|
||||
# Always provide the z parameter for ST_Translate
|
||||
clone.source_expressions.append(Value(0))
|
||||
return super(Translate, clone).as_sqlite(compiler, connection, **extra_context)
|
||||
|
||||
|
||||
class Union(OracleToleranceMixin, GeomOutputGeoFunc):
|
||||
arity = 2
|
||||
geom_param_pos = (0, 1)
|
359
venv/Lib/site-packages/django/contrib/gis/db/models/lookups.py
Normal file
359
venv/Lib/site-packages/django/contrib/gis/db/models/lookups.py
Normal file
@@ -0,0 +1,359 @@
|
||||
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:
|
||||
# Check if a band index was passed in the query argument.
|
||||
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):
|
||||
"""
|
||||
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.
|
||||
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):
|
||||
# get_db_prep_lookup is called by process_rhs from super class
|
||||
return ('%s', [connection.ops.Adapter(value)])
|
||||
|
||||
def process_rhs(self, compiler, connection):
|
||||
if isinstance(self.rhs, Query):
|
||||
# If rhs is some Query, don't touch it.
|
||||
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):
|
||||
# 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).
|
||||
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)
|
||||
|
||||
|
||||
# ------------------
|
||||
# Geometry operators
|
||||
# ------------------
|
||||
|
||||
@BaseSpatialField.register_lookup
|
||||
class OverlapsLeftLookup(GISLookup):
|
||||
"""
|
||||
The overlaps_left operator returns true if A's bounding box overlaps or is to the
|
||||
left of B's bounding box.
|
||||
"""
|
||||
lookup_name = 'overlaps_left'
|
||||
|
||||
|
||||
@BaseSpatialField.register_lookup
|
||||
class OverlapsRightLookup(GISLookup):
|
||||
"""
|
||||
The 'overlaps_right' operator returns true if A's bounding box overlaps or is to the
|
||||
right of B's bounding box.
|
||||
"""
|
||||
lookup_name = 'overlaps_right'
|
||||
|
||||
|
||||
@BaseSpatialField.register_lookup
|
||||
class OverlapsBelowLookup(GISLookup):
|
||||
"""
|
||||
The 'overlaps_below' operator returns true if A's bounding box overlaps or is below
|
||||
B's bounding box.
|
||||
"""
|
||||
lookup_name = 'overlaps_below'
|
||||
|
||||
|
||||
@BaseSpatialField.register_lookup
|
||||
class OverlapsAboveLookup(GISLookup):
|
||||
"""
|
||||
The 'overlaps_above' operator returns true if A's bounding box overlaps or is above
|
||||
B's bounding box.
|
||||
"""
|
||||
lookup_name = 'overlaps_above'
|
||||
|
||||
|
||||
@BaseSpatialField.register_lookup
|
||||
class LeftLookup(GISLookup):
|
||||
"""
|
||||
The 'left' operator returns true if A's bounding box is strictly to the left
|
||||
of B's bounding box.
|
||||
"""
|
||||
lookup_name = 'left'
|
||||
|
||||
|
||||
@BaseSpatialField.register_lookup
|
||||
class RightLookup(GISLookup):
|
||||
"""
|
||||
The 'right' operator returns true if A's bounding box is strictly to the right
|
||||
of B's bounding box.
|
||||
"""
|
||||
lookup_name = 'right'
|
||||
|
||||
|
||||
@BaseSpatialField.register_lookup
|
||||
class StrictlyBelowLookup(GISLookup):
|
||||
"""
|
||||
The 'strictly_below' operator returns true if A's bounding box is strictly below B's
|
||||
bounding box.
|
||||
"""
|
||||
lookup_name = 'strictly_below'
|
||||
|
||||
|
||||
@BaseSpatialField.register_lookup
|
||||
class StrictlyAboveLookup(GISLookup):
|
||||
"""
|
||||
The 'strictly_above' operator returns true if A's bounding box is strictly above B's
|
||||
bounding box.
|
||||
"""
|
||||
lookup_name = 'strictly_above'
|
||||
|
||||
|
||||
@BaseSpatialField.register_lookup
|
||||
class SameAsLookup(GISLookup):
|
||||
"""
|
||||
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.
|
||||
"""
|
||||
lookup_name = 'same_as'
|
||||
|
||||
|
||||
BaseSpatialField.register_lookup(SameAsLookup, 'exact')
|
||||
|
||||
|
||||
@BaseSpatialField.register_lookup
|
||||
class BBContainsLookup(GISLookup):
|
||||
"""
|
||||
The 'bbcontains' operator returns true if A's bounding box completely contains
|
||||
by B's bounding box.
|
||||
"""
|
||||
lookup_name = 'bbcontains'
|
||||
|
||||
|
||||
@BaseSpatialField.register_lookup
|
||||
class BBOverlapsLookup(GISLookup):
|
||||
"""
|
||||
The 'bboverlaps' operator returns true if A's bounding box overlaps B's bounding box.
|
||||
"""
|
||||
lookup_name = 'bboverlaps'
|
||||
|
||||
|
||||
@BaseSpatialField.register_lookup
|
||||
class ContainedLookup(GISLookup):
|
||||
"""
|
||||
The 'contained' operator returns true if A's bounding box is completely contained
|
||||
by B's bounding box.
|
||||
"""
|
||||
lookup_name = 'contained'
|
||||
|
||||
|
||||
# ------------------
|
||||
# Geometry functions
|
||||
# ------------------
|
||||
|
||||
@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):
|
||||
# Check the pattern argument
|
||||
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.")
|
||||
|
||||
# Check if the second parameter is a band index.
|
||||
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 = '<='
|
79
venv/Lib/site-packages/django/contrib/gis/db/models/proxy.py
Normal file
79
venv/Lib/site-packages/django/contrib/gis/db/models/proxy.py
Normal file
@@ -0,0 +1,79 @@
|
||||
"""
|
||||
The SpatialProxy object allows for lazy-geometries and lazy-rasters. The proxy
|
||||
uses Python descriptors for instantiating and setting Geometry or Raster
|
||||
objects corresponding to geographic model fields.
|
||||
|
||||
Thanks to Robert Coup for providing this functionality (see #4322).
|
||||
"""
|
||||
from django.db.models.query_utils import DeferredAttribute
|
||||
|
||||
|
||||
class SpatialProxy(DeferredAttribute):
|
||||
def __init__(self, klass, field, load_func=None):
|
||||
"""
|
||||
Initialize on the given Geometry or Raster class (not an instance)
|
||||
and the corresponding field.
|
||||
"""
|
||||
self._klass = klass
|
||||
self._load_func = load_func or klass
|
||||
super().__init__(field)
|
||||
|
||||
def __get__(self, instance, cls=None):
|
||||
"""
|
||||
Retrieve the geometry or raster, initializing it using the
|
||||
corresponding class specified during initialization and the value of
|
||||
the field. Currently, GEOS or OGR geometries as well as GDALRasters are
|
||||
supported.
|
||||
"""
|
||||
if instance is None:
|
||||
# Accessed on a class, not an instance
|
||||
return self
|
||||
|
||||
# Getting the value of the field.
|
||||
try:
|
||||
geo_value = instance.__dict__[self.field.attname]
|
||||
except KeyError:
|
||||
geo_value = super().__get__(instance, cls)
|
||||
|
||||
if isinstance(geo_value, self._klass):
|
||||
geo_obj = geo_value
|
||||
elif (geo_value is None) or (geo_value == ''):
|
||||
geo_obj = None
|
||||
else:
|
||||
# Otherwise, a geometry or raster object is built using the field's
|
||||
# contents, and the model's corresponding attribute is set.
|
||||
geo_obj = self._load_func(geo_value)
|
||||
setattr(instance, self.field.attname, geo_obj)
|
||||
return geo_obj
|
||||
|
||||
def __set__(self, instance, value):
|
||||
"""
|
||||
Retrieve the proxied geometry or raster with the corresponding class
|
||||
specified during initialization.
|
||||
|
||||
To set geometries, use values of None, HEXEWKB, or WKT.
|
||||
To set rasters, use JSON or dict values.
|
||||
"""
|
||||
# The geographic type of the field.
|
||||
gtype = self.field.geom_type
|
||||
|
||||
if gtype == 'RASTER' and (value is None or isinstance(value, (str, dict, self._klass))):
|
||||
# For raster fields, ensure input is None or a string, dict, or
|
||||
# raster instance.
|
||||
pass
|
||||
elif isinstance(value, self._klass):
|
||||
# The geometry type must match that of the field -- unless the
|
||||
# general GeometryField is used.
|
||||
if value.srid is None:
|
||||
# Assigning the field SRID if the geometry has no SRID.
|
||||
value.srid = self.field.srid
|
||||
elif value is None or isinstance(value, (str, memoryview)):
|
||||
# Set geometries with None, WKT, HEX, or WKB
|
||||
pass
|
||||
else:
|
||||
raise TypeError('Cannot set %s SpatialProxy (%s) with value of type: %s' % (
|
||||
instance.__class__.__name__, gtype, type(value)))
|
||||
|
||||
# Setting the objects dictionary with the value, and returning.
|
||||
instance.__dict__[self.field.attname] = value
|
||||
return value
|
@@ -0,0 +1,7 @@
|
||||
from django.contrib.gis.db.models.sql.conversion import (
|
||||
AreaField, DistanceField,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
'AreaField', 'DistanceField',
|
||||
]
|
@@ -0,0 +1,69 @@
|
||||
"""
|
||||
This module holds simple classes to convert geospatial values from the
|
||||
database.
|
||||
"""
|
||||
from decimal import Decimal
|
||||
|
||||
from django.contrib.gis.measure import Area, Distance
|
||||
from django.db import models
|
||||
|
||||
|
||||
class AreaField(models.FloatField):
|
||||
"Wrapper for Area values."
|
||||
def __init__(self, geo_field):
|
||||
super().__init__()
|
||||
self.geo_field = geo_field
|
||||
|
||||
def get_prep_value(self, value):
|
||||
if not isinstance(value, Area):
|
||||
raise ValueError('AreaField only accepts Area measurement objects.')
|
||||
return value
|
||||
|
||||
def get_db_prep_value(self, value, connection, prepared=False):
|
||||
if value is None:
|
||||
return
|
||||
area_att = connection.ops.get_area_att_for_field(self.geo_field)
|
||||
return getattr(value, area_att) if area_att else value
|
||||
|
||||
def from_db_value(self, value, expression, connection):
|
||||
if value is None:
|
||||
return
|
||||
# If the database returns a Decimal, convert it to a float as expected
|
||||
# by the Python geometric objects.
|
||||
if isinstance(value, Decimal):
|
||||
value = float(value)
|
||||
# If the units are known, convert value into area measure.
|
||||
area_att = connection.ops.get_area_att_for_field(self.geo_field)
|
||||
return Area(**{area_att: value}) if area_att else value
|
||||
|
||||
def get_internal_type(self):
|
||||
return 'AreaField'
|
||||
|
||||
|
||||
class DistanceField(models.FloatField):
|
||||
"Wrapper for Distance values."
|
||||
def __init__(self, geo_field):
|
||||
super().__init__()
|
||||
self.geo_field = geo_field
|
||||
|
||||
def get_prep_value(self, value):
|
||||
if isinstance(value, Distance):
|
||||
return value
|
||||
return super().get_prep_value(value)
|
||||
|
||||
def get_db_prep_value(self, value, connection, prepared=False):
|
||||
if not isinstance(value, Distance):
|
||||
return value
|
||||
distance_att = connection.ops.get_distance_att_for_field(self.geo_field)
|
||||
if not distance_att:
|
||||
raise ValueError('Distance measure is supplied, but units are unknown for result.')
|
||||
return getattr(value, distance_att)
|
||||
|
||||
def from_db_value(self, value, expression, connection):
|
||||
if value is None:
|
||||
return
|
||||
distance_att = connection.ops.get_distance_att_for_field(self.geo_field)
|
||||
return Distance(**{distance_att: value}) if distance_att else value
|
||||
|
||||
def get_internal_type(self):
|
||||
return 'DistanceField'
|
Reference in New Issue
Block a user