Django

Code

Changeset 6919

Show
Ignore:
Timestamp:
12/14/07 18:30:48 (10 months ago)
Author:
jbronn
Message:

gis: Applied DRY to spatial SQL generation in anticipation of queryset-refactor; fixed gml function for PostGIS 1.3.2 parameter ordering.

Files:

Legend:

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

    r6886 r6919  
    2727 
    2828# These routines (needed by GeoManager), default to False. 
    29 ASGML, ASKML, DISTANCE, TRANSFORM, UNION= (False, False, False, False, False) 
     29ASGML, ASKML, DISTANCE, TRANSFORM, UNION, VERSION = (False, False, False, False, False, False) 
    3030 
    3131if settings.DATABASE_ENGINE == 'postgresql_psycopg2': 
     
    3535        PostGISField as GeoBackendField, POSTGIS_TERMS as GIS_TERMS, \ 
    3636        create_spatial_db, get_geo_where_clause, \ 
    37         ASGML, ASKML, DISTANCE, GEOM_SELECT, TRANSFORM, UNION 
     37        ASGML, ASKML, DISTANCE, GEOM_SELECT, TRANSFORM, UNION, \ 
     38        MAJOR_VERSION, MINOR_VERSION1, MINOR_VERSION2 
     39    VERSION = (MAJOR_VERSION, MINOR_VERSION1, MINOR_VERSION2) 
    3840    SPATIAL_BACKEND = 'postgis' 
    3941elif settings.DATABASE_ENGINE == 'oracle': 
     
    284286             
    285287            # Getting the adapted geometry from the field. 
    286             gwc = get_geo_where_clause(lookup_type, current_table + '.', column, value) 
     288            gwc = get_geo_where_clause(lookup_type, current_table, column, value) 
    287289 
    288290            # Substituting in the the where parameters into the geographic where 
  • django/branches/gis/django/contrib/gis/db/backend/mysql/query.py

    r6527 r6919  
    55from django.db import connection 
    66qn = connection.ops.quote_name 
     7 
     8# To ease implementation, WKT is passed to/from MySQL. 
     9GEOM_FROM_TEXT = 'GeomFromText' 
     10GEOM_FROM_WKB = 'GeomFromWKB' 
     11GEOM_SELECT = 'AsText(%s)' 
    712 
    813# WARNING: MySQL is NOT compliant w/the OpenGIS specification and 
     
    3237def get_geo_where_clause(lookup_type, table_prefix, field_name, value): 
    3338    "Returns the SQL WHERE clause for use in MySQL spatial SQL construction." 
    34     if table_prefix.endswith('.'): 
    35         table_prefix = qn(table_prefix[:-1])+'.' 
    36     field_name = qn(field_name) 
     39    # Getting the quoted field as `geo_col`. 
     40    geo_col = '%s.%s' % (qn(table_prefix), qn(field_name)) 
    3741 
    3842    # See if a MySQL Geometry function matches the lookup type next 
    3943    lookup_info = MYSQL_GIS_FUNCTIONS.get(lookup_type, False) 
    4044    if lookup_info: 
    41         return "%s(%s, %%s)" % (lookup_info, table_prefix + field_name
     45        return "%s(%s, %%s)" % (lookup_info, geo_col
    4246     
    4347    # Handling 'isnull' lookup type 
     
    4549    # geometries in its spatial indices. 
    4650    if lookup_type == 'isnull': 
    47         return "%s%s IS %sNULL" % (table_prefix, field_name, (not value and 'NOT ' or '')) 
     51        return "%s IS %sNULL" % (geo_col, (not value and 'NOT ' or '')) 
    4852 
    4953    raise TypeError("Got invalid lookup_type: %s" % repr(lookup_type)) 
    50  
    51 # To ease implementation, WKT is passed to/from MySQL. 
    52 GEOM_FROM_TEXT = 'GeomFromText' 
    53 GEOM_FROM_WKB = 'GeomFromWKB' 
    54 GEOM_SELECT = 'AsText(%s)' 
  • django/branches/gis/django/contrib/gis/db/backend/oracle/field.py

    r6886 r6919  
    55from django.db.models.fields import Field # Django base Field class 
    66from django.contrib.gis.geos import GEOSGeometry 
    7 from django.contrib.gis.db.backend.util import get_srid, GeoFieldSQL 
     7from django.contrib.gis.db.backend.util import GeoFieldSQL 
    88from django.contrib.gis.db.backend.oracle.adaptor import OracleSpatialAdaptor 
    99from django.contrib.gis.db.backend.oracle.query import ORACLE_SPATIAL_TERMS, DISTANCE_FUNCTIONS, TRANSFORM 
  • django/branches/gis/django/contrib/gis/db/backend/oracle/models.py

    r6886 r6919  
    1313class GeometryColumns(models.Model): 
    1414    "Maps to the Oracle USER_SDO_GEOM_METADATA table." 
    15     table_name = models.CharField(maxlength=32) 
    16     column_name = models.CharField(maxlength=1024) 
     15    table_name = models.CharField(max_length=32) 
     16    column_name = models.CharField(max_length=1024) 
    1717    srid = models.IntegerField(primary_key=True) 
    1818    # TODO: Add support for `diminfo` column (type MDSYS.SDO_DIM_ARRAY). 
     
    2929class SpatialRefSys(models.Model, SpatialRefSysMixin): 
    3030    "Maps to the Oracle MDSYS.CS_SRS table." 
    31     cs_name = models.CharField(maxlength=68) 
     31    cs_name = models.CharField(max_length=68) 
    3232    srid = models.IntegerField(primary_key=True) 
    3333    auth_srid = models.IntegerField() 
    34     auth_name = models.CharField(maxlength=256) 
    35     wktext = models.CharField(maxlength=2046) 
     34    auth_name = models.CharField(max_length=256) 
     35    wktext = models.CharField(max_length=2046) 
    3636    #cs_bounds = models.GeometryField() 
    3737 
  • django/branches/gis/django/contrib/gis/db/backend/oracle/query.py

    r6886 r6919  
    66from decimal import Decimal 
    77from django.db import connection 
     8from django.contrib.gis.db.backend.util import SpatialFunction 
    89from django.contrib.gis.measure import Distance 
    910qn = connection.ops.quote_name 
     
    1516UNION = 'SDO_AGGR_UNION' 
    1617 
    17 class SDOOperation(object): 
     18# We want to get SDO Geometries as WKT because it is much easier to  
     19# instantiate GEOS proxies from WKT than SDO_GEOMETRY(...) strings.   
     20# However, this adversely affects performance (i.e., Java is called  
     21# to convert to WKT on every query).  If someone wishes to write a  
     22# SDO_GEOMETRY(...) parser in Python, let me know =) 
     23GEOM_SELECT = 'SDO_UTIL.TO_WKTGEOMETRY(%s)' 
     24 
     25#### Classes used in constructing Oracle spatial SQL #### 
     26class SDOOperation(SpatialFunction): 
    1827    "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') 
    1930 
    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  
    39 class SDODistance(SDOOperation): 
     31class SDODistance(SpatialFunction): 
    4032    "Class for Distance queries." 
    4133    def __init__(self, op, tolerance=0.05): 
    42         super(SDODistance, self).__init__(DISTANCE, subst=", %s", operator=op, result='%%s') 
    43         self.tolerance = tolerance 
     34        super(SDODistance, self).__init__(DISTANCE, end_subst=', %s) %%s %%s' % tolerance,  
     35                                          operator=op, result='%%s') 
    4436 
    45     def params(self, table, field): 
    46         return (self.lookup, table, field, self.tolerance) 
    47  
    48 class SDOGeomRelate(SDOOperation): 
     37class SDOGeomRelate(SpatialFunction): 
    4938    "Class for using SDO_GEOM.RELATE." 
    5039    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 
     40        # SDO_GEOM.RELATE(...) has a peculiar argument order: column, mask, geom, tolerance. 
     41        # Moreover, the runction result is the mask (e.g., 'DISJOINT' instead of 'TRUE'). 
     42        end_subst = "%s%s) %s '%s'" % (', %%s, ', tolerance, '=', mask) 
     43        beg_subst = "%%s(%%s, '%s'" % mask  
     44        super(SDOGeomRelate, self).__init__('SDO_GEOM.RELATE', beg_subst=beg_subst, end_subst=end_subst) 
    5545 
    56     def params(self, table, field): 
    57         return (self.lookup, table, field, self.mask, self.tolerance) 
    58  
    59 class SDORelate(SDOOperation): 
     46class SDORelate(SpatialFunction): 
    6047    "Class for using SDO_RELATE." 
    6148    masks = 'TOUCH|OVERLAPBDYDISJOINT|OVERLAPBDYINTERSECT|EQUAL|INSIDE|COVEREDBY|CONTAINS|COVERS|ANYINTERACT|ON' 
    6249    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) 
     50    def __init__(self, mask): 
    6651        if not self.mask_regex.match(mask): 
    6752            raise ValueError('Invalid %s mask: "%s"' % (self.lookup, mask)) 
    68         self.mask = mask 
     53        super(SDORelate, self).__init__('SDO_RELATE', end_subst=", 'mask=%s') = 'TRUE'" % mask) 
    6954 
    70     def params(self, table, field): 
    71         return (self.lookup, table, field, self.mask) 
     55#### Lookup type mapping dictionaries of Oracle spatial operations #### 
    7256 
    7357# Valid distance types and substitutions 
     
    8569    'covers' : SDOOperation('SDO_COVERS'), 
    8670    'disjoint' : SDOGeomRelate('DISJOINT'), 
    87     'dwithin' : (SDOOperation('SDO_WITHIN_DISTANCE', "%%s, 'distance=%%s'"), dtypes), 
     71    'dwithin' : (SDOOperation('SDO_WITHIN_DISTANCE', end_subst=", %%s, 'distance=%%s') %s '%s'"), dtypes), 
    8872    'intersects' : SDOOperation('SDO_OVERLAPBDYINTERSECT'), # TODO: Is this really the same as ST_Intersects()? 
    8973    'equals' : SDOOperation('SDO_EQUAL'), 
     
    10589ORACLE_SPATIAL_TERMS = tuple(ORACLE_SPATIAL_TERMS) # Making immutable 
    10690 
     91#### The `get_geo_where_clause` function for Oracle #### 
    10792def get_geo_where_clause(lookup_type, table_prefix, field_name, value): 
    10893    "Returns the SQL WHERE clause for use in Oracle spatial SQL construction." 
    109     if table_prefix.endswith('.'): 
    110         table_prefix = qn(table_prefix[:-1])+'.' 
    111     field_name = qn(field_name) 
     94    # Getting the quoted table name as `geo_col`. 
     95    geo_col = '%s.%s' % (qn(table_prefix), qn(field_name)) 
    11296 
    11397    # See if a Oracle Geometry function matches the lookup type next 
     
    11599    if lookup_info: 
    116100        # Lookup types that are tuples take tuple arguments, e.g., 'relate' and  
    117         # 'dwithin' lookup types. 
     101        # 'dwithin' lookup types. 
    118102        if isinstance(lookup_info, tuple): 
    119103            # First element of tuple is lookup type, second element is the type 
    120             # of the expected argument (e.g., str, float) 
     104            # of the expected argument (e.g., str, float) 
    121105            sdo_op, arg_type = lookup_info 
    122106 
     
    132116 
    133117            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) 
     118                # The SDORelate class handles construction for these queries,  
     119                # and verifies the mask argument. 
     120                return sdo_op(value[1]).as_sql(geo_col) 
    141121            else: 
    142                 return sdo_op.as_sql(table_prefix, field_name) 
     122                # Otherwise, just call the `as_sql` method on the SDOOperation instance. 
     123                return sdo_op.as_sql(geo_col) 
    143124        else: 
    144125            # Lookup info is a SDOOperation instance, whos `as_sql` method returns 
    145126            # the SQL necessary for the geometry function call. For example:   
    146127            #  SDO_CONTAINS("geoapp_country"."poly", SDO_GEOMTRY('POINT(5 23)', 4326)) = 'TRUE' 
    147             return lookup_info.as_sql(table_prefix, field_name) 
    148      
    149     # Handling 'isnull' lookup type 
    150     if lookup_type == 'isnull': 
    151         return "%s%s IS %sNULL" % (table_prefix, field_name, (not value and 'NOT ' or '')) 
     128            return lookup_info.as_sql(geo_col) 
     129    elif lookup_type == 'isnull': 
     130        # Handling 'isnull' lookup type 
     131        return "%s IS %sNULL" % (geo_col, (not value and 'NOT ' or '')) 
    152132 
    153133    raise TypeError("Got invalid lookup_type: %s" % repr(lookup_type)) 
    154  
    155 # Want to get SDO Geometries as WKT (much easier to instantiate GEOS proxies 
    156 # from WKT than SDO_GEOMETRY(...) strings ;) 
    157 GEOM_SELECT = 'SDO_UTIL.TO_WKTGEOMETRY(%s)' 
  • django/branches/gis/django/contrib/gis/db/backend/postgis/creation.py

    r6439 r6919  
    4646        # Trying to create the database first. 
    4747        cursor.execute(create_sql) 
     48        #print create_sql 
    4849    except Exception, e: 
    4950        # Drop and recreate, if necessary. 
     
    5758        else: 
    5859            raise Exception('Spatial Database Creation canceled.') 
     60foo = _create_with_cursor 
    5961     
    6062created_regex = re.compile(r'^createdb: database creation failed: ERROR:  database ".+" already exists') 
  • django/branches/gis/django/contrib/gis/db/backend/postgis/field.py

    r6886 r6919  
    22from django.db import connection 
    33from django.db.models.fields import Field # Django base Field class 
    4 from django.contrib.gis.geos import GEOSGeometry, GEOSException  
    5 from django.contrib.gis.db.backend.util import get_srid, GeoFieldSQL 
     4from django.contrib.gis.geos import GEOSGeometry 
     5from django.contrib.gis.db.backend.util import GeoFieldSQL 
    66from django.contrib.gis.db.backend.postgis.adaptor import PostGISAdaptor 
    77from django.contrib.gis.db.backend.postgis.query import \ 
    8     DISTANCE, DISTANCE_FUNCTIONS, POSTGIS_TERMS, TRANSFORM 
     8    DISTANCE_FUNCTIONS, POSTGIS_TERMS, TRANSFORM 
    99 
    1010# Quotename & geographic quotename, respectively 
  • django/branches/gis/django/contrib/gis/db/backend/postgis/models.py

    r6886 r6919  
    1515    documentation at Ch. 4.2.2. 
    1616    """ 
    17     f_table_catalog = models.CharField(maxlength=256) 
    18     f_table_schema = models.CharField(maxlength=256) 
    19     f_table_name = models.CharField(maxlength=256) 
    20     f_geometry_column = models.CharField(maxlength=256) 
     17    f_table_catalog = models.CharField(max_length=256) 
     18    f_table_schema = models.CharField(max_length=256) 
     19    f_table_name = models.CharField(max_length=256) 
     20    f_geometry_column = models.CharField(max_length=256) 
    2121    coord_dimension = models.IntegerField() 
    2222    srid = models.IntegerField(primary_key=True) 
    23     type = models.CharField(maxlength=30) 
     23    type = models.CharField(max_length=30) 
    2424 
    2525    class Meta: 
     
    4242    """ 
    4343    srid = models.IntegerField(primary_key=True) 
    44     auth_name = models.CharField(maxlength=256) 
     44    auth_name = models.CharField(max_length=256) 
    4545    auth_srid = models.IntegerField() 
    46     srtext = models.CharField(maxlength=2048) 
    47     proj4text = models.CharField(maxlength=2048) 
     46    srtext = models.CharField(max_length=2048) 
     47    proj4text = models.CharField(max_length=2048) 
    4848 
    4949    class Meta: 
  • django/branches/gis/django/contrib/gis/db/backend/postgis/query.py

    r6886 r6919  
    33 routine for PostGIS. 
    44""" 
     5import re 
    56from decimal import Decimal 
    67from django.db import connection 
    78from django.contrib.gis.measure import Distance 
    89from django.contrib.gis.db.backend.postgis.management import postgis_version_tuple 
     10from django.contrib.gis.db.backend.util import SpatialOperation, SpatialFunction 
    911qn = connection.ops.quote_name 
    1012 
     
    1820    raise Exception('PostGIS version %s not supported.' % POSTGIS_VERSION) 
    1921 
    20 # PostGIS-specific operators. The commented descriptions of these 
    21 # operators come from Section 6.2.2 of the official PostGIS documentation. 
    22 POSTGIS_OPERATORS = { 
    23     # The "&<" operator returns true if A's bounding box overlaps or 
    24     #  is to the left of B's bounding box. 
    25     'overlaps_left' : '&<', 
    26     # The "&>" operator returns true if A's bounding box overlaps or 
    27     #  is to the right of B's bounding box. 
    28     'overlaps_right' : '&>', 
    29     # The "<<" operator returns true if A's bounding box is strictly 
    30     #  to the left of B's bounding box. 
    31     'left' : '<<', 
    32     # The ">>" operator returns true if A's bounding box is strictly 
    33     #  to the right of B's bounding box. 
    34     'right' : '>>', 
    35     # The "&<|" operator returns true if A's bounding box overlaps or 
    36     #  is below B's bounding box. 
    37     'overlaps_below' : '&<|', 
    38     # The "|&>" operator returns true if A's bounding box overlaps or 
    39     #  is above B's bounding box. 
    40     'overlaps_above' : '|&>', 
    41     # The "<<|" operator returns true if A's bounding box is strictly 
    42     #  below B's bounding box. 
    43     'strictly_below' : '<<|', 
    44     # The "|>>" operator returns true if A's bounding box is strictly 
    45     # above B's bounding box. 
    46     'strictly_above' : '|>>', 
    47     # The "~=" operator is the "same as" operator. It tests actual 
    48     #  geometric equality of two features. So if A and B are the same feature, 
    49     #  vertex-by-vertex, the operator returns true. 
    50     'same_as' : '~=', 
    51     'exact' : '~=', 
    52     # The "@" operator returns true if A's bounding box is completely contained 
    53     #  by B's bounding box. 
    54     'contained' : '@', 
    55     # The "~" operator returns true if A's bounding box completely contains 
    56     #  by B's bounding box. 
    57     'bbcontains' : '~', 
    58     # The "&&" operator returns true if A's bounding box overlaps 
    59     #  B's bounding box. 
    60     'bboverlaps' : '&&', 
    61     } 
    62  
    6322# Versions of PostGIS >= 1.2.2 changed their naming convention to be 
    64 #  'SQL-MM-centric' to conform with the ISO standard. Practically, this  
     23#  'SQL-MM-centric' to conform with the ISO standard. Practically, this 
    6524#  means that 'ST_' is prefixes geometry function names. 
    6625GEOM_FUNC_PREFIX = '' 
    6726if MAJOR_VERSION >= 1: 
    68     if (MINOR_VERSION1 > 2 or  
     27    if (MINOR_VERSION1 > 2 or 
    6928        (MINOR_VERSION1 == 2 and MINOR_VERSION2 >= 2)): 
    7029        GEOM_FUNC_PREFIX = 'ST_' 
    71      
     30 
    7231    def get_func(func): return '%s%s' % (GEOM_FUNC_PREFIX, func) 
     32 
     33    # Custom selection not needed for PostGIS since GEOS geometries may be 
     34    # instantiated directly from the HEXEWKB returned by default.  If 
     35    # WKT is needed for some reason in the future, this value may be changed, 
     36    # 'AsText(%s)' 
     37    GEOM_SELECT = None 
    7338 
    7439    # Functions used by the GeoManager & GeoQuerySet 
     
    9156    raise NotImplementedError('PostGIS versions < 1.0 are not supported.') 
    9257 
     58#### Classes used in constructing PostGIS spatial SQL #### 
     59class PostGISOperator(SpatialOperation): 
     60    "For PostGIS operators (e.g. `&&`, `~`)." 
     61    def __init__(self, operator): 
     62        super(PostGISOperator, self).__init__(operator=operator, beg_subst='%s %s %%s') 
     63 
     64class PostGISFunction(SpatialFunction): 
     65    "For PostGIS function calls (e.g., `ST_Contains(table, geom)`)." 
     66    def __init__(self, function, **kwargs): 
     67        super(PostGISFunction, self).__init__(get_func(function), **kwargs) 
     68 
     69class PostGISFunctionParam(PostGISFunction): 
     70    "For PostGIS functions that take another parameter (e.g. DWithin, Relate)." 
     71    def __init__(self, func): 
     72        super(PostGISFunctionParam, self).__init__(func, end_subst=', %%s)') 
     73 
     74class PostGISDistance(PostGISFunction): 
     75    "For PostGIS distance operations." 
     76    def __init__(self, operator): 
     77        super(PostGISDistance, self).__init__('Distance', end_subst=') %s %s', operator=operator, result='%%s') 
     78 
     79class PostGISRelate(PostGISFunctionParam): 
     80    "For PostGIS Relate(<geom>, <pattern>) calls." 
     81    pattern_regex = re.compile(r'^[012TF\*]{9}$') 
     82    def __init__(self, pattern): 
     83        if not self.pattern_regex.match(pattern): 
     84            raise ValueError('Invalid intersection matrix pattern "%s".' % pattern) 
     85        super(PostGISRelate, self).__init__('Relate') 
     86 
     87#### Lookup type mapping dictionaries of PostGIS operations. #### 
     88 
     89# PostGIS-specific operators. The commented descriptions of these 
     90# operators come from Section 6.2.2 of the official PostGIS documentation. 
     91POSTGIS_OPERATORS = { 
     92    # The "&<" operator returns true if A's bounding box overlaps or 
     93    #  is to the left of B's bounding box. 
     94    'overlaps_left' : PostGISOperator('&<'), 
     95    # The "&>" operator returns true if A's bounding box overlaps or 
     96    #  is to the right of B's bounding box. 
     97    'overlaps_right' : PostGISOperator('&>'), 
     98    # The "<<" operator returns true if A's bounding box is strictly 
     99    #  to the left of B's bounding box. 
     100    'left' : PostGISOperator('<<'), 
     101    # The ">>" operator returns true if A's bounding box is strictly 
     102    #  to the right of B's bounding box. 
     103    'right' : PostGISOperator('>>'), 
     104    # The "&<|" operator returns true if A's bounding box overlaps or 
     105    #  is below B's bounding box. 
     106    'overlaps_below' : PostGISOperator('&<|'), 
     107    # The "|&>" operator returns true if A's bounding box overlaps or 
     108    #  is above B's bounding box. 
     109    'overlaps_above' : PostGISOperator('|&>'), 
     110    # The "<<|" operator returns true if A's bounding box is strictly 
     111    #  below B's bounding box. 
     112    'strictly_below' : PostGISOperator('<<|'), 
     113    # The "|>>" operator returns true if A's bounding box is strictly 
     114    # above B's bounding box. 
     115    'strictly_above' : PostGISOperator('|>>'), 
     116    # The "~=" operator is the "same as" operator. It tests actual 
     117    #  geometric equality of two features. So if A and B are the same feature, 
     118    #  vertex-by-vertex, the operator returns true. 
     119    'same_as' : PostGISOperator('~='), 
     120    'exact' : PostGISOperator('~='), 
     121    # The "@" operator returns true if A's bounding box is completely contained 
     122    #  by B's bounding box. 
     123    'contained' : PostGISOperator('@'), 
     124    # The "~" operator returns true if A's bounding box completely contains 
     125    #  by B's bounding box. 
     126    'bbcontains' : PostGISOperator('~'), 
     127    # The "&&" operator returns true if A's bounding box overlaps 
     128    #  B's bounding box. 
     129    'bboverlaps' : PostGISOperator('&&'), 
     130    } 
     131 
    93132# For PostGIS >= 1.2.2 the following lookup types will do a bounding box query 
    94133# first before calling the more computationally expensive GEOS routines (called 
     
    97136# 'covers'. 
    98137POSTGIS_GEOMETRY_FUNCTIONS = { 
    99     'equals' : 'Equals'
    100     'disjoint' : 'Disjoint'
    101     'touches' : 'Touches'
    102     'crosses' : 'Crosses'
    103     'within' : 'Within'
    104     'overlaps' : 'Overlaps'
    105     'contains' : 'Contains'
    106     'intersects' : 'Intersects'
    107     'relate' : ('Relate', basestring), 
     138    'equals' : PostGISFunction('Equals')
     139    'disjoint' : PostGISFunction('Disjoint')
     140    'touches' : PostGISFunction('Touches')
     141    'crosses' : PostGISFunction('Crosses')
     142    'within' : PostGISFunction('Within')
     143    'overlaps' : PostGISFunction('Overlaps')
     144    'contains' : PostGISFunction('Contains')
     145    'intersects' : PostGISFunction('Intersects')
     146    'relate' : (PostGISRelate, basestring), 
    108147    } 
    109148 
     
    111150dtypes = (Decimal, Distance, float, int) 
    112151DISTANCE_FUNCTIONS = { 
    113     'distance_gt' : ('>', dtypes), 
    114     'distance_gte' : ('>=', dtypes), 
    115     'distance_lt' : ('<', dtypes), 
    116     'distance_lte' : ('<=', dtypes), 
     152    'distance_gt' : (PostGISDistance('>'), dtypes), 
     153    'distance_gte' : (PostGISDistance('>='), dtypes), 
     154    'distance_lt' : (PostGISDistance('<'), dtypes), 
     155    'distance_lte' : (PostGISDistance('<='), dtypes), 
    117156    } 
    118157 
    119158if GEOM_FUNC_PREFIX == 'ST_': 
    120     # Adding the GEOM_FUNC_PREFIX to the lookup functions. 
    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]) 
    124         else: 
    125             POSTGIS_GEOMETRY_FUNCTIONS[lookup] = get_func(f) 
    126  
    127159    # The ST_DWithin, ST_CoveredBy, and ST_Covers routines become available in 1.2.2+ 
    128160    POSTGIS_GEOMETRY_FUNCTIONS.update( 
    129         {'dwithin' : ('ST_DWithin', dtypes), 
    130          'coveredby' : 'ST_CoveredBy'
    131          'covers' : 'ST_Covers'
    132          } 
    133         ) 
    134  
     161        {'dwithin' : (PostGISFunctionParam('DWithin'), dtypes), 
     162         'coveredby' : PostGISFunction('CoveredBy')
     163         'covers' : PostGISFunction('Covers')
     164         }) 
     165 
     166# Distance functions are a part of PostGIS geometry functions. 
    135167POSTGIS_GEOMETRY_FUNCTIONS.update(DISTANCE_FUNCTIONS) 
    136168 
     
    145177POSTGIS_TERMS = tuple(POSTGIS_TERMS) # Making immutable 
    146178 
    147 ### PostGIS-specific Methods ### 
    148 def get_geom_func(lookup_type): 
    149     func_info = POSTGIS_GEOMETRY_FUNCTIONS[lookup_type] 
    150     if isinstance(func_info, tuple): 
    151         return func_info[0] 
    152     else: 
    153         return func_info 
    154  
     179#### The `get_geo_where_clause` function for PostGIS. #### 
    155180def get_geo_where_clause(lookup_type, table_prefix, field_name, value): 
    156181    "Returns the SQL WHERE clause for use in PostGIS SQL construction." 
    157     if table_prefix.endswith('.'): 
    158         table_prefix = qn(table_prefix[:-1])+'.' 
    159     field_name = qn(field_name) 
    160  
    161     # See if a PostGIS operator matches the lookup type first 
     182    # Getting the quoted field as `geo_col`. 
     183    geo_col = '%s.%s' % (qn(table_prefix), qn(field_name)) 
    162184    if lookup_type in POSTGIS_OPERATORS: 
    163         return '%s%s %s %%s' % (table_prefix, field_name, POSTGIS_OPERATORS[lookup_type]) 
    164  
    165     # See if a PostGIS Geometry function matches the lookup type next 
    166     if lookup_type in POSTGIS_GEOMETRY_FUNCTIONS: 
    167         lookup_info = POSTGIS_GEOMETRY_FUNCTIONS[lookup_type] 
     185        # See if a PostGIS operator matches the lookup type. 
     186        return POSTGIS_OPERATORS[lookup_type].as_sql(geo_col) 
     187    elif lookup_type in POSTGIS_GEOMETRY_FUNCTIONS: 
     188        # See if a PostGIS geometry function matches the lookup type. 
     189        tmp = POSTGIS_GEOMETRY_FUNCTIONS[lookup_type] 
     190 
    168191        # Lookup types that are tuples take tuple arguments, e.g., 'relate' and  
    169         #  'dwithin' lookup types. 
    170         if isinstance(lookup_info, tuple): 
    171             # First element of tuple is lookup type, second element is the type 
    172             #  of the expected argument (e.g., str, float) 
    173             func, arg_type = lookup_info 
     192        # distance lookups. 
     193        if isinstance(tmp, tuple): 
     194            # First element of tuple is the PostGISOperation instance, and the 
     195            # second element is either the type or a tuple of acceptable types 
     196            # that may passed in as further parameters for the lookup type. 
     197            op, arg_type = tmp 
    174198 
    175199            # Ensuring that a tuple _value_ was passed in from the user 
     
    183207                raise TypeError('Argument type should be %s, got %s instead.' % (arg_type, type(value[1]))) 
    184208 
    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) 
     209            # For lookup type `relate`, the op instance is not yet created (has 
     210            # to be instantiated here to check the pattern parameter). 
     211            if lookup_type == 'relate': op = op(value[1]) 
    190212        else: 
    191             # Returning the SQL necessary for the geometry function call. For example:  
    192             #  ST_Contains("geoapp_country"."poly", ST_GeomFromWKB(..)) 
    193             return '%s(%s%s, %%s)' % (lookup_info, table_prefix, field_name) 
    194  
    195     # Handling 'isnull' lookup type 
    196     if lookup_type == 'isnull': 
    197         return "%s%s IS %sNULL" % (table_prefix, field_name, (not value and 'NOT ' or '')) 
     213            op = tmp 
     214        # Calling the `as_sql` function on the operation instance. 
     215        return op.as_sql(geo_col) 
     216    elif lookup_type == 'isnull': 
     217        # Handling 'isnull' lookup type 
     218        return "%s IS %sNULL" % (geo_col, (not value and 'NOT ' or '')) 
    198219 
    199220    raise TypeError("Got invalid lookup_type: %s" % repr(lookup_type)) 
    200  
    201 # Custom selection not needed for PostGIS since GEOS geometries may be 
    202 # instantiated directly from the HEXEWKB returned by default.  If 
    203 # WKT is needed for some reason in the future, this value may be changed, 
    204 # 'AsText(%s)' 
    205 GEOM_SELECT = None 
  • django/branches/gis/django/contrib/gis/db/backend/util.py

    r6886 r6919  
    99 
    1010    def __str__(self): 
    11         return self.where[0] % tuple(self.params
     11        return self.as_sql(
    1212 
    13 def get_srid(field, geom): 
     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) 
     21 
     22class SpatialOperation(object): 
    1423    """ 
    15     Gets the SRID depending on the value of the SRID setting of the field 
    16     and that of the given geometry. 
     24    Base class for generating spatial SQL. 
    1725    """ 
    18     if geom.srid is None or (geom.srid == -1 and field._srid != -1): 
    19         return field._srid 
    20     else: 
    21         return geom.srid 
     26    def __init__(self, function='', operator='', result='', beg_subst='', end_subst=''): 
     27        self.function = function 
     28        self.operator = operator 
     29        self.result = result 
     30        self.beg_subst = beg_subst 
     31        try: 
     32            # Try and put the operator and result into to the 
     33            # end substitution. 
     34            self.end_subst = end_subst % (operator, result) 
     35        except TypeError: 
     36            self.end_subst = end_subst 
     37 
     38    @property 
     39    def sql_subst(self): 
     40        return ''.join([self.beg_subst, self.end_subst]) 
     41 
     42    def as_sql(self, geo_col): 
     43        return self.sql_subst % self.params(geo_col) 
     44 
     45    def params(self, geo_col): 
     46        return (geo_col, self.operator) 
     47 
     48class SpatialFunction(SpatialOperation): 
     49    """ 
     50    Base class for generating spatial SQL related to a function. 
     51    """ 
     52    def __init__(self, func, beg_subst='%s(%s, %%s', end_subst=')', result='', operator=''): 
     53        # Getting the function prefix. 
     54        kwargs = {'function' : func, 'operator' : operator, 'result' : result, 
     55                  'beg_subst' : beg_subst, 'end_subst' : end_subst,} 
     56        super(SpatialFunction, self).__init__(**kwargs) 
     57 
     58    def params(self, geo_col): 
     59        return (self.function, geo_col) 
  • django/branches/gis/django/contrib/gis/db/models/query.py

    r6886 r6919  
    88# parse_lookup depends on the spatial database backend. 
    99from django.contrib.gis.db.backend import parse_lookup, \ 
    10     ASGML, ASKML, DISTANCE, GEOM_SELECT, SPATIAL_BACKEND, TRANSFORM, UNION 
     10    ASGML, ASKML, DISTANCE, GEOM_SELECT, SPATIAL_BACKEND, TRANSFORM, UNION, VERSION 
    1111from django.contrib.gis.geos import GEOSGeometry 
    1212 
    13 # Flag indicating whether the backend is Oracle
     13# Shortcut booleans for determining the backend
    1414oracle = SPATIAL_BACKEND == 'oracle' 
     15postgis = SPATIAL_BACKEND == 'postgis' 
    1516 
    1617class GeoQ(Q): 
     
    326327        if oracle: 
    327328            gml_select = {'gml':'%s(%s)' % (ASGML, field_col)} 
    328         else: 
    329             gml_select = {'gml':'%s(%s,%s,%s)' % (ASGML, field_col, precision, version)} 
     329        elif postgis: 
     330            # PostGIS AsGML() aggregate function parameter order depends on the 
     331            # version -- uggh. 
     332            major, minor1, minor2 = VERSION 
     333            if major >= 1 and (minor1 > 3 or (minor1 == 3 and minor2 > 1)): 
     334                gml_select = {'gml':'%s(%s,%s,%s)' % (ASGML, version, field_col, precision)} 
     335            else: 
     336                gml_select = {'gml':'%s(%s,%s,%s)' % (ASGML, field_col, precision, version)} 
    330337 
    331338        # Adding GML function call to SELECT part of the SQL.