Author: jbronn
Date: 2007-09-30 12:44:13 -0500 (Sun, 30 Sep 2007)
New Revision: 6441

Modified:
   django/branches/gis/django/contrib/gis/db/backend/__init__.py
   django/branches/gis/django/contrib/gis/db/models/manager.py
   django/branches/gis/django/contrib/gis/db/models/query.py
   django/branches/gis/django/contrib/gis/tests/__init__.py
   django/branches/gis/django/contrib/gis/tests/geoapp/tests.py
Log:
gis: Added the gml() and union() GeoQuerySet routines w/tests.


Modified: django/branches/gis/django/contrib/gis/db/backend/__init__.py
===================================================================
--- django/branches/gis/django/contrib/gis/db/backend/__init__.py       
2007-09-30 15:16:22 UTC (rev 6440)
+++ django/branches/gis/django/contrib/gis/db/backend/__init__.py       
2007-09-30 17:44:13 UTC (rev 6441)
@@ -17,6 +17,9 @@
     FieldFound, LOOKUP_SEPARATOR, QUERY_TERMS
 from django.utils.datastructures import SortedDict
 
+# These routines default to False
+ASGML, ASKML, UNION = (False, False, False)
+
 if settings.DATABASE_ENGINE == 'postgresql_psycopg2':
     # PostGIS is the spatial database, getting the rquired modules, renaming 
as necessary.
     from django.contrib.gis.db.backend.postgis import \

Modified: django/branches/gis/django/contrib/gis/db/models/manager.py
===================================================================
--- django/branches/gis/django/contrib/gis/db/models/manager.py 2007-09-30 
15:16:22 UTC (rev 6440)
+++ django/branches/gis/django/contrib/gis/db/models/manager.py 2007-09-30 
17:44:13 UTC (rev 6441)
@@ -7,8 +7,14 @@
     def get_query_set(self):
         return GeoQuerySet(model=self.model)
 
-    def kml(self, field_name, **kwargs):
-        return self.get_query_set().kml(field_name, **kwargs)
+    def gml(self, *args, **kwargs):
+        return self.get_query_set().gml(*args, **kwargs)
 
-    def transform(self, field_name, **kwargs):
-        return self.get_query_set().transform(field_name, **kwargs)
+    def kml(self, *args, **kwargs):
+        return self.get_query_set().kml(*args, **kwargs)
+
+    def transform(self, *args, **kwargs):
+        return self.get_query_set().transform(*args, **kwargs)
+
+    def union(self, *args, **kwargs):
+        return self.get_query_set().union(*args, **kwargs)

Modified: django/branches/gis/django/contrib/gis/db/models/query.py
===================================================================
--- django/branches/gis/django/contrib/gis/db/models/query.py   2007-09-30 
15:16:22 UTC (rev 6440)
+++ django/branches/gis/django/contrib/gis/db/models/query.py   2007-09-30 
17:44:13 UTC (rev 6441)
@@ -1,11 +1,13 @@
 import operator
 from django.core.exceptions import ImproperlyConfigured
 from django.db import connection
-from django.db.models.query import Q, QuerySet, handle_legacy_orderlist, 
quote_only_if_word, orderfield2column, fill_table_cache
+from django.db.models.query import EmptyResultSet, Q, QuerySet, 
handle_legacy_orderlist, quote_only_if_word, orderfield2column, fill_table_cache
 from django.db.models.fields import FieldDoesNotExist
 from django.utils.datastructures import SortedDict
 from django.contrib.gis.db.models.fields import GeometryField
-from django.contrib.gis.db.backend import parse_lookup # parse_lookup depends 
on the spatial database backend.
+# parse_lookup depends on the spatial database backend.
+from django.contrib.gis.db.backend import parse_lookup, ASGML, ASKML, UNION
+from django.contrib.gis.geos import GEOSGeometry
 
 class GeoQ(Q):
     "Geographical query encapsulation object."
@@ -143,34 +145,66 @@
 
     #### Methods specific to the GeoQuerySet ####
     def _field_column(self, field):
+        "Helper function that returns the database column for the given field."
         qn = connection.ops.quote_name
         return "%s.%s" % (qn(self.model._meta.db_table),
                           qn(field.column))
