Django

Code

Changeset 7104

Show
Ignore:
Timestamp:
02/10/08 20:25:01 (5 months ago)
Author:
jbronn
Message:

gis: Fixed 6414, and applied DRY to spatial backend internals. Changes include:

(1) Support for distance calculations on geometry fields with geodetic coordinate systems (e.g., WGS84, the default).
(2) The get_db_prep_save and get_db_prep_lookup have been moved from the spatial backends to common implementations in GeometryField.
(3) Simplified SQL construction for GeoQuerySet methods.
(4) SpatialBackend now contains all spatial backend dependent settings.

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • django/branches/gis/django/contrib/gis/db/backend/__init__.py

    r7028 r7104  
    1010 (3) The `parse_lookup` function, used for spatial SQL construction by 
    1111     the GeoQuerySet. 
    12  (4) The `create_spatial_db`, and `get_geo_where_clause` 
    13      routines (needed by `parse_lookup`)
     12 (4) The `create_spatial_db`, and `get_geo_where_clause`  
     13     (needed by `parse_lookup`) functions
    1414 (5) The `SpatialBackend` object, which contains information specific 
    1515     to the spatial backend. 
    1616""" 
    17 from types import StringType, UnicodeType 
    1817from django.conf import settings 
    1918from django.db import connection 
     
    2120    FieldFound, LOOKUP_SEPARATOR, QUERY_TERMS 
    2221from django.utils.datastructures import SortedDict 
    23 from django.contrib.gis.geos import GEOSGeometry 
     22from django.contrib.gis.db.backend.util import gqn 
    2423 
    2524# These routines (needed by GeoManager), default to False. 
    26 ASGML, ASKML, DISTANCE, EXTENT, TRANSFORM, UNION, VERSION = (False, False, False, False, False, False, False) 
    27  
     25ASGML, ASKML, DISTANCE, DISTANCE_SPHEROID, EXTENT, TRANSFORM, UNION, VERSION = tuple(False for i in range(8)) 
     26 
     27# Lookup types in which the rest of the parameters are not  
     28# needed to be substitute in the WHERE SQL (e.g., the 'relate' 
     29# operation on Oracle does not need the mask substituted back 
     30# into the query SQL.). 
     31LIMITED_WHERE = [] 
     32 
     33# Retrieving the necessary settings from the backend. 
    2834if settings.DATABASE_ENGINE == 'postgresql_psycopg2': 
    29     # PostGIS is the spatial database, getting the rquired modules,  
    30     # renaming as necessary. 
    31     from django.contrib.gis.db.backend.postgis import \ 
    32         PostGISField as GeoBackendField, POSTGIS_TERMS as GIS_TERMS, \ 
    33         create_spatial_db, get_geo_where_clause, \ 
    34         ASGML, ASKML, DISTANCE, EXTENT, GEOM_SELECT, TRANSFORM, UNION, \ 
     35    from django.contrib.gis.db.backend.postgis.adaptor import \ 
     36        PostGISAdaptor as GeoAdaptor 
     37    from django.contrib.gis.db.backend.postgis.field import \ 
     38        PostGISField as GeoBackendField 
     39    from django.contrib.gis.db.backend.postgis.creation import create_spatial_db 
     40    from django.contrib.gis.db.backend.postgis.query import \ 
     41        get_geo_where_clause, POSTGIS_TERMS as GIS_TERMS, \ 
     42        ASGML, ASKML, DISTANCE, DISTANCE_SPHEROID, DISTANCE_FUNCTIONS, \ 
     43        EXTENT, GEOM_SELECT, TRANSFORM, UNION, \ 
    3544        MAJOR_VERSION, MINOR_VERSION1, MINOR_VERSION2 
     45    # PostGIS version info is needed to determine calling order of some 
     46    # stored procedures (e.g., AsGML()). 
    3647    VERSION = (MAJOR_VERSION, MINOR_VERSION1, MINOR_VERSION2) 
    3748    SPATIAL_BACKEND = 'postgis' 
    3849elif settings.DATABASE_ENGINE == 'oracle': 
    39     from django.contrib.gis.db.backend.oracle import \ 
    40          OracleSpatialField as GeoBackendField, \ 
    41          ORACLE_SPATIAL_TERMS as GIS_TERMS, \ 
    42          create_spatial_db, get_geo_where_clause, \ 
    43          ASGML, DISTANCE, GEOM_SELECT, TRANSFORM, UNION 
     50    from django.contrib.gis.db.backend.oracle.adaptor import \ 
     51        OracleSpatialAdaptor as GeoAdaptor 
     52    from django.contrib.gis.db.backend.oracle.field import \ 
     53        OracleSpatialField as GeoBackendField 
     54    from django.contrib.gis.db.backend.oracle.creation import create_spatial_db 
     55    from django.contrib.gis.db.backend.oracle.query import \ 
     56        get_geo_where_clause, ORACLE_SPATIAL_TERMS as GIS_TERMS, \ 
     57        ASGML, DISTANCE, DISTANCE_FUNCTIONS, GEOM_SELECT, TRANSFORM, UNION 
    4458    SPATIAL_BACKEND = 'oracle' 
     59    LIMITED_WHERE = ['relate'] 
    4560elif settings.DATABASE_ENGINE == 'mysql': 
    46     from django.contrib.gis.db.backend.mysql import \ 
    47         MySQLGeoField as GeoBackendField, \ 
    48         MYSQL_GIS_TERMS as GIS_TERMS, \ 
    49         create_spatial_db, get_geo_where_clause, \ 
    50         GEOM_SELECT 
     61    from django.contrib.gis.db.backend.mysql.adaptor import \ 
     62        MySQLAdaptor as GeoAdaptor 
     63    from django.contrib.gis.db.backend.mysql.field import \ 
     64        MySQLGeoField as GeoBackendField 
     65    from django.contrib.gis.db.backend.mysql.creation import create_spatial_db 
     66    from django.contrib.gis.db.backend.mysql.query import \ 
     67        get_geo_where_clause, MYSQL_GIS_TERMS as GIS_TERMS, GEOM_SELECT 
     68    DISTANCE_FUNCTIONS = {} 
    5169    SPATIAL_BACKEND = 'mysql' 
    5270else: 
     
    5472 
    5573class SpatialBackend(object): 
    56     "A container for properties of the Spatial Backend." 
     74    "A container for properties of the SpatialBackend." 
     75    # Stored procedure names used by the `GeoManager`. 
    5776    as_kml = ASKML 
    5877    as_gml = ASGML 
    5978    distance = DISTANCE 
     79    distance_spheroid = DISTANCE_SPHEROID 
    6080    extent = EXTENT 
    6181    name = SPATIAL_BACKEND 
     
    6383    transform = TRANSFORM 
    6484    union = UNION 
     85     
     86    # Version information, if defined. 
    6587    version = VERSION 
     88     
     89    # All valid GIS lookup terms, and distance functions. 
     90    gis_terms = GIS_TERMS 
     91    distance_functions = DISTANCE_FUNCTIONS 
     92     
     93    # Lookup types where additional WHERE parameters are excluded. 
     94    limited_where = LIMITED_WHERE 
     95 
     96    # Class for the backend field. 
     97    Field = GeoBackendField 
     98 
     99    # Adaptor class used for quoting GEOS geometries in the database. 
     100    Adaptor = GeoAdaptor 
    66101 
    67102####    query.py overloaded functions    #### 
     
    69104#  counterparts to support constructing SQL for geographic queries. 
    70105# 
    71 # Status: Synced with r5982
     106# Status: Synced with r7098
    72107# 
    73108def parse_lookup(kwarg_items, opts): 
     
    291326        # with the get_geo_where_clause() 
    292327        if hasattr(field, '_geom'): 
    293             # Getting the preparation SQL object from the field. 
    294             geo_prep = field.get_db_prep_lookup(lookup_type, value) 
     328            # Getting additional SQL WHERE and params arrays associated with  
     329            # the geographic field. 
     330            geo_where, geo_params = field.get_db_prep_lookup(lookup_type, value) 
    295331             
    296             # Getting the adapted geometry from the field
    297             gwc = get_geo_where_clause(lookup_type, current_table, column, value) 
    298  
    299             # Substituting in the the where parameters into the geographic where 
    300             # clause, and extending the parameters. 
    301             where.append(gwc % tuple(geo_prep.where)) 
    302             params.extend(geo_prep.params) 
     332            # Getting the geographic WHERE clause
     333            gwc = get_geo_where_clause(lookup_type, current_table, field, value) 
     334 
     335            # Appending the geographic WHERE componnents and parameters onto 
     336            # the where and params arrays.  
     337            where.append(gwc % tuple(geo_where)) 
     338            params.extend(geo_params) 
    303339        else: 
    304340            where.append(get_where_clause(lookup_type, current_table + '.', column, value, db_type)) 
  • django/branches/gis/django/contrib/gis/db/backend/mysql/field.py

    r6527 r7104  
    1 import re 
    2 from types import StringType, UnicodeType 
    31from django.db import connection 
    42from django.db.models.fields import Field # Django base Field class 
    5 from django.contrib.gis.geos import GEOSGeometry 
    6 from django.contrib.gis.db.backend.util import GeoFieldSQL 
    7 from django.contrib.gis.db.backend.mysql.query import MYSQL_GIS_TERMS, GEOM_FROM_TEXT 
     3from django.contrib.gis.db.backend.mysql.query import GEOM_FROM_TEXT 
    84 
    95# Quotename & geographic quotename, respectively. 
    106qn = connection.ops.quote_name 
    11 def gqn(value): 
    12     if isinstance(value, UnicodeType): value = value.encode('ascii') 
    13     return "'%s'" % value 
    147 
    158class MySQLGeoField(Field): 
     
    2417        Thus, for best spatial performance, you should use MyISAM tables 
    2518        (which do not support transactions).  For more information, see Ch.  
    26         17.6.1 of the MySQL 5.0 documentation. 
     19        16.6.1 of the MySQL 5.0 documentation. 
    2720        """ 
    2821 
     
    5144        "The OpenGIS name is returned for the MySQL database column type." 
    5245        return self._geom 
    53          
    54     def get_db_prep_lookup(self, lookup_type, value): 
    55         """ 
    56         Returns field's value prepared for database lookup, accepts WKT and  
    57         GEOS Geometries for the value. 
    58         """ 
    59         if lookup_type in MYSQL_GIS_TERMS: 
    60             # special case for isnull lookup 
    61             if lookup_type == 'isnull': return GeoFieldSQL([], []) 
    62  
    63             # When the input is not a GEOS geometry, attempt to construct one 
    64             # from the given string input. 
    65             if isinstance(value, GEOSGeometry): 
    66                 pass 
    67             elif isinstance(value, (StringType, UnicodeType)): 
    68                 try: 
    69                     value = GEOSGeometry(value) 
    70                 except GEOSException: 
    71                     raise TypeError("Could not create geometry from lookup value: %s" % str(value)) 
    72             else: 
    73                 raise TypeError('Cannot use parameter of %s type as lookup parameter.' % type(value)) 
    74  
    75             return GeoFieldSQL(['%s(%%s)' % GEOM_FROM_TEXT], [value]) 
    76  
    77         else: 
    78             raise TypeError("Field has invalid lookup: %s" % lookup_type) 
    79  
    80     def get_db_prep_save(self, value): 
    81         "Prepares the value for saving in the database." 
    82         if not bool(value): return None 
    83         if isinstance(value, GEOSGeometry): 
    84             return value 
    85         else: 
    86             raise TypeError('Geometry Proxy should only return GEOSGeometry objects.') 
    8746 
    8847    def get_placeholder(self, value): 
    8948        """ 
    90         Nothing special happens here because MySQL does not support transformations. 
     49        The placeholder here has to include MySQL's WKT constructor.  Because  
     50        MySQL does not support spatial transformations, there is no need to  
     51        modify the placeholder based on the contents of the given value. 
    9152        """ 
    9253        return '%s(%%s)' % GEOM_FROM_TEXT 
  • django/branches/gis/django/contrib/gis/db/backend/mysql/__init__.py

    r6527 r7104  
    1 """ 
    2  The MySQL spatial database backend module. 
    31 
    4  Please note that MySQL only supports bounding box queries, also 
    5  known as MBRs (Minimum Bounding Rectangles).  Moreover, spatial 
    6  indices may only be used on MyISAM tables -- if you need  
    7  transactions, take a look at PostGIS. 
    8 """ 
    9  
    10 from django.contrib.gis.db.backend.mysql.creation import create_spatial_db 
    11 from django.contrib.gis.db.backend.mysql.field import MySQLGeoField, gqn 
    12 from django.contrib.gis.db.backend.mysql.query import get_geo_where_clause, MYSQL_GIS_TERMS, GEOM_SELECT 
  • django/branches/gis/django/contrib/gis/db/backend/mysql/query.py

    r6919 r7104  
    11""" 
    2  This module contains the spatial lookup types, and the get_geo_where_clause() 
    3  routine for MySQL 
     2 This module contains the spatial lookup types, and the `get_geo_where_clause` 
     3 routine for MySQL. 
     4 
     5 Please note that MySQL only supports bounding box queries, also 
     6 known as MBRs (Minimum Bounding Rectangles).  Moreover, spatial 
     7 indices may only be used on MyISAM tables -- if you need  
     8 transactions, take a look at PostGIS. 
    49""" 
    510from django.db import connection 
     
    3540MYSQL_GIS_TERMS = tuple(MYSQL_GIS_TERMS) # Making immutable 
    3641 
    37 def get_geo_where_clause(lookup_type, table_prefix, field_name, value): 
     42def get_geo_where_clause(lookup_type, table_prefix, field, value): 
    3843    "Returns the SQL WHERE clause for use in MySQL spatial SQL construction." 
    3944    # Getting the quoted field as `geo_col`. 
    40     geo_col = '%s.%s' % (qn(table_prefix), qn(field_name)) 
     45    geo_col = '%s.%s' % (qn(table_prefix), qn(field.column)) 
    4146 
    4247    # See if a MySQL Geometry function matches the lookup type next 
  • django/branches/gis/django/contrib/gis/db/backend/oracle/adaptor.py

    r6524 r7104  
    22 This object provides the database adaptor for Oracle geometries. 
    33""" 
    4 from cx_Oracle import CLOB 
    5  
    64class OracleSpatialAdaptor(object): 
    75    def __init__(self, geom): 
     
    1210        "WKT is used for the substitution value of the geometry." 
    1311        return self.wkt 
    14  
    15     def oracle_type(self): 
    16         """ 
    17         The parameter type is a CLOB because no string (VARCHAR2) greater 
    18         than 4000 characters will be accepted through the Oracle database 
    19         API and/or SQL*Plus. 
    20         """ 
    21         return CLOB 
  • django/branches/gis/django/contrib/gis/db/backend/oracle/field.py

    r6919 r7104  
    1 import re 
    2 from types import StringType, UnicodeType 
    31from django.db import connection 
    42from django.db.backends.util import truncate_name 
    53from django.db.models.fields import Field # Django base Field class 
    6 from django.contrib.gis.geos import GEOSGeometry 
    7 from django.contrib.gis.db.backend.util import GeoFieldSQL 
    8 from django.contrib.gis.db.backend.oracle.adaptor import OracleSpatialAdaptor 
    9 from django.contrib.gis.db.backend.oracle.query import ORACLE_SPATIAL_TERMS, DISTANCE_FUNCTIONS, TRANSFORM 
     4from django.contrib.gis.db.backend.util import gqn 
     5from django.contrib.gis.db.backend.oracle.query import TRANSFORM 
    106 
    117# Quotename & geographic quotename, respectively. 
    128qn = connection.ops.quote_name 
    13 def gqn(value): 
    14     if isinstance(value, UnicodeType): value = value.encode('ascii') 
    15     return "'%s'" % value 
    169 
    1710class OracleSpatialField(Field): 
     
    9689        return 'MDSYS.SDO_GEOMETRY' 
    9790         
    98     def get_db_prep_lookup(self, lookup_type, value): 
    99         """ 
    100         Returns field's value prepared for database lookup, accepts WKT and  
    101         GEOS Geometries for the value. 
    102         """ 
    103         if lookup_type in ORACLE_SPATIAL_TERMS: 
    104             # special case for isnull lookup 
    105             if lookup_type == 'isnull': return GeoFieldSQL([], []) 
    106  
    107             # Get the geometry with SRID; defaults SRID to that 
    108             # of the field if it is None 
    109             geom = self.get_geometry(value) 
    110              
    111             # The adaptor will be used by psycopg2 for quoting the WKT. 
    112             adapt = OracleSpatialAdaptor(geom) 
    113  
    114             if geom.srid != self._srid: 
    115                 # Adding the necessary string substitutions and parameters 
    116                 # to perform a geometry transformation. 
    117                 where = ['%s(SDO_GEOMETRY(%%s, %s), %%s)' % (TRANSFORM, geom.srid)] 
    118                 params = [adapt, self._srid] 
    119             else: 
    120                 where = ['SDO_GEOMETRY(%%s, %s)' % geom.srid] 
    121                 params = [adapt] 
    122  
    123             if isinstance(value, tuple): 
    124                 if lookup_type in DISTANCE_FUNCTIONS or lookup_type == 'dwithin': 
    125                     # Getting the distance parameter in the units of the field 
    126                     where += [self.get_distance(value[1])] 
    127                 elif lookup_type == 'relate': 
    128                     # No extra where parameters for SDO_RELATE queries. 
    129                     pass 
    130                 else: 
    131                     where += map(gqn, value[1:]) 
    132             return GeoFieldSQL(where, params) 
    133         else: 
    134             raise TypeError("Field has invalid lookup: %s" % lookup_type) 
    135  
    136     def get_db_prep_save(self, value): 
    137         "Prepares the value for saving in the database." 
    138         if not bool(value): 
    139             # Return an empty string for NULL -- but this doesn't work yet. 
    140             return '' 
    141         if isinstance(value, GEOSGeometry): 
    142             return OracleSpatialAdaptor(value) 
    143         else: 
    144             raise TypeError('Geometry Proxy should only return GEOSGeometry objects.') 
    145  
    14691    def get_placeholder(self, value): 
    14792        """ 
     
    15095        SDO_CS.TRANSFORM() function call. 
    15196        """ 
    152         if isinstance(value, GEOSGeometry) and value.srid != self._srid: 
     97        if value is None: 
     98            return '%s' 
     99        elif value.srid != self._srid: 
    153100            # Adding Transform() to the SQL placeholder. 
    154101            return '%s(SDO_GEOMETRY(%%s, %s), %s)' % (TRANSFORM, value.srid, self._srid) 
    155         elif value is None: 
    156             return '%s' 
    157102        else: 
    158103            return 'SDO_GEOMETRY(%%s, %s)' % self._srid 
  • django/branches/gis/django/contrib/gis/db/backend/oracle/__init__.py

    r6886 r7104  
    1 """ 
    2  The Oracle spatial database backend module. 
    3  
    4  Please note that WKT support is broken on the XE version, and thus 
    5  this backend will not work on such platforms.  Specifically, XE lacks  
    6  support for an internal JVM, and Java libraries are required to use  
    7  the WKT constructors. 
    8 """ 
    9 from django.contrib.gis.db.backend.oracle.creation import create_spatial_db 
    10 from django.contrib.gis.db.backend.oracle.field import OracleSpatialField, gqn 
    11 from django.contrib.gis.db.backend.oracle.query import \ 
    12      get_geo_where_clause, ORACLE_SPATIAL_TERMS, \ 
    13      ASGML, DISTANCE, GEOM_SELECT, TRANSFORM, UNION 
    14  
  • django/branches/gis/django/contrib/gis/db/backend/oracle/query.py

    r6919 r7104  
    11""" 
    2  This module contains the spatial lookup types, and the get_geo_where_clause() 
     2 This module contains the spatial lookup types, and the `get_geo_where_clause` 
    33 routine for Oracle Spatial. 
     4 
     5 Please note that WKT support is broken on the XE version, and thus 
     6 this backend will not work on such platforms.  Specifically, XE lacks  
     7 support for an internal JVM, and Java libraries are required to use  
     8 the WKT constructors. 
    49""" 
    510import re 
     
    2631class SDOOperation(SpatialFunction): 
    2732    "Base class for SDO* Oracle operations." 
    28     def __init__(self, func, end_subst=") %s '%s'"): 
    29         super(SDOOperation, self).__init__(func, end_subst=end_subst, operator='=', result='TRUE') 
     33    def __init__(self, func, **kwargs): 
     34        kwargs.setdefault('operator', '=') 
     35        kwargs.setdefault('result', 'TRUE') 
     36        kwargs.setdefault('end_subst', ") %s '%s'") 
     37        super(SDOOperation, self).__init__(func, **kwargs) 
    3038 
    3139class SDODistance(SpatialFunction): 
     
    5664 
    5765# Valid distance types and substitutions 
    58 dtypes = (Decimal, Distance, float, int
     66dtypes = (Decimal, Distance, float, int, long
    5967DISTANCE_FUNCTIONS = { 
    6068    'distance_gt' : (SDODistance('>'), dtypes), 
     
    6270    'distance_lt' : (SDODistance('<'), dtypes), 
    6371    'distance_lte' : (SDODistance('<='), dtypes), 
     72    'dwithin' : (SDOOperation('SDO_WITHIN_DISTANCE',  
     73                              beg_subst="%s(%s, %%s, 'distance=%%s'"), dtypes), 
    6474    } 
    6575 
     
    6979    'covers' : SDOOperation('SDO_COVERS'), 
    7080    'disjoint' : SDOGeomRelate('DISJOINT'), 
    71     'dwithin' : (SDOOperation('SDO_WITHIN_DISTANCE', end_subst=", %%s, 'distance=%%s') %s '%s'"), dtypes), 
    7281    'intersects' : SDOOperation('SDO_OVERLAPBDYINTERSECT'), # TODO: Is this really the same as ST_Intersects()? 
    7382    'equals' : SDOOperation('SDO_EQUAL'), 
     
    9099 
    91100#### The `get_geo_where_clause` function for Oracle #### 
    92 def get_geo_where_clause(lookup_type, table_prefix, field_name, value): 
     101def get_geo_where_clause(lookup_type, table_prefix, field, value): 
    93102    "Returns the SQL WHERE clause for use in Oracle spatial SQL construction." 
    94103    # Getting the quoted table name as `geo_col`. 
    95     geo_col = '%s.%s' % (qn(table_prefix), qn(field_name)) 
     104    geo_col = '%s.%s' % (qn(table_prefix), qn(field.column)) 
    96105 
    97106    # See if a Oracle Geometry function matches the lookup type next 
     
    123132                return sdo_op.as_sql(geo_col) 
    124133        else: 
    125             # Lookup info is a SDOOperation instance, whos `as_sql` method returns 
     134            # Lookup info is a SDOOperation instance, whose `as_sql` method returns 
    126135            # the SQL necessary for the geometry function call. For example:   
    127136            #  SDO_CONTAINS("geoapp_country"."poly", SDO_GEOMTRY('POINT(5 23)', 4326)) = 'TRUE' 
  • django/branches/gis/django/contrib/gis/db/backend/postgis/field.py

    r6919 r7104  
    1 from types import UnicodeType 
    21from django.db import connection 
    32from django.db.models.fields import Field # Django base Field class 
    43from django.contrib.gis.geos import GEOSGeometry 
    5 from django.contrib.gis.db.backend.util import GeoFieldSQL 
    6 from django.contrib.gis.db.backend.postgis.adaptor import PostGISAdaptor 
    7 from django.contrib.gis.db.backend.postgis.query import \ 
    8     DISTANCE_FUNCTIONS, POSTGIS_TERMS, TRANSFORM 
     4from django.contrib.gis.db.backend.util import gqn 
     5from django.contrib.gis.db.backend.postgis.query import TRANSFORM 
    96 
    107# Quotename & geographic quotename, respectively 
    118qn = connection.ops.quote_name 
    12 def gqn(value): 
    13     if isinstance(value, basestring): 
    14         if isinstance(value, UnicodeType): value = value.encode('ascii') 
    15         return "'%s'" % value 
    16     else:  
    17         return str(value) 
    189 
    1910class PostGISField(Field): 
     
    9384        return None 
    9485 
    95     def get_db_prep_lookup(self, lookup_type, value): 
    96         """ 
    97         Returns field's value prepared for database lookup, accepts WKT and  
    98         GEOS Geometries for the value. 
    99         """ 
    100         if lookup_type in POSTGIS_TERMS: 
    101             # special case for isnull lookup 
    102             if lookup_type == 'isnull': return GeoFieldSQL([], []) 
    103  
    104             # Get the geometry with SRID; defaults SRID to  
    105             # that of the field if it is None. 
    106             geom = self.get_geometry(value) 
    107  
    108             # The adaptor will be used by psycopg2 for quoting the WKB. 
    109             adapt = PostGISAdaptor(geom) 
    110  
    111             if geom.srid != self._srid: 
    112                 # Adding the necessary string substitutions and parameters 
    113                 # to perform a geometry transformation. 
    114                 where = ['%s(%%s,%%s)' % TRANSFORM] 
    115                 params = [adapt, self._srid] 
    116             else: 
    117                 # Otherwise, the adaptor will take care of everything. 
    118                 where = ['%s'] 
    119                 params = [adapt] 
    120  
    121             if isinstance(value, tuple): 
    122                 if lookup_type in DISTANCE_FUNCTIONS or lookup_type == 'dwithin': 
    123                     # Getting the distance parameter in the units of the field. 
    124                     where += [self.get_distance(value[1])] 
    125                 else: 
    126                     where += map(gqn, value[1:]) 
    127             return GeoFieldSQL(where, params) 
    128         else: 
    129             raise TypeError("Field has invalid lookup: %s" % lookup_type) 
    130  
    131  
    132     def get_db_prep_save(self, value): 
    133         "Prepares the value for saving in the database." 
    134         if not bool(value): return None 
    135         if isinstance(value, GEOSGeometry): 
    136             return PostGISAdaptor(value) 
    137         else: 
    138             raise TypeError('Geometry Proxy should only return GEOSGeometry objects.') 
    139  
    14086    def get_placeholder(self, value): 
    14187        """ 
     
    14490        ST_Transform() function call. 
    14591        """ 
    146         if isinstance(value, GEOSGeometry) and value.srid != self._srid: 
     92        if value is None or value.srid == self._srid: 
     93            return '%s' 
     94        else: 
    14795            # Adding Transform() to the SQL placeholder. 
    14896            return '%s(%%s, %s)' % (TRANSFORM, self._srid) 
    149         else: 
    150             return '%s' 
  • django/branches/gis/django/contrib/gis/db/backend/postgis/__init__.py

    r7028 r7104  
    1 """ 
    2  The PostGIS spatial database backend module. 
    3 """ 
    4 from django.contrib.gis.db.backend.postgis.creation import create_spatial_db 
    5 from django.contrib.gis.db.backend.postgis.field import PostGISField, gqn 
    6 from django.contrib.gis.db.backend.postgis.query import \ 
    7     get_geo_where_clause, GEOM_FUNC_PREFIX, POSTGIS_TERMS, \ 
    8     MAJOR_VERSION, MINOR_VERSION1, MINOR_VERSION2, \ 
    9     ASKML, ASGML, DISTANCE, EXTENT, GEOM_FROM_TEXT, UNION, TRANSFORM, GEOM_SELECT 
  • django/branches/gis/django/contrib/gis/db/backend/postgis/query.py

    r7028 r7104  
    4141    ASGML = get_func('AsGML') 
    4242    DISTANCE = get_func('Distance') 
     43    DISTANCE_SPHEROID = get_func('distance_spheroid') 
    4344    EXTENT = get_func('extent') 
    4445    GEOM_FROM_TEXT = get_func('GeomFromText') 
     
    7576class PostGISDistance(PostGISFunction): 
    7677    "For PostGIS distance operations." 
     78    dist_func = 'Distance' 
    7779    def __init__(self, operator): 
    78         super(PostGISDistance, self).__init__('Distance', end_subst=') %s %s', operator=operator, result='%%s') 
     80        super(PostGISDistance, self).__init__(self.dist_func, end_subst=') %s %s',  
     81                                              operator=operator, result='%%s') 
     82 
     83class PostGISSphereDistance(PostGISFunction): 
     84    "For PostGIS spherical distance operations." 
     85    dist_func = 'distance_spheroid' 
     86    def __init__(self, operator): 
     87        # An extra parameter in `end_subst` is needed for the spheroid string. 
     88        super(PostGISSphereDistance, self).__init__(self.dist_func,  
     89                                                    beg_subst='%s(%s, %%s, %%s',  
     90                                                    end_subst=') %s %s', 
     91                                                    operator=operator, result='%%s') 
    7992 
    8093class PostGISRelate(PostGISFunctionParam): 
     
    149162 
    150163# Valid distance types and substitutions 
    151 dtypes = (Decimal, Distance, float, int) 
     164dtypes = (Decimal, Distance, float, int, long) 
     165def get_dist_ops(operator): 
     166    "Returns operations for both regular and spherical distances." 
     167    return (PostGISDistance(operator), PostGISSphereDistance(operator)) 
    152168DISTANCE_FUNCTIONS = { 
    153     'distance_gt' : (PostGISDistance('>'), dtypes), 
    154     'distance_gte' : (PostGISDistance('>='), dtypes), 
    155     'distance_lt' : (PostGISDistance('<'), dtypes), 
    156     'distance_lte' : (PostGISDistance('<='), dtypes), 
     169    'distance_gt' : (get_dist_ops('>'), dtypes), 
     170    'distance_gte' : (get_dist_ops('>='), dtypes), 
     171    'distance_lt' : (get_dist_ops('<'), dtypes), 
     172    'distance_lte' : (get_dist_ops('<='), dtypes), 
    157173    } 
    158174 
     
    160176    # The ST_DWithin, ST_CoveredBy, and ST_Covers routines become available in 1.2.2+ 
    161177    POSTGIS_GEOMETRY_FUNCTIONS.update( 
    162         {'dwithin' : (PostGISFunctionParam('DWithin'), dtypes), 
    163          'coveredby' : PostGISFunction('CoveredBy'), 
     178        {'coveredby' : PostGISFunction('CoveredBy'), 
    164179         'covers' : PostGISFunction('Covers'), 
    165180         }) 
     181    DISTANCE_FUNCTIONS['dwithin'] = (PostGISFunctionParam('DWithin'), dtypes) 
    166182 
    167183# Distance functions are a part of PostGIS geometry functions. 
     
    179195 
    180196#### The `get_geo_where_clause` function for PostGIS. #### 
    181 def get_geo_where_clause(lookup_type, table_prefix, field_name, value): 
     197def get_geo_where_clause(lookup_type, table_prefix, field, value): 
    182198    "Returns the SQL WHERE clause for use in PostGIS SQL construction." 
    183199    # Getting the quoted field as `geo_col`. 
    184     geo_col = '%s.%s' % (qn(table_prefix), qn(field_name)) 
     200    geo_col = '%s.%s' % (qn(table_prefix), qn(field.column)) 
    185201    if lookup_type in POSTGIS_OPERATORS: 
    186202        # See if a PostGIS operator matches the lookup type. 
     
    199215 
    200216            # Ensuring that a tuple _value_ was passed in from the user 
    201             if not isinstance(value, tuple):  
     217            if not isinstance(value, (tuple, list)):  
    202218                raise TypeError('Tuple required for `%s` lookup type.' % lookup_type) 
    203219            if len(value) != 2: 
     
    210226            # For lookup type `relate`, the op instance is not yet created (has 
    211227            # to be instantiated here to check the pattern parameter). 
    212             if lookup_type == 'relate': op = op(value[1]) 
     228            if lookup_type == 'relate':  
     229                op = op(value[1]) 
     230            elif lookup_type in DISTANCE_FUNCTIONS and lookup_type != 'dwithin': 
     231                if field._unit_name == 'degree': 
     232                    # Geodetic distances are only availble from Points to PointFields. 
     233                    if field._geom != 'POINT': 
     234                        raise TypeError('PostGIS spherical operations are only valid on PointFields.') 
     235                    if value[0].geom_typeid != 0: 
     236                        raise TypeError('PostGIS geometry distance parameter is required to be of type Point.') 
     237                    op = op[1] 
     238                else: 
     239                    op = op[0] 
    213240        else: 
    214241            op = tmp 
  • django/branches/gis/django/contrib/gis/db/backend/util.py

    r6919 r7104  
    1 class GeoFieldSQL(object): 
     1from types import UnicodeType 
     2 
     3def gqn(val): 
    24    """ 
    3     Container for passing values to `parse_lookup` from the various 
    4     backend geometry fields. 
     5    The geographic quote name function; used for quoting tables and  
     6    geometries (they use single rather than the double quotes of the 
     7    backend quotename function). 
    58    """ 
    6     def __init__(self, where=[], params=[]): 
    7         self.where = where 
    8         self.params = params 
    9  
    10     def __str__(self): 
    11         return self.as_sql() 
    12  
    13     def as_sql(self, quote=False): 
    14         if not quote: 
    15             return self.where[0] % tuple(self.params) 
    16         else: 
    17             # Used for quoting WKT on certain backends. 
    18             tmp_params = ["'%s'" % self.params[0]] 
    19             tmp_params.extend(self.params[1:]) 
    20             return self.where[0] % tuple(tmp_params) 
     9    if isinstance(val, basestring): 
     10        if isinstance(val, UnicodeType): val = val.encode('ascii') 
     11        return "'%s'" % val 
     12    else: 
     13        return str(val) 
    2114 
    2215class SpatialOperation(object): 
  • django/branches/gis/django/contrib/gis/db/models/fields/__init__.py

    r6886 r7104  
    22from django.conf import settings 
    33from django.db import connection 
    4 from django.contrib.gis.db.backend import GeoBackendField # these depend on the spatial database backend. 
     4# Getting the SpatialBackend container and the geographic quoting method. 
     5from django.contrib.gis.db.backend import SpatialBackend, gqn 
     6# GeometryProxy, GEOS, Distance, and oldforms imports. 
    57from django.contrib.gis.db.models.proxy import GeometryProxy 
    68from django.contrib.gis.geos import GEOSException, GEOSGeometry 
     
    1517 
    1618#TODO: Flesh out widgets; consider adding support for OGR Geometry proxies. 
    17 class GeometryField(GeoBackendField): 
     19class GeometryField(SpatialBackend.Field): 
    1820    "The base GIS field -- maps to the OpenGIS Specification Geometry type." 
    1921 
     
    3941        """ 
    4042 
    41         # Backward-compatibility notice, this will disappear in future revisions. 
    42         if 'index' in kwargs: 
    43             from warnings import warn 
    44             warn('The `index` keyword has been deprecated, please use the `spatial_index` keyword instead.') 
    45             self._index = kwargs['index'] 
    46         else: 
    47             self._index = spatial_index 
    48  
    49         # Setting the SRID and getting the units. 
     43        # Setting the index flag with the value of the `spatial_index` keyword. 
     44        self._index = spatial_index 
     45 
     46        # Setting the SRID and getting the units.  Unit information must be  
     47        # easily available in the field instance for distance queries. 
    5048        self._srid = srid 
    5149        if SpatialRefSys: 
    52             # This doesn't work when we actually use: SpatialRefSys.objects.get(srid=srid) 
     50            # Getting the spatial reference WKT associated with the SRID from the 
     51            # `spatial_ref_sys` (or equivalent) spatial database table. 
     52            # 
     53            # The following doesn't work: SpatialRefSys.objects.get(srid=srid) 
    5354            # Why?  `syncdb` fails to recognize installed geographic models when there's 
    54             # an ORM query instantiated within a model field.  No matter, this works fine 
    55             # too. 
     55            # an ORM query instantiated within a model field. 
    5656            cur = connection.cursor() 
    5757            qn = connection.ops.quote_name 
     
    6363                           } 
    6464            cur.execute(stmt) 
    65             row = cur.fetchone() 
    66             self._unit, self._unit_name = SpatialRefSys.get_units(row[0]) 
     65            srs_wkt = cur.fetchone()[0] 
    6766             
     67            # Getting metadata associated with the spatial reference system identifier. 
     68            # Specifically, getting the unit information and spheroid information  
     69            # (both required for distance queries). 
     70            self._unit, self._unit_name = SpatialRefSys.get_units(srs_wkt) 
     71            self._spheroid = SpatialRefSys.get_spheroid(srs_wkt) 
     72 
    6873        # Setting the dimension of the geometry field. 
    6974        self._dim = dim 
     75 
    7076        super(GeometryField, self).__init__(**kwargs) # Calling the parent initializtion function 
    7177 
    7278    ### Routines specific to GeometryField ### 
    7379    def get_distance(self, dist): 
     80        """ 
     81        Returns a distance number in units of the field.  For example, if  
     82        `D(km=1)` was passed in and the units of the field were in meters, 
     83        then 1000 would be returned. 
     84        """ 
    7485        if isinstance(dist, Distance): 
    75             return getattr(dist, Distance.unit_attname(self._unit_name)) 
    76         elif isinstance(dist, (int, float, Decimal)): 
     86            if self._unit_name in ('Decimal Degree', 'degree'): 
     87                # Spherical distance calculation parameter should be in meters. 
     88                dist_param = dist.m 
     89            else: 
     90                dist_param = getattr(dist, Distance.unit_attname(self._unit_name)) 
     91        else: 
    7792            # Assuming the distance is in the units of the field. 
    78             return dist 
     93            dist_param = dist 
     94 
     95        # Sphereical distance query; returning meters. 
     96        if SpatialBackend.name == 'postgis' and self._unit_name == 'degree': 
     97            return [gqn(self._spheroid), dist_param] 
     98        else: 
     99            return [dist_param] 
    79100 
    80101    def get_geometry(self, value): 
     
    83104        lookup parameters. 
    84105        """ 
    85         if isinstance(value, tuple):  
     106        if isinstance(value, (tuple, list)):  
    86107            geom = value[0] 
    87108        else: 
     
    122143        setattr(cls, self.attname, GeometryProxy(GEOSGeometry, self)) 
    123144 
     145    def get_db_prep_lookup(self, lookup_type, value): 
     146        """ 
     147        Returns the spatial WHERE clause and associated parameters for the 
     148        given lookup type and value.  The value will be prepared for database 
     149        lookup (e.g., spatial transformation SQL will be added if necessary). 
     150        """ 
     151        if lookup_type in SpatialBackend.gis_terms: 
     152            # special case for isnull lookup 
     153            if lookup_type == 'isnull': return [], [] 
     154 
     155            # Get the geometry with SRID; defaults SRID to that of the field 
     156            # if it is None. 
     157            geom = self.get_geometry(value) 
     158