Django

Code

Changeset 6441

Show
Ignore:
Timestamp:
09/30/07 12:44:13 (1 year ago)
Author:
jbronn
Message:

gis: Added the gml() and union() GeoQuerySet? routines w/tests.

Files:

Legend:

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

    r6439 r6441  
    1717    FieldFound, LOOKUP_SEPARATOR, QUERY_TERMS 
    1818from django.utils.datastructures import SortedDict 
     19 
     20# These routines default to False 
     21ASGML, ASKML, UNION = (False, False, False) 
    1922 
    2023if settings.DATABASE_ENGINE == 'postgresql_psycopg2': 
  • django/branches/gis/django/contrib/gis/db/models/manager.py

    r5881 r6441  
    88        return GeoQuerySet(model=self.model) 
    99 
    10     def kml(self, field_name, **kwargs): 
    11         return self.get_query_set().kml(field_name, **kwargs) 
     10    def gml(self, *args, **kwargs): 
     11        return self.get_query_set().gml(*args, **kwargs) 
    1212 
    13     def transform(self, field_name, **kwargs): 
    14         return self.get_query_set().transform(field_name, **kwargs) 
     13    def kml(self, *args, **kwargs): 
     14        return self.get_query_set().kml(*args, **kwargs) 
     15 
     16    def transform(self, *args, **kwargs): 
     17        return self.get_query_set().transform(*args, **kwargs) 
     18 
     19    def union(self, *args, **kwargs): 
     20        return self.get_query_set().union(*args, **kwargs) 
  • django/branches/gis/django/contrib/gis/db/models/query.py

    r6437 r6441  
    22from django.core.exceptions import ImproperlyConfigured 
    33from django.db import connection 
    4 from django.db.models.query import Q, QuerySet, handle_legacy_orderlist, quote_only_if_word, orderfield2column, fill_table_cache 
     4from django.db.models.query import EmptyResultSet, Q, QuerySet, handle_legacy_orderlist, quote_only_if_word, orderfield2column, fill_table_cache 
    55from django.db.models.fields import FieldDoesNotExist 
    66from django.utils.datastructures import SortedDict 
    77from django.contrib.gis.db.models.fields import GeometryField 
    8 from django.contrib.gis.db.backend import parse_lookup # parse_lookup depends on the spatial database backend. 
     8# parse_lookup depends on the spatial database backend. 
     9from django.contrib.gis.db.backend import parse_lookup, ASGML, ASKML, UNION 
     10from django.contrib.gis.geos import GEOSGeometry 
    911 
    1012class GeoQ(Q): 
     
    144146    #### Methods specific to the GeoQuerySet #### 
    145147    def _field_column(self, field): 
     148        "Helper function that returns the database column for the given field." 
    146149        qn = connection.ops.quote_name 
    147150        return "%s.%s" % (qn(self.model._meta.db_table), 
    148151                          qn(field.column)) 
    149      
     152 
     153    def _geo_column(self, field_name): 
     154        """ 
     155        Helper function that returns False when the given field name is not an 
     156        instance of a GeographicField, otherwise, the database column for the 
     157        geographic field is returned. 
     158        """ 
     159        field = self.model._meta.get_field(field_name) 
     160        if isinstance(field, GeometryField): 
     161            return self._field_column(field) 
     162        else: 
     163            return False 
     164 
     165    def gml(self, field_name, precision=8, version=2): 
     166        """ 
     167        Returns GML representation of the given field in a `gml` attribute 
     168        on each element of the GeoQuerySet. 
     169        """ 
     170        # Is GML output supported? 
     171        if not ASGML: 
     172            raise ImproperlyConfigured('AsGML() stored procedure not available.') 
     173 
     174        # Is the given field name a geographic field? 
     175        field_col = self._geo_column(field_name) 
     176        if not field_col: 
     177            raise TypeError('GML output only available on GeometryFields') 
     178 
     179        # Adding AsGML function call to SELECT part of the SQL. 
     180        return self.extra(select={'gml':'%s(%s,%s,%s)' % (ASGML, field_col, precision, version)}) 
     181 
    150182    def kml(self, field_name, precision=8): 
    151         """Returns KML representation of the given field name in a `kml`  
    152         attribute on each element of the QuerySet.""" 
     183        """ 
     184        Returns KML representation of the given field name in a `kml`  
     185        attribute on each element of the GeoQuerySet. 
     186        """ 
    153187        # Is KML output supported? 
    154         try: 
    155             from django.contrib.gis.db.backend.postgis import ASKML 
    156         except ImportError: 
    157             raise ImproperlyConfigured, 'AsKML() only available in PostGIS versions 1.2.1 and greater.' 
    158  
     188        if not ASKML: 
     189            raise ImproperlyConfigured('AsKML() stored procedure not available.') 
     190 
     191        # Is the given field name a geographic field? 
     192        field_col = self._geo_column(field_name) 
     193        if not field_col: 
     194            raise TypeError('KML output only available on GeometryFields.') 
     195         
     196        # Adding the AsKML function call to SELECT part of the SQL. 
     197        return self.extra(select={'kml':'%s(%s,%s)' % (ASKML, field_col, precision)}) 
     198 
     199    def transform(self, field_name, srid=4326): 
     200        """ 
     201        Transforms the given geometry field to the given SRID.  If no SRID is 
     202        provided, the transformation will default to using 4326 (WGS84). 
     203        """ 
    159204        # Is the given field name a geographic field? 
    160205        field = self.model._meta.get_field(field_name) 
    161206        if not isinstance(field, GeometryField): 
    162             raise TypeError, 'KML output only available on GeometryField fields.' 
    163         field_col = self._field_column(field) 
    164          
    165         # Adding the AsKML function call to the SELECT part of the SQL. 
    166         return self.extra(select={'kml':'%s(%s,%s)' % (ASKML, field_col, precision)}) 
    167  
    168     def transform(self, field_name, srid=4326): 
    169         """Transforms the given geometry field to the given SRID.  If no SRID is 
    170         provided, the transformation will default to using 4326 (WGS84).""" 
    171         field = self.model._meta.get_field(field_name) 
    172         if not isinstance(field, GeometryField): 
    173             raise TypeError, 'ST_Transform() only available for GeometryField fields.' 
     207            raise TypeError('ST_Transform() only available for GeometryFields') 
    174208 
    175209        # Setting the key for the field's column with the custom SELECT SQL to  
     
    180214        return self._clone() 
    181215 
    182      
     216    def union(self, field_name): 
     217        """ 
     218        Performs an aggregate union on the given geometry field.  Returns 
     219        None if the GeoQuerySet is empty. 
     220        """ 
     221        # Making sure backend supports the Union stored procedure 
     222        if not UNION: 
     223            raise ImproperlyConfigured('Union stored procedure not available.') 
     224 
     225        # Getting the geographic field column 
     226        field_col = self._geo_column(field_name) 
     227        if not field_col: 
     228            raise TypeError('Aggregate Union only available on GeometryFields.') 
     229 
     230        # Getting the SQL for the query. 
     231        try: 
     232            select, sql, params = self._get_sql_clause() 
     233        except EmptyResultSet: 
     234            return None 
     235 
     236        # Replacing the select with a call to the ST_Union stored procedure 
     237        #  on the geographic field column. 
     238        union_sql = ('SELECT %s(%s)' % (UNION, field_col)) + sql 
     239        cursor = connection.cursor() 
     240        cursor.execute(union_sql, params) 
     241 
     242        # Pulling the HEXEWKB from the returned cursor. 
     243        hex = cursor.fetchone()[0] 
     244        if hex: return GEOSGeometry(hex) 
     245        else: return None 
  • django/branches/gis/django/contrib/gis/tests/geoapp/tests.py

    r6243 r6441  
    77    def test01_initial_sql(self): 
    88        "Testing geographic initial SQL." 
    9  
    109        # Ensuring that data was loaded from initial SQL. 
    1110        self.assertEqual(2, Country.objects.count()) 
     
    7271        nullstate.delete() 
    7372 
    74     def test03_kml(self): 
     73    def test03a_kml(self): 
    7574        "Testing KML output from the database using GeoManager.kml()." 
    76         # Should throw an error trying to get KML from a non-geometry field. 
    77         try: 
    78             qs = City.objects.all().kml('name') 
    79         except TypeError: 
    80             pass 
    81         else: 
    82             self.fail('Expected a TypeError exception') 
     75        # Should throw a TypeError when trying to obtain KML from a 
     76        #  non-geometry field. 
     77        qs = City.objects.all() 
     78        self.assertRaises(TypeError, qs.kml, 'name') 
    8379 
    8480        # Ensuring the KML is as expected. 
     
    8682        self.assertEqual('<Point><coordinates>-104.609252,38.255001,0</coordinates></Point>', ptown.kml) 
    8783 
     84    def test03b_gml(self): 
     85        "Testing GML output from the database using GeoManager.gml()." 
     86        # Should throw a TypeError when tyring to obtain GML from a 
     87        #  non-geometry field. 
     88        qs = City.objects.all() 
     89        self.assertRaises(TypeError, qs.gml, 'name') 
     90        ptown = City.objects.gml('point', precision=9).get(name='Pueblo') 
     91        self.assertEqual('<gml:Point srsName="EPSG:4326"><gml:coordinates>-104.609252,38.255001</gml:coordinates></gml:Point>', ptown.gml) 
     92 
    8893    def test04_transform(self): 
    89         "Testing the transform() queryset method." 
    90  
     94        "Testing the transform() GeoManager method." 
    9195        # Pre-transformed points for Houston and Pueblo. 
    9296        htown = fromstr('POINT(1947516.83115183 6322297.06040572)', srid=3084) 
     
    104108 
    105109    def test10_contains_contained(self): 
    106         "Testing the 'contained' and 'contains' lookup types." 
    107  
     110        "Testing the 'contained', 'contains', and 'bbcontains' lookup types." 
    108111        # Getting Texas, yes we were a country -- once ;) 
    109112        texas = Country.objects.get(name='Texas') 
     
    138141        self.assertEqual(0, len(Country.objects.filter(mpoly__contains=okcity.point.wkt))) # Qeury w/WKT 
    139142 
     143        # OK City is contained w/in bounding box of Texas. 
     144        qs = Country.objects.filter(mpoly__bbcontains=okcity.point) 
     145        self.assertEqual(1, len(qs)) 
     146        self.assertEqual('Texas', qs[0].name) 
     147 
    140148    def test11_lookup_insert_transform(self): 
    141149        "Testing automatic transform for lookups and inserts." 
    142  
    143150        # San Antonio in 'WGS84' (SRID 4326) and 'NAD83(HARN) / Texas Centric Lambert Conformal' (SRID 3084) 
    144151        sa_4326 = 'POINT (-98.493183 29.424170)' 
     
    162169    def test12_null_geometries(self): 
    163170        "Testing NULL geometry support." 
    164  
    165171        # Querying for both NULL and Non-NULL values. 
    166172        nullqs = State.objects.filter(poly__isnull=True) 
     
    183189    def test13_left_right(self): 
    184190        "Testing the 'left' and 'right' lookup types." 
    185          
    186191        # Left: A << B => true if xmax(A) < xmin(B) 
    187192        # Right: A >> B => true if xmin(A) > xmax(B)  
     
    262267        self.assertEqual(c.point, None) 
    263268 
     269    def test17_union(self): 
     270        "Testing the union() GeoManager method." 
     271        tx = Country.objects.get(name='Texas').mpoly 
     272        # Houston, Dallas, San Antonio 
     273        union = fromstr('MULTIPOINT(-98.493183 29.424170,-96.801611 32.782057,-95.363151 29.763374)') 
     274        qs = City.objects.filter(point__within=tx) 
     275        self.assertRaises(TypeError, qs.union, 'name') 
     276        u = qs.union('point') 
     277        self.assertEqual(True, union.equals_exact(u, 10)) # Going up to 10 digits of precision. 
     278        qs = City.objects.filter(name='NotACity') 
     279        self.assertEqual(None, qs.union('point')) 
     280     
    264281def suite(): 
    265282    s = unittest.TestSuite() 
  • django/branches/gis/django/contrib/gis/tests/__init__.py

    r6436 r6441  
    33from unittest import TestSuite, TextTestRunner 
    44from django.contrib.gis.gdal import HAS_GDAL 
    5  
    65 
    76# Tests that do not require setting up and tearing down a spatial database. 
     
    3837def run_tests(module_list, verbosity=1, interactive=True): 
    3938    """ 
    40     Run the tests that require creation of a spatial database.  Does not 
    41      yet work on Windows platforms. 
     39    Run the tests that require creation of a spatial database. 
    4240     
    4341    In order to run geographic model tests the DATABASE_USER will require 
     
    5048     (3) Start this database `pg_ctl -D /path/to/user/db start` 
    5149 
    52     Make sure your settings.py matches the settings of the user database. For example, set the  
    53      same port number (`DATABASE_PORT=5433`).  DATABASE_NAME or TEST_DATABSE_NAME must be set, 
    54      along with DATABASE_USER. 
     50    On Windows platforms simply use the pgAdmin III utility to add superuser  
     51     priviliges to your database user. 
     52 
     53    Make sure your settings.py matches the settings of the user database.  
     54     For example, set the same port number (`DATABASE_PORT=5433`).   
     55     DATABASE_NAME or TEST_DATABSE_NAME must be set, along with DATABASE_USER. 
    5556       
    5657    In settings.py set TEST_RUNNER='django.contrib.gis.tests.run_tests'. 
    5758 
    58     Finally, this assumes that the PostGIS SQL files (lwpostgis.sql and spatial_ref_sys.sql) 
    59      are installed in /usr/local/share.  If they are not, add `POSTGIS_SQL_PATH=/path/to/sql` 
    60      in your settings.py. 
     59    Finally, this assumes that the PostGIS SQL files (lwpostgis.sql and  
     60     spatial_ref_sys.sql) are installed in the directory specified by  
     61     `pg_config --sharedir` (and defaults to /usr/local/share if that fails). 
     62     This behavior is overridden if `POSTGIS_SQL_PATH` is in your settings. 
     63     
     64    Windows users should use the POSTGIS_SQL_PATH because the output 
     65     of `pg_config` uses paths like 'C:/PROGRA~1/POSTGR~1/..'. 
    6166 
    62     The tests may be run by invoking `./manage.py test`. 
     67    Finally, the tests may be run by invoking `./manage.py test`. 
    6368    """ 
    6469    from django.conf import settings