2015-01-19 23:09:41 +08:00
|
|
|
from decimal import Decimal
|
|
|
|
|
2017-04-01 21:47:49 +08:00
|
|
|
from django.contrib.gis.db.models.fields import BaseSpatialField, GeometryField
|
2015-01-19 23:09:41 +08:00
|
|
|
from django.contrib.gis.db.models.sql import AreaField
|
2016-08-16 01:21:12 +08:00
|
|
|
from django.contrib.gis.geometry.backend import Geometry
|
2015-01-19 23:09:41 +08:00
|
|
|
from django.contrib.gis.measure import (
|
|
|
|
Area as AreaMeasure, Distance as DistanceMeasure,
|
|
|
|
)
|
|
|
|
from django.core.exceptions import FieldError
|
2017-03-29 20:56:58 +08:00
|
|
|
from django.db.models import (
|
|
|
|
BooleanField, FloatField, IntegerField, TextField, Transform,
|
|
|
|
)
|
2015-01-19 23:09:41 +08:00
|
|
|
from django.db.models.expressions import Func, Value
|
2017-04-01 21:47:49 +08:00
|
|
|
from django.db.models.functions import Cast
|
2015-01-19 23:09:41 +08:00
|
|
|
|
2016-12-29 23:27:49 +08:00
|
|
|
NUMERIC_TYPES = (int, float, Decimal)
|
2015-01-19 23:09:41 +08:00
|
|
|
|
|
|
|
|
2017-03-29 20:56:58 +08:00
|
|
|
class GeoFuncMixin:
|
2015-01-19 23:09:41 +08:00
|
|
|
function = None
|
|
|
|
output_field_class = None
|
2017-04-01 21:47:49 +08:00
|
|
|
geom_param_pos = (0,)
|
2015-01-19 23:09:41 +08:00
|
|
|
|
|
|
|
def __init__(self, *expressions, **extra):
|
|
|
|
if 'output_field' not in extra and self.output_field_class:
|
|
|
|
extra['output_field'] = self.output_field_class()
|
2017-01-21 21:13:44 +08:00
|
|
|
super().__init__(*expressions, **extra)
|
2015-01-19 23:09:41 +08:00
|
|
|
|
2017-04-01 21:47:49 +08:00
|
|
|
# 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, Geometry) 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))
|
|
|
|
|
2015-01-19 23:09:41 +08:00
|
|
|
@property
|
|
|
|
def name(self):
|
|
|
|
return self.__class__.__name__
|
|
|
|
|
|
|
|
@property
|
|
|
|
def srid(self):
|
2017-04-01 21:47:49 +08:00
|
|
|
return self.source_expressions[self.geom_param_pos[0]].field.srid
|
2015-01-19 23:09:41 +08:00
|
|
|
|
2016-11-16 20:07:36 +08:00
|
|
|
@property
|
|
|
|
def geo_field(self):
|
|
|
|
return GeometryField(srid=self.srid) if self.srid else None
|
|
|
|
|
2017-03-30 01:29:18 +08:00
|
|
|
def as_sql(self, compiler, connection, function=None, **extra_context):
|
|
|
|
if not self.function and not function:
|
|
|
|
function = connection.ops.spatial_function_name(self.name)
|
|
|
|
return super().as_sql(compiler, connection, function=function, **extra_context)
|
2015-01-19 23:09:41 +08:00
|
|
|
|
|
|
|
def resolve_expression(self, *args, **kwargs):
|
2017-01-21 21:13:44 +08:00
|
|
|
res = super().resolve_expression(*args, **kwargs)
|
2015-01-19 23:09:41 +08:00
|
|
|
|
2017-04-01 21:47:49 +08:00
|
|
|
# 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.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.
|
2015-01-19 23:09:41 +08:00
|
|
|
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." % (
|
2017-01-20 17:20:53 +08:00
|
|
|
param_name, check_types)
|
2015-01-19 23:09:41 +08:00
|
|
|
)
|
|
|
|
return value
|
|
|
|
|
|
|
|
|
2017-03-29 20:56:58 +08:00
|
|
|
class GeoFunc(GeoFuncMixin, Func):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
2017-04-01 21:47:49 +08:00
|
|
|
class GeomOutputGeoFunc(GeoFunc):
|
|
|
|
def __init__(self, *expressions, **extra):
|
|
|
|
if 'output_field' not in extra:
|
|
|
|
extra['output_field'] = GeometryField()
|
|
|
|
super(GeomOutputGeoFunc, self).__init__(*expressions, **extra)
|
2015-01-19 23:09:41 +08:00
|
|
|
|
2017-04-01 21:47:49 +08:00
|
|
|
def resolve_expression(self, *args, **kwargs):
|
|
|
|
res = super().resolve_expression(*args, **kwargs)
|
|
|
|
res.output_field.srid = res.srid
|
|
|
|
return res
|
2015-01-19 23:09:41 +08:00
|
|
|
|
|
|
|
|
2017-01-19 15:39:46 +08:00
|
|
|
class SQLiteDecimalToFloatMixin:
|
2015-01-24 05:45:57 +08:00
|
|
|
"""
|
|
|
|
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):
|
|
|
|
for expr in self.get_source_expressions():
|
|
|
|
if hasattr(expr, 'value') and isinstance(expr.value, Decimal):
|
|
|
|
expr.value = float(expr.value)
|
2017-01-21 21:13:44 +08:00
|
|
|
return super().as_sql(compiler, connection)
|
2015-01-24 05:45:57 +08:00
|
|
|
|
|
|
|
|
2017-01-19 15:39:46 +08:00
|
|
|
class OracleToleranceMixin:
|
2015-09-20 18:41:25 +08:00
|
|
|
tolerance = 0.05
|
|
|
|
|
|
|
|
def as_oracle(self, compiler, connection):
|
|
|
|
tol = self.extra.get('tolerance', self.tolerance)
|
2017-03-30 01:29:18 +08:00
|
|
|
return super().as_sql(compiler, connection, template="%%(function)s(%%(expressions)s, %s)" % tol)
|
2015-09-20 18:41:25 +08:00
|
|
|
|
|
|
|
|
|
|
|
class Area(OracleToleranceMixin, GeoFunc):
|
2016-01-22 19:03:05 +08:00
|
|
|
output_field_class = AreaField
|
2015-10-30 22:47:35 +08:00
|
|
|
arity = 1
|
|
|
|
|
2016-11-16 20:07:36 +08:00
|
|
|
def as_sql(self, compiler, connection, **extra_context):
|
2015-09-20 18:41:25 +08:00
|
|
|
if connection.ops.geography:
|
2016-01-22 19:03:05 +08:00
|
|
|
self.output_field.area_att = 'sq_m'
|
2015-09-20 18:41:25 +08:00
|
|
|
else:
|
2016-01-22 19:03:05 +08:00
|
|
|
# Getting the area units of the geographic field.
|
2016-11-16 20:07:36 +08:00
|
|
|
geo_field = self.geo_field
|
|
|
|
if geo_field.geodetic(connection):
|
|
|
|
if connection.features.supports_area_geodetic:
|
|
|
|
self.output_field.area_att = 'sq_m'
|
|
|
|
else:
|
2016-01-22 19:03:05 +08:00
|
|
|
# TODO: Do we want to support raw number areas for geodetic fields?
|
|
|
|
raise NotImplementedError('Area on geodetic coordinate systems not supported.')
|
2016-11-16 20:07:36 +08:00
|
|
|
else:
|
|
|
|
units_name = geo_field.units_name(connection)
|
2016-01-22 19:03:05 +08:00
|
|
|
if units_name:
|
|
|
|
self.output_field.area_att = AreaMeasure.unit_attname(units_name)
|
2017-01-21 21:13:44 +08:00
|
|
|
return super().as_sql(compiler, connection, **extra_context)
|
2015-01-19 23:09:41 +08:00
|
|
|
|
2015-09-20 18:41:25 +08:00
|
|
|
def as_oracle(self, compiler, connection):
|
|
|
|
self.output_field = AreaField('sq_m') # Oracle returns area in units of meters.
|
2017-01-21 21:13:44 +08:00
|
|
|
return super().as_oracle(compiler, connection)
|
2015-09-20 18:41:25 +08:00
|
|
|
|
2016-11-16 20:07:36 +08:00
|
|
|
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)
|
|
|
|
|
2015-01-19 23:09:41 +08:00
|
|
|
|
|
|
|
class AsGeoJSON(GeoFunc):
|
|
|
|
output_field_class = TextField
|
|
|
|
|
|
|
|
def __init__(self, expression, bbox=False, crs=False, precision=8, **extra):
|
|
|
|
expressions = [expression]
|
|
|
|
if precision is not None:
|
2016-12-29 23:27:49 +08:00
|
|
|
expressions.append(self._handle_param(precision, 'precision', int))
|
2015-01-19 23:09:41 +08:00
|
|
|
options = 0
|
|
|
|
if crs and bbox:
|
|
|
|
options = 3
|
|
|
|
elif bbox:
|
|
|
|
options = 1
|
|
|
|
elif crs:
|
|
|
|
options = 2
|
|
|
|
if options:
|
|
|
|
expressions.append(options)
|
2017-01-21 21:13:44 +08:00
|
|
|
super().__init__(*expressions, **extra)
|
2015-01-19 23:09:41 +08:00
|
|
|
|
|
|
|
|
|
|
|
class AsGML(GeoFunc):
|
2017-04-01 21:47:49 +08:00
|
|
|
geom_param_pos = (1,)
|
2015-01-19 23:09:41 +08:00
|
|
|
output_field_class = TextField
|
|
|
|
|
|
|
|
def __init__(self, expression, version=2, precision=8, **extra):
|
|
|
|
expressions = [version, expression]
|
|
|
|
if precision is not None:
|
2016-12-29 23:27:49 +08:00
|
|
|
expressions.append(self._handle_param(precision, 'precision', int))
|
2017-01-21 21:13:44 +08:00
|
|
|
super().__init__(*expressions, **extra)
|
2015-01-19 23:09:41 +08:00
|
|
|
|
2016-12-16 03:00:08 +08:00
|
|
|
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)
|
|
|
|
|
2015-01-19 23:09:41 +08:00
|
|
|
|
|
|
|
class AsKML(AsGML):
|
2015-01-24 05:45:57 +08:00
|
|
|
def as_sqlite(self, compiler, connection):
|
|
|
|
# No version parameter
|
2017-03-24 02:27:13 +08:00
|
|
|
clone = self.copy()
|
|
|
|
clone.set_source_expressions(self.get_source_expressions()[1:])
|
|
|
|
return clone.as_sql(compiler, connection)
|
2015-01-19 23:09:41 +08:00
|
|
|
|
|
|
|
|
|
|
|
class AsSVG(GeoFunc):
|
|
|
|
output_field_class = TextField
|
|
|
|
|
|
|
|
def __init__(self, expression, relative=False, precision=8, **extra):
|
|
|
|
relative = relative if hasattr(relative, 'resolve_expression') else int(relative)
|
|
|
|
expressions = [
|
|
|
|
expression,
|
|
|
|
relative,
|
2016-12-29 23:27:49 +08:00
|
|
|
self._handle_param(precision, 'precision', int),
|
2015-01-19 23:09:41 +08:00
|
|
|
]
|
2017-01-21 21:13:44 +08:00
|
|
|
super().__init__(*expressions, **extra)
|
2015-01-19 23:09:41 +08:00
|
|
|
|
|
|
|
|
2016-12-14 18:44:23 +08:00
|
|
|
class BoundingCircle(OracleToleranceMixin, GeoFunc):
|
2015-01-19 23:09:41 +08:00
|
|
|
def __init__(self, expression, num_seg=48, **extra):
|
2017-01-21 21:13:44 +08:00
|
|
|
super().__init__(*[expression, num_seg], **extra)
|
2015-01-19 23:09:41 +08:00
|
|
|
|
2016-12-14 18:44:23 +08:00
|
|
|
def as_oracle(self, compiler, connection):
|
|
|
|
clone = self.copy()
|
|
|
|
clone.set_source_expressions([self.get_source_expressions()[0]])
|
|
|
|
return super(BoundingCircle, clone).as_oracle(compiler, connection)
|
|
|
|
|
2015-01-19 23:09:41 +08:00
|
|
|
|
2017-04-01 21:47:49 +08:00
|
|
|
class Centroid(OracleToleranceMixin, GeomOutputGeoFunc):
|
2015-10-30 22:47:35 +08:00
|
|
|
arity = 1
|
2015-01-19 23:09:41 +08:00
|
|
|
|
|
|
|
|
2017-04-01 21:47:49 +08:00
|
|
|
class Difference(OracleToleranceMixin, GeomOutputGeoFunc):
|
2015-10-30 22:47:35 +08:00
|
|
|
arity = 2
|
2017-04-01 21:47:49 +08:00
|
|
|
geom_param_pos = (0, 1)
|
2015-01-19 23:09:41 +08:00
|
|
|
|
|
|
|
|
2017-01-19 15:39:46 +08:00
|
|
|
class DistanceResultMixin:
|
2015-10-11 01:22:42 +08:00
|
|
|
def source_is_geography(self):
|
|
|
|
return self.get_source_fields()[0].geography and self.srid == 4326
|
|
|
|
|
2015-01-19 23:09:41 +08:00
|
|
|
def convert_value(self, value, expression, connection, context):
|
|
|
|
if value is None:
|
|
|
|
return None
|
2016-11-16 20:07:36 +08:00
|
|
|
geo_field = self.geo_field
|
2015-01-19 23:09:41 +08:00
|
|
|
if geo_field.geodetic(connection):
|
|
|
|
dist_att = 'm'
|
|
|
|
else:
|
2015-01-25 07:03:02 +08:00
|
|
|
units = geo_field.units_name(connection)
|
|
|
|
if units:
|
|
|
|
dist_att = DistanceMeasure.unit_attname(units)
|
|
|
|
else:
|
|
|
|
dist_att = None
|
|
|
|
if dist_att:
|
|
|
|
return DistanceMeasure(**{dist_att: value})
|
|
|
|
return value
|
2015-01-19 23:09:41 +08:00
|
|
|
|
|
|
|
|
2017-04-01 21:47:49 +08:00
|
|
|
class Distance(DistanceResultMixin, OracleToleranceMixin, GeoFunc):
|
|
|
|
geom_param_pos = (0, 1)
|
2015-01-19 23:09:41 +08:00
|
|
|
output_field_class = FloatField
|
|
|
|
spheroid = None
|
|
|
|
|
|
|
|
def __init__(self, expr1, expr2, spheroid=None, **extra):
|
|
|
|
expressions = [expr1, expr2]
|
|
|
|
if spheroid is not None:
|
|
|
|
self.spheroid = spheroid
|
|
|
|
expressions += (self._handle_param(spheroid, 'spheroid', bool),)
|
2017-01-21 21:13:44 +08:00
|
|
|
super().__init__(*expressions, **extra)
|
2015-01-19 23:09:41 +08:00
|
|
|
|
|
|
|
def as_postgresql(self, compiler, connection):
|
2017-03-30 01:29:18 +08:00
|
|
|
function = None
|
2015-01-19 23:09:41 +08:00
|
|
|
geo_field = GeometryField(srid=self.srid) # Fake field to get SRID info
|
2017-04-01 21:47:49 +08:00
|
|
|
expr2 = self.source_expressions[1]
|
|
|
|
geography = self.source_is_geography()
|
|
|
|
if expr2.output_field.geography != geography:
|
|
|
|
if isinstance(expr2, Value):
|
|
|
|
expr2.output_field.geography = geography
|
|
|
|
else:
|
|
|
|
self.source_expressions[1] = Cast(
|
|
|
|
expr2,
|
|
|
|
GeometryField(srid=expr2.output_field.srid, geography=geography),
|
|
|
|
)
|
|
|
|
|
|
|
|
if not geography and geo_field.geodetic(connection):
|
2015-01-19 23:09:41 +08:00
|
|
|
# Geometry fields with geodetic (lon/lat) coordinates need special distance functions
|
|
|
|
if self.spheroid:
|
2016-11-13 05:18:22 +08:00
|
|
|
# DistanceSpheroid is more accurate and resource intensive than DistanceSphere
|
2017-03-30 01:29:18 +08:00
|
|
|
function = connection.ops.spatial_function_name('DistanceSpheroid')
|
2015-01-19 23:09:41 +08:00
|
|
|
# Replace boolean param by the real spheroid of the base field
|
|
|
|
self.source_expressions[2] = Value(geo_field._spheroid)
|
|
|
|
else:
|
2017-03-30 01:29:18 +08:00
|
|
|
function = connection.ops.spatial_function_name('DistanceSphere')
|
|
|
|
return super().as_sql(compiler, connection, function=function)
|
2015-01-19 23:09:41 +08:00
|
|
|
|
2015-09-20 18:41:25 +08:00
|
|
|
def as_oracle(self, compiler, connection):
|
|
|
|
if self.spheroid:
|
|
|
|
self.source_expressions.pop(2)
|
2017-01-21 21:13:44 +08:00
|
|
|
return super().as_oracle(compiler, connection)
|
2015-09-20 18:41:25 +08:00
|
|
|
|
2016-11-16 20:07:36 +08:00
|
|
|
def as_sqlite(self, compiler, connection, **extra_context):
|
|
|
|
if self.spheroid:
|
|
|
|
self.source_expressions.pop(2)
|
|
|
|
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))
|
2017-01-21 21:13:44 +08:00
|
|
|
return super().as_sql(compiler, connection, **extra_context)
|
2016-11-16 20:07:36 +08:00
|
|
|
|
2015-01-19 23:09:41 +08:00
|
|
|
|
2017-04-01 21:47:49 +08:00
|
|
|
class Envelope(GeomOutputGeoFunc):
|
2015-10-30 22:47:35 +08:00
|
|
|
arity = 1
|
2015-01-19 23:09:41 +08:00
|
|
|
|
|
|
|
|
2017-04-01 21:47:49 +08:00
|
|
|
class ForceRHR(GeomOutputGeoFunc):
|
2015-10-30 22:47:35 +08:00
|
|
|
arity = 1
|
2015-01-19 23:09:41 +08:00
|
|
|
|
|
|
|
|
|
|
|
class GeoHash(GeoFunc):
|
|
|
|
output_field_class = TextField
|
|
|
|
|
|
|
|
def __init__(self, expression, precision=None, **extra):
|
|
|
|
expressions = [expression]
|
|
|
|
if precision is not None:
|
2016-12-29 23:27:49 +08:00
|
|
|
expressions.append(self._handle_param(precision, 'precision', int))
|
2017-01-21 21:13:44 +08:00
|
|
|
super().__init__(*expressions, **extra)
|
2015-01-19 23:09:41 +08:00
|
|
|
|
|
|
|
|
2017-04-01 21:47:49 +08:00
|
|
|
class Intersection(OracleToleranceMixin, GeomOutputGeoFunc):
|
2015-10-30 22:47:35 +08:00
|
|
|
arity = 2
|
2017-04-01 21:47:49 +08:00
|
|
|
geom_param_pos = (0, 1)
|
2015-01-19 23:09:41 +08:00
|
|
|
|
|
|
|
|
2017-03-29 20:56:58 +08:00
|
|
|
@BaseSpatialField.register_lookup
|
|
|
|
class IsValid(OracleToleranceMixin, GeoFuncMixin, Transform):
|
|
|
|
lookup_name = 'isvalid'
|
|
|
|
output_field = BooleanField()
|
2016-04-06 19:50:25 +08:00
|
|
|
|
2016-12-01 00:22:56 +08:00
|
|
|
def as_oracle(self, compiler, connection, **extra_context):
|
2017-01-21 21:13:44 +08:00
|
|
|
sql, params = super().as_oracle(compiler, connection, **extra_context)
|
2016-12-01 00:22:56 +08:00
|
|
|
return "CASE %s WHEN 'TRUE' THEN 1 ELSE 0 END" % sql, params
|
|
|
|
|
2016-04-06 19:50:25 +08:00
|
|
|
|
2015-09-20 18:41:25 +08:00
|
|
|
class Length(DistanceResultMixin, OracleToleranceMixin, GeoFunc):
|
2015-01-19 23:09:41 +08:00
|
|
|
output_field_class = FloatField
|
|
|
|
|
|
|
|
def __init__(self, expr1, spheroid=True, **extra):
|
|
|
|
self.spheroid = spheroid
|
2017-01-21 21:13:44 +08:00
|
|
|
super().__init__(expr1, **extra)
|
2015-01-19 23:09:41 +08:00
|
|
|
|
2015-01-25 07:03:02 +08:00
|
|
|
def as_sql(self, compiler, connection):
|
|
|
|
geo_field = GeometryField(srid=self.srid) # Fake field to get SRID info
|
|
|
|
if geo_field.geodetic(connection) and not connection.features.supports_length_geodetic:
|
|
|
|
raise NotImplementedError("This backend doesn't support Length on geodetic fields")
|
2017-01-21 21:13:44 +08:00
|
|
|
return super().as_sql(compiler, connection)
|
2015-01-25 07:03:02 +08:00
|
|
|
|
2015-01-19 23:09:41 +08:00
|
|
|
def as_postgresql(self, compiler, connection):
|
2017-03-30 01:29:18 +08:00
|
|
|
function = None
|
2015-01-19 23:09:41 +08:00
|
|
|
geo_field = GeometryField(srid=self.srid) # Fake field to get SRID info
|
2015-10-11 01:22:42 +08:00
|
|
|
if self.source_is_geography():
|
2015-01-19 23:09:41 +08:00
|
|
|
self.source_expressions.append(Value(self.spheroid))
|
|
|
|
elif geo_field.geodetic(connection):
|
|
|
|
# Geometry fields with geodetic (lon/lat) coordinates need length_spheroid
|
2017-03-30 01:29:18 +08:00
|
|
|
function = connection.ops.spatial_function_name('LengthSpheroid')
|
2015-01-19 23:09:41 +08:00
|
|
|
self.source_expressions.append(Value(geo_field._spheroid))
|
|
|
|
else:
|
|
|
|
dim = min(f.dim for f in self.get_source_fields() if f)
|
|
|
|
if dim > 2:
|
2017-03-30 01:29:18 +08:00
|
|
|
function = connection.ops.length3d
|
|
|
|
return super().as_sql(compiler, connection, function=function)
|
2015-01-19 23:09:41 +08:00
|
|
|
|
2015-01-24 05:45:57 +08:00
|
|
|
def as_sqlite(self, compiler, connection):
|
2017-03-30 01:29:18 +08:00
|
|
|
function = None
|
2015-01-24 05:45:57 +08:00
|
|
|
geo_field = GeometryField(srid=self.srid)
|
|
|
|
if geo_field.geodetic(connection):
|
2017-03-30 01:29:18 +08:00
|
|
|
function = 'GeodesicLength' if self.spheroid else 'GreatCircleLength'
|
|
|
|
return super().as_sql(compiler, connection, function=function)
|
2015-01-24 05:45:57 +08:00
|
|
|
|
2015-01-19 23:09:41 +08:00
|
|
|
|
2016-04-06 19:50:25 +08:00
|
|
|
class MakeValid(GeoFunc):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
2015-01-19 23:09:41 +08:00
|
|
|
class MemSize(GeoFunc):
|
|
|
|
output_field_class = IntegerField
|
2015-10-30 22:47:35 +08:00
|
|
|
arity = 1
|
2015-01-19 23:09:41 +08:00
|
|
|
|
|
|
|
|
|
|
|
class NumGeometries(GeoFunc):
|
|
|
|
output_field_class = IntegerField
|
2015-10-30 22:47:35 +08:00
|
|
|
arity = 1
|
2015-01-19 23:09:41 +08:00
|
|
|
|
|
|
|
|
|
|
|
class NumPoints(GeoFunc):
|
|
|
|
output_field_class = IntegerField
|
2015-10-30 22:47:35 +08:00
|
|
|
arity = 1
|
2015-01-19 23:09:41 +08:00
|
|
|
|
2016-12-16 11:36:16 +08:00
|
|
|
def as_sql(self, compiler, connection):
|
2017-04-01 21:47:49 +08:00
|
|
|
if self.source_expressions[self.geom_param_pos[0]].output_field.geom_type != 'LINESTRING':
|
2016-12-16 11:36:16 +08:00
|
|
|
if not connection.features.supports_num_points_poly:
|
|
|
|
raise TypeError('NumPoints can only operate on LineString content on this database.')
|
2017-01-21 21:13:44 +08:00
|
|
|
return super().as_sql(compiler, connection)
|
2015-01-24 05:45:57 +08:00
|
|
|
|
2015-01-19 23:09:41 +08:00
|
|
|
|
2015-09-20 18:41:25 +08:00
|
|
|
class Perimeter(DistanceResultMixin, OracleToleranceMixin, GeoFunc):
|
2015-01-19 23:09:41 +08:00
|
|
|
output_field_class = FloatField
|
2015-10-30 22:47:35 +08:00
|
|
|
arity = 1
|
2015-01-19 23:09:41 +08:00
|
|
|
|
|
|
|
def as_postgresql(self, compiler, connection):
|
2017-03-30 01:29:18 +08:00
|
|
|
function = None
|
2015-10-11 01:22:42 +08:00
|
|
|
geo_field = GeometryField(srid=self.srid) # Fake field to get SRID info
|
|
|
|
if geo_field.geodetic(connection) and not self.source_is_geography():
|
|
|
|
raise NotImplementedError("ST_Perimeter cannot use a non-projected non-geography field.")
|
2015-01-19 23:09:41 +08:00
|
|
|
dim = min(f.dim for f in self.get_source_fields())
|
|
|
|
if dim > 2:
|
2017-03-30 01:29:18 +08:00
|
|
|
function = connection.ops.perimeter3d
|
|
|
|
return super().as_sql(compiler, connection, function=function)
|
2015-01-19 23:09:41 +08:00
|
|
|
|
2015-10-11 01:22:42 +08:00
|
|
|
def as_sqlite(self, compiler, connection):
|
|
|
|
geo_field = GeometryField(srid=self.srid) # Fake field to get SRID info
|
|
|
|
if geo_field.geodetic(connection):
|
|
|
|
raise NotImplementedError("Perimeter cannot use a non-projected field.")
|
2017-01-21 21:13:44 +08:00
|
|
|
return super().as_sql(compiler, connection)
|
2015-10-11 01:22:42 +08:00
|
|
|
|
2015-01-19 23:09:41 +08:00
|
|
|
|
2017-04-01 21:47:49 +08:00
|
|
|
class PointOnSurface(OracleToleranceMixin, GeomOutputGeoFunc):
|
2015-10-30 22:47:35 +08:00
|
|
|
arity = 1
|
2015-01-19 23:09:41 +08:00
|
|
|
|
|
|
|
|
|
|
|
class Reverse(GeoFunc):
|
2015-10-30 22:47:35 +08:00
|
|
|
arity = 1
|
2015-01-19 23:09:41 +08:00
|
|
|
|
|
|
|
|
2017-04-01 21:47:49 +08:00
|
|
|
class Scale(SQLiteDecimalToFloatMixin, GeomOutputGeoFunc):
|
2015-01-19 23:09:41 +08:00
|
|
|
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))
|
2017-01-21 21:13:44 +08:00
|
|
|
super().__init__(*expressions, **extra)
|
2015-01-19 23:09:41 +08:00
|
|
|
|
|
|
|
|
2017-04-01 21:47:49 +08:00
|
|
|
class SnapToGrid(SQLiteDecimalToFloatMixin, GeomOutputGeoFunc):
|
2015-01-19 23:09:41 +08:00
|
|
|
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.extend(
|
|
|
|
[self._handle_param(arg, '', NUMERIC_TYPES) for arg in args[2:]]
|
|
|
|
)
|
|
|
|
expressions.extend(
|
|
|
|
[self._handle_param(arg, '', NUMERIC_TYPES) for arg in args[0:2]]
|
|
|
|
)
|
|
|
|
else:
|
|
|
|
raise ValueError('Must provide 1, 2, or 4 arguments to `SnapToGrid`.')
|
2017-01-21 21:13:44 +08:00
|
|
|
super().__init__(*expressions, **extra)
|
2015-01-19 23:09:41 +08:00
|
|
|
|
|
|
|
|
2017-04-01 21:47:49 +08:00
|
|
|
class SymDifference(OracleToleranceMixin, GeomOutputGeoFunc):
|
2015-10-30 22:47:35 +08:00
|
|
|
arity = 2
|
2017-04-01 21:47:49 +08:00
|
|
|
geom_param_pos = (0, 1)
|
2015-01-19 23:09:41 +08:00
|
|
|
|
|
|
|
|
2017-04-01 21:47:49 +08:00
|
|
|
class Transform(GeomOutputGeoFunc):
|
2015-01-19 23:09:41 +08:00
|
|
|
def __init__(self, expression, srid, **extra):
|
|
|
|
expressions = [
|
|
|
|
expression,
|
2016-12-29 23:27:49 +08:00
|
|
|
self._handle_param(srid, 'srid', int),
|
2015-01-19 23:09:41 +08:00
|
|
|
]
|
2016-12-08 22:48:26 +08:00
|
|
|
if 'output_field' not in extra:
|
|
|
|
extra['output_field'] = GeometryField(srid=srid)
|
2017-01-21 21:13:44 +08:00
|
|
|
super().__init__(*expressions, **extra)
|
2015-01-19 23:09:41 +08:00
|
|
|
|
|
|
|
@property
|
|
|
|
def srid(self):
|
|
|
|
# Make srid the resulting srid of the transformation
|
2017-04-01 21:47:49 +08:00
|
|
|
return self.source_expressions[1].value
|
2015-01-19 23:09:41 +08:00
|
|
|
|
|
|
|
|
|
|
|
class Translate(Scale):
|
2015-01-24 05:45:57 +08:00
|
|
|
def as_sqlite(self, compiler, connection):
|
2016-06-13 22:32:07 +08:00
|
|
|
if len(self.source_expressions) < 4:
|
|
|
|
# Always provide the z parameter for ST_Translate
|
2015-01-24 05:45:57 +08:00
|
|
|
self.source_expressions.append(Value(0))
|
2017-01-21 21:13:44 +08:00
|
|
|
return super().as_sqlite(compiler, connection)
|
2015-01-19 23:09:41 +08:00
|
|
|
|
|
|
|
|
2017-04-01 21:47:49 +08:00
|
|
|
class Union(OracleToleranceMixin, GeomOutputGeoFunc):
|
2015-10-30 22:47:35 +08:00
|
|
|
arity = 2
|
2017-04-01 21:47:49 +08:00
|
|
|
geom_param_pos = (0, 1)
|