Django

Code

Changeset 6886

Show
Ignore:
Timestamp:
12/04/07 11:44:37 (8 months ago)
Author:
jbronn
Message:

gis: Added distance querying capabilites via the distance manager method and the distance_[gt|gte|lt|lte] lookup types (works for both PostGIS and Oracle); improved Oracle query construction and fixed transform issues.

Files:

Legend:

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

    r6527 r6886  
    2727 
    2828# These routines (needed by GeoManager), default to False. 
    29 ASGML, ASKML, TRANSFORM, UNION= (False, False, False, False) 
     29ASGML, ASKML, DISTANCE, TRANSFORM, UNION= (False, False, False, False, False) 
    3030 
    3131if settings.DATABASE_ENGINE == 'postgresql_psycopg2': 
     
    3434    from django.contrib.gis.db.backend.postgis import \ 
    3535        PostGISField as GeoBackendField, POSTGIS_TERMS as GIS_TERMS, \ 
    36         create_spatial_db, get_geo_where_clause, gqn,
    37         ASGML, ASKML, GEOM_SELECT, TRANSFORM, UNION 
     36        create_spatial_db, get_geo_where_clause,
     37        ASGML, ASKML, DISTANCE, GEOM_SELECT, TRANSFORM, UNION 
    3838    SPATIAL_BACKEND = 'postgis' 
    3939elif settings.DATABASE_ENGINE == 'oracle': 
     
    4141         OracleSpatialField as GeoBackendField, \ 
    4242         ORACLE_SPATIAL_TERMS as GIS_TERMS, \ 
    43          create_spatial_db, get_geo_where_clause, gqn,
    44          ASGML, GEOM_SELECT, TRANSFORM, UNION 
     43         create_spatial_db, get_geo_where_clause,
     44         ASGML, DISTANCE, GEOM_SELECT, TRANSFORM, UNION 
    4545    SPATIAL_BACKEND = 'oracle' 
    4646elif settings.DATABASE_ENGINE == 'mysql': 
     
    4848        MySQLGeoField as GeoBackendField, \ 
    4949        MYSQL_GIS_TERMS as GIS_TERMS, \ 
    50         create_spatial_db, get_geo_where_clause, gqn,
     50        create_spatial_db, get_geo_where_clause,
    5151        GEOM_SELECT 
    5252    SPATIAL_BACKEND = 'mysql' 
    5353else: 
    5454    raise NotImplementedError('No Geographic Backend exists for %s' % settings.DATABASE_ENGINE) 
    55  
    56 def geo_quotename(value): 
    57     """ 
    58     Returns the quotation used on a given Geometry value using the geometry 
    59     quoting from the backend (the `gqn` function). 
    60     """ 
    61     if isinstance(value, (StringType, UnicodeType)): return gqn(value) 
    62     else: return str(value) 
    6355 
    6456####    query.py overloaded functions    #### 
     
    118110        elif callable(value): 
    119111            value = value() 
    120  
     112         
    121113        joins2, where2, params2 = lookup_inner(path, lookup_type, value, opts, opts.db_table, None) 
    122114        joins.update(joins2) 
     
    288280        # with the get_geo_where_clause() 
    289281        if hasattr(field, '_geom'): 
    290             # Do we have multiple arguments, e.g., `relate`, `dwithin` lookup types 
    291             # need more than argument. 
    292             multiple_args = isinstance(value, tuple) 
    293  
    294282            # Getting the preparation SQL object from the field. 
    295             if multiple_args: 
    296                 geo_prep = field.get_db_prep_lookup(lookup_type, value[0]) 
    297             else: 
    298                 geo_prep = field.get_db_prep_lookup(lookup_type, value) 
    299  
     283            geo_prep = field.get_db_prep_lookup(lookup_type, value) 
     284             
    300285            # Getting the adapted geometry from the field. 
    301286            gwc = get_geo_where_clause(lookup_type, current_table + '.', column, value) 
    302              
    303             # A GeoFieldSQL object is returned by `get_db_prep_lookup` --  
    304             # getting the substitution list and the geographic parameters. 
    305             subst_list = geo_prep.where 
    306             if multiple_args: subst_list += map(geo_quotename, value[1:]) 
    307             gwc = gwc % tuple(subst_list) 
    308              
    309             # Finally, appending onto the WHERE clause, and extending with 
    310             # the additional parameters. 
    311             where.append(gwc) 
     287 
     288            # Substituting in the the where parameters into the geographic where 
     289            # clause, and extending the parameters. 
     290            where.append(gwc % tuple(geo_prep.where)) 
    312291            params.extend(geo_prep.params) 
    313292        else: 
  • django/branches/gis/django/contrib/gis/db/backend/oracle/field.py

    r6596 r6886  
    77from django.contrib.gis.db.backend.util import get_srid, GeoFieldSQL 
    88from django.contrib.gis.db.backend.oracle.adaptor import OracleSpatialAdaptor 
    9 from django.contrib.gis.db.backend.oracle.query import ORACLE_SPATIAL_TERMS, TRANSFORM 
     9from django.contrib.gis.db.backend.oracle.query import ORACLE_SPATIAL_TERMS, DISTANCE_FUNCTIONS, TRANSFORM 
    1010 
    1111# Quotename & geographic quotename, respectively. 
     
    2222    empty_strings_allowed = False 
    2323 
    24     def __init__(self, extent=(-180.0, -90.0, 180.0, 90.0), tolerance=0.00005, **kwargs): 
     24    def __init__(self, extent=(-180.0, -90.0, 180.0, 90.0), tolerance=0.05, **kwargs): 
    2525        """ 
    2626        Oracle Spatial backend needs to have the extent -- for projected coordinate 
    2727        systems _you must define the extent manually_, since the coordinates are 
    2828        for geodetic systems.  The `tolerance` keyword specifies the tolerance 
    29         for error (in meters)
     29        for error (in meters), and defaults to 0.05 (5 centimeters)
    3030        """ 
    3131        # Oracle Spatial specific keyword arguments. 
     
    105105            if lookup_type == 'isnull': return GeoFieldSQL([], []) 
    106106 
    107             # When the input is not a GEOS geometry, attempt to construct one 
    108             # from the given string input. 
    109             if isinstance(value, GEOSGeometry): 
    110                 pass 
    111             elif isinstance(value, (StringType, UnicodeType)): 
    112                 try: 
    113                     value = GEOSGeometry(value) 
    114                 except GEOSException: 
    115                     raise TypeError("Could not create geometry from lookup value: %s" % str(value)) 
    116             else: 
    117                 raise TypeError('Cannot use parameter of %s type as lookup parameter.' % type(value)) 
    118  
    119             # Getting the SRID of the geometry, or defaulting to that of the field if 
    120             # it is None. 
    121             srid = get_srid(self, value) 
     107            # Get the geometry with SRID; defaults SRID to that 
     108            # of the field if it is None 
     109            geom = self.get_geometry(value) 
    122110             
    123111            # The adaptor will be used by psycopg2 for quoting the WKT. 
    124             adapt = OracleSpatialAdaptor(value) 
    125             if srid != self._srid: 
     112            adapt = OracleSpatialAdaptor(geom) 
     113 
     114            if geom.srid != self._srid: 
    126115                # Adding the necessary string substitutions and parameters 
    127116                # to perform a geometry transformation. 
    128                 return GeoFieldSQL(['%s(SDO_GEOMETRY(%%s, %s), %%s)' % (TRANSFORM, srid)], 
    129                                    [adapt, self._srid]) 
     117                where = ['%s(SDO_GEOMETRY(%%s, %s), %%s)' % (TRANSFORM, geom.srid)] 
     118                params = [adapt, self._srid] 
    130119            else: 
    131                 return GeoFieldSQL(['SDO_GEOMETRY(%%s, %s)' % srid], [adapt]) 
     120                where = ['SDO_GEOMETRY(%%s, %s)' % geom.srid] 
     121                params = [adapt] 
    132122 
     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) 
    133133        else: 
    134134            raise TypeError("Field has invalid lookup: %s" % lookup_type) 
  • django/branches/gis/django/contrib/gis/db/backend/oracle/__init__.py

    r6527 r6886  
    1111from django.contrib.gis.db.backend.oracle.query import \ 
    1212     get_geo_where_clause, ORACLE_SPATIAL_TERMS, \ 
    13      ASGML, GEOM_SELECT, TRANSFORM, UNION 
     13     ASGML, DISTANCE, GEOM_SELECT, TRANSFORM, UNION 
    1414 
  • django/branches/gis/django/contrib/gis/db/backend/oracle/models.py

    r6524 r6886  
    2121 
    2222    @classmethod 
    23     def table_name_col(self): 
     23    def table_name_col(cls): 
    2424        return 'table_name' 
    2525 
     
    4444    def wkt(self): 
    4545        return self.wktext 
     46 
     47    @classmethod 
     48    def wkt_col(cls): 
     49        return 'wktext' 
  • django/branches/gis/django/contrib/gis/db/backend/oracle/query.py

    r6527 r6886  
    33 routine for Oracle Spatial. 
    44""" 
     5import re 
     6from decimal import Decimal 
    57from django.db import connection 
     8from django.contrib.gis.measure import Distance 
    69qn = connection.ops.quote_name 
    710 
     11# The GML, distance, transform, and union procedures. 
     12ASGML = 'SDO_UTIL.TO_GMLGEOMETRY' 
     13DISTANCE = 'SDO_GEOM.SDO_DISTANCE' 
     14TRANSFORM = 'SDO_CS.TRANSFORM' 
     15UNION = 'SDO_AGGR_UNION' 
     16 
     17class SDOOperation(object): 
     18    "Base class for SDO* Oracle operations." 
     19 
     20    def __init__(self, lookup, subst='', operator='=', result="'TRUE'", 
     21                 beg_subst='%s(%s%s, %%s'): 
     22        self.lookup = lookup 
     23        self.subst = subst 
     24        self.operator = operator 
     25        self.result = result 
     26        self.beg_subst = beg_subst 
     27        self.end_subst = ') %s %s' % (self.operator, self.result) 
     28 
     29    @property 
     30    def sql_subst(self): 
     31        return ''.join([self.beg_subst, self.subst, self.end_subst]) 
     32 
     33    def as_sql(self, table, field): 
     34        return self.sql_subst % self.params(table, field) 
     35 
     36    def params(self, table, field): 
     37        return (self.lookup, table, field) 
     38 
     39class SDODistance(SDOOperation): 
     40    "Class for Distance queries." 
     41    def __init__(self, op, tolerance=0.05): 
     42        super(SDODistance, self).__init__(DISTANCE, subst=", %s", operator=op, result='%%s') 
     43        self.tolerance = tolerance 
     44 
     45    def params(self, table, field): 
     46        return (self.lookup, table, field, self.tolerance) 
     47 
     48class SDOGeomRelate(SDOOperation): 
     49    "Class for using SDO_GEOM.RELATE." 
     50    def __init__(self, mask, tolerance=0.05): 
     51        super(SDOGeomRelate, self).__init__('SDO_GEOM.RELATE',  beg_subst="%s(%s%s, '%s'", 
     52                                            subst=", %%s, %s", result="'%s'" % mask) 
     53        self.mask = mask 
     54        self.tolerance = tolerance 
     55 
     56    def params(self, table, field): 
     57        return (self.lookup, table, field, self.mask, self.tolerance) 
     58 
     59class SDORelate(SDOOperation): 
     60    "Class for using SDO_RELATE." 
     61    masks = 'TOUCH|OVERLAPBDYDISJOINT|OVERLAPBDYINTERSECT|EQUAL|INSIDE|COVEREDBY|CONTAINS|COVERS|ANYINTERACT|ON' 
     62    mask_regex = re.compile(r'^(%s)(\+(%s))*$' % (masks, masks), re.I) 
     63     
     64    def __init__(self, mask, **kwargs): 
     65        super(SDORelate, self).__init__('SDO_RELATE',  subst=", 'mask=%s'", **kwargs) 
     66        if not self.mask_regex.match(mask): 
     67            raise ValueError('Invalid %s mask: "%s"' % (self.lookup, mask)) 
     68        self.mask = mask 
     69 
     70    def params(self, table, field): 
     71        return (self.lookup, table, field, self.mask) 
     72 
     73# Valid distance types and substitutions 
     74dtypes = (Decimal, Distance, float, int) 
     75DISTANCE_FUNCTIONS = { 
     76    'distance_gt' : (SDODistance('>'), dtypes), 
     77    'distance_gte' : (SDODistance('>='), dtypes), 
     78    'distance_lt' : (SDODistance('<'), dtypes), 
     79    'distance_lte' : (SDODistance('<='), dtypes), 
     80    } 
     81 
    882ORACLE_GEOMETRY_FUNCTIONS = { 
    9     'contains' : 'SDO_CONTAINS'
    10     'coveredby' : 'SDO_COVEREDBY'
    11     'covers' : 'SDO_COVERS'
    12     'disjoint' : 'SDO_DISJOINT'
    13     'dwithin' : ('SDO_WITHIN_DISTANCE', float), 
    14     'intersects' : 'SDO_OVERLAPBDYINTERSECT', # TODO: Is this really the same as ST_Intersects()? 
    15     'equals' : 'SDO_EQUAL'
    16     'exact' : 'SDO_EQUAL'
    17     'overlaps' : 'SDO_OVERLAPS'
    18     'same_as' : 'SDO_EQUAL'
    19     #'relate' : ('SDO_RELATE', str), # Oracle uses a different syntax, e.g., 'mask=inside+touch' 
    20     'touches' : 'SDO_TOUCH'
    21     'within' : 'SDO_INSIDE'
     83    'contains' : SDOOperation('SDO_CONTAINS')
     84    'coveredby' : SDOOperation('SDO_COVEREDBY')
     85    'covers' : SDOOperation('SDO_COVERS')
     86    'disjoint' : SDOGeomRelate('DISJOINT')
     87    'dwithin' : (SDOOperation('SDO_WITHIN_DISTANCE', "%%s, 'distance=%%s'"), dtypes), 
     88    'intersects' : SDOOperation('SDO_OVERLAPBDYINTERSECT'), # TODO: Is this really the same as ST_Intersects()? 
     89    'equals' : SDOOperation('SDO_EQUAL')
     90    'exact' : SDOOperation('SDO_EQUAL')
     91    'overlaps' : SDOOperation('SDO_OVERLAPS')
     92    'same_as' : SDOOperation('SDO_EQUAL')
     93    'relate' : (SDORelate, basestring), # Oracle uses a different syntax, e.g., 'mask=inside+touch' 
     94    'touches' : SDOOperation('SDO_TOUCH')
     95    'within' : SDOOperation('SDO_INSIDE')
    2296    } 
     97ORACLE_GEOMETRY_FUNCTIONS.update(DISTANCE_FUNCTIONS) 
    2398 
    2499# This lookup type does not require a mapping. 
     
    44119            # First element of tuple is lookup type, second element is the type 
    45120            #  of the expected argument (e.g., str, float) 
    46             func, arg_type = lookup_info 
     121            sdo_op, arg_type = lookup_info 
    47122 
    48123            # Ensuring that a tuple _value_ was passed in from the user 
    49             if not isinstance(value, tuple) or len(value) != 2:  
    50                 raise TypeError('2-element tuple required for %s lookup type.' % lookup_type) 
     124            if not isinstance(value, tuple): 
     125                raise TypeError('Tuple required for `%s` lookup type.' % lookup_type) 
     126            if len(value) != 2:  
     127                raise ValueError('2-element tuple required for %s lookup type.' % lookup_type) 
    51128             
    52129            # Ensuring the argument type matches what we expect. 
     
    54131                raise TypeError('Argument type should be %s, got %s instead.' % (arg_type, type(value[1]))) 
    55132 
    56             if func == 'dwithin': 
    57                 # TODO: test and consider adding different distance options. 
    58                 return "%s(%s, %%s, 'distance=%s')" % (func, table_prefix + field_name, value[1]) 
     133            if lookup_type == 'relate': 
     134                # The SDORelate class handles construction for these queries, and verifies 
     135                # the mask argument. 
     136                return sdo_op(value[1]).as_sql(table_prefix, field_name) 
     137            elif lookup_type in DISTANCE_FUNCTIONS: 
     138                op = DISTANCE_FUNCTIONS[lookup_type][0] 
     139                return op.as_sql(table_prefix, field_name) 
     140                #    return '%s(%s%s, %%s) %s %%s' % (DISTANCE, table_prefix, field_name, op) 
    59141            else: 
    60                 return "%s(%s, %%s, %%s) = 'TRUE'" % (func, table_prefix + field_name) 
     142                return sdo_op.as_sql(table_prefix, field_name) 
    61143        else: 
    62             # Returning the SQL necessary for the geometry function call. For example:  
     144            # Lookup info is a SDOOperation instance, whos `as_sql` method returns 
     145            # the SQL necessary for the geometry function call. For example:   
    63146            #  SDO_CONTAINS("geoapp_country"."poly", SDO_GEOMTRY('POINT(5 23)', 4326)) = 'TRUE' 
    64             return "%s(%s, %%s) = 'TRUE'" % (lookup_info, table_prefix + field_name) 
     147            return lookup_info.as_sql(table_prefix, field_name) 
    65148     
    66149    # Handling 'isnull' lookup type 
     
    70153    raise TypeError("Got invalid lookup_type: %s" % repr(lookup_type)) 
    71154 
    72 ASGML = 'SDO_UTIL.TO_GMLGEOMETRY' 
    73 UNION = 'SDO_AGGR_UNION' 
    74 TRANSFORM = 'SDO_CS.TRANSFORM' 
    75  
    76155# Want to get SDO Geometries as WKT (much easier to instantiate GEOS proxies 
    77156# from WKT than SDO_GEOMETRY(...) strings ;) 
  • django/branches/gis/django/contrib/gis/db/backend/postgis/adaptor.py

    r6508 r6886  
    88 
    99class PostGISAdaptor(object): 
    10     def __init__(self, geom, srid): 
     10    def __init__(self, geom): 
    1111        "Initializes on the geometry and the SRID." 
    1212        # Getting the WKB and the SRID 
    1313        self.wkb = geom.wkb 
    14         self.srid = srid 
     14        self.srid = geom.srid 
    1515 
    1616    def __conform__(self, proto): 
  • django/branches/gis/django/contrib/gis/db/backend/postgis/field.py

    r6596 r6886  
    1 from types import StringType, UnicodeType 
     1from types import UnicodeType 
    22from django.db import connection 
    33from django.db.models.fields import Field # Django base Field class 
     
    55from django.contrib.gis.db.backend.util import get_srid, GeoFieldSQL 
    66from django.contrib.gis.db.backend.postgis.adaptor import PostGISAdaptor 
    7 from django.contrib.gis.db.backend.postgis.query import POSTGIS_TERMS, TRANSFORM 
    8 from psycopg2 import Binary 
     7from django.contrib.gis.db.backend.postgis.query import \ 
     8    DISTANCE, DISTANCE_FUNCTIONS, POSTGIS_TERMS, TRANSFORM 
    99 
    1010# Quotename & geographic quotename, respectively 
    1111qn = connection.ops.quote_name 
    1212def gqn(value): 
    13     if isinstance(value, UnicodeType): value = value.encode('ascii') 
    14     return "'%s'" % 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) 
    1518 
    1619class PostGISField(Field): 
     20    """ 
     21    The backend-specific geographic field for PostGIS. 
     22    """ 
     23 
    1724    def _add_geom(self, style, db_table): 
    1825        """ 
     
    93100        if lookup_type in POSTGIS_TERMS: 
    94101            # special case for isnull lookup 
    95             if lookup_type == 'isnull': 
    96                 return GeoFieldSQL([], [value]) 
     102            if lookup_type == 'isnull': return GeoFieldSQL([], []) 
    97103 
    98             # When the input is not a GEOS geometry, attempt to construct one 
    99             # from the given string input. 
    100             if isinstance(value, GEOSGeometry): 
    101                 pass 
    102             elif isinstance(value, (StringType, UnicodeType)): 
    103                 try: 
    104                     value = GEOSGeometry(value) 
    105                 except GEOSException: 
    106                     raise TypeError("Could not create geometry from lookup value: %s" % str(value)) 
    107             else: 
    108                 raise TypeError('Cannot use parameter of %s type as lookup parameter.' % type(value)) 
    109  
    110             # Getting the SRID of the geometry, or defaulting to that of the field if 
    111             # it is None. 
    112             srid = get_srid(self, value) 
     104            # Get the geometry with SRID; defaults SRID to  
     105            # that of the field if it is None. 
     106            geom = self.get_geometry(value) 
    113107 
    114108            # The adaptor will be used by psycopg2 for quoting the WKB. 
    115             adapt = PostGISAdaptor(value, srid
     109            adapt = PostGISAdaptor(geom
    116110 
    117             if srid != self._srid: 
     111            if geom.srid != self._srid: 
    118112                # Adding the necessary string substitutions and parameters 
    119113                # to perform a geometry transformation. 
    120                 return GeoFieldSQL(['%s(%%s,%%s)' % TRANSFORM], 
    121                                    [adapt, self._srid]) 
     114                where = ['%s(%%s,%%s)' % TRANSFORM] 
     115                params = [adapt, self._srid] 
    122116            else: 
    123                 return GeoFieldSQL(['%s'], [adapt]) 
     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) 
    124128        else: 
    125129            raise TypeError("Field has invalid lookup: %s" % lookup_type) 
     130 
    126131 
    127132    def get_db_prep_save(self, value): 
     
    129134        if not bool(value): return None 
    130135        if isinstance(value, GEOSGeometry): 
    131             return PostGISAdaptor(value, value.srid
     136            return PostGISAdaptor(value
    132137        else: 
    133138            raise TypeError('Geometry Proxy should only return GEOSGeometry objects.') 
    134  
    135     def get_internal_type(self): 
    136         """ 
    137         Returns NoField because a stored procedure is used by PostGIS to create 
    138         the Geometry Fields. 
    139         """ 
    140         return 'NoField' 
    141139 
    142140    def get_placeholder(self, value): 
  • django/branches/gis/django/contrib/gis/db/backend/postgis/__init__.py

    r6509 r6886  
    77    get_geo_where_clause, GEOM_FUNC_PREFIX, POSTGIS_TERMS, \ 
    88    MAJOR_VERSION, MINOR_VERSION1, MINOR_VERSION2, \ 
    9     ASKML, ASGML, GEOM_FROM_TEXT, UNION, TRANSFORM, GEOM_SELECT 
     9    ASKML, ASGML, DISTANCE, GEOM_FROM_TEXT, UNION, TRANSFORM, GEOM_SELECT 
  • django/branches/gis/django/contrib/gis/db/backend/postgis/models.py

    r6524 r6886  
    1717    f_table_catalog = models.CharField(maxlength=256) 
    1818    f_table_schema = models.CharField(maxlength=256) 
    19     f_table_name = models.CharField(maxlength=256, primary_key=True
     19    f_table_name = models.CharField(maxlength=256
    2020    f_geometry_column = models.CharField(maxlength=256) 
    2121    coord_dimension = models.IntegerField() 
    22     srid = models.IntegerField(
     22    srid = models.IntegerField(primary_key=True
    2323    type = models.CharField(maxlength=30) 
    2424 
     
    2727 
    2828    @classmethod 
    29     def table_name_col(self): 
     29    def table_name_col(cls): 
    3030        "Class method for returning the table name column for this model." 
    3131        return 'f_table_name' 
     
    5353    def wkt(self): 
    5454        return self.srtext 
     55 
     56    @classmethod 
     57    def wkt_col(cls): 
     58        return 'srtext' 
  • django/branches/gis/django/contrib/gis/db/backend/postgis/query.py

    r6524 r6886  
    33 routine for PostGIS. 
    44""" 
     5from decimal import Decimal 
    56from django.db import connection 
     7from django.contrib.gis.measure import Distance 
    68from django.contrib.gis.db.backend.postgis.management import postgis_version_tuple 
    7 from types import StringType, UnicodeType 
    89qn = connection.ops.quote_name 
    910 
     
    6364#  'SQL-MM-centric' to conform with the ISO standard. Practically, this  
    6465#  means that 'ST_' is prefixes geometry function names. 
    65 if MAJOR_VERSION > 1 or (MAJOR_VERSION == 1 and (MINOR_VERSION1 > 2 or (MINOR_VERSION1 == 2 and MINOR_VERSION2 >= 2))): 
    66     GEOM_FUNC_PREFIX = 'ST_' 
     66GEOM_FUNC_PREFIX = '' 
     67if MAJOR_VERSION >= 1: 
     68    if (MINOR_VERSION1 > 2 or  
     69        (MINOR_VERSION1 == 2 and MINOR_VERSION2 >= 2)): 
     70        GEOM_FUNC_PREFIX = 'ST_' 
     71     
     72    def get_func(func): return '%s%s' % (GEOM_FUNC_PREFIX, func) 
     73 
     74    # Functions used by the GeoManager & GeoQuerySet 
     75    ASKML = get_func('AsKML') 
     76    ASGML = get_func('AsGML') 
     77    DISTANCE = get_func('Distance') 
     78    GEOM_FROM_TEXT = get_func('GeomFromText') 
     79    GEOM_FROM_WKB = get_func('GeomFromWKB') 
     80    TRANSFORM = get_func('Transform') 
     81 
     82    # Special cases for union and KML methods. 
     83    if MINOR_VERSION1 < 3: 
     84        UNION = 'GeomUnion' 
     85    else: 
     86        UNION = 'ST_Union' 
     87 
     88    if MINOR_VERSION1 == 1: 
     89        ASKML = False 
    6790else: 
    68     GEOM_FUNC_PREFIX = '' 
     91    raise NotImplementedError('PostGIS versions < 1.0 are not supported.') 
    6992 
    7093# For PostGIS >= 1.2.2 the following lookup types will do a bounding box query 
    71 # first before calling the more computationally expensive GEOS routines (called 
    72 # "inline index magic"): 
    73 #    'touches', 'crosses', 'contains', 'intersects', 'within', 'overlaps', and 
    74 #    'covers'. 
     94# first before calling the more computationally expensive GEOS routines (called 
     95# "inline index magic"): 
     96# 'touches', 'crosses', 'contains', 'intersects', 'within', 'overlaps', and 
     97# 'covers'. 
    7598POSTGIS_GEOMETRY_FUNCTIONS = { 
    7699    'equals' : 'Equals', 
     
    82105    'contains' : 'Contains', 
    83106    'intersects' : 'Intersects', 
    84     'relate' : ('Relate', str), 
     107    'relate' : ('Relate', basestring), 
     108    } 
     109 
     110# Valid distance types and substitutions 
     111dtypes = (Decimal, Distance, float, int) 
     112DISTANCE_FUNCTIONS = { 
     113    'distance_gt' : ('>', dtypes), 
     114    'distance_gte' : ('>=', dtypes), 
     115    'distance_lt' : ('<', dtypes), 
     116    'distance_lte' : ('<=', dtypes), 
    85117    } 
    86118 
    87119if GEOM_FUNC_PREFIX == 'ST_': 
    88120    # Adding the GEOM_FUNC_PREFIX to the lookup functions. 
    89     for lookup, func in POSTGIS_GEOMETRY_FUNCTIONS.items(): 
    90         if isinstance(func, tuple): 
    91             POSTGIS_GEOMETRY_FUNCTIONS[lookup] = (GEOM_FUNC_PREFIX + func[0], func[1]) 
     121    for lookup, f in POSTGIS_GEOMETRY_FUNCTIONS.items(): 
     122        if isinstance(f, tuple): 
     123            POSTGIS_GEOMETRY_FUNCTIONS[lookup] = (get_func(f[0]), f[1]) 
    92124        else: 
    93             POSTGIS_GEOMETRY_FUNCTIONS[lookup] = GEOM_FUNC_PREFIX + func 
     125            POSTGIS_GEOMETRY_FUNCTIONS[lookup] = get_func(f) 
    94126 
    95127    # The ST_DWithin, ST_CoveredBy, and ST_Covers routines become available in 1.2.2+ 
    96128    POSTGIS_GEOMETRY_FUNCTIONS.update( 
    97         {'dwithin' : ('ST_DWithin', float), 
     129        {'dwithin' : ('ST_DWithin', dtypes), 
    98130         'coveredby' : 'ST_CoveredBy', 
    99131         'covers' : 'ST_Covers', 
    100132         } 
    101133        ) 
     134 
     135POSTGIS_GEOMETRY_FUNCTIONS.update(DISTANCE_FUNCTIONS) 
    102136 
    103137# Any other lookup types that do not require a mapping. 
     
    140174 
    141175            # Ensuring that a tuple _value_ was passed in from the user 
    142             if not isinstance(value, tuple) or len(value) != 2:  
    143                 raise TypeError('2-element tuple required for `%s` lookup type.' % lookup_type) 
     176            if not isinstance(value, tuple):  
     177                raise TypeError('Tuple required for `%s` lookup type.' % lookup_type) 
     178            if len(value) != 2: 
     179                raise ValueError('2-element tuple required or `%s` lookup type.' % lookup_type) 
    144180             
    145181            # Ensuring the argument type matches what we expect. 
    146182            if not isinstance(value[1], arg_type): 
    147183                raise TypeError('Argument type should be %s, got %s instead.' % (arg_type, type(value[1]))) 
    148              
    149             return "%s(%s%s, %%s, %%s)" % (func, table_prefix, field_name) 
     184 
     185            if lookup_type in DISTANCE_FUNCTIONS: 
     186                op = DISTANCE_FUNCTIONS[lookup_type][0] 
     187                return '%s(%s%s, %%s) %s %%s' % (DISTANCE, table_prefix, field_name, op) 
     188            else: 
     189                return "%s(%s%s, %%s, %%s)" % (func, table_prefix, field_name) 
    150190        else: 
    151191            # Returning the SQL necessary for the geometry function call. For example:  
    152192            #  ST_Contains("geoapp_country"."poly", ST_GeomFromWKB(..)) 
    153193            return '%s(%s%s, %%s)' % (lookup_info, table_prefix, field_name) 
    154      
     194 
    155195    # Handling 'isnull' lookup type 
    156196    if lookup_type == 'isnull': 
     
    158198 
    159199    raise TypeError("Got invalid lookup_type: %s" % repr(lookup_type)) 
    160  
    161 # Functions that we define manually. 
    162 if MAJOR_VERSION == 1: 
    163     if MINOR_VERSION1 == 3: 
    164         # PostGIS versions 1.3.x 
    165         ASKML = 'ST_AsKML' 
    166         ASGML = 'ST_AsGML' 
    167         GEOM_FROM_TEXT = 'ST_GeomFromText' 
    168         GEOM_FROM_WKB = 'ST_GeomFromWKB' 
    169         UNION = 'ST_Union' 
    170         TRANSFORM = 'ST_Transform' 
    171     elif MINOR_VERSION1 == 2 and MINOR_VERSION2 >= 1: 
    172         # PostGIS versions 1.2.x 
    173         ASKML = 'AsKML' 
    174         ASGML = 'AsGML' 
    175         GEOM_FROM_TEXT = 'GeomFromText' 
    176         GEOM_FROM_WKB = 'GeomFromWKB' 
    177         UNION = 'GeomUnion' 
    178         TRANSFORM = 'Transform' 
    179     elif MINOR_VERSION1 == 1 and MINOR_VERSION2 >= 0: 
    180         # PostGIS versions 1.1.x 
    181         ASKML = False 
    182         ASGML = 'AsGML' 
    183         GEOM_FROM_TEXT = 'GeomFromText' 
    184         GEOM_FROM_WKB = 'GeomFromWKB' 
    185         TRANSFORM = 'Transform' 
    186         UNION = 'GeomUnion' 
    187200 
    188201# Custom selection not needed for PostGIS since GEOS geometries may be 
  • django/branches/gis/django/contrib/gis/db/backend/util.py

    r6596 r6886  
    77        self.where = where 
    88        self.params = params 
     9 
     10    def __str__(self): 
     11        return self.where[0] % tuple(self.params) 
    912 
    1013def get_srid(field, geom): 
  • django/branches/gis/django/contrib/gis/db/models/fields/__init__.py

    <
    r6508 r6886  
     1from decimal import Decimal 
    12from django.conf import settings 
     3from django.db import connection 
    24from django.contrib.gis.db.backend import GeoBackendField # these depend on the spatial database backend. 
    35from django.contrib.gis.db.models.proxy import GeometryProxy 
     6from django.contrib.gis.geos import GEOSException, GEOSGeometry 
     7from django.contrib.gis.measure import Distance 
    48from django.contrib.gis.oldforms import WKTField 
    5 from django.contrib.gis.geos import GEOSGeometry 
     9 
     10# Attempting to get the spatial reference system. 
     11try: 
     12    from django.contrib.gis.models import SpatialRefSys 
     13except NotImplementedError: 
     14    SpatialRefSys = None 
    615 
    716#TODO: Flesh out widgets; consider adding support for OGR Geometry proxies. 
     
    1221    _geom = 'GEOMETRY' 
    1322 
    14     def __init__(self, srid=4326, index=True, dim=2, **kwargs): 
    15         """The initialization function for geometry fields.  Takes the following 
     23    def __init__(self, srid=4326, spatial_index=True, dim=2, **kwargs): 
     24        """ 
     25        The initialization function for geometry fields.  Takes the following 
    1626        as keyword arguments: 
    1727 
    18           srid  - The spatial reference system identifier.  An OGC standard. 
    19                   Defaults to 4326 (WGS84) 
     28        srid: 
     29         The spatial reference system identifier, an OGC standard. 
     30         Defaults to 4326 (WGS84). 
    2031 
    21           index - Indicates whether to create a GiST index.  Defaults to True. 
    22                   Set this instead of 'db_index' for geographic fields since index 
    23                   creation is different for geometry columns. 
     32        spatial_index: 
     33         Indicates whether to create a spatial index.  Defaults to True. 
     34         Set this instead of 'db_index' for geographic fields since index 
     35         creation is different for geometry columns. 
    2436                   
    25           dim   - The number of dimensions for this geometry.  Defaults to 2. 
     37        dim: 
     38         The number of dimensions for this geometry.  Defaults to 2. 
    2639        """ 
    27         self._index = index 
     40 
     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. 
    2850        self._srid = srid 
     51        if SpatialRefSys: 
     52            # This doesn't work when we actually use: SpatialRefSys.objects.get(srid=srid) 
     53            # 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. 
     56            cur = connection.cursor() 
     57            qn = connection.ops.quote_name 
     58            stmt = 'SELECT %(table)s.%(wkt_col)s FROM %(table)s WHERE (%(table)s.%(srid_col)s = %(srid)s)' 
     59            stmt = stmt % {'table' : qn(SpatialRefSys._meta.db_table), 
     60                           'wkt_col' : qn(SpatialRefSys.wkt_col()), 
     61                           'srid_col' : qn('srid'), 
     62                           'srid' : srid, 
     63                           } 
     64            cur.execute(stmt) 
     65            row = cur.fetchone() 
     66            self._unit, self._unit_name = SpatialRefSys.get_units(row[0]) 
     67             
     68        # Setting the dimension of the geometry field. 
    2969        self._dim = dim 
    3070        super(GeometryField, self).__init__(**kwargs) # Calling the parent initializtion function 
    3171 
     72    ### Routines specific to GeometryField ### 
     73    def get_distance(self, dist): 
     74        if isinstance(dist, Distance): 
     75            return getattr(dist, Distance.unit_attname(self._unit_name)) 
     76        elif isinstance(dist, (int, float, Decimal)): 
     77            # Assuming the distance is in the units of the field. 
     78            return dist 
     79 
     80    def get_geometry(self, value): 
     81        """ 
     82        Retrieves the geometry, setting the default SRID from the given 
     83        lookup parameters. 
     84        """ 
     85        if isinstance(value, tuple):  
     86            geom = value[0] 
     87        else: 
     88            geom = value 
     89 
     90        # When the input is not a GEOS geometry, attempt to construct one 
     91        # from the given string input. 
     92        if isinstance(geom, GEOSGeometry): 
     93            pass 
     94        elif isinstance(geom, basestring): 
     95            try: 
     96                geom = GEOSGeometry(geom) 
     97            except GEOSException: 
     98                raise ValueError('Could not create geometry from lookup value: %s' % str(value)) 
     99        else: 
     100            raise TypeError('Cannot use parameter of `%s` type as lookup parameter.' % type(value)) 
     101 
     102        # Assigning the SRID value. 
     103        geom.srid = self.get_srid(geom) 
     104         
     105        return geom 
     106 
     107    def get_srid(self, geom): 
     108        """