diff options
Diffstat (limited to 'lib/python2.7/site-packages/django/contrib/gis/db/models/query.py')
-rw-r--r-- | lib/python2.7/site-packages/django/contrib/gis/db/models/query.py | 784 |
1 files changed, 0 insertions, 784 deletions
diff --git a/lib/python2.7/site-packages/django/contrib/gis/db/models/query.py b/lib/python2.7/site-packages/django/contrib/gis/db/models/query.py deleted file mode 100644 index c89912b..0000000 --- a/lib/python2.7/site-packages/django/contrib/gis/db/models/query.py +++ /dev/null @@ -1,784 +0,0 @@ -from django.db import connections -from django.db.models.query import QuerySet, ValuesQuerySet, ValuesListQuerySet - -from django.contrib.gis import memoryview -from django.contrib.gis.db.models import aggregates -from django.contrib.gis.db.models.fields import get_srid_info, PointField, LineStringField -from django.contrib.gis.db.models.sql import AreaField, DistanceField, GeomField, GeoQuery -from django.contrib.gis.geometry.backend import Geometry -from django.contrib.gis.measure import Area, Distance - -from django.utils import six - - -class GeoQuerySet(QuerySet): - "The Geographic QuerySet." - - ### Methods overloaded from QuerySet ### - def __init__(self, model=None, query=None, using=None): - super(GeoQuerySet, self).__init__(model=model, query=query, using=using) - self.query = query or GeoQuery(self.model) - - def values(self, *fields): - return self._clone(klass=GeoValuesQuerySet, setup=True, _fields=fields) - - def values_list(self, *fields, **kwargs): - flat = kwargs.pop('flat', False) - if kwargs: - raise TypeError('Unexpected keyword arguments to values_list: %s' - % (list(kwargs),)) - if flat and len(fields) > 1: - raise TypeError("'flat' is not valid when values_list is called with more than one field.") - return self._clone(klass=GeoValuesListQuerySet, setup=True, flat=flat, - _fields=fields) - - ### 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. - """ - # Peforming 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', None)) - 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 collect(self, **kwargs): - """ - Performs an aggregate collect operation on the given geometry field. - This is analagous to a union operation, but much faster because - boundaries are not dissolved. - """ - return self._spatial_aggregate(aggregates.Collect, **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 extent(self, **kwargs): - """ - Returns the extent (aggregate) of the features in the GeoQuerySet. The - extent will be returned as a 4-tuple, consisting of (xmin, ymin, xmax, ymax). - """ - return self._spatial_aggregate(aggregates.Extent, **kwargs) - - def extent3d(self, **kwargs): - """ - Returns the aggregate extent, in 3D, of the features in the - GeoQuerySet. It is returned as a 6-tuple, comprising: - (xmin, ymin, zmin, xmax, ymax, zmax). - """ - return self._spatial_aggregate(aggregates.Extent3D, **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 geomtry field in a `geojson` - attribute on each element of the GeoQuerySet. - - The `crs` and `bbox` keywords may be set to True if the users 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 1.3.4+ and SpatiaLite 3.0+ ' - 'support GeoJSON serialization.') - - if not isinstance(precision, six.integer_types): - raise TypeError('Precision keyword must be set with an integer.') - - # Setting the options flag -- which depends on which version of - # PostGIS we're using. SpatiaLite only uses the first group of options. - if backend.spatial_version >= (1, 4, 0): - options = 0 - if crs and bbox: options = 3 - elif bbox: options = 1 - elif crs: options = 2 - else: - options = 0 - if crs and bbox: options = 3 - elif crs: options = 1 - elif bbox: 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: - # PostGIS AsGML() aggregate function parameter order depends on the - # version -- uggh. - if backend.spatial_version > (1, 3, 1): - s['procedure_fmt'] = '%(version)s,%(geo_col)s,%(precision)s' - else: - s['procedure_fmt'] = '%(geo_col)s,%(precision)s,%(version)s' - s['procedure_args'] = {'precision' : precision, 'version' : version} - - 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 make_line(self, **kwargs): - """ - Creates a linestring from all of the PointField geometries in the - this GeoQuerySet and returns it. This is a spatial aggregate - method, and thus returns a geometry rather than a GeoQuerySet. - """ - return self._spatial_aggregate(aggregates.MakeLine, geo_field_type=PointField, **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', None) - tmp, geo_field = self._spatial_setup('transform', field_name=field_name) - - # Getting the selection SQL for the given geographic field. - field_col = self._geocol_select(geo_field, field_name) - - # Why cascading substitutions? Because spatial backends like - # Oracle and MySQL already require a function call to convert to text, thus - # when there's also a transformation we need to cascade the substitutions. - # For example, 'SDO_UTIL.TO_WKTGEOMETRY(SDO_CS.TRANSFORM( ... )' - geo_col = self.query.custom_select.get(geo_field, field_col) - - # Setting the key for the field's column with the custom SELECT SQL to - # override the geometry column returned from the database. - custom_sel = '%s(%s, %s)' % (connections[self.db].ops.transform, geo_col, srid) - # TODO: Should we have this as an alias? - # custom_sel = '(%s(%s, %s)) AS %s' % (SpatialBackend.transform, geo_col, srid, qn(geo_field.name)) - self.query.transformed_srid = srid # So other GeoQuerySet methods - self.query.custom_select[geo_field] = custom_sel - 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) - - def unionagg(self, **kwargs): - """ - Performs an aggregate union on the given geometry field. Returns - None if the GeoQuerySet is empty. The `tolerance` keyword is for - Oracle backends only. - """ - return self._spatial_aggregate(aggregates.Union, **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.query._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 not geo_field_type is 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_aggregate(self, aggregate, field_name=None, - geo_field_type=None, tolerance=0.05): - """ - DRY routine for calling aggregate spatial stored procedures and - returning their result to the caller of the function. - """ - # Getting the field the geographic aggregate will be called on. - geo_field = self.query._geo_field(field_name) - if not geo_field: - raise TypeError('%s aggregate only available on GeometryFields.' % aggregate.name) - - # Checking if there are any geo field type limitations on this - # aggregate (e.g. ST_Makeline only operates on PointFields). - if not geo_field_type is None and not isinstance(geo_field, geo_field_type): - raise TypeError('%s aggregate may only be called on %ss.' % (aggregate.name, geo_field_type.__name__)) - - # Getting the string expression of the field name, as this is the - # argument taken by `Aggregate` objects. - agg_col = field_name or geo_field.name - - # Adding any keyword parameters for the Aggregate object. Oracle backends - # in particular need an additional `tolerance` parameter. - agg_kwargs = {} - if connections[self.db].ops.oracle: agg_kwargs['tolerance'] = tolerance - - # Calling the QuerySet.aggregate, and returning only the value of the aggregate. - return self.aggregate(geoagg=aggregate(agg_col, **agg_kwargs))['geoagg'] - - 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: - Dictonary 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. - """ - # 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] - backend = connection.ops - - # 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', None)) - 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, 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', False): - sel_fld = settings['select_field'] - if isinstance(sel_fld, GeomField) and backend.select: - self.query.custom_select[model_att] = backend.select - if connection.ops.oracle: - sel_fld.empty_strings_allowed = False - self.query.extra_select_fields[model_att] = sel_fld - - # Finally, setting the extra selection attribute with - # the format string expanded with the stored procedure - # arguments. - return self.extra(select={model_att : fmt % settings['procedure_args']}, - select_params=settings['select_params']) - - 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', None)) - - # 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. - if self.query.transformed_srid: - u, unit_name, s = get_srid_info(self.query.transformed_srid, connection) - geodetic = unit_name in geo_field.geodetic_units - - if backend.spatialite and geodetic: - raise ValueError('SQLite does not support linear distance calculations on geodetic coordinate systems.') - - if distance: - if self.query.transformed_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, self.query.transformed_srid) - if geom.srid is None or geom.srid == self.query.transformed_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, self.query.transformed_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, self.query.transformed_srid) - else: - procedure_fmt += ', %s(%%%%s, %s)' % (backend.transform, self.query.transformed_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(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 backend.postgis: - # Use 3D variants of perimeter and length routines on PostGIS. - 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. - """ - opts = self.model._meta - if not geo_field 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). - self.query.add_select_related([field_name]) - compiler = self.query.get_compiler(self.db) - compiler.pre_sql_setup() - for (rel_table, rel_col), field in self.query.related_select_cols: - if field == geo_field: - return compiler._field_column(geo_field, rel_table) - raise ValueError("%r not in self.query.related_select_cols" % geo_field) - elif not geo_field 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. - tmp_fld, parent_model, direct, m2m = opts.get_field_by_name(geo_field.name) - return self.query.get_compiler(self.db)._field_column(geo_field, parent_model._meta.db_table) - else: - return self.query.get_compiler(self.db)._field_column(geo_field) - -class GeoValuesQuerySet(ValuesQuerySet): - def __init__(self, *args, **kwargs): - super(GeoValuesQuerySet, self).__init__(*args, **kwargs) - # This flag tells `resolve_columns` to run the values through - # `convert_values`. This ensures that Geometry objects instead - # of string values are returned with `values()` or `values_list()`. - self.query.geo_values = True - -class GeoValuesListQuerySet(GeoValuesQuerySet, ValuesListQuerySet): - pass |