web/lib/django/contrib/gis/db/models/query.py
changeset 29 cc9b7e14412b
parent 0 0d40e90630ef
equal deleted inserted replaced
28:b758351d191f 29:cc9b7e14412b
     1 from django.core.exceptions import ImproperlyConfigured
     1 from django.db import connections
     2 from django.db import connection
       
     3 from django.db.models.query import QuerySet, Q, ValuesQuerySet, ValuesListQuerySet
     2 from django.db.models.query import QuerySet, Q, ValuesQuerySet, ValuesListQuerySet
     4 
     3 
     5 from django.contrib.gis.db.backend import SpatialBackend
       
     6 from django.contrib.gis.db.models import aggregates
     4 from django.contrib.gis.db.models import aggregates
     7 from django.contrib.gis.db.models.fields import get_srid_info, GeometryField, PointField
     5 from django.contrib.gis.db.models.fields import get_srid_info, GeometryField, PointField, LineStringField
     8 from django.contrib.gis.db.models.sql import AreaField, DistanceField, GeomField, GeoQuery, GeoWhereNode
     6 from django.contrib.gis.db.models.sql import AreaField, DistanceField, GeomField, GeoQuery, GeoWhereNode
       
     7 from django.contrib.gis.geometry.backend import Geometry
     9 from django.contrib.gis.measure import Area, Distance
     8 from django.contrib.gis.measure import Area, Distance
    10 
     9 
    11 class GeoQuerySet(QuerySet):
    10 class GeoQuerySet(QuerySet):
    12     "The Geographic QuerySet."
    11     "The Geographic QuerySet."
    13 
    12 
    14     ### Methods overloaded from QuerySet ###
    13     ### Methods overloaded from QuerySet ###
    15     def __init__(self, model=None, query=None):
    14     def __init__(self, model=None, query=None, using=None):
    16         super(GeoQuerySet, self).__init__(model=model, query=query)
    15         super(GeoQuerySet, self).__init__(model=model, query=query, using=using)
    17         self.query = query or GeoQuery(self.model, connection)
    16         self.query = query or GeoQuery(self.model)
    18 
    17 
    19     def values(self, *fields):
    18     def values(self, *fields):
    20         return self._clone(klass=GeoValuesQuerySet, setup=True, _fields=fields)
    19         return self._clone(klass=GeoValuesQuerySet, setup=True, _fields=fields)
    21 
    20 
    22     def values_list(self, *fields, **kwargs):
    21     def values_list(self, *fields, **kwargs):
    40         procedure_args, geo_field = self._spatial_setup('area', field_name=kwargs.get('field_name', None))
    39         procedure_args, geo_field = self._spatial_setup('area', field_name=kwargs.get('field_name', None))
    41         s = {'procedure_args' : procedure_args,
    40         s = {'procedure_args' : procedure_args,
    42              'geo_field' : geo_field,
    41              'geo_field' : geo_field,
    43              'setup' : False,
    42              'setup' : False,
    44              }
    43              }
    45         if SpatialBackend.oracle:
    44         connection = connections[self.db]
       
    45         backend = connection.ops
       
    46         if backend.oracle:
    46             s['procedure_fmt'] = '%(geo_col)s,%(tolerance)s'
    47             s['procedure_fmt'] = '%(geo_col)s,%(tolerance)s'
    47             s['procedure_args']['tolerance'] = tolerance
    48             s['procedure_args']['tolerance'] = tolerance
    48             s['select_field'] = AreaField('sq_m') # Oracle returns area in units of meters.
    49             s['select_field'] = AreaField('sq_m') # Oracle returns area in units of meters.
    49         elif SpatialBackend.postgis or SpatialBackend.spatialite:
    50         elif backend.postgis or backend.spatialite:
    50             if not geo_field.geodetic:
    51             if not geo_field.geodetic(connection):
    51                 # Getting the area units of the geographic field.
    52                 # Getting the area units of the geographic field.
    52                 s['select_field'] = AreaField(Area.unit_attname(geo_field.units_name))
    53                 s['select_field'] = AreaField(Area.unit_attname(geo_field.units_name(connection)))
    53             else:
    54             else:
    54                 # TODO: Do we want to support raw number areas for geodetic fields?
    55                 # TODO: Do we want to support raw number areas for geodetic fields?
    55                 raise Exception('Area on geodetic coordinate systems not supported.')
    56                 raise Exception('Area on geodetic coordinate systems not supported.')
    56         return self._spatial_attribute('area', s, **kwargs)
    57         return self._spatial_attribute('area', s, **kwargs)
    57 
    58 
   108         Returns the extent (aggregate) of the features in the GeoQuerySet.  The
   109         Returns the extent (aggregate) of the features in the GeoQuerySet.  The
   109         extent will be returned as a 4-tuple, consisting of (xmin, ymin, xmax, ymax).
   110         extent will be returned as a 4-tuple, consisting of (xmin, ymin, xmax, ymax).
   110         """
   111         """
   111         return self._spatial_aggregate(aggregates.Extent, **kwargs)
   112         return self._spatial_aggregate(aggregates.Extent, **kwargs)
   112 
   113 
       
   114     def extent3d(self, **kwargs):
       
   115         """
       
   116         Returns the aggregate extent, in 3D, of the features in the
       
   117         GeoQuerySet. It is returned as a 6-tuple, comprising:
       
   118           (xmin, ymin, zmin, xmax, ymax, zmax).
       
   119         """
       
   120         return self._spatial_aggregate(aggregates.Extent3D, **kwargs)
       
   121 
       
   122     def force_rhr(self, **kwargs):
       
   123         """
       
   124         Returns a modified version of the Polygon/MultiPolygon in which
       
   125         all of the vertices follow the Right-Hand-Rule.  By default,
       
   126         this is attached as the `force_rhr` attribute on each element
       
   127         of the GeoQuerySet.
       
   128         """
       
   129         return self._geom_attribute('force_rhr', **kwargs)
       
   130 
   113     def geojson(self, precision=8, crs=False, bbox=False, **kwargs):
   131     def geojson(self, precision=8, crs=False, bbox=False, **kwargs):
   114         """
   132         """
   115         Returns a GeoJSON representation of the geomtry field in a `geojson`
   133         Returns a GeoJSON representation of the geomtry field in a `geojson`
   116         attribute on each element of the GeoQuerySet.
   134         attribute on each element of the GeoQuerySet.
   117 
   135 
   118         The `crs` and `bbox` keywords may be set to True if the users wants
   136         The `crs` and `bbox` keywords may be set to True if the users wants
   119         the coordinate reference system and the bounding box to be included
   137         the coordinate reference system and the bounding box to be included
   120         in the GeoJSON representation of the geometry.
   138         in the GeoJSON representation of the geometry.
   121         """
   139         """
   122         if not SpatialBackend.postgis or not SpatialBackend.geojson:
   140         backend = connections[self.db].ops
       
   141         if not backend.geojson:
   123             raise NotImplementedError('Only PostGIS 1.3.4+ supports GeoJSON serialization.')
   142             raise NotImplementedError('Only PostGIS 1.3.4+ supports GeoJSON serialization.')
   124         
   143 
   125         if not isinstance(precision, (int, long)):
   144         if not isinstance(precision, (int, long)):
   126             raise TypeError('Precision keyword must be set with an integer.')
   145             raise TypeError('Precision keyword must be set with an integer.')
   127         
   146 
   128         # Setting the options flag -- which depends on which version of
   147         # Setting the options flag -- which depends on which version of
   129         # PostGIS we're using.
   148         # PostGIS we're using.
   130         major, minor1, minor2 = SpatialBackend.version
   149         if backend.spatial_version >= (1, 4, 0):
   131         if major >=1 and (minor1 >= 4):
       
   132             options = 0
   150             options = 0
   133             if crs and bbox: options = 3
   151             if crs and bbox: options = 3
   134             elif bbox: options = 1
   152             elif bbox: options = 1
   135             elif crs: options = 2
   153             elif crs: options = 2
   136         else:
   154         else:
   137             options = 0
   155             options = 0
   138             if crs and bbox: options = 3
   156             if crs and bbox: options = 3
   139             elif crs: options = 1
   157             elif crs: options = 1
   140             elif bbox: options = 2
   158             elif bbox: options = 2
   141         s = {'desc' : 'GeoJSON', 
   159         s = {'desc' : 'GeoJSON',
   142              'procedure_args' : {'precision' : precision, 'options' : options},
   160              'procedure_args' : {'precision' : precision, 'options' : options},
   143              'procedure_fmt' : '%(geo_col)s,%(precision)s,%(options)s',
   161              'procedure_fmt' : '%(geo_col)s,%(precision)s,%(options)s',
   144              }
   162              }
   145         return self._spatial_attribute('geojson', s, **kwargs)
   163         return self._spatial_attribute('geojson', s, **kwargs)
   146 
   164 
       
   165     def geohash(self, precision=20, **kwargs):
       
   166         """
       
   167         Returns a GeoHash representation of the given field in a `geohash`
       
   168         attribute on each element of the GeoQuerySet.
       
   169 
       
   170         The `precision` keyword may be used to custom the number of
       
   171         _characters_ used in the output GeoHash, the default is 20.
       
   172         """
       
   173         s = {'desc' : 'GeoHash', 
       
   174              'procedure_args': {'precision': precision},
       
   175              'procedure_fmt': '%(geo_col)s,%(precision)s',
       
   176              }
       
   177         return self._spatial_attribute('geohash', s, **kwargs)
       
   178 
   147     def gml(self, precision=8, version=2, **kwargs):
   179     def gml(self, precision=8, version=2, **kwargs):
   148         """
   180         """
   149         Returns GML representation of the given field in a `gml` attribute
   181         Returns GML representation of the given field in a `gml` attribute
   150         on each element of the GeoQuerySet.
   182         on each element of the GeoQuerySet.
   151         """
   183         """
       
   184         backend = connections[self.db].ops
   152         s = {'desc' : 'GML', 'procedure_args' : {'precision' : precision}}
   185         s = {'desc' : 'GML', 'procedure_args' : {'precision' : precision}}
   153         if SpatialBackend.postgis:
   186         if backend.postgis:
   154             # PostGIS AsGML() aggregate function parameter order depends on the
   187             # PostGIS AsGML() aggregate function parameter order depends on the
   155             # version -- uggh.
   188             # version -- uggh.
   156             major, minor1, minor2 = SpatialBackend.version
   189             if backend.spatial_version > (1, 3, 1):
   157             if major >= 1 and (minor1 > 3 or (minor1 == 3 and minor2 > 1)):
       
   158                 procedure_fmt = '%(version)s,%(geo_col)s,%(precision)s'
   190                 procedure_fmt = '%(version)s,%(geo_col)s,%(precision)s'
   159             else:
   191             else:
   160                 procedure_fmt = '%(geo_col)s,%(precision)s,%(version)s'
   192                 procedure_fmt = '%(geo_col)s,%(precision)s,%(version)s'
   161             s['procedure_args'] = {'precision' : precision, 'version' : version}
   193             s['procedure_args'] = {'precision' : precision, 'version' : version}
   162 
   194 
   233         Geometry field in a `point_on_surface` attribute on each element
   265         Geometry field in a `point_on_surface` attribute on each element
   234         of this GeoQuerySet; otherwise sets with None.
   266         of this GeoQuerySet; otherwise sets with None.
   235         """
   267         """
   236         return self._geom_attribute('point_on_surface', **kwargs)
   268         return self._geom_attribute('point_on_surface', **kwargs)
   237 
   269 
       
   270     def reverse_geom(self, **kwargs):
       
   271         """
       
   272         Reverses the coordinate order of the geometry, and attaches as a
       
   273         `reverse` attribute on each element of this GeoQuerySet.
       
   274         """
       
   275         s = {'select_field' : GeomField(),}
       
   276         kwargs.setdefault('model_att', 'reverse_geom')
       
   277         if connections[self.db].ops.oracle:
       
   278             s['geo_field_type'] = LineStringField
       
   279         return self._spatial_attribute('reverse', s, **kwargs)
       
   280 
   238     def scale(self, x, y, z=0.0, **kwargs):
   281     def scale(self, x, y, z=0.0, **kwargs):
   239         """
   282         """
   240         Scales the geometry to a new size by multiplying the ordinates
   283         Scales the geometry to a new size by multiplying the ordinates
   241         with the given x,y,z scale factors.
   284         with the given x,y,z scale factors.
   242         """
   285         """
   243         if SpatialBackend.spatialite:
   286         if connections[self.db].ops.spatialite:
   244             if z != 0.0:
   287             if z != 0.0:
   245                 raise NotImplementedError('SpatiaLite does not support 3D scaling.')
   288                 raise NotImplementedError('SpatiaLite does not support 3D scaling.')
   246             s = {'procedure_fmt' : '%(geo_col)s,%(x)s,%(y)s',
   289             s = {'procedure_fmt' : '%(geo_col)s,%(x)s,%(y)s',
   247                  'procedure_args' : {'x' : x, 'y' : y},
   290                  'procedure_args' : {'x' : x, 'y' : y},
   248                  'select_field' : GeomField(),
   291                  'select_field' : GeomField(),
   298         Keyword Arguments:
   341         Keyword Arguments:
   299          `relative`  => If set to True, this will evaluate the path in
   342          `relative`  => If set to True, this will evaluate the path in
   300                         terms of relative moves (rather than absolute).
   343                         terms of relative moves (rather than absolute).
   301 
   344 
   302          `precision` => May be used to set the maximum number of decimal
   345          `precision` => May be used to set the maximum number of decimal
   303                         digits used in output (defaults to 8).        
   346                         digits used in output (defaults to 8).
   304         """
   347         """
   305         relative = int(bool(relative))
   348         relative = int(bool(relative))
   306         if not isinstance(precision, (int, long)): 
   349         if not isinstance(precision, (int, long)):
   307             raise TypeError('SVG precision keyword argument must be an integer.')
   350             raise TypeError('SVG precision keyword argument must be an integer.')
   308         s = {'desc' : 'SVG',
   351         s = {'desc' : 'SVG',
   309              'procedure_fmt' : '%(geo_col)s,%(rel)s,%(precision)s',
   352              'procedure_fmt' : '%(geo_col)s,%(rel)s,%(precision)s',
   310              'procedure_args' : {'rel' : relative,
   353              'procedure_args' : {'rel' : relative,
   311                                  'precision' : precision,
   354                                  'precision' : precision,
   323     def translate(self, x, y, z=0.0, **kwargs):
   366     def translate(self, x, y, z=0.0, **kwargs):
   324         """
   367         """
   325         Translates the geometry to a new location using the given numeric
   368         Translates the geometry to a new location using the given numeric
   326         parameters as offsets.
   369         parameters as offsets.
   327         """
   370         """
   328         if SpatialBackend.spatialite:
   371         if connections[self.db].ops.spatialite:
   329             if z != 0.0:
   372             if z != 0.0:
   330                 raise NotImplementedError('SpatiaLite does not support 3D translation.')
   373                 raise NotImplementedError('SpatiaLite does not support 3D translation.')
   331             s = {'procedure_fmt' : '%(geo_col)s,%(x)s,%(y)s',
   374             s = {'procedure_fmt' : '%(geo_col)s,%(x)s,%(y)s',
   332                  'procedure_args' : {'x' : x, 'y' : y},
   375                  'procedure_args' : {'x' : x, 'y' : y},
   333                  'select_field' : GeomField(),
   376                  'select_field' : GeomField(),
   358         # For example, 'SDO_UTIL.TO_WKTGEOMETRY(SDO_CS.TRANSFORM( ... )'
   401         # For example, 'SDO_UTIL.TO_WKTGEOMETRY(SDO_CS.TRANSFORM( ... )'
   359         geo_col = self.query.custom_select.get(geo_field, field_col)
   402         geo_col = self.query.custom_select.get(geo_field, field_col)
   360 
   403 
   361         # Setting the key for the field's column with the custom SELECT SQL to
   404         # Setting the key for the field's column with the custom SELECT SQL to
   362         # override the geometry column returned from the database.
   405         # override the geometry column returned from the database.
   363         custom_sel = '%s(%s, %s)' % (SpatialBackend.transform, geo_col, srid)
   406         custom_sel = '%s(%s, %s)' % (connections[self.db].ops.transform, geo_col, srid)
   364         # TODO: Should we have this as an alias?
   407         # TODO: Should we have this as an alias?
   365         # custom_sel = '(%s(%s, %s)) AS %s' % (SpatialBackend.transform, geo_col, srid, qn(geo_field.name))
   408         # custom_sel = '(%s(%s, %s)) AS %s' % (SpatialBackend.transform, geo_col, srid, qn(geo_field.name))
   366         self.query.transformed_srid = srid # So other GeoQuerySet methods
   409         self.query.transformed_srid = srid # So other GeoQuerySet methods
   367         self.query.custom_select[geo_field] = custom_sel
   410         self.query.custom_select[geo_field] = custom_sel
   368         return self._clone()
   411         return self._clone()
   386     def _spatial_setup(self, att, desc=None, field_name=None, geo_field_type=None):
   429     def _spatial_setup(self, att, desc=None, field_name=None, geo_field_type=None):
   387         """
   430         """
   388         Performs set up for executing the spatial function.
   431         Performs set up for executing the spatial function.
   389         """
   432         """
   390         # Does the spatial backend support this?
   433         # Does the spatial backend support this?
   391         func = getattr(SpatialBackend, att, False)
   434         connection = connections[self.db]
       
   435         func = getattr(connection.ops, att, False)
   392         if desc is None: desc = att
   436         if desc is None: desc = att
   393         if not func: raise ImproperlyConfigured('%s stored procedure not available.' % desc)
   437         if not func:
       
   438             raise NotImplementedError('%s stored procedure not available on '
       
   439                                       'the %s backend.' %
       
   440                                       (desc, connection.ops.name))
   394 
   441 
   395         # Initializing the procedure arguments.
   442         # Initializing the procedure arguments.
   396         procedure_args = {'function' : func}
   443         procedure_args = {'function' : func}
   397 
   444 
   398         # Is there a geographic field in the model to perform this
   445         # Is there a geographic field in the model to perform this
   432         agg_col = field_name or geo_field.name
   479         agg_col = field_name or geo_field.name
   433 
   480 
   434         # Adding any keyword parameters for the Aggregate object. Oracle backends
   481         # Adding any keyword parameters for the Aggregate object. Oracle backends
   435         # in particular need an additional `tolerance` parameter.
   482         # in particular need an additional `tolerance` parameter.
   436         agg_kwargs = {}
   483         agg_kwargs = {}
   437         if SpatialBackend.oracle: agg_kwargs['tolerance'] = tolerance
   484         if connections[self.db].ops.oracle: agg_kwargs['tolerance'] = tolerance
   438 
   485 
   439         # Calling the QuerySet.aggregate, and returning only the value of the aggregate.
   486         # Calling the QuerySet.aggregate, and returning only the value of the aggregate.
   440         return self.aggregate(geoagg=aggregate(agg_col, **agg_kwargs))['geoagg']
   487         return self.aggregate(geoagg=aggregate(agg_col, **agg_kwargs))['geoagg']
   441 
   488 
   442     def _spatial_attribute(self, att, settings, field_name=None, model_att=None):
   489     def _spatial_attribute(self, att, settings, field_name=None, model_att=None):
   469         settings.setdefault('geom_field', None)
   516         settings.setdefault('geom_field', None)
   470         settings.setdefault('procedure_args', {})
   517         settings.setdefault('procedure_args', {})
   471         settings.setdefault('procedure_fmt', '%(geo_col)s')
   518         settings.setdefault('procedure_fmt', '%(geo_col)s')
   472         settings.setdefault('select_params', [])
   519         settings.setdefault('select_params', [])
   473 
   520 
       
   521         connection = connections[self.db]
       
   522         backend = connection.ops
       
   523 
   474         # Performing setup for the spatial column, unless told not to.
   524         # Performing setup for the spatial column, unless told not to.
   475         if settings.get('setup', True):
   525         if settings.get('setup', True):
   476             default_args, geo_field = self._spatial_setup(att, desc=settings['desc'], field_name=field_name)
   526             default_args, geo_field = self._spatial_setup(att, desc=settings['desc'], field_name=field_name,
       
   527                                                           geo_field_type=settings.get('geo_field_type', None))
   477             for k, v in default_args.iteritems(): settings['procedure_args'].setdefault(k, v)
   528             for k, v in default_args.iteritems(): settings['procedure_args'].setdefault(k, v)
   478         else:
   529         else:
   479             geo_field = settings['geo_field']
   530             geo_field = settings['geo_field']
   480 
   531 
   481         # The attribute to attach to the model.
   532         # The attribute to attach to the model.
   482         if not isinstance(model_att, basestring): model_att = att
   533         if not isinstance(model_att, basestring): model_att = att
   483 
   534 
   484         # Special handling for any argument that is a geometry.
   535         # Special handling for any argument that is a geometry.
   485         for name in settings['geom_args']:
   536         for name in settings['geom_args']:
   486             # Using the field's get_db_prep_lookup() to get any needed
   537             # Using the field's get_placeholder() routine to get any needed
   487             # transformation SQL -- we pass in a 'dummy' `contains` lookup.
   538             # transformation SQL.
   488             where, params = geo_field.get_db_prep_lookup('contains', settings['procedure_args'][name])
   539             geom = geo_field.get_prep_value(settings['procedure_args'][name])
       
   540             params = geo_field.get_db_prep_lookup('contains', geom, connection=connection)
       
   541             geom_placeholder = geo_field.get_placeholder(geom, connection)
       
   542 
   489             # Replacing the procedure format with that of any needed
   543             # Replacing the procedure format with that of any needed
   490             # transformation SQL.
   544             # transformation SQL.
   491             old_fmt = '%%(%s)s' % name
   545             old_fmt = '%%(%s)s' % name
   492             new_fmt = where[0] % '%%s'
   546             new_fmt = geom_placeholder % '%%s'
   493             settings['procedure_fmt'] = settings['procedure_fmt'].replace(old_fmt, new_fmt)
   547             settings['procedure_fmt'] = settings['procedure_fmt'].replace(old_fmt, new_fmt)
   494             settings['select_params'].extend(params)
   548             settings['select_params'].extend(params)
   495 
   549 
   496         # Getting the format for the stored procedure.
   550         # Getting the format for the stored procedure.
   497         fmt = '%%(function)s(%s)' % settings['procedure_fmt']
   551         fmt = '%%(function)s(%s)' % settings['procedure_fmt']
   498 
   552 
   499         # If the result of this function needs to be converted.
   553         # If the result of this function needs to be converted.
   500         if settings.get('select_field', False):
   554         if settings.get('select_field', False):
   501             sel_fld = settings['select_field']
   555             sel_fld = settings['select_field']
   502             if isinstance(sel_fld, GeomField) and SpatialBackend.select:
   556             if isinstance(sel_fld, GeomField) and backend.select:
   503                 self.query.custom_select[model_att] = SpatialBackend.select
   557                 self.query.custom_select[model_att] = backend.select
       
   558             if connection.ops.oracle:
       
   559                 sel_fld.empty_strings_allowed = False
   504             self.query.extra_select_fields[model_att] = sel_fld
   560             self.query.extra_select_fields[model_att] = sel_fld
   505 
   561 
   506         # Finally, setting the extra selection attribute with
   562         # Finally, setting the extra selection attribute with
   507         # the format string expanded with the stored procedure
   563         # the format string expanded with the stored procedure
   508         # arguments.
   564         # arguments.
   517         procedure_args, geo_field = self._spatial_setup(func, field_name=kwargs.get('field_name', None))
   573         procedure_args, geo_field = self._spatial_setup(func, field_name=kwargs.get('field_name', None))
   518 
   574 
   519         # If geodetic defaulting distance attribute to meters (Oracle and
   575         # If geodetic defaulting distance attribute to meters (Oracle and
   520         # PostGIS spherical distances return meters).  Otherwise, use the
   576         # PostGIS spherical distances return meters).  Otherwise, use the
   521         # units of the geometry field.
   577         # units of the geometry field.
   522         if geo_field.geodetic:
   578         connection = connections[self.db]
       
   579         geodetic = geo_field.geodetic(connection)
       
   580         geography = geo_field.geography
       
   581 
       
   582         if geodetic:
   523             dist_att = 'm'
   583             dist_att = 'm'
   524         else:
   584         else:
   525             dist_att = Distance.unit_attname(geo_field.units_name)
   585             dist_att = Distance.unit_attname(geo_field.units_name(connection))
   526 
   586 
   527         # Shortcut booleans for what distance function we're using.
   587         # Shortcut booleans for what distance function we're using and
       
   588         # whether the geometry field is 3D.
   528         distance = func == 'distance'
   589         distance = func == 'distance'
   529         length = func == 'length'
   590         length = func == 'length'
   530         perimeter = func == 'perimeter'
   591         perimeter = func == 'perimeter'
   531         if not (distance or length or perimeter):
   592         if not (distance or length or perimeter):
   532             raise ValueError('Unknown distance function: %s' % func)
   593             raise ValueError('Unknown distance function: %s' % func)
       
   594         geom_3d = geo_field.dim == 3
   533 
   595 
   534         # The field's get_db_prep_lookup() is used to get any
   596         # The field's get_db_prep_lookup() is used to get any
   535         # extra distance parameters.  Here we set up the
   597         # extra distance parameters.  Here we set up the
   536         # parameters that will be passed in to field's function.
   598         # parameters that will be passed in to field's function.
   537         lookup_params = [geom or 'POINT (0 0)', 0]
   599         lookup_params = [geom or 'POINT (0 0)', 0]
   538 
   600 
       
   601         # Getting the spatial backend operations.
       
   602         backend = connection.ops
       
   603 
   539         # If the spheroid calculation is desired, either by the `spheroid`
   604         # If the spheroid calculation is desired, either by the `spheroid`
   540         # keyword or when calculating the length of geodetic field, make
   605         # keyword or when calculating the length of geodetic field, make
   541         # sure the 'spheroid' distance setting string is passed in so we
   606         # sure the 'spheroid' distance setting string is passed in so we
   542         # get the correct spatial stored procedure.
   607         # get the correct spatial stored procedure.
   543         if spheroid or (SpatialBackend.postgis and geo_field.geodetic and length):
   608         if spheroid or (backend.postgis and geodetic and
       
   609                         (not geography) and length):
   544             lookup_params.append('spheroid')
   610             lookup_params.append('spheroid')
   545         where, params = geo_field.get_db_prep_lookup('distance_lte', lookup_params)
   611         lookup_params = geo_field.get_prep_value(lookup_params)
       
   612         params = geo_field.get_db_prep_lookup('distance_lte', lookup_params, connection=connection)
   546 
   613 
   547         # The `geom_args` flag is set to true if a geometry parameter was
   614         # The `geom_args` flag is set to true if a geometry parameter was
   548         # passed in.
   615         # passed in.
   549         geom_args = bool(geom)
   616         geom_args = bool(geom)
   550 
   617 
   551         if SpatialBackend.oracle:
   618         if backend.oracle:
   552             if distance:
   619             if distance:
   553                 procedure_fmt = '%(geo_col)s,%(geom)s,%(tolerance)s'
   620                 procedure_fmt = '%(geo_col)s,%(geom)s,%(tolerance)s'
   554             elif length or perimeter:
   621             elif length or perimeter:
   555                 procedure_fmt = '%(geo_col)s,%(tolerance)s'
   622                 procedure_fmt = '%(geo_col)s,%(tolerance)s'
   556             procedure_args['tolerance'] = tolerance
   623             procedure_args['tolerance'] = tolerance
   557         else:
   624         else:
   558             # Getting whether this field is in units of degrees since the field may have
   625             # Getting whether this field is in units of degrees since the field may have
   559             # been transformed via the `transform` GeoQuerySet method.
   626             # been transformed via the `transform` GeoQuerySet method.
   560             if self.query.transformed_srid:
   627             if self.query.transformed_srid:
   561                 u, unit_name, s = get_srid_info(self.query.transformed_srid)
   628                 u, unit_name, s = get_srid_info(self.query.transformed_srid, connection)
   562                 geodetic = unit_name in geo_field.geodetic_units
   629                 geodetic = unit_name in geo_field.geodetic_units
   563             else:
   630 
   564                 geodetic = geo_field.geodetic
   631             if backend.spatialite and geodetic:
   565 
       
   566             if SpatialBackend.spatialite and geodetic:
       
   567                 raise ValueError('SQLite does not support linear distance calculations on geodetic coordinate systems.')
   632                 raise ValueError('SQLite does not support linear distance calculations on geodetic coordinate systems.')
   568 
   633 
   569             if distance:
   634             if distance:
   570                 if self.query.transformed_srid:
   635                 if self.query.transformed_srid:
   571                     # Setting the `geom_args` flag to false because we want to handle
   636                     # Setting the `geom_args` flag to false because we want to handle
   572                     # transformation SQL here, rather than the way done by default
   637                     # transformation SQL here, rather than the way done by default
   573                     # (which will transform to the original SRID of the field rather
   638                     # (which will transform to the original SRID of the field rather
   574                     #  than to what was transformed to).
   639                     #  than to what was transformed to).
   575                     geom_args = False
   640                     geom_args = False
   576                     procedure_fmt = '%s(%%(geo_col)s, %s)' % (SpatialBackend.transform, self.query.transformed_srid)
   641                     procedure_fmt = '%s(%%(geo_col)s, %s)' % (backend.transform, self.query.transformed_srid)
   577                     if geom.srid is None or geom.srid == self.query.transformed_srid:
   642                     if geom.srid is None or geom.srid == self.query.transformed_srid:
   578                         # If the geom parameter srid is None, it is assumed the coordinates
   643                         # If the geom parameter srid is None, it is assumed the coordinates
   579                         # are in the transformed units.  A placeholder is used for the
   644                         # are in the transformed units.  A placeholder is used for the
   580                         # geometry parameter.  `GeomFromText` constructor is also needed
   645                         # geometry parameter.  `GeomFromText` constructor is also needed
   581                         # to wrap geom placeholder for SpatiaLite.
   646                         # to wrap geom placeholder for SpatiaLite.
   582                         if SpatialBackend.spatialite:
   647                         if backend.spatialite:
   583                             procedure_fmt += ', %s(%%%%s, %s)' % (SpatialBackend.from_text, self.query.transformed_srid)
   648                             procedure_fmt += ', %s(%%%%s, %s)' % (backend.from_text, self.query.transformed_srid)
   584                         else:
   649                         else:
   585                             procedure_fmt += ', %%s'
   650                             procedure_fmt += ', %%s'
   586                     else:
   651                     else:
   587                         # We need to transform the geom to the srid specified in `transform()`,
   652                         # We need to transform the geom to the srid specified in `transform()`,
   588                         # so wrapping the geometry placeholder in transformation SQL.
   653                         # so wrapping the geometry placeholder in transformation SQL.
   589                         # SpatiaLite also needs geometry placeholder wrapped in `GeomFromText`
   654                         # SpatiaLite also needs geometry placeholder wrapped in `GeomFromText`
   590                         # constructor.
   655                         # constructor.
   591                         if SpatialBackend.spatialite:
   656                         if backend.spatialite:
   592                             procedure_fmt += ', %s(%s(%%%%s, %s), %s)' % (SpatialBackend.transform, SpatialBackend.from_text,
   657                             procedure_fmt += ', %s(%s(%%%%s, %s), %s)' % (backend.transform, backend.from_text,
   593                                                                           geom.srid, self.query.transformed_srid)
   658                                                                           geom.srid, self.query.transformed_srid)
   594                         else:
   659                         else:
   595                             procedure_fmt += ', %s(%%%%s, %s)' % (SpatialBackend.transform, self.query.transformed_srid)
   660                             procedure_fmt += ', %s(%%%%s, %s)' % (backend.transform, self.query.transformed_srid)
   596                 else:
   661                 else:
   597                     # `transform()` was not used on this GeoQuerySet.
   662                     # `transform()` was not used on this GeoQuerySet.
   598                     procedure_fmt  = '%(geo_col)s,%(geom)s'
   663                     procedure_fmt  = '%(geo_col)s,%(geom)s'
   599 
   664 
   600                 if geodetic:
   665                 if not geography and geodetic:
   601                     # Spherical distance calculation is needed (because the geographic
   666                     # Spherical distance calculation is needed (because the geographic
   602                     # field is geodetic). However, the PostGIS ST_distance_sphere/spheroid()
   667                     # field is geodetic). However, the PostGIS ST_distance_sphere/spheroid()
   603                     # procedures may only do queries from point columns to point geometries
   668                     # procedures may only do queries from point columns to point geometries
   604                     # some error checking is required.
   669                     # some error checking is required.
   605                     if not isinstance(geo_field, PointField):
   670                     if not backend.geography:
   606                         raise ValueError('Spherical distance calculation only supported on PointFields.')
   671                         if not isinstance(geo_field, PointField):
   607                     if not str(SpatialBackend.Geometry(buffer(params[0].wkb)).geom_type) == 'Point':
   672                             raise ValueError('Spherical distance calculation only supported on PointFields.')
   608                         raise ValueError('Spherical distance calculation only supported with Point Geometry parameters')
   673                         if not str(Geometry(buffer(params[0].ewkb)).geom_type) == 'Point':
       
   674                             raise ValueError('Spherical distance calculation only supported with Point Geometry parameters')
   609                     # The `function` procedure argument needs to be set differently for
   675                     # The `function` procedure argument needs to be set differently for
   610                     # geodetic distance calculations.
   676                     # geodetic distance calculations.
   611                     if spheroid:
   677                     if spheroid:
   612                         # Call to distance_spheroid() requires spheroid param as well.
   678                         # Call to distance_spheroid() requires spheroid param as well.
   613                         procedure_fmt += ',%(spheroid)s'
   679                         procedure_fmt += ",'%(spheroid)s'"
   614                         procedure_args.update({'function' : SpatialBackend.distance_spheroid, 'spheroid' : where[1]})
   680                         procedure_args.update({'function' : backend.distance_spheroid, 'spheroid' : params[1]})
   615                     else:
   681                     else:
   616                         procedure_args.update({'function' : SpatialBackend.distance_sphere})
   682                         procedure_args.update({'function' : backend.distance_sphere})
   617             elif length or perimeter:
   683             elif length or perimeter:
   618                 procedure_fmt = '%(geo_col)s'
   684                 procedure_fmt = '%(geo_col)s'
   619                 if geodetic and length:
   685                 if not geography and geodetic and length:
   620                     # There's no `length_sphere`
   686                     # There's no `length_sphere`, and `length_spheroid` also
   621                     procedure_fmt += ',%(spheroid)s'
   687                     # works on 3D geometries.
   622                     procedure_args.update({'function' : SpatialBackend.length_spheroid, 'spheroid' : where[1]})
   688                     procedure_fmt += ",'%(spheroid)s'"
       
   689                     procedure_args.update({'function' : backend.length_spheroid, 'spheroid' : params[1]})
       
   690                 elif geom_3d and backend.postgis:
       
   691                     # Use 3D variants of perimeter and length routines on PostGIS.
       
   692                     if perimeter:
       
   693                         procedure_args.update({'function' : backend.perimeter3d})
       
   694                     elif length:
       
   695                         procedure_args.update({'function' : backend.length3d})
   623 
   696 
   624         # Setting up the settings for `_spatial_attribute`.
   697         # Setting up the settings for `_spatial_attribute`.
   625         s = {'select_field' : DistanceField(dist_att),
   698         s = {'select_field' : DistanceField(dist_att),
   626              'setup' : False,
   699              'setup' : False,
   627              'geo_field' : geo_field,
   700              'geo_field' : geo_field,
   632             s['geom_args'] = ('geom',)
   705             s['geom_args'] = ('geom',)
   633             s['procedure_args']['geom'] = geom
   706             s['procedure_args']['geom'] = geom
   634         elif geom:
   707         elif geom:
   635             # The geometry is passed in as a parameter because we handled
   708             # The geometry is passed in as a parameter because we handled
   636             # transformation conditions in this routine.
   709             # transformation conditions in this routine.
   637             s['select_params'] = [SpatialBackend.Adaptor(geom)]
   710             s['select_params'] = [backend.Adapter(geom)]
   638         return self._spatial_attribute(func, s, **kwargs)
   711         return self._spatial_attribute(func, s, **kwargs)
   639 
   712 
   640     def _geom_attribute(self, func, tolerance=0.05, **kwargs):
   713     def _geom_attribute(self, func, tolerance=0.05, **kwargs):
   641         """
   714         """
   642         DRY routine for setting up a GeoQuerySet method that attaches a
   715         DRY routine for setting up a GeoQuerySet method that attaches a
   643         Geometry attribute (e.g., `centroid`, `point_on_surface`).
   716         Geometry attribute (e.g., `centroid`, `point_on_surface`).
   644         """
   717         """
   645         s = {'select_field' : GeomField(),}
   718         s = {'select_field' : GeomField(),}
   646         if SpatialBackend.oracle:
   719         if connections[self.db].ops.oracle:
   647             s['procedure_fmt'] = '%(geo_col)s,%(tolerance)s'
   720             s['procedure_fmt'] = '%(geo_col)s,%(tolerance)s'
   648             s['procedure_args'] = {'tolerance' : tolerance}
   721             s['procedure_args'] = {'tolerance' : tolerance}
   649         return self._spatial_attribute(func, s, **kwargs)
   722         return self._spatial_attribute(func, s, **kwargs)
   650 
   723 
   651     def _geomset_attribute(self, func, geom, tolerance=0.05, **kwargs):
   724     def _geomset_attribute(self, func, geom, tolerance=0.05, **kwargs):
   658         s = {'geom_args' : ('geom',),
   731         s = {'geom_args' : ('geom',),
   659              'select_field' : GeomField(),
   732              'select_field' : GeomField(),
   660              'procedure_fmt' : '%(geo_col)s,%(geom)s',
   733              'procedure_fmt' : '%(geo_col)s,%(geom)s',
   661              'procedure_args' : {'geom' : geom},
   734              'procedure_args' : {'geom' : geom},
   662             }
   735             }
   663         if SpatialBackend.oracle:
   736         if connections[self.db].ops.oracle:
   664             s['procedure_fmt'] += ',%(tolerance)s'
   737             s['procedure_fmt'] += ',%(tolerance)s'
   665             s['procedure_args']['tolerance'] = tolerance
   738             s['procedure_args']['tolerance'] = tolerance
   666         return self._spatial_attribute(func, s, **kwargs)
   739         return self._spatial_attribute(func, s, **kwargs)
   667 
   740 
   668     def _geocol_select(self, geo_field, field_name):
   741     def _geocol_select(self, geo_field, field_name):
   675         if not geo_field in opts.fields:
   748         if not geo_field in opts.fields:
   676             # Is this operation going to be on a related geographic field?
   749             # Is this operation going to be on a related geographic field?
   677             # If so, it'll have to be added to the select related information
   750             # If so, it'll have to be added to the select related information
   678             # (e.g., if 'location__point' was given as the field name).
   751             # (e.g., if 'location__point' was given as the field name).
   679             self.query.add_select_related([field_name])
   752             self.query.add_select_related([field_name])
   680             self.query.pre_sql_setup()
   753             compiler = self.query.get_compiler(self.db)
       
   754             compiler.pre_sql_setup()
   681             rel_table, rel_col = self.query.related_select_cols[self.query.related_select_fields.index(geo_field)]
   755             rel_table, rel_col = self.query.related_select_cols[self.query.related_select_fields.index(geo_field)]
   682             return self.query._field_column(geo_field, rel_table)
   756             return compiler._field_column(geo_field, rel_table)
   683         elif not geo_field in opts.local_fields:
   757         elif not geo_field in opts.local_fields:
   684             # This geographic field is inherited from another model, so we have to
   758             # This geographic field is inherited from another model, so we have to
   685             # use the db table for the _parent_ model instead.
   759             # use the db table for the _parent_ model instead.
   686             tmp_fld, parent_model, direct, m2m = opts.get_field_by_name(geo_field.name)
   760             tmp_fld, parent_model, direct, m2m = opts.get_field_by_name(geo_field.name)
   687             return self.query._field_column(geo_field, parent_model._meta.db_table)
   761             return self.query.get_compiler(self.db)._field_column(geo_field, parent_model._meta.db_table)
   688         else:
   762         else:
   689             return self.query._field_column(geo_field)
   763             return self.query.get_compiler(self.db)._field_column(geo_field)
   690 
   764 
   691 class GeoValuesQuerySet(ValuesQuerySet):
   765 class GeoValuesQuerySet(ValuesQuerySet):
   692     def __init__(self, *args, **kwargs):
   766     def __init__(self, *args, **kwargs):
   693         super(GeoValuesQuerySet, self).__init__(*args, **kwargs)
   767         super(GeoValuesQuerySet, self).__init__(*args, **kwargs)
   694         # This flag tells `resolve_columns` to run the values through
   768         # This flag tells `resolve_columns` to run the values through