diff options
| author | Tim Graham <timograham@gmail.com> | 2016-11-07 13:00:40 +0100 |
|---|---|---|
| committer | Tim Graham <timograham@gmail.com> | 2017-01-17 20:51:56 -0500 |
| commit | a0d166306fbdc41f49e6fadf4ec84b17eb147daa (patch) | |
| tree | 112678f9dc1005900415f6c1564c241c17a6d688 /django | |
| parent | e90c745afdcf89bc3416c9cb2d4a06dfacb2a250 (diff) | |
Removed GeoManager and GeoQuerySet per deprecation timeline.
Diffstat (limited to 'django')
| -rw-r--r-- | django/contrib/gis/db/backends/base/features.py | 22 | ||||
| -rw-r--r-- | django/contrib/gis/db/backends/oracle/models.py | 1 | ||||
| -rw-r--r-- | django/contrib/gis/db/models/__init__.py | 3 | ||||
| -rw-r--r-- | django/contrib/gis/db/models/fields.py | 21 | ||||
| -rw-r--r-- | django/contrib/gis/db/models/manager.py | 26 | ||||
| -rw-r--r-- | django/contrib/gis/db/models/query.py | 727 |
6 files changed, 1 insertions, 799 deletions
diff --git a/django/contrib/gis/db/backends/base/features.py b/django/contrib/gis/db/backends/base/features.py index 166495a6cc..5ca2881330 100644 --- a/django/contrib/gis/db/backends/base/features.py +++ b/django/contrib/gis/db/backends/base/features.py @@ -1,5 +1,4 @@ import re -from functools import partial from django.contrib.gis.db.models import aggregates @@ -71,17 +70,6 @@ class BaseSpatialFeatures(object): def supports_isvalid_lookup(self): return 'isvalid' in self.connection.ops.gis_operators - # For each of those methods, the class will have a property named - # `has_<name>_method` (defined in __init__) which accesses connection.ops - # to determine GIS method availability. - geoqueryset_methods = ( - 'area', 'bounding_circle', 'centroid', 'difference', 'distance', - 'distance_spheroid', 'envelope', 'force_rhr', 'geohash', 'gml', - 'intersection', 'kml', 'length', 'mem_size', 'num_geom', 'num_points', - 'perimeter', 'point_on_surface', 'reverse', 'scale', 'snap_to_grid', - 'svg', 'sym_difference', 'transform', 'translate', 'union', 'unionagg', - ) - # Is the aggregate supported by the database? @property def supports_collect_aggr(self): @@ -99,19 +87,9 @@ class BaseSpatialFeatures(object): def supports_union_aggr(self): return aggregates.Union not in self.connection.ops.disallowed_aggregates - def __init__(self, *args): - super(BaseSpatialFeatures, self).__init__(*args) - for method in self.geoqueryset_methods: - # Add dynamically properties for each GQS method, e.g. has_force_rhr_method, etc. - setattr(self.__class__, 'has_%s_method' % method, - property(partial(BaseSpatialFeatures.has_ops_method, method=method))) - def __getattr__(self, name): m = re.match(r'has_(\w*)_function$', name) if m: func_name = m.group(1) return func_name not in self.connection.ops.unsupported_functions raise AttributeError - - def has_ops_method(self, method): - return getattr(self.connection.ops, method, False) diff --git a/django/contrib/gis/db/backends/oracle/models.py b/django/contrib/gis/db/backends/oracle/models.py index 02dcf212ea..e1bf49050b 100644 --- a/django/contrib/gis/db/backends/oracle/models.py +++ b/django/contrib/gis/db/backends/oracle/models.py @@ -55,7 +55,6 @@ class OracleSpatialRefSys(models.Model, SpatialRefSysMixin): # Optional geometry representing the bounds of this coordinate # system. By default, all are NULL in the table. cs_bounds = models.PolygonField(null=True) - objects = models.GeoManager() class Meta: app_label = 'gis' diff --git a/django/contrib/gis/db/models/__init__.py b/django/contrib/gis/db/models/__init__.py index 84f84d32b0..5f6c2374a7 100644 --- a/django/contrib/gis/db/models/__init__.py +++ b/django/contrib/gis/db/models/__init__.py @@ -7,11 +7,10 @@ from django.contrib.gis.db.models.fields import ( MultiLineStringField, MultiPointField, MultiPolygonField, PointField, PolygonField, RasterField, ) -from django.contrib.gis.db.models.manager import GeoManager __all__ = models_all + aggregates_all __all__ += [ 'GeometryCollectionField', 'GeometryField', 'LineStringField', 'MultiLineStringField', 'MultiPointField', 'MultiPolygonField', 'PointField', - 'PolygonField', 'RasterField', 'GeoManager', + 'PolygonField', 'RasterField', ] diff --git a/django/contrib/gis/db/models/fields.py b/django/contrib/gis/db/models/fields.py index 2fd74da632..215a17b6b5 100644 --- a/django/contrib/gis/db/models/fields.py +++ b/django/contrib/gis/db/models/fields.py @@ -346,27 +346,6 @@ class GeometryField(GeoSelectFormatMixin, BaseSpatialField): defaults['widget'] = forms.Textarea return super(GeometryField, self).formfield(**defaults) - def _get_db_prep_lookup(self, lookup_type, value, connection): - """ - Prepare for the database lookup, and return any spatial parameters - necessary for the query. This includes wrapping any geometry - parameters with a backend-specific adapter and formatting any distance - parameters into the correct units for the coordinate system of the - field. - - Only used by the deprecated GeoQuerySet and to be - RemovedInDjango20Warning. - """ - # Populating the parameters list, and wrapping the Geometry - # with the Adapter of the spatial backend. - if isinstance(value, (tuple, list)): - params = [connection.ops.Adapter(value[0])] - # Getting the distance parameter in the units of the field. - params += self.get_distance(value[1:], lookup_type, connection) - else: - params = [connection.ops.Adapter(value)] - return params - # The OpenGIS Geometry Type Fields class PointField(GeometryField): diff --git a/django/contrib/gis/db/models/manager.py b/django/contrib/gis/db/models/manager.py deleted file mode 100644 index 44f0140090..0000000000 --- a/django/contrib/gis/db/models/manager.py +++ /dev/null @@ -1,26 +0,0 @@ -import warnings - -from django.contrib.gis.db.models.query import GeoQuerySet -from django.db.models.manager import Manager -from django.utils.deprecation import RemovedInDjango20Warning - - -class GeoManager(Manager.from_queryset(GeoQuerySet)): - "Overrides Manager to return Geographic QuerySets." - - # This manager should be used for queries on related fields - # so that geometry columns on Oracle and MySQL are selected - # properly. - use_for_related_fields = True - - # No need to bother users with the use_for_related_fields - # deprecation for this manager which is itself deprecated. - silence_use_for_related_fields_deprecation = True - - def __init__(self, *args, **kwargs): - warnings.warn( - "The GeoManager class is deprecated. Simply use a normal manager " - "once you have replaced all calls to GeoQuerySet methods by annotations.", - RemovedInDjango20Warning, stacklevel=2 - ) - super(GeoManager, self).__init__(*args, **kwargs) diff --git a/django/contrib/gis/db/models/query.py b/django/contrib/gis/db/models/query.py deleted file mode 100644 index d1933721b6..0000000000 --- a/django/contrib/gis/db/models/query.py +++ /dev/null @@ -1,727 +0,0 @@ -import warnings - -from django.contrib.gis.db.models.fields import ( - GeometryField, LineStringField, PointField, get_srid_info, -) -from django.contrib.gis.db.models.lookups import GISLookup -from django.contrib.gis.db.models.sql import ( - AreaField, DistanceField, GeomField, GMLField, -) -from django.contrib.gis.geometry.backend import Geometry -from django.contrib.gis.measure import Area, Distance -from django.db import connections -from django.db.models.constants import LOOKUP_SEP -from django.db.models.expressions import RawSQL -from django.db.models.fields import Field -from django.db.models.query import QuerySet -from django.utils import six -from django.utils.deprecation import RemovedInDjango20Warning - - -class GeoQuerySet(QuerySet): - "The Geographic QuerySet." - - # ### GeoQuerySet Methods ### - def area(self, tolerance=0.05, **kwargs): - """ - Returns the area of the geographic field in an `area` attribute on - each element of this GeoQuerySet. - """ - # Performing setup here rather than in `_spatial_attribute` so that - # we can get the units for `AreaField`. - procedure_args, geo_field = self._spatial_setup( - 'area', field_name=kwargs.get('field_name')) - s = {'procedure_args': procedure_args, - 'geo_field': geo_field, - 'setup': False, - } - connection = connections[self.db] - backend = connection.ops - if backend.oracle: - s['procedure_fmt'] = '%(geo_col)s,%(tolerance)s' - s['procedure_args']['tolerance'] = tolerance - s['select_field'] = AreaField('sq_m') # Oracle returns area in units of meters. - elif backend.postgis or backend.spatialite: - if backend.geography: - # Geography fields support area calculation, returns square meters. - s['select_field'] = AreaField('sq_m') - elif not geo_field.geodetic(connection): - # Getting the area units of the geographic field. - s['select_field'] = AreaField(Area.unit_attname(geo_field.units_name(connection))) - else: - # TODO: Do we want to support raw number areas for geodetic fields? - raise Exception('Area on geodetic coordinate systems not supported.') - return self._spatial_attribute('area', s, **kwargs) - - def centroid(self, **kwargs): - """ - Returns the centroid of the geographic field in a `centroid` - attribute on each element of this GeoQuerySet. - """ - return self._geom_attribute('centroid', **kwargs) - - def difference(self, geom, **kwargs): - """ - Returns the spatial difference of the geographic field in a `difference` - attribute on each element of this GeoQuerySet. - """ - return self._geomset_attribute('difference', geom, **kwargs) - - def distance(self, geom, **kwargs): - """ - Returns the distance from the given geographic field name to the - given geometry in a `distance` attribute on each element of the - GeoQuerySet. - - Keyword Arguments: - `spheroid` => If the geometry field is geodetic and PostGIS is - the spatial database, then the more accurate - spheroid calculation will be used instead of the - quicker sphere calculation. - - `tolerance` => Used only for Oracle. The tolerance is - in meters -- a default of 5 centimeters (0.05) - is used. - """ - return self._distance_attribute('distance', geom, **kwargs) - - def envelope(self, **kwargs): - """ - Returns a Geometry representing the bounding box of the - Geometry field in an `envelope` attribute on each element of - the GeoQuerySet. - """ - return self._geom_attribute('envelope', **kwargs) - - def force_rhr(self, **kwargs): - """ - Returns a modified version of the Polygon/MultiPolygon in which - all of the vertices follow the Right-Hand-Rule. By default, - this is attached as the `force_rhr` attribute on each element - of the GeoQuerySet. - """ - return self._geom_attribute('force_rhr', **kwargs) - - def geojson(self, precision=8, crs=False, bbox=False, **kwargs): - """ - Returns a GeoJSON representation of the geometry field in a `geojson` - attribute on each element of the GeoQuerySet. - - The `crs` and `bbox` keywords may be set to True if the user wants - the coordinate reference system and the bounding box to be included - in the GeoJSON representation of the geometry. - """ - backend = connections[self.db].ops - if not backend.geojson: - raise NotImplementedError('Only PostGIS and SpatiaLite support GeoJSON serialization.') - - if not isinstance(precision, six.integer_types): - raise TypeError('Precision keyword must be set with an integer.') - - options = 0 - if crs and bbox: - options = 3 - elif bbox: - options = 1 - elif crs: - options = 2 - s = {'desc': 'GeoJSON', - 'procedure_args': {'precision': precision, 'options': options}, - 'procedure_fmt': '%(geo_col)s,%(precision)s,%(options)s', - } - return self._spatial_attribute('geojson', s, **kwargs) - - def geohash(self, precision=20, **kwargs): - """ - Returns a GeoHash representation of the given field in a `geohash` - attribute on each element of the GeoQuerySet. - - The `precision` keyword may be used to custom the number of - _characters_ used in the output GeoHash, the default is 20. - """ - s = {'desc': 'GeoHash', - 'procedure_args': {'precision': precision}, - 'procedure_fmt': '%(geo_col)s,%(precision)s', - } - return self._spatial_attribute('geohash', s, **kwargs) - - def gml(self, precision=8, version=2, **kwargs): - """ - Returns GML representation of the given field in a `gml` attribute - on each element of the GeoQuerySet. - """ - backend = connections[self.db].ops - s = {'desc': 'GML', 'procedure_args': {'precision': precision}} - if backend.postgis: - s['procedure_fmt'] = '%(version)s,%(geo_col)s,%(precision)s' - s['procedure_args'] = {'precision': precision, 'version': version} - if backend.oracle: - s['select_field'] = GMLField() - - return self._spatial_attribute('gml', s, **kwargs) - - def intersection(self, geom, **kwargs): - """ - Returns the spatial intersection of the Geometry field in - an `intersection` attribute on each element of this - GeoQuerySet. - """ - return self._geomset_attribute('intersection', geom, **kwargs) - - def kml(self, **kwargs): - """ - Returns KML representation of the geometry field in a `kml` - attribute on each element of this GeoQuerySet. - """ - s = {'desc': 'KML', - 'procedure_fmt': '%(geo_col)s,%(precision)s', - 'procedure_args': {'precision': kwargs.pop('precision', 8)}, - } - return self._spatial_attribute('kml', s, **kwargs) - - def length(self, **kwargs): - """ - Returns the length of the geometry field as a `Distance` object - stored in a `length` attribute on each element of this GeoQuerySet. - """ - return self._distance_attribute('length', None, **kwargs) - - def mem_size(self, **kwargs): - """ - Returns the memory size (number of bytes) that the geometry field takes - in a `mem_size` attribute on each element of this GeoQuerySet. - """ - return self._spatial_attribute('mem_size', {}, **kwargs) - - def num_geom(self, **kwargs): - """ - Returns the number of geometries if the field is a - GeometryCollection or Multi* Field in a `num_geom` - attribute on each element of this GeoQuerySet; otherwise - the sets with None. - """ - return self._spatial_attribute('num_geom', {}, **kwargs) - - def num_points(self, **kwargs): - """ - Returns the number of points in the first linestring in the - Geometry field in a `num_points` attribute on each element of - this GeoQuerySet; otherwise sets with None. - """ - return self._spatial_attribute('num_points', {}, **kwargs) - - def perimeter(self, **kwargs): - """ - Returns the perimeter of the geometry field as a `Distance` object - stored in a `perimeter` attribute on each element of this GeoQuerySet. - """ - return self._distance_attribute('perimeter', None, **kwargs) - - def point_on_surface(self, **kwargs): - """ - Returns a Point geometry guaranteed to lie on the surface of the - Geometry field in a `point_on_surface` attribute on each element - of this GeoQuerySet; otherwise sets with None. - """ - return self._geom_attribute('point_on_surface', **kwargs) - - def reverse_geom(self, **kwargs): - """ - Reverses the coordinate order of the geometry, and attaches as a - `reverse` attribute on each element of this GeoQuerySet. - """ - s = {'select_field': GeomField()} - kwargs.setdefault('model_att', 'reverse_geom') - if connections[self.db].ops.oracle: - s['geo_field_type'] = LineStringField - return self._spatial_attribute('reverse', s, **kwargs) - - def scale(self, x, y, z=0.0, **kwargs): - """ - Scales the geometry to a new size by multiplying the ordinates - with the given x,y,z scale factors. - """ - if connections[self.db].ops.spatialite: - if z != 0.0: - raise NotImplementedError('SpatiaLite does not support 3D scaling.') - s = {'procedure_fmt': '%(geo_col)s,%(x)s,%(y)s', - 'procedure_args': {'x': x, 'y': y}, - 'select_field': GeomField(), - } - else: - s = {'procedure_fmt': '%(geo_col)s,%(x)s,%(y)s,%(z)s', - 'procedure_args': {'x': x, 'y': y, 'z': z}, - 'select_field': GeomField(), - } - return self._spatial_attribute('scale', s, **kwargs) - - def snap_to_grid(self, *args, **kwargs): - """ - Snap all points of the input geometry to the grid. How the - geometry is snapped to the grid depends on how many arguments - were given: - - 1 argument : A single size to snap both the X and Y grids to. - - 2 arguments: X and Y sizes to snap the grid to. - - 4 arguments: X, Y sizes and the X, Y origins. - """ - if False in [isinstance(arg, (float,) + six.integer_types) for arg in args]: - raise TypeError('Size argument(s) for the grid must be a float or integer values.') - - nargs = len(args) - if nargs == 1: - size = args[0] - procedure_fmt = '%(geo_col)s,%(size)s' - procedure_args = {'size': size} - elif nargs == 2: - xsize, ysize = args - procedure_fmt = '%(geo_col)s,%(xsize)s,%(ysize)s' - procedure_args = {'xsize': xsize, 'ysize': ysize} - elif nargs == 4: - xsize, ysize, xorigin, yorigin = args - procedure_fmt = '%(geo_col)s,%(xorigin)s,%(yorigin)s,%(xsize)s,%(ysize)s' - procedure_args = {'xsize': xsize, 'ysize': ysize, - 'xorigin': xorigin, 'yorigin': yorigin} - else: - raise ValueError('Must provide 1, 2, or 4 arguments to `snap_to_grid`.') - - s = {'procedure_fmt': procedure_fmt, - 'procedure_args': procedure_args, - 'select_field': GeomField(), - } - - return self._spatial_attribute('snap_to_grid', s, **kwargs) - - def svg(self, relative=False, precision=8, **kwargs): - """ - Returns SVG representation of the geographic field in a `svg` - attribute on each element of this GeoQuerySet. - - Keyword Arguments: - `relative` => If set to True, this will evaluate the path in - terms of relative moves (rather than absolute). - - `precision` => May be used to set the maximum number of decimal - digits used in output (defaults to 8). - """ - relative = int(bool(relative)) - if not isinstance(precision, six.integer_types): - raise TypeError('SVG precision keyword argument must be an integer.') - s = { - 'desc': 'SVG', - 'procedure_fmt': '%(geo_col)s,%(rel)s,%(precision)s', - 'procedure_args': { - 'rel': relative, - 'precision': precision, - } - } - return self._spatial_attribute('svg', s, **kwargs) - - def sym_difference(self, geom, **kwargs): - """ - Returns the symmetric difference of the geographic field in a - `sym_difference` attribute on each element of this GeoQuerySet. - """ - return self._geomset_attribute('sym_difference', geom, **kwargs) - - def translate(self, x, y, z=0.0, **kwargs): - """ - Translates the geometry to a new location using the given numeric - parameters as offsets. - """ - if connections[self.db].ops.spatialite: - if z != 0.0: - raise NotImplementedError('SpatiaLite does not support 3D translation.') - s = {'procedure_fmt': '%(geo_col)s,%(x)s,%(y)s', - 'procedure_args': {'x': x, 'y': y}, - 'select_field': GeomField(), - } - else: - s = {'procedure_fmt': '%(geo_col)s,%(x)s,%(y)s,%(z)s', - 'procedure_args': {'x': x, 'y': y, 'z': z}, - 'select_field': GeomField(), - } - return self._spatial_attribute('translate', s, **kwargs) - - def transform(self, srid=4326, **kwargs): - """ - Transforms the given geometry field to the given SRID. If no SRID is - provided, the transformation will default to using 4326 (WGS84). - """ - if not isinstance(srid, six.integer_types): - raise TypeError('An integer SRID must be provided.') - field_name = kwargs.get('field_name') - self._spatial_setup('transform', field_name=field_name) - self.query.add_context('transformed_srid', srid) - return self._clone() - - def union(self, geom, **kwargs): - """ - Returns the union of the geographic field with the given - Geometry in a `union` attribute on each element of this GeoQuerySet. - """ - return self._geomset_attribute('union', geom, **kwargs) - - # ### Private API -- Abstracted DRY routines. ### - def _spatial_setup(self, att, desc=None, field_name=None, geo_field_type=None): - """ - Performs set up for executing the spatial function. - """ - # Does the spatial backend support this? - connection = connections[self.db] - func = getattr(connection.ops, att, False) - if desc is None: - desc = att - if not func: - raise NotImplementedError('%s stored procedure not available on ' - 'the %s backend.' % - (desc, connection.ops.name)) - - # Initializing the procedure arguments. - procedure_args = {'function': func} - - # Is there a geographic field in the model to perform this - # operation on? - geo_field = self._geo_field(field_name) - if not geo_field: - raise TypeError('%s output only available on GeometryFields.' % func) - - # If the `geo_field_type` keyword was used, then enforce that - # type limitation. - if geo_field_type is not None and not isinstance(geo_field, geo_field_type): - raise TypeError('"%s" stored procedures may only be called on %ss.' % (func, geo_field_type.__name__)) - - # Setting the procedure args. - procedure_args['geo_col'] = self._geocol_select(geo_field, field_name) - - return procedure_args, geo_field - - def _spatial_attribute(self, att, settings, field_name=None, model_att=None): - """ - DRY routine for calling a spatial stored procedure on a geometry column - and attaching its output as an attribute of the model. - - Arguments: - att: - The name of the spatial attribute that holds the spatial - SQL function to call. - - settings: - Dictionary of internal settings to customize for the spatial procedure. - - Public Keyword Arguments: - - field_name: - The name of the geographic field to call the spatial - function on. May also be a lookup to a geometry field - as part of a foreign key relation. - - model_att: - The name of the model attribute to attach the output of - the spatial function to. - """ - warnings.warn( - "The %s GeoQuerySet method is deprecated. See GeoDjango Functions " - "documentation to find the expression-based replacement." % att, - RemovedInDjango20Warning, stacklevel=2 - ) - # Default settings. - settings.setdefault('desc', None) - settings.setdefault('geom_args', ()) - settings.setdefault('geom_field', None) - settings.setdefault('procedure_args', {}) - settings.setdefault('procedure_fmt', '%(geo_col)s') - settings.setdefault('select_params', []) - - connection = connections[self.db] - - # Performing setup for the spatial column, unless told not to. - if settings.get('setup', True): - default_args, geo_field = self._spatial_setup( - att, desc=settings['desc'], field_name=field_name, - geo_field_type=settings.get('geo_field_type')) - for k, v in six.iteritems(default_args): - settings['procedure_args'].setdefault(k, v) - else: - geo_field = settings['geo_field'] - - # The attribute to attach to the model. - if not isinstance(model_att, six.string_types): - model_att = att - - # Special handling for any argument that is a geometry. - for name in settings['geom_args']: - # Using the field's get_placeholder() routine to get any needed - # transformation SQL. - geom = geo_field.get_prep_value(settings['procedure_args'][name]) - params = geo_field._get_db_prep_lookup('contains', geom, connection=connection) - geom_placeholder = geo_field.get_placeholder(geom, None, connection) - - # Replacing the procedure format with that of any needed - # transformation SQL. - old_fmt = '%%(%s)s' % name - new_fmt = geom_placeholder % '%%s' - settings['procedure_fmt'] = settings['procedure_fmt'].replace(old_fmt, new_fmt) - settings['select_params'].extend(params) - - # Getting the format for the stored procedure. - fmt = '%%(function)s(%s)' % settings['procedure_fmt'] - - # If the result of this function needs to be converted. - if settings.get('select_field'): - select_field = settings['select_field'] - if connection.ops.oracle: - select_field.empty_strings_allowed = False - else: - select_field = Field() - - # Finally, setting the extra selection attribute with - # the format string expanded with the stored procedure - # arguments. - self.query.add_annotation( - RawSQL(fmt % settings['procedure_args'], settings['select_params'], select_field), - model_att) - return self - - def _distance_attribute(self, func, geom=None, tolerance=0.05, spheroid=False, **kwargs): - """ - DRY routine for GeoQuerySet distance attribute routines. - """ - # Setting up the distance procedure arguments. - procedure_args, geo_field = self._spatial_setup(func, field_name=kwargs.get('field_name')) - - # If geodetic defaulting distance attribute to meters (Oracle and - # PostGIS spherical distances return meters). Otherwise, use the - # units of the geometry field. - connection = connections[self.db] - geodetic = geo_field.geodetic(connection) - geography = geo_field.geography - - if geodetic: - dist_att = 'm' - else: - dist_att = Distance.unit_attname(geo_field.units_name(connection)) - - # Shortcut booleans for what distance function we're using and - # whether the geometry field is 3D. - distance = func == 'distance' - length = func == 'length' - perimeter = func == 'perimeter' - if not (distance or length or perimeter): - raise ValueError('Unknown distance function: %s' % func) - geom_3d = geo_field.dim == 3 - - # The field's _get_db_prep_lookup() is used to get any - # extra distance parameters. Here we set up the - # parameters that will be passed in to field's function. - lookup_params = [geom or 'POINT (0 0)', 0] - - # Getting the spatial backend operations. - backend = connection.ops - - # If the spheroid calculation is desired, either by the `spheroid` - # keyword or when calculating the length of geodetic field, make - # sure the 'spheroid' distance setting string is passed in so we - # get the correct spatial stored procedure. - if spheroid or (backend.postgis and geodetic and - (not geography) and length): - lookup_params.append('spheroid') - lookup_params = geo_field.get_prep_value(lookup_params) - params = geo_field._get_db_prep_lookup('distance_lte', lookup_params, connection=connection) - - # The `geom_args` flag is set to true if a geometry parameter was - # passed in. - geom_args = bool(geom) - - if backend.oracle: - if distance: - procedure_fmt = '%(geo_col)s,%(geom)s,%(tolerance)s' - elif length or perimeter: - procedure_fmt = '%(geo_col)s,%(tolerance)s' - procedure_args['tolerance'] = tolerance - else: - # Getting whether this field is in units of degrees since the field may have - # been transformed via the `transform` GeoQuerySet method. - srid = self.query.get_context('transformed_srid') - if srid: - u, unit_name, s = get_srid_info(srid, connection) - geodetic = unit_name.lower() in geo_field.geodetic_units - - if geodetic and (not connection.features.supports_distance_geodetic or connection.ops.spatialite): - raise ValueError( - 'This database does not support linear distance ' - 'calculations on geodetic coordinate systems.' - ) - - if distance: - if srid: - # Setting the `geom_args` flag to false because we want to handle - # transformation SQL here, rather than the way done by default - # (which will transform to the original SRID of the field rather - # than to what was transformed to). - geom_args = False - procedure_fmt = '%s(%%(geo_col)s, %s)' % (backend.transform, srid) - if geom.srid is None or geom.srid == srid: - # If the geom parameter srid is None, it is assumed the coordinates - # are in the transformed units. A placeholder is used for the - # geometry parameter. `GeomFromText` constructor is also needed - # to wrap geom placeholder for SpatiaLite. - if backend.spatialite: - procedure_fmt += ', %s(%%%%s, %s)' % (backend.from_text, srid) - else: - procedure_fmt += ', %%s' - else: - # We need to transform the geom to the srid specified in `transform()`, - # so wrapping the geometry placeholder in transformation SQL. - # SpatiaLite also needs geometry placeholder wrapped in `GeomFromText` - # constructor. - if backend.spatialite: - procedure_fmt += (', %s(%s(%%%%s, %s), %s)' % ( - backend.transform, backend.from_text, - geom.srid, srid)) - else: - procedure_fmt += ', %s(%%%%s, %s)' % (backend.transform, srid) - else: - # `transform()` was not used on this GeoQuerySet. - procedure_fmt = '%(geo_col)s,%(geom)s' - - if not geography and geodetic: - # Spherical distance calculation is needed (because the geographic - # field is geodetic). However, the PostGIS ST_distance_sphere/spheroid() - # procedures may only do queries from point columns to point geometries - # some error checking is required. - if not backend.geography: - if not isinstance(geo_field, PointField): - raise ValueError('Spherical distance calculation only supported on PointFields.') - if not str(Geometry(six.memoryview(params[0].ewkb)).geom_type) == 'Point': - raise ValueError( - 'Spherical distance calculation only supported with ' - 'Point Geometry parameters' - ) - # The `function` procedure argument needs to be set differently for - # geodetic distance calculations. - if spheroid: - # Call to distance_spheroid() requires spheroid param as well. - procedure_fmt += ",'%(spheroid)s'" - procedure_args.update({'function': backend.distance_spheroid, 'spheroid': params[1]}) - else: - procedure_args.update({'function': backend.distance_sphere}) - elif length or perimeter: - procedure_fmt = '%(geo_col)s' - if not geography and geodetic and length: - # There's no `length_sphere`, and `length_spheroid` also - # works on 3D geometries. - procedure_fmt += ",'%(spheroid)s'" - procedure_args.update({'function': backend.length_spheroid, 'spheroid': params[1]}) - elif geom_3d and connection.features.supports_3d_functions: - # Use 3D variants of perimeter and length routines on supported backends. - if perimeter: - procedure_args.update({'function': backend.perimeter3d}) - elif length: - procedure_args.update({'function': backend.length3d}) - - # Setting up the settings for `_spatial_attribute`. - s = {'select_field': DistanceField(dist_att), - 'setup': False, - 'geo_field': geo_field, - 'procedure_args': procedure_args, - 'procedure_fmt': procedure_fmt, - } - if geom_args: - s['geom_args'] = ('geom',) - s['procedure_args']['geom'] = geom - elif geom: - # The geometry is passed in as a parameter because we handled - # transformation conditions in this routine. - s['select_params'] = [backend.Adapter(geom)] - return self._spatial_attribute(func, s, **kwargs) - - def _geom_attribute(self, func, tolerance=0.05, **kwargs): - """ - DRY routine for setting up a GeoQuerySet method that attaches a - Geometry attribute (e.g., `centroid`, `point_on_surface`). - """ - s = {'select_field': GeomField()} - if connections[self.db].ops.oracle: - s['procedure_fmt'] = '%(geo_col)s,%(tolerance)s' - s['procedure_args'] = {'tolerance': tolerance} - return self._spatial_attribute(func, s, **kwargs) - - def _geomset_attribute(self, func, geom, tolerance=0.05, **kwargs): - """ - DRY routine for setting up a GeoQuerySet method that attaches a - Geometry attribute and takes a Geoemtry parameter. This is used - for geometry set-like operations (e.g., intersection, difference, - union, sym_difference). - """ - s = { - 'geom_args': ('geom',), - 'select_field': GeomField(), - 'procedure_fmt': '%(geo_col)s,%(geom)s', - 'procedure_args': {'geom': geom}, - } - if connections[self.db].ops.oracle: - s['procedure_fmt'] += ',%(tolerance)s' - s['procedure_args']['tolerance'] = tolerance - return self._spatial_attribute(func, s, **kwargs) - - def _geocol_select(self, geo_field, field_name): - """ - Helper routine for constructing the SQL to select the geographic - column. Takes into account if the geographic field is in a - ForeignKey relation to the current model. - """ - compiler = self.query.get_compiler(self.db) - opts = self.model._meta - if geo_field not in opts.fields: - # Is this operation going to be on a related geographic field? - # If so, it'll have to be added to the select related information - # (e.g., if 'location__point' was given as the field name, then - # chop the non-relational field and add select_related('location')). - # Note: the operation really is defined as "must add select related!" - self.query.add_select_related([field_name.rsplit(LOOKUP_SEP, 1)[0]]) - # Call pre_sql_setup() so that compiler.select gets populated. - compiler.pre_sql_setup() - for col, _, _ in compiler.select: - if col.output_field == geo_field: - return col.as_sql(compiler, compiler.connection)[0] - raise ValueError("%r not in compiler's related_select_cols" % geo_field) - elif geo_field not in opts.local_fields: - # This geographic field is inherited from another model, so we have to - # use the db table for the _parent_ model instead. - parent_model = geo_field.model._meta.concrete_model - return self._field_column(compiler, geo_field, parent_model._meta.db_table) - else: - return self._field_column(compiler, geo_field) - - # Private API utilities, subject to change. - def _geo_field(self, field_name=None): - """ - Returns the first Geometry field encountered or the one specified via - the `field_name` keyword. The `field_name` may be a string specifying - the geometry field on this GeoQuerySet's model, or a lookup string - to a geometry field via a ForeignKey relation. - """ - if field_name is None: - # Incrementing until the first geographic field is found. - for field in self.model._meta.fields: - if isinstance(field, GeometryField): - return field - return False - else: - # Otherwise, check by the given field name -- which may be - # a lookup to a _related_ geographic field. - return GISLookup._check_geo_field(self.model._meta, field_name) - - def _field_column(self, compiler, field, table_alias=None, column=None): - """ - Helper function that returns the database column for the given field. - The table and column are returned (quoted) in the proper format, e.g., - `"geoapp_city"."point"`. If `table_alias` is not specified, the - database table associated with the model of this `GeoQuerySet` will be - used. If `column` is specified, it will be used instead of the value - in `field.column`. - """ - if table_alias is None: - table_alias = compiler.query.get_meta().db_table - return "%s.%s" % (compiler.quote_name_unless_alias(table_alias), - compiler.connection.ops.quote_name(column or field.column)) |