-    
+
+    def _geo_column(self, field_name):
+        """
+        Helper function that returns False when the given field name is not an
+        instance of a GeographicField, otherwise, the database column for the
+        geographic field is returned.
+        """
+        field = self.model._meta.get_field(field_name)
+        if isinstance(field, GeometryField):
+            return self._field_column(field)
+        else:
+            return False
+
+    def gml(self, field_name, precision=8, version=2):
+        """
+        Returns GML representation of the given field in a `gml` attribute
+        on each element of the GeoQuerySet.
+        """
+        # Is GML output supported?
+        if not ASGML:
+            raise ImproperlyConfigured('AsGML() stored procedure not 
available.')
+
+        # Is the given field name a geographic field?
+        field_col = self._geo_column(field_name)
+        if not field_col:
+            raise TypeError('GML output only available on GeometryFields')
+
+        # Adding AsGML function call to SELECT part of the SQL.
+        return self.extra(select={'gml':'%s(%s,%s,%s)' % (ASGML, field_col, 
precision, version)})
+
     def kml(self, field_name, precision=8):
-        """Returns KML representation of the given field name in a `kml` 
-        attribute on each element of the QuerySet."""
+        """
+        Returns KML representation of the given field name in a `kml` 
+        attribute on each element of the GeoQuerySet.
+        """
         # Is KML output supported?
-        try:
-            from django.contrib.gis.db.backend.postgis import ASKML
-        except ImportError:
-            raise ImproperlyConfigured, 'AsKML() only available in PostGIS 
versions 1.2.1 and greater.'
+        if not ASKML:
+            raise ImproperlyConfigured('AsKML() stored procedure not 
available.')
 
         # Is the given field name a geographic field?
-        field = self.model._meta.get_field(field_name)
-        if not isinstance(field, GeometryField):
-            raise TypeError, 'KML output only available on GeometryField 
fields.'
-        field_col = self._field_column(field)
+        field_col = self._geo_column(field_name)
+        if not field_col:
+            raise TypeError('KML output only available on GeometryFields.')
         
-        # Adding the AsKML function call to the SELECT part of the SQL.
+        # Adding the AsKML function call to SELECT part of the SQL.
         return self.extra(select={'kml':'%s(%s,%s)' % (ASKML, field_col, 
precision)})
 
     def transform(self, field_name, srid=4326):
-        """Transforms the given geometry field to the given SRID.  If no SRID 
is
-        provided, the transformation will default to using 4326 (WGS84)."""
+        """
+        Transforms the given geometry field to the given SRID.  If no SRID is
+        provided, the transformation will default to using 4326 (WGS84).
+        """
+        # Is the given field name a geographic field?
         field = self.model._meta.get_field(field_name)
         if not isinstance(field, GeometryField):
-            raise TypeError, 'ST_Transform() only available for GeometryField 
fields.'
+            raise TypeError('ST_Transform() only available for GeometryFields')
 
         # Setting the key for the field's column with the custom SELECT SQL to 
         #  override the geometry column returned from the database.
@@ -179,4 +213,33 @@
                                               
connection.ops.quote_name(field.column))
         return self._clone()
 
-    
+    def union(self, field_name):
+        """
+        Performs an aggregate union on the given geometry field.  Returns
+        None if the GeoQuerySet is empty.
+        """
+        # Making sure backend supports the Union stored procedure
+        if not UNION:
+            raise ImproperlyConfigured('Union stored procedure not available.')
+
+        # Getting the geographic field column
+        field_col = self._geo_column(field_name)
+        if not field_col:
+            raise TypeError('Aggregate Union only available on 
GeometryFields.')
+
+        # Getting the SQL for the query.
+        try:
+            select, sql, params = self._get_sql_clause()
+        except EmptyResultSet:
+            return None
+
+        # Replacing the select with a call to the ST_Union stored procedure
+        #  on the geographic field column.
+        union_sql = ('SELECT %s(%s)' % (UNION, field_col)) + sql
+        cursor = connection.cursor()
+        cursor.execute(union_sql, params)
+
+        # Pulling the HEXEWKB from the returned cursor.
+        hex = cursor.fetchone()[0]
+        if hex: return GEOSGeometry(hex)
+        else: return None

Modified: django/branches/gis/django/contrib/gis/tests/__init__.py
===================================================================
--- django/branches/gis/django/contrib/gis/tests/__init__.py    2007-09-30 
15:16:22 UTC (rev 6440)
+++ django/branches/gis/django/contrib/gis/tests/__init__.py    2007-09-30 
17:44:13 UTC (rev 6441)
@@ -3,7 +3,6 @@
 from unittest import TestSuite, TextTestRunner
 from django.contrib.gis.gdal import HAS_GDAL
 
-
 # Tests that do not require setting up and tearing down a spatial database.
 test_suite_names = [
     'test_geos',
@@ -37,8 +36,7 @@
 
 def run_tests(module_list, verbosity=1, interactive=True):
     """
-    Run the tests that require creation of a spatial database.  Does not
-     yet work on Windows platforms.
+    Run the tests that require creation of a spatial database.
     
     In order to run geographic model tests the DATABASE_USER will require
      superuser priviliges.  To accomplish this outside the `postgres` user,
@@ -49,17 +47,24 @@
          /path/to/user/db) to change the database port (e.g. `port = 5433`).  
      (3) Start this database `pg_ctl -D /path/to/user/db start`
 
-    Make sure your settings.py matches the settings of the user database. For 
example, set the 
-     same port number (`DATABASE_PORT=5433`).  DATABASE_NAME or 
TEST_DATABSE_NAME must be set,
-     along with DATABASE_USER.
+    On Windows platforms simply use the pgAdmin III utility to add superuser 
+     priviliges to your database user.
+
+    Make sure your settings.py matches the settings of the user database. 
+     For example, set the same port number (`DATABASE_PORT=5433`).  
+     DATABASE_NAME or TEST_DATABSE_NAME must be set, along with DATABASE_USER.
       
     In settings.py set TEST_RUNNER='django.contrib.gis.tests.run_tests'.
 
-    Finally, this assumes that the PostGIS SQL files (lwpostgis.sql and 
spatial_ref_sys.sql)
-     are installed in /usr/local/share.  If they are not, add 
`POSTGIS_SQL_PATH=/path/to/sql`
-     in your settings.py.
+    Finally, this assumes that the PostGIS SQL files (lwpostgis.sql and 
+     spatial_ref_sys.sql) are installed in the directory specified by 
+     `pg_config --sharedir` (and defaults to /usr/local/share if that fails).
+     This behavior is overridden if `POSTGIS_SQL_PATH` is in your settings.
+    
+    Windows users should use the POSTGIS_SQL_PATH because the output
+     of `pg_config` uses paths like 'C:/PROGRA~1/POSTGR~1/..'.
 
-    The tests may be run by invoking `./manage.py test`.
+    Finally, the tests may be run by invoking `./manage.py test`.
     """
     from django.conf import settings
     from django.contrib.gis.db.backend import create_spatial_db

Modified: django/branches/gis/django/contrib/gis/tests/geoapp/tests.py
===================================================================
--- django/branches/gis/django/contrib/gis/tests/geoapp/tests.py        
2007-09-30 15:16:22 UTC (rev 6440)
+++ django/branches/gis/django/contrib/gis/tests/geoapp/tests.py        
2007-09-30 17:44:13 UTC (rev 6441)
@@ -6,7 +6,6 @@
     
     def test01_initial_sql(self):
         "Testing geographic initial SQL."
-
         # Ensuring that data was loaded from initial SQL.
         self.assertEqual(2, Country.objects.count())
         self.assertEqual(8, City.objects.count())
@@ -71,23 +70,28 @@
         self.assertEqual(ply, State.objects.get(name='NullState').poly)
         nullstate.delete()
 
-    def test03_kml(self):
+    def test03a_kml(self):
         "Testing KML output from the database using GeoManager.kml()."
-        # Should throw an error trying to get KML from a non-geometry field.
-        try:
-            qs = City.objects.all().kml('name')
-        except TypeError:
-            pass
-        else:
-            self.fail('Expected a TypeError exception')
+        # Should throw a TypeError when trying to obtain KML from a
+        #  non-geometry field.
+        qs = City.objects.all()
+        self.assertRaises(TypeError, qs.kml, 'name')
 
         # Ensuring the KML is as expected.
         ptown = City.objects.kml('point', precision=9).get(name='Pueblo')
         
self.assertEqual('<Point><coordinates>-104.609252,38.255001,0</coordinates></Point>',
 ptown.kml)
 
+    def test03b_gml(self):
+        "Testing GML output from the database using GeoManager.gml()."
+        # Should throw a TypeError when tyring to obtain GML from a
+        #  non-geometry field.
+        qs = City.objects.all()
+        self.assertRaises(TypeError, qs.gml, 'name')
+        ptown = City.objects.gml('point', precision=9).get(name='Pueblo')
+        self.assertEqual('<gml:Point 
srsName="EPSG:4326"><gml:coordinates>-104.609252,38.255001</gml:coordinates></gml:Point>',
 ptown.gml)
+
     def test04_transform(self):
-        "Testing the transform() queryset method."
-
+        "Testing the transform() GeoManager method."
         # Pre-transformed points for Houston and Pueblo.
         htown = fromstr('POINT(1947516.83115183 6322297.06040572)', srid=3084)
         ptown = fromstr('POINT(992363.390841912 481455.395105533)', srid=2774)
@@ -103,8 +107,7 @@
         self.assertAlmostEqual(ptown.y, p.point.y, 8)
 
     def test10_contains_contained(self):
-        "Testing the 'contained' and 'contains' lookup types."
-
+        "Testing the 'contained', 'contains', and 'bbcontains' lookup types."
         # Getting Texas, yes we were a country -- once ;)
         texas = Country.objects.get(name='Texas')
         
@@ -137,9 +140,13 @@
         self.assertEqual(0, 
len(Country.objects.filter(mpoly__contains=pueblo.point))) # Query 
w/GEOSGeometry object
         self.assertEqual(0, 
len(Country.objects.filter(mpoly__contains=okcity.point.wkt))) # Qeury w/WKT
 
+        # OK City is contained w/in bounding box of Texas.
+        qs = Country.objects.filter(mpoly__bbcontains=okcity.point)
+        self.assertEqual(1, len(qs))
+        self.assertEqual('Texas', qs[0].name)
+
     def test11_lookup_insert_transform(self):
         "Testing automatic transform for lookups and inserts."
-
         # San Antonio in 'WGS84' (SRID 4326) and 'NAD83(HARN) / Texas Centric 
Lambert Conformal' (SRID 3084)
         sa_4326 = 'POINT (-98.493183 29.424170)'
         sa_3084 = 'POINT (1645978.362408288754523 6276356.025927528738976)' # 
Used ogr.py in gdal 1.4.1 for this transform
@@ -161,7 +168,6 @@
 
     def test12_null_geometries(self):
         "Testing NULL geometry support."
-
         # Querying for both NULL and Non-NULL values.
         nullqs = State.objects.filter(poly__isnull=True)
         validqs = State.objects.filter(poly__isnull=False)
@@ -182,7 +188,6 @@
     
     def test13_left_right(self):
         "Testing the 'left' and 'right' lookup types."
-        
         # Left: A << B => true if xmax(A) < xmin(B)
         # Right: A >> B => true if xmin(A) > xmax(B) 
         #  See: BOX2D_left() and BOX2D_right() in lwgeom_box2dfloat4.c in 
PostGIS source.
@@ -261,6 +266,18 @@
         c = City()
         self.assertEqual(c.point, None)
 
+    def test17_union(self):
+        "Testing the union() GeoManager method."
+        tx = Country.objects.get(name='Texas').mpoly
+        # Houston, Dallas, San Antonio
+        union = fromstr('MULTIPOINT(-98.493183 29.424170,-96.801611 
32.782057,-95.363151 29.763374)')
+        qs = City.objects.filter(point__within=tx)
+        self.assertRaises(TypeError, qs.union, 'name')
+        u = qs.union('point')
+        self.assertEqual(True, union.equals_exact(u, 10)) # Going up to 10 
digits of precision.
+        qs = City.objects.filter(name='NotACity')
+        self.assertEqual(None, qs.union('point'))
+    
 def suite():
     s = unittest.TestSuite()
     s.addTest(unittest.makeSuite(GeoModelTest))


--~--~---------~--~----~------------~-------~--~----~
You received this message because you are subscribed to the Google Groups 
"Django updates" group.
To post to this group, send email to [email protected]
To unsubscribe from this group, send email to [EMAIL PROTECTED]
For more options, visit this group at 
http://groups.google.com/group/django-updates?hl=en
-~----------~----~----~----~------~----~------~--~---

Reply via email to