Changeset 5881
- Timestamp:
- 08/12/07 20:33:26 (1 year ago)
- Files:
-
- django/branches/gis/django/contrib/gis/db/backend/__init__.py (modified) (2 diffs)
- django/branches/gis/django/contrib/gis/db/backend/postgis/field.py (modified) (4 diffs)
- django/branches/gis/django/contrib/gis/db/backend/postgis/__init__.py (modified) (1 diff)
- django/branches/gis/django/contrib/gis/db/backend/postgis/management.py (modified) (3 diffs)
- django/branches/gis/django/contrib/gis/db/backend/postgis/query.py (modified) (5 diffs)
- django/branches/gis/django/contrib/gis/db/models/manager.py (modified) (1 diff)
- django/branches/gis/django/contrib/gis/db/models/query.py (modified) (5 diffs)
- django/branches/gis/django/contrib/gis/tests/geoapp/tests.py (modified) (2 diffs)
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
django/branches/gis/django/contrib/gis/db/backend/__init__.py
r5776 r5881 19 19 if settings.DATABASE_ENGINE == 'postgresql_psycopg2': 20 20 # PostGIS is the spatial database, getting the rquired modules, renaming as necessary. 21 from postgis import \21 from django.contrib.gis.db.backend.postgis import \ 22 22 PostGISField as GeoBackendField, \ 23 23 POSTGIS_TERMS as GIS_TERMS, \ 24 create_spatial_db, ge t_geo_where_clause24 create_spatial_db, geo_quotename, get_geo_where_clause 25 25 else: 26 26 raise NotImplementedError, 'No Geographic Backend exists for %s' % settings.DATABASE_NAME … … 250 250 # with the get_geo_where_clause() 251 251 if hasattr(field, '_geom'): 252 # Do we have multiple arguments, e.g., ST_Relate, ST_DWithin lookup types 253 # need more than argument. 254 multiple_args = isinstance(value, tuple) 255 252 256 # Getting the geographic where clause. 253 257 gwc = get_geo_where_clause(lookup_type, current_table + '.', column, value) 254 258 255 259 # Getting the geographic parameters from the field. 256 geo_params = field.get_db_prep_lookup(lookup_type, value) 260 if multiple_args: 261 geo_params = field.get_db_prep_lookup(lookup_type, value[0]) 262 else: 263 geo_params = field.get_db_prep_lookup(lookup_type, value) 257 264 258 265 # If a dictionary was passed back from the field modify the where clause. 259 if isinstance(geo_params, dict): 260 gwc = gwc % geo_params['where'] 261 geo_params = geo_params['params'] 266 param_dict = isinstance(geo_params, dict) 267 if param_dict: 268 subst_list = geo_params['where'] 269 if multiple_args: subst_list += map(geo_quotename, value[1:]) 270 geo_params = geo_params['params'] 271 gwc = gwc % tuple(subst_list) 272 elif multiple_args: 273 # Modify the where clause if we have multiple arguments -- the 274 # first substitution will be for another placeholder (for the 275 # geometry) since it is already apart of geo_params. 276 subst_list = ['%s'] 277 subst_list += map(geo_quotename, value[1:]) 278 gwc = gwc % tuple(subst_list) 279 280 # Finally, appending onto the WHERE clause, and extending with any 281 # additional parameters. 262 282 where.append(gwc) 263 283 params.extend(geo_params) django/branches/gis/django/contrib/gis/db/backend/postgis/field.py
r5776 r5881 1 1 from django.db.models.fields import Field # Django base Field class 2 2 from django.contrib.gis.geos import GEOSGeometry, GEOSException 3 from django.contrib.gis.db.backend.postgis.query import POSTGIS_TERMS, geo_quotename as quotename 3 4 from types import StringType 4 from query import POSTGIS_TERMS, quotename5 5 6 6 class PostGISField(Field): … … 67 67 68 68 def get_db_prep_lookup(self, lookup_type, value): 69 "Returns field's value prepared for database lookup, accepts WKT and GEOS Geometries for the value." 69 """Returns field's value prepared for database lookup, accepts WKT and 70 GEOS Geometries for the value.""" 70 71 if lookup_type in POSTGIS_TERMS: 71 72 if lookup_type == 'isnull': return [value] # special case for NULL geometries. … … 74 75 # GEOSGeometry instance passed in. 75 76 if value.srid != self._srid: 76 # Returning a dictionary instructs the parse_lookup() to add what's in the 'where' key 77 # to the where parameters, since we need to transform the geometry in the query. 78 return {'where' : "Transform(%s,%s)", 77 # Returning a dictionary instructs the parse_lookup() to add 78 # what's in the 'where' key to the where parameters, since we 79 # need to transform the geometry in the query. 80 return {'where' : ["ST_Transform(%s,%s)"], 79 81 'params' : [value, self._srid] 80 82 } … … 103 105 if isinstance(value, GEOSGeometry) and value.srid != self._srid: 104 106 # Adding Transform() to the SQL placeholder. 105 return ' Transform(%%s, %s)' % self._srid107 return 'ST_Transform(%%s, %s)' % self._srid 106 108 else: 107 109 return '%s' django/branches/gis/django/contrib/gis/db/backend/postgis/__init__.py
r5806 r5881 2 2 The PostGIS spatial database backend module. 3 3 """ 4 from query import \ 5 get_geo_where_clause, GEOM_FUNC_PREFIX, POSTGIS_TERMS, \ 4 from django.contrib.gis.db.backend.postgis.query import \ 5 get_geo_where_clause, geo_quotename, \ 6 GEOM_FUNC_PREFIX, POSTGIS_TERMS, \ 6 7 MAJOR_VERSION, MINOR_VERSION1, MINOR_VERSION2 7 from creation import create_spatial_db8 from field import PostGISField8 from django.contrib.gis.db.backend.postgis.creation import create_spatial_db 9 from django.contrib.gis.db.backend.postgis.field import PostGISField 9 10 10 11 # Whether PostGIS has AsKML() support. django/branches/gis/django/contrib/gis/db/backend/postgis/management.py
r5776 r5881 4 4 See PostGIS docs at Ch. 6.2.1 for more information on these functions. 5 5 """ 6 import re 6 7 7 8 def _get_postgis_func(func): … … 14 15 return row[0] 15 16 17 ### PostGIS management functions ### 16 18 def postgis_geos_version(): 17 19 "Returns the version of the GEOS library used with PostGIS." … … 34 36 return _get_postgis_func('postgis_full_version') 35 37 38 ### Routines for parsing output of management functions. ### 39 version_regex = re.compile('^(?P<major>\d)\.(?P<minor1>\d)\.(?P<minor2>\d+)') 40 def postgis_version_tuple(): 41 "Returns the PostGIS version as a tuple." 36 42 43 # Getting the PostGIS version 44 version = postgis_lib_version() 45 m = version_regex.match(version) 46 if m: 47 major = int(m.group('major')) 48 minor1 = int(m.group('minor1')) 49 minor2 = int(m.group('minor2')) 50 else: 51 raise Exception, 'Could not parse PostGIS version string: %s' % version 52 53 return (version, major, minor1, minor2) 54 55 56 django/branches/gis/django/contrib/gis/db/backend/postgis/query.py
r5776 r5881 4 4 """ 5 5 from django.db import backend 6 from management import postgis_lib_version 6 from django.contrib.gis.db.backend.postgis.management import postgis_version_tuple 7 from types import StringType, UnicodeType 7 8 8 # Getting the PostGIS version 9 POSTGIS_VERSION = postgis_lib_version() 10 MAJOR_VERSION, MINOR_VERSION1, MINOR_VERSION2 = map(int, POSTGIS_VERSION.split('.')) 9 # Getting the PostGIS version information 10 POSTGIS_VERSION, MAJOR_VERSION, MINOR_VERSION1, MINOR_VERSION2 = postgis_version_tuple() 11 11 12 12 # The supported PostGIS versions. 13 # TODO: Confirm tests with PostGIS versions 1.1.x -- should work. Versions <= 1.0.x didn't use GEOS C API. 14 if MAJOR_VERSION != 1 or MINOR_VERSION1 <= 1: 13 # TODO: Confirm tests with PostGIS versions 1.1.x -- should work. 14 # Versions <= 1.0.x do not use GEOS C API, and will not be supported. 15 if MAJOR_VERSION != 1 or (MAJOR_VERSION == 1 and MINOR_VERSION1 < 1): 15 16 raise Exception, 'PostGIS version %s not supported.' % POSTGIS_VERSION 16 17 … … 18 19 # operators come from Section 6.2.2 of the official PostGIS documentation. 19 20 POSTGIS_OPERATORS = { 20 # The "&<" operator returns true if A's bounding box overlaps or is to the left of B's bounding box. 21 # The "&<" operator returns true if A's bounding box overlaps or 22 # is to the left of B's bounding box. 21 23 'overlaps_left' : '&<', 22 # The "&>" operator returns true if A's bounding box overlaps or is to the right of B's bounding box. 24 # The "&>" operator returns true if A's bounding box overlaps or 25 # is to the right of B's bounding box. 23 26 'overlaps_right' : '&>', 24 # The "<<" operator returns true if A's bounding box is strictly to the left of B's bounding box. 27 # The "<<" operator returns true if A's bounding box is strictly 28 # to the left of B's bounding box. 25 29 'left' : '<<', 26 # The ">>" operator returns true if A's bounding box is strictly to the right of B's bounding box. 30 # The ">>" operator returns true if A's bounding box is strictly 31 # to the right of B's bounding box. 27 32 'right' : '>>', 28 # The "&<|" operator returns true if A's bounding box overlaps or is below B's bounding box. 33 # The "&<|" operator returns true if A's bounding box overlaps or 34 # is below B's bounding box. 29 35 'overlaps_below' : '&<|', 30 # The "|&>" operator returns true if A's bounding box overlaps or is above B's bounding box. 36 # The "|&>" operator returns true if A's bounding box overlaps or 37 # is above B's bounding box. 31 38 'overlaps_above' : '|&>', 32 # The "<<|" operator returns true if A's bounding box is strictly below B's bounding box. 39 # The "<<|" operator returns true if A's bounding box is strictly 40 # below B's bounding box. 33 41 'strictly_below' : '<<|', 34 # The "|>>" operator returns true if A's bounding box is strictly above B's bounding box. 42 # The "|>>" operator returns true if A's bounding box is strictly 43 # above B's bounding box. 35 44 'strictly_above' : '|>>', 36 # The "~=" operator is the "same as" operator. It tests actual geometric equality of two features. So if 37 # A and B are the same feature, vertex-by-vertex, the operator returns true. 45 # The "~=" operator is the "same as" operator. It tests actual 46 # geometric equality of two features. So if A and B are the same feature, 47 # vertex-by-vertex, the operator returns true. 38 48 'same_as' : '~=', 39 49 'exact' : '~=', 40 # The "@" operator returns true if A's bounding box is completely contained by B's bounding box. 50 # The "@" operator returns true if A's bounding box is completely contained 51 # by B's bounding box. 41 52 'contained' : '@', 42 # The "~" operator returns true if A's bounding box completely contains B's bounding box. 53 # The "~" operator returns true if A's bounding box completely contains 54 # by B's bounding box. 43 55 'bbcontains' : '~', 44 # The "&&" operator is the "overlaps" operator. If A's bounding boux overlaps B's bounding box the45 # operator returns true.56 # The "&&" operator returns true if A's bounding box overlaps 57 # B's bounding box. 46 58 'bboverlaps' : '&&', 47 59 } 48 60 49 # PostGIS Geometry Relationship Functions -- most of these use GEOS. 50 # 51 # For PostGIS >= 1.2.2 these routines will do a bounding box query first before calling 52 # the more expensive GEOS routines (called 'inline index magic'). 53 # 54 POSTGIS_GEOMETRY_FUNCTIONS = { 55 'equals' : '%sEquals', 56 'disjoint' : '%sDisjoint', 57 'touches' : '%sTouches', 58 'crosses' : '%sCrosses', 59 'within' : '%sWithin', 60 'overlaps' : '%sOverlaps', 61 'contains' : '%sContains', 62 'intersects' : '%sIntersects', 63 'relate' : ('%sRelate', str), 64 } 65 66 # Versions of PostGIS >= 1.2.2 changed their naming convention to be 'SQL-MM-centric'. 67 # Practically, this means that 'ST_' is appended to geometry function names. 68 if MINOR_VERSION1 >= 2 and MINOR_VERSION2 >= 2: 69 # The ST_DWithin, ST_CoveredBy, and ST_Covers routines become available in 1.2.2. 70 POSTGIS_GEOMETRY_FUNCTIONS.update( 71 {'dwithin' : ('%sDWithin', float), 72 'coveredby' : '%sCoveredBy', 73 'covers' : '%sCovers', 74 } 75 ) 61 # Versions of PostGIS >= 1.2.2 changed their naming convention to be 62 # 'SQL-MM-centric' to conform with the ISO standard. Practically, this 63 # means that 'ST_' is prefixes geometry function names. 64 if MAJOR_VERSION > 1 or (MAJOR_VERSION == 1 and (MINOR_VERSION1 > 2 or (MINOR_VERSION1 == 2 and MINOR_VERSION2 >= 2))): 76 65 GEOM_FUNC_PREFIX = 'ST_' 77 66 else: 78 67 GEOM_FUNC_PREFIX = '' 79 68 80 # Updating with the geometry function prefix. 81 for k, v in POSTGIS_GEOMETRY_FUNCTIONS.items(): 82 if isinstance(v, tuple): 83 v = list(v) 84 v[0] = v[0] % GEOM_FUNC_PREFIX 85 v = tuple(v) 86 else: 87 v = v % GEOM_FUNC_PREFIX 88 POSTGIS_GEOMETRY_FUNCTIONS[k] = v 69 # For PostGIS >= 1.2.2 the following lookup types will do a bounding box query 70 # first before calling the more computationally expensive GEOS routines (called 71 # "inline index magic"): 72 # 'touches', 'crosses', 'contains', 'intersects', 'within', 'overlaps', and 73 # 'covers'. 74 POSTGIS_GEOMETRY_FUNCTIONS = { 75 'equals' : 'Equals', 76 'disjoint' : 'Disjoint', 77 'touches' : 'Touches', 78 'crosses' : 'Crosses', 79 'within' : 'Within', 80 'overlaps' : 'Overlaps', 81 'contains' : 'Contains', 82 'intersects' : 'Intersects', 83 'relate' : ('Relate', str), 84 } 85 86 if GEOM_FUNC_PREFIX == 'ST_': 87 # Adding the GEOM_FUNC_PREFIX to the lookup functions. 88 for lookup, func in POSTGIS_GEOMETRY_FUNCTIONS.items(): 89 if isinstance(func, tuple): 90 POSTGIS_GEOMETRY_FUNCTIONS[lookup] = (GEOM_FUNC_PREFIX + func[0], func[1]) 91 else: 92 POSTGIS_GEOMETRY_FUNCTIONS[lookup] = GEOM_FUNC_PREFIX + func 93 94 # The ST_DWithin, ST_CoveredBy, and ST_Covers routines become available in 1.2.2+ 95 POSTGIS_GEOMETRY_FUNCTIONS.update( 96 {'dwithin' : ('ST_DWithin', float), 97 'coveredby' : 'ST_CoveredBy', 98 'covers' : 'ST_Covers', 99 } 100 ) 89 101 90 102 # Any other lookup types that do not require a mapping. 91 103 MISC_TERMS = ['isnull'] 92 93 # The quotation used for postgis (uses single quotes).94 def quotename(value, dbl=False):95 if dbl: return '"%s"' % value96 else: return "'%s'" % value97 104 98 105 # These are the PostGIS-customized QUERY_TERMS -- a list of the lookup types … … 102 109 POSTGIS_TERMS += MISC_TERMS # Adding any other miscellaneous terms (e.g., 'isnull') 103 110 POSTGIS_TERMS = tuple(POSTGIS_TERMS) # Making immutable 111 112 ### PostGIS-specific Methods ### 113 def get_geom_func(lookup_type): 114 func_info = POSTGIS_GEOMETRY_FUNCTIONS[lookup_type] 115 if isinstance(func_info, tuple): 116 return func_info[0] 117 else: 118 return func_info 104 119 105 120 def get_geo_where_clause(lookup_type, table_prefix, field_name, value): … … 117 132 # See if a PostGIS Geometry function matches the lookup type next 118 133 try: 119 return '%s(%s%s, %%s)' % (POSTGIS_GEOMETRY_FUNCTIONS[lookup_type], table_prefix, field_name)134 lookup_info = POSTGIS_GEOMETRY_FUNCTIONS[lookup_type] 120 135 except KeyError: 121 136 pass 137 else: 138 # Lookup types that are tuples take tuple arguments, e.g., 'relate' and 139 # 'dwithin' lookup types. 140 if isinstance(lookup_info, tuple): 141 # First element of tuple is lookup type, second element is the type 142 # of the expected argument (e.g., str, float) 143 func, arg_type = lookup_info 144 145 # Ensuring that a tuple _value_ was passed in from the user 146 if not isinstance(value, tuple) or len(value) != 2: 147 raise TypeError, '2-element tuple required for %s lookup type.' % lookup_type 148 149 # Ensuring the argument type matches what we expect. 150 if not isinstance(value[1], arg_type): 151 raise TypeError, 'Argument type should be %s, got %s instead.' % (arg_type, type(value[1])) 152 153 return "%s(%s%s, %%s, %%s)" % (func, table_prefix, field_name) 154 else: 155 # Returning the SQL necessary for the geometry function call. For example: 156 # ST_Contains("geoapp_country"."poly", ST_GeomFromText(..)) 157 return '%s(%s%s, %%s)' % (lookup_info, table_prefix, field_name) 122 158 123 159 # Handling 'isnull' lookup type … … 126 162 127 163 raise TypeError, "Got invalid lookup_type: %s" % repr(lookup_type) 164 165 def geo_quotename(value, dbl=False): 166 "Returns the quotation used for PostGIS on a given value (uses single quotes by default)." 167 if isinstance(value, (StringType, UnicodeType)): 168 if dbl: return '"%s"' % value 169 else: return "'%s'" % value 170 else: 171 return str(value) django/branches/gis/django/contrib/gis/db/models/manager.py
r5806 r5881 10 10 def kml(self, field_name, **kwargs): 11 11 return self.get_query_set().kml(field_name, **kwargs) 12 13 def transform(self, field_name, **kwargs): 14 return self.get_query_set().transform(field_name, **kwargs) django/branches/gis/django/contrib/gis/db/models/query.py
r5806 r5881 2 2 from django.core.exceptions import ImproperlyConfigured 3 3 from django.db import backend 4 from django.db.models.query import Q, QuerySet 4 from django.db.models.query import Q, QuerySet, handle_legacy_orderlist, quote_only_if_word 5 5 from django.db.models.fields import FieldDoesNotExist 6 from django.utils.datastructures import SortedDict 6 7 from django.contrib.gis.db.models.fields import GeometryField 7 8 from django.contrib.gis.db.backend import parse_lookup # parse_lookup depends on the spatial database backend. … … 17 18 "Geographical-enabled QuerySet object." 18 19 20 #### Overloaded QuerySet Routines #### 19 21 def __init__(self, model=None): 20 22 super(GeoQuerySet, self).__init__(model=model) … … 22 24 # We only want to use the GeoQ object for our queries 23 25 self._filters = GeoQ() 26 27 # For replacement fields in the SELECT. 28 self._custom_select = {} 24 29 25 30 def _filter_or_exclude(self, mapper, *args, **kwargs): … … 34 39 clone = self._clone() 35 40 if len(kwargs) > 0: 36 clone._filters = clone._filters & mapper(GeoQ(**kwargs)) # Using the GeoQ object for our filters instead 41 # Using the GeoQ object for our filters instead 42 clone._filters = clone._filters & mapper(GeoQ(**kwargs)) 37 43 if len(args) > 0: 38 44 clone._filters = clone._filters & reduce(operator.and_, map(mapper, args)) 39 45 return clone 40 46 47 def _get_sql_clause(self): 48 opts = self.model._meta 49 50 # Construct the fundamental parts of the query: SELECT X FROM Y WHERE Z. 51 select = [] 52 53 # This is the only component of this routine that is customized for the 54 # GeoQuerySet. Specifically, this allows operations to be done on fields 55 # in the SELECT, overriding their values -- this is different from using 56 # QuerySet.extra(select=foo) because extra() adds an an _additional_ 57 # field to be selected. Used in returning transformed geometries. 58 for f in opts.fields: 59 if f.column in self._custom_select: select.append(self._custom_select[f.column]) 60 else: select.append(self._field_column(f)) 61 62 tables = [quote_only_if_word(t) for t in self._tables] 63 joins = SortedDict() 64 where = self._where[:] 65 params = self._params[:] 66 67 # Convert self._filters into SQL. 68 joins2, where2, params2 = self._filters.get_sql(opts) 69 joins.update(joins2) 70 where.extend(where2) 71 params.extend(params2) 72 73 # Add additional tables and WHERE clauses based on select_related. 74 if self._select_related: 75 fill_table_cache(opts, select, tables, where, 76 old_prefix=opts.db_table, 77 cache_tables_seen=[opts.db_table], 78 max_depth=self._max_related_depth) 79 80 # Add any additional SELECTs. 81 if self._select: 82 select.extend(['(%s) AS %s' % (quote_only_if_word(s[1]), backend.quote_name(s[0])) for s in self._select.items()]) 83 84 # Start composing the body of the SQL statement. 85 sql = [" FROM", backend.quote_name(opts.db_table)] 86 87 # Compose the join dictionary into SQL describing the joins. 88 if joins: 89 sql.append(" ".join(["%s %s AS %s ON %s" % (join_type, table, alias, condition) 90 for (alias, (table, join_type, condition)) in joins.items()])) 91 92 # Compose the tables clause into SQL. 93 if tables: 94 sql.append(", " + ", ".join(tables)) 95 96 # Compose the where clause into SQL. 97 if where: 98 sql.append(where and "WHERE " + " AND ".join(where)) 99 100 # ORDER BY clause 101 order_by = [] 102 if self._order_by is not None: 103 ordering_to_use = self._order_by 104 else: 105 ordering_to_use = opts.ordering 106 for f in handle_legacy_orderlist(ordering_to_use): 107 if f == '?': # Special case. 108 order_by.append(backend.get_random_function_sql()) 109 else: 110 if f.startswith('-'): 111 col_name = f[1:] 112 order = "DESC" 113 else: 114 col_name = f 115 order = "ASC" 116 if "." in col_name: 117 table_prefix, col_name = col_name.split('.', 1) 118 table_prefix = backend.quote_name(table_prefix) + '.' 119 else: 120 # Use the database table as a column prefix if it wasn't given, 121 # and if the requested column isn't a custom SELECT. 122 if "." not in col_name and col_name not in (self._select or ()): 123 table_prefix = backend.quote_name(opts.db_table) + '.' 124 else: 125 table_prefix = '' 126 order_by.append('%s%s %s' % (table_prefix, backend.quote_name(orderfield2column(col_name, opts)), order)) 127 if order_by: 128 sql.append("ORDER BY " + ", ".join(order_by)) 129 130 # LIMIT and OFFSET clauses 131 if self._limit is not None: 132 sql.append("%s " % backend.get_limit_offset_sql(self._limit, self._offset)) 133 else: 134 assert self._offset is None, "'offset' is not allowed without 'limit'" 135 136 return select, " ".join(sql), params 137 138 def _clone(self, klass=None, **kwargs): 139 c = super(GeoQuerySet, self)._clone(klass, **kwargs) 140 c._custom_select = self._custom_select 141 return c 142 143 #### Methods specific to the GeoQuerySet #### 144 def _field_column(self, field): 145 return "%s.%s" % (backend.quote_name(self.model._meta.db_table), 146 backend.quote_name(field.column)) 147 41 148 def kml(self, field_name, precision=8): 42 149 """Returns KML representation of the given field name in a `kml` … … 52 159 if not isinstance(field, GeometryField): 53 160 raise TypeError, 'KML output only available on GeometryField fields.' 54 field_col = "%s.%s" % (backend.quote_name(self.model._meta.db_table), 55 backend.quote_name(field.column)) 161 field_col = self._field_column(field) 56 162 57 163 # Adding the AsKML function call to the SELECT part of the SQL. 58 164 return self.extra(select={'kml':'%s(%s,%s)' % (ASKML, field_col, precision)}) 165 166 def transform(self, field_name, srid=4326): 167 """Transforms the given geometry field to the given SRID. If no SRID is 168 provided, the transformation will default to using 4326 (WGS84).""" 169 field = self.model._meta.get_field(field_name) 170 if not isinstance(field, GeometryField): 171 raise TypeError, 'ST_Transform() only available for GeometryField fields.' 172 173 # Setting the key for the field's column with the custom SELECT SQL to 174 # override the geometry column returned from the database. 175 self._custom_select[field.column] = \ 176 '(ST_Transform(%s, %s)) AS %s' % (self._field_column(field), srid, 177 backend.quote_name(field.column)) 178 return self._clone() 179 180 django/branches/gis/django/contrib/gis/tests/geoapp/tests.py
r5806 r5881 86 86 self.assertEqual('<Point><coordinates>-104.609252,38.255001,0</coordinates></Point>', ptown.kml) 87 87 88 def test04_transform(self): 89 "Testing the transform() queryset method." 90 91 # Pre-transformed points for Houston and Pueblo. 92 htown = fromstr('POINT(1947516.83115183 6322297.06040572)', srid=3084) 93 ptown = fromstr('POINT(992363.390841912 481455.395105533)', srid=2774) 94 95 # Asserting the result of the transform operation with the values in 96 # the pre-transformed points. 97 h = City.objects.transform('point', srid=htown.srid).get(name='Houston') 98 self.assertAlmostEqual(htown.x, h.point.x, 8) 99 self.assertAlmostEqual(htown.y, h.point.y, 8) 100 101 p = City.objects.transform('point', srid=ptown.srid).get(name='Pueblo') 102 self.assertAlmostEqual(ptown.x, p.point.x, 8) 103 self.assertAlmostEqual(ptown.y, p.point.y, 8) 104 88 105 def test10_contains_contained(self): 89 106 "Testing the 'contained' and 'contains' lookup types." … … 201 218 for c in qs: self.assertEqual(True, c.name in cities) 202 219 220 def test14_equals(self): 221 "Testing the 'same_as' and 'equals' lookup types." 222 pnt = fromstr('POINT (-95.363151 29.763374)', srid=4326) 223 c1 = City.objects.get(point=pnt) 224 c2 = City.objects.get(point__same_as=pnt) 225 c3 = City.objects.get(point__equals=pnt) 226 for c in [c1, c2, c3]: self.assertEqual('Houston', c.name) 227 228 def test15_relate(self): 229 "Testing the 'relate' lookup type." 230 # To make things more interesting, we will have our Texas reference point in 231 # different SRIDs. 232 pnt1 = fromstr('POINT (649287.0363174345111474 4177429.4494686722755432)', srid=2847) 233 pnt2 = fromstr('POINT(-98.4919715741052 29.4333344025053)', srid=4326) 234 235 # Testing bad argument tuples that should return a TypeError 236 bad_args = [(pnt1, 0), (pnt2, 'T*T***FF*', 0), (23, 'foo')] 237 for args in bad_args: 238 try: 239 qs = Country.objects.filter(mpoly__relate=args) 240 cnt = qs.count() 241 except TypeError: 242 pass 243 else: 244 self.fail('Expected a TypeError') 245 246 # 'T*T***FF*' => Contains() 247 self.assertEqual('Texas', Country.objects.get(mpoly__relate=(pnt1, 'T*T***FF*')).name) 248 self.assertEqual('Texas', Country.objects.get(mpoly__relate=(pnt2, 'T*T***FF*')).name) 249 250 # 'T*F**F***' => Within() 251 ks = State.objects.get(name='Kansas') 252 self.assertEqual('Lawrence', City.objects.get(point__relate=(ks.poly, 'T*F**F***')).name) 253 254 # 'T********' => Intersects() 255 self.assertEqual('Texas', Country.objects.get(mpoly__relate=(pnt1, 'T********')).name) 256 self.assertEqual('Texas', Country.objects.get(mpoly__relate=(pnt2, 'T********')).name) 257 self.assertEqual('Lawrence', City.objects.get(point__relate=(ks.poly, 'T********')).name) 258 203 259 def suite(): 204 260 s = unittest.TestSuite()
