summaryrefslogtreecommitdiffstats
path: root/webapp/django/contrib/gis/db
diff options
context:
space:
mode:
Diffstat (limited to 'webapp/django/contrib/gis/db')
-rw-r--r--webapp/django/contrib/gis/db/__init__.py0
-rw-r--r--webapp/django/contrib/gis/db/backend/__init__.py18
-rw-r--r--webapp/django/contrib/gis/db/backend/adaptor.py14
-rw-r--r--webapp/django/contrib/gis/db/backend/base.py29
-rw-r--r--webapp/django/contrib/gis/db/backend/mysql/__init__.py13
-rw-r--r--webapp/django/contrib/gis/db/backend/mysql/creation.py5
-rw-r--r--webapp/django/contrib/gis/db/backend/mysql/field.py53
-rw-r--r--webapp/django/contrib/gis/db/backend/mysql/query.py59
-rw-r--r--webapp/django/contrib/gis/db/backend/oracle/__init__.py31
-rw-r--r--webapp/django/contrib/gis/db/backend/oracle/adaptor.py5
-rw-r--r--webapp/django/contrib/gis/db/backend/oracle/creation.py6
-rw-r--r--webapp/django/contrib/gis/db/backend/oracle/field.py103
-rw-r--r--webapp/django/contrib/gis/db/backend/oracle/models.py49
-rw-r--r--webapp/django/contrib/gis/db/backend/oracle/query.py154
-rw-r--r--webapp/django/contrib/gis/db/backend/postgis/__init__.py42
-rw-r--r--webapp/django/contrib/gis/db/backend/postgis/adaptor.py33
-rw-r--r--webapp/django/contrib/gis/db/backend/postgis/creation.py224
-rw-r--r--webapp/django/contrib/gis/db/backend/postgis/field.py95
-rw-r--r--webapp/django/contrib/gis/db/backend/postgis/management.py54
-rw-r--r--webapp/django/contrib/gis/db/backend/postgis/models.py58
-rw-r--r--webapp/django/contrib/gis/db/backend/postgis/query.py287
-rw-r--r--webapp/django/contrib/gis/db/backend/util.py52
-rw-r--r--webapp/django/contrib/gis/db/models/__init__.py17
-rw-r--r--webapp/django/contrib/gis/db/models/fields/__init__.py211
-rw-r--r--webapp/django/contrib/gis/db/models/manager.py82
-rw-r--r--webapp/django/contrib/gis/db/models/mixin.py11
-rw-r--r--webapp/django/contrib/gis/db/models/proxy.py62
-rw-r--r--webapp/django/contrib/gis/db/models/query.py617
-rw-r--r--webapp/django/contrib/gis/db/models/sql/__init__.py2
-rw-r--r--webapp/django/contrib/gis/db/models/sql/query.py327
-rw-r--r--webapp/django/contrib/gis/db/models/sql/where.py64
31 files changed, 2777 insertions, 0 deletions
diff --git a/webapp/django/contrib/gis/db/__init__.py b/webapp/django/contrib/gis/db/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/webapp/django/contrib/gis/db/__init__.py
diff --git a/webapp/django/contrib/gis/db/backend/__init__.py b/webapp/django/contrib/gis/db/backend/__init__.py
new file mode 100644
index 0000000000..172c1268a7
--- /dev/null
+++ b/webapp/django/contrib/gis/db/backend/__init__.py
@@ -0,0 +1,18 @@
+"""
+ This module provides the backend for spatial SQL construction with Django.
+
+ Specifically, this module will import the correct routines and modules
+ needed for GeoDjango to interface with the spatial database.
+"""
+from django.conf import settings
+from django.contrib.gis.db.backend.util import gqn
+
+# Retrieving the necessary settings from the backend.
+if settings.DATABASE_ENGINE == 'postgresql_psycopg2':
+ from django.contrib.gis.db.backend.postgis import create_spatial_db, get_geo_where_clause, SpatialBackend
+elif settings.DATABASE_ENGINE == 'oracle':
+ from django.contrib.gis.db.backend.oracle import create_spatial_db, get_geo_where_clause, SpatialBackend
+elif settings.DATABASE_ENGINE == 'mysql':
+ from django.contrib.gis.db.backend.mysql import create_spatial_db, get_geo_where_clause, SpatialBackend
+else:
+ raise NotImplementedError('No Geographic Backend exists for %s' % settings.DATABASE_ENGINE)
diff --git a/webapp/django/contrib/gis/db/backend/adaptor.py b/webapp/django/contrib/gis/db/backend/adaptor.py
new file mode 100644
index 0000000000..b2397e61dd
--- /dev/null
+++ b/webapp/django/contrib/gis/db/backend/adaptor.py
@@ -0,0 +1,14 @@
+class WKTAdaptor(object):
+ """
+ This provides an adaptor for Geometries sent to the
+ MySQL and Oracle database backends.
+ """
+ def __init__(self, geom):
+ self.wkt = geom.wkt
+ self.srid = geom.srid
+
+ def __eq__(self, other):
+ return self.wkt == other.wkt and self.srid == other.srid
+
+ def __str__(self):
+ return self.wkt
diff --git a/webapp/django/contrib/gis/db/backend/base.py b/webapp/django/contrib/gis/db/backend/base.py
new file mode 100644
index 0000000000..d45ac7b6f1
--- /dev/null
+++ b/webapp/django/contrib/gis/db/backend/base.py
@@ -0,0 +1,29 @@
+"""
+ This module holds the base `SpatialBackend` object, which is
+ instantiated by each spatial backend with the features it has.
+"""
+# TODO: Create a `Geometry` protocol and allow user to use
+# different Geometry objects -- for now we just use GEOSGeometry.
+from django.contrib.gis.geos import GEOSGeometry, GEOSException
+
+class BaseSpatialBackend(object):
+ Geometry = GEOSGeometry
+ GeometryException = GEOSException
+
+ def __init__(self, **kwargs):
+ kwargs.setdefault('distance_functions', {})
+ kwargs.setdefault('limited_where', {})
+ for k, v in kwargs.iteritems(): setattr(self, k, v)
+
+ def __getattr__(self, name):
+ """
+ All attributes of the spatial backend return False by default.
+ """
+ try:
+ return self.__dict__[name]
+ except KeyError:
+ return False
+
+
+
+
diff --git a/webapp/django/contrib/gis/db/backend/mysql/__init__.py b/webapp/django/contrib/gis/db/backend/mysql/__init__.py
new file mode 100644
index 0000000000..0484e5f9b2
--- /dev/null
+++ b/webapp/django/contrib/gis/db/backend/mysql/__init__.py
@@ -0,0 +1,13 @@
+__all__ = ['create_spatial_db', 'get_geo_where_clause', 'SpatialBackend']
+
+from django.contrib.gis.db.backend.base import BaseSpatialBackend
+from django.contrib.gis.db.backend.adaptor import WKTAdaptor
+from django.contrib.gis.db.backend.mysql.creation import create_spatial_db
+from django.contrib.gis.db.backend.mysql.field import MySQLGeoField
+from django.contrib.gis.db.backend.mysql.query import *
+
+SpatialBackend = BaseSpatialBackend(name='mysql', mysql=True,
+ gis_terms=MYSQL_GIS_TERMS,
+ select=GEOM_SELECT,
+ Adaptor=WKTAdaptor,
+ Field=MySQLGeoField)
diff --git a/webapp/django/contrib/gis/db/backend/mysql/creation.py b/webapp/django/contrib/gis/db/backend/mysql/creation.py
new file mode 100644
index 0000000000..3da21a0cdd
--- /dev/null
+++ b/webapp/django/contrib/gis/db/backend/mysql/creation.py
@@ -0,0 +1,5 @@
+
+def create_spatial_db(test=True, verbosity=1, autoclobber=False):
+ if not test: raise NotImplementedError('This uses `create_test_db` from test/utils.py')
+ from django.db import connection
+ connection.creation.create_test_db(verbosity, autoclobber)
diff --git a/webapp/django/contrib/gis/db/backend/mysql/field.py b/webapp/django/contrib/gis/db/backend/mysql/field.py
new file mode 100644
index 0000000000..f3151f93ff
--- /dev/null
+++ b/webapp/django/contrib/gis/db/backend/mysql/field.py
@@ -0,0 +1,53 @@
+from django.db import connection
+from django.db.models.fields import Field # Django base Field class
+from django.contrib.gis.db.backend.mysql.query import GEOM_FROM_TEXT
+
+# Quotename & geographic quotename, respectively.
+qn = connection.ops.quote_name
+
+class MySQLGeoField(Field):
+ """
+ The backend-specific geographic field for MySQL.
+ """
+
+ def _geom_index(self, style, db_table):
+ """
+ Creates a spatial index for the geometry column. If MyISAM tables are
+ used an R-Tree index is created, otherwise a B-Tree index is created.
+ Thus, for best spatial performance, you should use MyISAM tables
+ (which do not support transactions). For more information, see Ch.
+ 16.6.1 of the MySQL 5.0 documentation.
+ """
+
+ # Getting the index name.
+ idx_name = '%s_%s_id' % (db_table, self.column)
+
+ sql = style.SQL_KEYWORD('CREATE SPATIAL INDEX ') + \
+ style.SQL_TABLE(qn(idx_name)) + \
+ style.SQL_KEYWORD(' ON ') + \
+ style.SQL_TABLE(qn(db_table)) + '(' + \
+ style.SQL_FIELD(qn(self.column)) + ');'
+ return sql
+
+ def _post_create_sql(self, style, db_table):
+ """
+ Returns SQL that will be executed after the model has been
+ created.
+ """
+ # Getting the geometric index for this Geometry column.
+ if self._index:
+ return (self._geom_index(style, db_table),)
+ else:
+ return ()
+
+ def db_type(self):
+ "The OpenGIS name is returned for the MySQL database column type."
+ return self._geom
+
+ def get_placeholder(self, value):
+ """
+ The placeholder here has to include MySQL's WKT constructor. Because
+ MySQL does not support spatial transformations, there is no need to
+ modify the placeholder based on the contents of the given value.
+ """
+ return '%s(%%s)' % GEOM_FROM_TEXT
diff --git a/webapp/django/contrib/gis/db/backend/mysql/query.py b/webapp/django/contrib/gis/db/backend/mysql/query.py
new file mode 100644
index 0000000000..2fa984f325
--- /dev/null
+++ b/webapp/django/contrib/gis/db/backend/mysql/query.py
@@ -0,0 +1,59 @@
+"""
+ This module contains the spatial lookup types, and the `get_geo_where_clause`
+ routine for MySQL.
+
+ Please note that MySQL only supports bounding box queries, also
+ known as MBRs (Minimum Bounding Rectangles). Moreover, spatial
+ indices may only be used on MyISAM tables -- if you need
+ transactions, take a look at PostGIS.
+"""
+from django.db import connection
+qn = connection.ops.quote_name
+
+# To ease implementation, WKT is passed to/from MySQL.
+GEOM_FROM_TEXT = 'GeomFromText'
+GEOM_FROM_WKB = 'GeomFromWKB'
+GEOM_SELECT = 'AsText(%s)'
+
+# WARNING: MySQL is NOT compliant w/the OpenGIS specification and
+# _every_ one of these lookup types is on the _bounding box_ only.
+MYSQL_GIS_FUNCTIONS = {
+ 'bbcontains' : 'MBRContains', # For consistency w/PostGIS API
+ 'bboverlaps' : 'MBROverlaps', # .. ..
+ 'contained' : 'MBRWithin', # .. ..
+ 'contains' : 'MBRContains',
+ 'disjoint' : 'MBRDisjoint',
+ 'equals' : 'MBREqual',
+ 'exact' : 'MBREqual',
+ 'intersects' : 'MBRIntersects',
+ 'overlaps' : 'MBROverlaps',
+ 'same_as' : 'MBREqual',
+ 'touches' : 'MBRTouches',
+ 'within' : 'MBRWithin',
+ }
+
+# This lookup type does not require a mapping.
+MISC_TERMS = ['isnull']
+
+# Assacceptable lookup types for Oracle spatial.
+MYSQL_GIS_TERMS = MYSQL_GIS_FUNCTIONS.keys()
+MYSQL_GIS_TERMS += MISC_TERMS
+MYSQL_GIS_TERMS = dict((term, None) for term in MYSQL_GIS_TERMS) # Making dictionary
+
+def get_geo_where_clause(table_alias, name, lookup_type, geo_annot):
+ "Returns the SQL WHERE clause for use in MySQL spatial SQL construction."
+ # Getting the quoted field as `geo_col`.
+ geo_col = '%s.%s' % (qn(table_alias), qn(name))
+
+ # See if a MySQL Geometry function matches the lookup type next
+ lookup_info = MYSQL_GIS_FUNCTIONS.get(lookup_type, False)
+ if lookup_info:
+ return "%s(%s, %%s)" % (lookup_info, geo_col)
+
+ # Handling 'isnull' lookup type
+ # TODO: Is this needed because MySQL cannot handle NULL
+ # geometries in its spatial indices.
+ if lookup_type == 'isnull':
+ return "%s IS %sNULL" % (geo_col, (not geo_annot.value and 'NOT ' or ''))
+
+ raise TypeError("Got invalid lookup_type: %s" % repr(lookup_type))
diff --git a/webapp/django/contrib/gis/db/backend/oracle/__init__.py b/webapp/django/contrib/gis/db/backend/oracle/__init__.py
new file mode 100644
index 0000000000..3eee56ea23
--- /dev/null
+++ b/webapp/django/contrib/gis/db/backend/oracle/__init__.py
@@ -0,0 +1,31 @@
+__all__ = ['create_spatial_db', 'get_geo_where_clause', 'SpatialBackend']
+
+from django.contrib.gis.db.backend.base import BaseSpatialBackend
+from django.contrib.gis.db.backend.oracle.adaptor import OracleSpatialAdaptor
+from django.contrib.gis.db.backend.oracle.creation import create_spatial_db
+from django.contrib.gis.db.backend.oracle.field import OracleSpatialField
+from django.contrib.gis.db.backend.oracle.query import *
+
+SpatialBackend = BaseSpatialBackend(name='oracle', oracle=True,
+ area=AREA,
+ centroid=CENTROID,
+ difference=DIFFERENCE,
+ distance=DISTANCE,
+ distance_functions=DISTANCE_FUNCTIONS,
+ gis_terms=ORACLE_SPATIAL_TERMS,
+ gml=ASGML,
+ intersection=INTERSECTION,
+ length=LENGTH,
+ limited_where = {'relate' : None},
+ num_geom=NUM_GEOM,
+ num_points=NUM_POINTS,
+ perimeter=LENGTH,
+ point_on_surface=POINT_ON_SURFACE,
+ select=GEOM_SELECT,
+ sym_difference=SYM_DIFFERENCE,
+ transform=TRANSFORM,
+ unionagg=UNIONAGG,
+ union=UNION,
+ Adaptor=OracleSpatialAdaptor,
+ Field=OracleSpatialField,
+ )
diff --git a/webapp/django/contrib/gis/db/backend/oracle/adaptor.py b/webapp/django/contrib/gis/db/backend/oracle/adaptor.py
new file mode 100644
index 0000000000..95dc265795
--- /dev/null
+++ b/webapp/django/contrib/gis/db/backend/oracle/adaptor.py
@@ -0,0 +1,5 @@
+from cx_Oracle import CLOB
+from django.contrib.gis.db.backend.adaptor import WKTAdaptor
+
+class OracleSpatialAdaptor(WKTAdaptor):
+ input_size = CLOB
diff --git a/webapp/django/contrib/gis/db/backend/oracle/creation.py b/webapp/django/contrib/gis/db/backend/oracle/creation.py
new file mode 100644
index 0000000000..d9b53d2049
--- /dev/null
+++ b/webapp/django/contrib/gis/db/backend/oracle/creation.py
@@ -0,0 +1,6 @@
+
+def create_spatial_db(test=True, verbosity=1, autoclobber=False):
+ "A wrapper over the Oracle `create_test_db` routine."
+ if not test: raise NotImplementedError('This uses `create_test_db` from db/backends/oracle/creation.py')
+ from django.db import connection
+ connection.creation.create_test_db(verbosity, autoclobber)
diff --git a/webapp/django/contrib/gis/db/backend/oracle/field.py b/webapp/django/contrib/gis/db/backend/oracle/field.py
new file mode 100644
index 0000000000..22625f5e10
--- /dev/null
+++ b/webapp/django/contrib/gis/db/backend/oracle/field.py
@@ -0,0 +1,103 @@
+from django.db import connection
+from django.db.backends.util import truncate_name
+from django.db.models.fields import Field # Django base Field class
+from django.contrib.gis.db.backend.util import gqn
+from django.contrib.gis.db.backend.oracle.query import TRANSFORM
+
+# Quotename & geographic quotename, respectively.
+qn = connection.ops.quote_name
+
+class OracleSpatialField(Field):
+ """
+ The backend-specific geographic field for Oracle Spatial.
+ """
+
+ empty_strings_allowed = False
+
+ def __init__(self, extent=(-180.0, -90.0, 180.0, 90.0), tolerance=0.05, **kwargs):
+ """
+ Oracle Spatial backend needs to have the extent -- for projected coordinate
+ systems _you must define the extent manually_, since the coordinates are
+ for geodetic systems. The `tolerance` keyword specifies the tolerance
+ for error (in meters), and defaults to 0.05 (5 centimeters).
+ """
+ # Oracle Spatial specific keyword arguments.
+ self._extent = extent
+ self._tolerance = tolerance
+ # Calling the Django field initialization.
+ super(OracleSpatialField, self).__init__(**kwargs)
+
+ def _add_geom(self, style, db_table):
+ """
+ Adds this geometry column into the Oracle USER_SDO_GEOM_METADATA
+ table.
+ """
+
+ # Checking the dimensions.
+ # TODO: Add support for 3D geometries.
+ if self._dim != 2:
+ raise Exception('3D geometries not yet supported on Oracle Spatial backend.')
+
+ # Constructing the SQL that will be used to insert information about
+ # the geometry column into the USER_GSDO_GEOM_METADATA table.
+ meta_sql = style.SQL_KEYWORD('INSERT INTO ') + \
+ style.SQL_TABLE('USER_SDO_GEOM_METADATA') + \
+ ' (%s, %s, %s, %s)\n ' % tuple(map(qn, ['TABLE_NAME', 'COLUMN_NAME', 'DIMINFO', 'SRID'])) + \
+ style.SQL_KEYWORD(' VALUES ') + '(\n ' + \
+ style.SQL_TABLE(gqn(db_table)) + ',\n ' + \
+ style.SQL_FIELD(gqn(self.column)) + ',\n ' + \
+ style.SQL_KEYWORD("MDSYS.SDO_DIM_ARRAY") + '(\n ' + \
+ style.SQL_KEYWORD("MDSYS.SDO_DIM_ELEMENT") + \
+ ("('LONG', %s, %s, %s),\n " % (self._extent[0], self._extent[2], self._tolerance)) + \
+ style.SQL_KEYWORD("MDSYS.SDO_DIM_ELEMENT") + \
+ ("('LAT', %s, %s, %s)\n ),\n" % (self._extent[1], self._extent[3], self._tolerance)) + \
+ ' %s\n );' % self._srid
+ return meta_sql
+
+ def _geom_index(self, style, db_table):
+ "Creates an Oracle Geometry index (R-tree) for this geometry field."
+
+ # Getting the index name, Oracle doesn't allow object
+ # names > 30 characters.
+ idx_name = truncate_name('%s_%s_id' % (db_table, self.column), 30)
+
+ sql = style.SQL_KEYWORD('CREATE INDEX ') + \
+ style.SQL_TABLE(qn(idx_name)) + \
+ style.SQL_KEYWORD(' ON ') + \
+ style.SQL_TABLE(qn(db_table)) + '(' + \
+ style.SQL_FIELD(qn(self.column)) + ') ' + \
+ style.SQL_KEYWORD('INDEXTYPE IS ') + \
+ style.SQL_TABLE('MDSYS.SPATIAL_INDEX') + ';'
+ return sql
+
+ def post_create_sql(self, style, db_table):
+ """
+ Returns SQL that will be executed after the model has been
+ created.
+ """
+ # Getting the meta geometry information.
+ post_sql = self._add_geom(style, db_table)
+
+ # Getting the geometric index for this Geometry column.
+ if self._index:
+ return (post_sql, self._geom_index(style, db_table))
+ else:
+ return (post_sql,)
+
+ def db_type(self):
+ "The Oracle geometric data type is MDSYS.SDO_GEOMETRY."
+ return 'MDSYS.SDO_GEOMETRY'
+
+ def get_placeholder(self, value):
+ """
+ Provides a proper substitution value for Geometries that are not in the
+ SRID of the field. Specifically, this routine will substitute in the
+ SDO_CS.TRANSFORM() function call.
+ """
+ if value is None:
+ return '%s'
+ elif value.srid != self._srid:
+ # Adding Transform() to the SQL placeholder.
+ return '%s(SDO_GEOMETRY(%%s, %s), %s)' % (TRANSFORM, value.srid, self._srid)
+ else:
+ return 'SDO_GEOMETRY(%%s, %s)' % self._srid
diff --git a/webapp/django/contrib/gis/db/backend/oracle/models.py b/webapp/django/contrib/gis/db/backend/oracle/models.py
new file mode 100644
index 0000000000..c740b48efe
--- /dev/null
+++ b/webapp/django/contrib/gis/db/backend/oracle/models.py
@@ -0,0 +1,49 @@
+"""
+ The GeometryColumns and SpatialRefSys models for the Oracle spatial
+ backend.
+
+ It should be noted that Oracle Spatial does not have database tables
+ named according to the OGC standard, so the closest analogs are used.
+ For example, the `USER_SDO_GEOM_METADATA` is used for the GeometryColumns
+ model and the `SDO_COORD_REF_SYS` is used for the SpatialRefSys model.
+"""
+from django.db import models
+from django.contrib.gis.models import SpatialRefSysMixin
+
+class GeometryColumns(models.Model):
+ "Maps to the Oracle USER_SDO_GEOM_METADATA table."
+ table_name = models.CharField(max_length=32)
+ column_name = models.CharField(max_length=1024)
+ srid = models.IntegerField(primary_key=True)
+ # TODO: Add support for `diminfo` column (type MDSYS.SDO_DIM_ARRAY).
+ class Meta:
+ db_table = 'USER_SDO_GEOM_METADATA'
+
+ @classmethod
+ def table_name_col(cls):
+ return 'table_name'
+
+ def __unicode__(self):
+ return '%s - %s (SRID: %s)' % (self.table_name, self.column_name, self.srid)
+
+class SpatialRefSys(models.Model, SpatialRefSysMixin):
+ "Maps to the Oracle MDSYS.CS_SRS table."
+ cs_name = models.CharField(max_length=68)
+ srid = models.IntegerField(primary_key=True)
+ auth_srid = models.IntegerField()
+ auth_name = models.CharField(max_length=256)
+ wktext = models.CharField(max_length=2046)
+ #cs_bounds = models.GeometryField()
+
+ class Meta:
+ # TODO: Figure out way to have this be MDSYS.CS_SRS without
+ # having django's quoting mess up the SQL.
+ db_table = 'CS_SRS'
+
+ @property
+ def wkt(self):
+ return self.wktext
+
+ @classmethod
+ def wkt_col(cls):
+ return 'wktext'
diff --git a/webapp/django/contrib/gis/db/backend/oracle/query.py b/webapp/django/contrib/gis/db/backend/oracle/query.py
new file mode 100644
index 0000000000..dcf6f67ae2
--- /dev/null
+++ b/webapp/django/contrib/gis/db/backend/oracle/query.py
@@ -0,0 +1,154 @@
+"""
+ This module contains the spatial lookup types, and the `get_geo_where_clause`
+ routine for Oracle Spatial.
+
+ Please note that WKT support is broken on the XE version, and thus
+ this backend will not work on such platforms. Specifically, XE lacks
+ support for an internal JVM, and Java libraries are required to use
+ the WKT constructors.
+"""
+import re
+from decimal import Decimal
+from django.db import connection
+from django.contrib.gis.db.backend.util import SpatialFunction
+from django.contrib.gis.measure import Distance
+qn = connection.ops.quote_name
+
+# The GML, distance, transform, and union procedures.
+AREA = 'SDO_GEOM.SDO_AREA'
+ASGML = 'SDO_UTIL.TO_GMLGEOMETRY'
+CENTROID = 'SDO_GEOM.SDO_CENTROID'
+DIFFERENCE = 'SDO_GEOM.SDO_DIFFERENCE'
+DISTANCE = 'SDO_GEOM.SDO_DISTANCE'
+EXTENT = 'SDO_AGGR_MBR'
+INTERSECTION = 'SDO_GEOM.SDO_INTERSECTION'
+LENGTH = 'SDO_GEOM.SDO_LENGTH'
+NUM_GEOM = 'SDO_UTIL.GETNUMELEM'
+NUM_POINTS = 'SDO_UTIL.GETNUMVERTICES'
+POINT_ON_SURFACE = 'SDO_GEOM.SDO_POINTONSURFACE'
+SYM_DIFFERENCE = 'SDO_GEOM.SDO_XOR'
+TRANSFORM = 'SDO_CS.TRANSFORM'
+UNION = 'SDO_GEOM.SDO_UNION'
+UNIONAGG = 'SDO_AGGR_UNION'
+
+# We want to get SDO Geometries as WKT because it is much easier to
+# instantiate GEOS proxies from WKT than SDO_GEOMETRY(...) strings.
+# However, this adversely affects performance (i.e., Java is called
+# to convert to WKT on every query). If someone wishes to write a
+# SDO_GEOMETRY(...) parser in Python, let me know =)
+GEOM_SELECT = 'SDO_UTIL.TO_WKTGEOMETRY(%s)'
+
+#### Classes used in constructing Oracle spatial SQL ####
+class SDOOperation(SpatialFunction):
+ "Base class for SDO* Oracle operations."
+ def __init__(self, func, **kwargs):
+ kwargs.setdefault('operator', '=')
+ kwargs.setdefault('result', 'TRUE')
+ kwargs.setdefault('end_subst', ") %s '%s'")
+ super(SDOOperation, self).__init__(func, **kwargs)
+
+class SDODistance(SpatialFunction):
+ "Class for Distance queries."
+ def __init__(self, op, tolerance=0.05):
+ super(SDODistance, self).__init__(DISTANCE, end_subst=', %s) %%s %%s' % tolerance,
+ operator=op, result='%%s')
+
+class SDOGeomRelate(SpatialFunction):
+ "Class for using SDO_GEOM.RELATE."
+ def __init__(self, mask, tolerance=0.05):
+ # SDO_GEOM.RELATE(...) has a peculiar argument order: column, mask, geom, tolerance.
+ # Moreover, the runction result is the mask (e.g., 'DISJOINT' instead of 'TRUE').
+ end_subst = "%s%s) %s '%s'" % (', %%s, ', tolerance, '=', mask)
+ beg_subst = "%%s(%%s, '%s'" % mask
+ super(SDOGeomRelate, self).__init__('SDO_GEOM.RELATE', beg_subst=beg_subst, end_subst=end_subst)
+
+class SDORelate(SpatialFunction):
+ "Class for using SDO_RELATE."
+ masks = 'TOUCH|OVERLAPBDYDISJOINT|OVERLAPBDYINTERSECT|EQUAL|INSIDE|COVEREDBY|CONTAINS|COVERS|ANYINTERACT|ON'
+ mask_regex = re.compile(r'^(%s)(\+(%s))*$' % (masks, masks), re.I)
+ def __init__(self, mask):
+ func = 'SDO_RELATE'
+ if not self.mask_regex.match(mask):
+ raise ValueError('Invalid %s mask: "%s"' % (func, mask))
+ super(SDORelate, self).__init__(func, end_subst=", 'mask=%s') = 'TRUE'" % mask)
+
+#### Lookup type mapping dictionaries of Oracle spatial operations ####
+
+# Valid distance types and substitutions
+dtypes = (Decimal, Distance, float, int, long)
+DISTANCE_FUNCTIONS = {
+ 'distance_gt' : (SDODistance('>'), dtypes),
+ 'distance_gte' : (SDODistance('>='), dtypes),
+ 'distance_lt' : (SDODistance('<'), dtypes),
+ 'distance_lte' : (SDODistance('<='), dtypes),
+ 'dwithin' : (SDOOperation('SDO_WITHIN_DISTANCE',
+ beg_subst="%s(%s, %%s, 'distance=%%s'"), dtypes),
+ }
+
+ORACLE_GEOMETRY_FUNCTIONS = {
+ 'contains' : SDOOperation('SDO_CONTAINS'),
+ 'coveredby' : SDOOperation('SDO_COVEREDBY'),
+ 'covers' : SDOOperation('SDO_COVERS'),
+ 'disjoint' : SDOGeomRelate('DISJOINT'),
+ 'intersects' : SDOOperation('SDO_OVERLAPBDYINTERSECT'), # TODO: Is this really the same as ST_Intersects()?
+ 'equals' : SDOOperation('SDO_EQUAL'),
+ 'exact' : SDOOperation('SDO_EQUAL'),
+ 'overlaps' : SDOOperation('SDO_OVERLAPS'),
+ 'same_as' : SDOOperation('SDO_EQUAL'),
+ 'relate' : (SDORelate, basestring), # Oracle uses a different syntax, e.g., 'mask=inside+touch'
+ 'touches' : SDOOperation('SDO_TOUCH'),
+ 'within' : SDOOperation('SDO_INSIDE'),
+ }
+ORACLE_GEOMETRY_FUNCTIONS.update(DISTANCE_FUNCTIONS)
+
+# This lookup type does not require a mapping.
+MISC_TERMS = ['isnull']
+
+# Acceptable lookup types for Oracle spatial.
+ORACLE_SPATIAL_TERMS = ORACLE_GEOMETRY_FUNCTIONS.keys()
+ORACLE_SPATIAL_TERMS += MISC_TERMS
+ORACLE_SPATIAL_TERMS = dict((term, None) for term in ORACLE_SPATIAL_TERMS) # Making dictionary for fast lookups
+
+#### The `get_geo_where_clause` function for Oracle ####
+def get_geo_where_clause(table_alias, name, lookup_type, geo_annot):
+ "Returns the SQL WHERE clause for use in Oracle spatial SQL construction."
+ # Getting the quoted table name as `geo_col`.
+ geo_col = '%s.%s' % (qn(table_alias), qn(name))
+
+ # See if a Oracle Geometry function matches the lookup type next
+ lookup_info = ORACLE_GEOMETRY_FUNCTIONS.get(lookup_type, False)
+ if lookup_info:
+ # Lookup types that are tuples take tuple arguments, e.g., 'relate' and
+ # 'dwithin' lookup types.
+ if isinstance(lookup_info, tuple):
+ # First element of tuple is lookup type, second element is the type
+ # of the expected argument (e.g., str, float)
+ sdo_op, arg_type = lookup_info
+
+ # Ensuring that a tuple _value_ was passed in from the user
+ if not isinstance(geo_annot.value, tuple):
+ raise TypeError('Tuple required for `%s` lookup type.' % lookup_type)
+ if len(geo_annot.value) != 2:
+ raise ValueError('2-element tuple required for %s lookup type.' % lookup_type)
+
+ # Ensuring the argument type matches what we expect.
+ if not isinstance(geo_annot.value[1], arg_type):
+ raise TypeError('Argument type should be %s, got %s instead.' % (arg_type, type(geo_annot.value[1])))
+
+ if lookup_type == 'relate':
+ # The SDORelate class handles construction for these queries,
+ # and verifies the mask argument.
+ return sdo_op(geo_annot.value[1]).as_sql(geo_col)
+ else:
+ # Otherwise, just call the `as_sql` method on the SDOOperation instance.
+ return sdo_op.as_sql(geo_col)
+ else:
+ # Lookup info is a SDOOperation instance, whose `as_sql` method returns
+ # the SQL necessary for the geometry function call. For example:
+ # SDO_CONTAINS("geoapp_country"."poly", SDO_GEOMTRY('POINT(5 23)', 4326)) = 'TRUE'
+ return lookup_info.as_sql(geo_col)
+ elif lookup_type == 'isnull':
+ # Handling 'isnull' lookup type
+ return "%s IS %sNULL" % (geo_col, (not geo_annot.value and 'NOT ' or ''))
+
+ raise TypeError("Got invalid lookup_type: %s" % repr(lookup_type))
diff --git a/webapp/django/contrib/gis/db/backend/postgis/__init__.py b/webapp/django/contrib/gis/db/backend/postgis/__init__.py
new file mode 100644
index 0000000000..8a4d09e0d5
--- /dev/null
+++ b/webapp/django/contrib/gis/db/backend/postgis/__init__.py
@@ -0,0 +1,42 @@
+__all__ = ['create_spatial_db', 'get_geo_where_clause', 'SpatialBackend']
+
+from django.contrib.gis.db.backend.base import BaseSpatialBackend
+from django.contrib.gis.db.backend.postgis.adaptor import PostGISAdaptor
+from django.contrib.gis.db.backend.postgis.creation import create_spatial_db
+from django.contrib.gis.db.backend.postgis.field import PostGISField
+from django.contrib.gis.db.backend.postgis.query import *
+
+SpatialBackend = BaseSpatialBackend(name='postgis', postgis=True,
+ area=AREA,
+ centroid=CENTROID,
+ difference=DIFFERENCE,
+ distance=DISTANCE,
+ distance_functions=DISTANCE_FUNCTIONS,
+ distance_sphere=DISTANCE_SPHERE,
+ distance_spheroid=DISTANCE_SPHEROID,
+ envelope=ENVELOPE,
+ extent=EXTENT,
+ gis_terms=POSTGIS_TERMS,
+ gml=ASGML,
+ intersection=INTERSECTION,
+ kml=ASKML,
+ length=LENGTH,
+ length_spheroid=LENGTH_SPHEROID,
+ make_line=MAKE_LINE,
+ mem_size=MEM_SIZE,
+ num_geom=NUM_GEOM,
+ num_points=NUM_POINTS,
+ perimeter=PERIMETER,
+ point_on_surface=POINT_ON_SURFACE,
+ scale=SCALE,
+ select=GEOM_SELECT,
+ svg=ASSVG,
+ sym_difference=SYM_DIFFERENCE,
+ transform=TRANSFORM,
+ translate=TRANSLATE,
+ union=UNION,
+ unionagg=UNIONAGG,
+ version=(MAJOR_VERSION, MINOR_VERSION1, MINOR_VERSION2),
+ Adaptor=PostGISAdaptor,
+ Field=PostGISField,
+ )
diff --git a/webapp/django/contrib/gis/db/backend/postgis/adaptor.py b/webapp/django/contrib/gis/db/backend/postgis/adaptor.py
new file mode 100644
index 0000000000..c094a9825a
--- /dev/null
+++ b/webapp/django/contrib/gis/db/backend/postgis/adaptor.py
@@ -0,0 +1,33 @@
+"""
+ This object provides quoting for GEOS geometries into PostgreSQL/PostGIS.
+"""
+
+from django.contrib.gis.db.backend.postgis.query import GEOM_FROM_WKB
+from psycopg2 import Binary
+from psycopg2.extensions import ISQLQuote
+
+class PostGISAdaptor(object):
+ def __init__(self, geom):
+ "Initializes on the geometry."
+ # Getting the WKB (in string form, to allow easy pickling of
+ # the adaptor) and the SRID from the geometry.
+ self.wkb = str(geom.wkb)
+ self.srid = geom.srid
+
+ def __conform__(self, proto):
+ # Does the given protocol conform to what Psycopg2 expects?
+ if proto == ISQLQuote:
+ return self
+ else:
+ raise Exception('Error implementing psycopg2 protocol. Is psycopg2 installed?')
+
+ def __eq__(self, other):
+ return (self.wkb == other.wkb) and (self.srid == other.srid)
+
+ def __str__(self):
+ return self.getquoted()
+
+ def getquoted(self):
+ "Returns a properly quoted string for use in PostgreSQL/PostGIS."
+ # Want to use WKB, so wrap with psycopg2 Binary() to quote properly.
+ return "%s(%s, %s)" % (GEOM_FROM_WKB, Binary(self.wkb), self.srid or -1)
diff --git a/webapp/django/contrib/gis/db/backend/postgis/creation.py b/webapp/django/contrib/gis/db/backend/postgis/creation.py
new file mode 100644
index 0000000000..44d9346364
--- /dev/null
+++ b/webapp/django/contrib/gis/db/backend/postgis/creation.py
@@ -0,0 +1,224 @@
+import os, re, sys
+
+from django.conf import settings
+from django.core.management import call_command
+from django.db import connection
+from django.db.backends.creation import TEST_DATABASE_PREFIX
+
+def getstatusoutput(cmd):
+ "A simpler version of getstatusoutput that works on win32 platforms."
+ stdin, stdout, stderr = os.popen3(cmd)
+ output = stdout.read()
+ if output.endswith('\n'): output = output[:-1]
+ status = stdin.close()
+ return status, output
+
+def create_lang(db_name, verbosity=1):
+ "Sets up the pl/pgsql language on the given database."
+
+ # Getting the command-line options for the shell command
+ options = get_cmd_options(db_name)
+
+ # Constructing the 'createlang' command.
+ createlang_cmd = 'createlang %splpgsql' % options
+ if verbosity >= 1: print createlang_cmd
+
+ # Must have database super-user privileges to execute createlang -- it must
+ # also be in your path.
+ status, output = getstatusoutput(createlang_cmd)
+
+ # Checking the status of the command, 0 => execution successful
+ if status:
+ raise Exception("Error executing 'plpgsql' command: %s\n" % output)
+
+def _create_with_cursor(db_name, verbosity=1, autoclobber=False):
+ "Creates database with psycopg2 cursor."
+
+ # Constructing the necessary SQL to create the database (the DATABASE_USER
+ # must possess the privileges to create a database)
+ create_sql = 'CREATE DATABASE %s' % connection.ops.quote_name(db_name)
+ if settings.DATABASE_USER:
+ create_sql += ' OWNER %s' % settings.DATABASE_USER
+
+ cursor = connection.cursor()
+ connection.creation.set_autocommit()
+
+ try:
+ # Trying to create the database first.
+ cursor.execute(create_sql)
+ #print create_sql
+ except Exception, e:
+ # Drop and recreate, if necessary.
+ if not autoclobber:
+ confirm = raw_input("\nIt appears the database, %s, already exists. Type 'yes' to delete it, or 'no' to cancel: " % db_name)
+ if autoclobber or confirm == 'yes':
+ if verbosity >= 1: print 'Destroying old spatial database...'
+ drop_db(db_name)
+ if verbosity >= 1: print 'Creating new spatial database...'
+ cursor.execute(create_sql)
+ else:
+ raise Exception('Spatial Database Creation canceled.')
+
+created_regex = re.compile(r'^createdb: database creation failed: ERROR: database ".+" already exists')
+def _create_with_shell(db_name, verbosity=1, autoclobber=False):
+ """
+ If no spatial database already exists, then using a cursor will not work.
+ Thus, a `createdb` command will be issued through the shell to bootstrap
+ creation of the spatial database.
+ """
+
+ # Getting the command-line options for the shell command
+ options = get_cmd_options(False)
+ create_cmd = 'createdb -O %s %s%s' % (settings.DATABASE_USER, options, db_name)
+ if verbosity >= 1: print create_cmd
+
+ # Attempting to create the database.
+ status, output = getstatusoutput(create_cmd)
+
+ if status:
+ if created_regex.match(output):
+ if not autoclobber:
+ confirm = raw_input("\nIt appears the database, %s, already exists. Type 'yes' to delete it, or 'no' to cancel: " % db_name)
+ if autoclobber or confirm == 'yes':
+ if verbosity >= 1: print 'Destroying old spatial database...'
+ drop_cmd = 'dropdb %s%s' % (options, db_name)
+ status, output = getstatusoutput(drop_cmd)
+ if status != 0:
+ raise Exception('Could not drop database %s: %s' % (db_name, output))
+ if verbosity >= 1: print 'Creating new spatial database...'
+ status, output = getstatusoutput(create_cmd)
+ if status != 0:
+ raise Exception('Could not create database after dropping: %s' % output)
+ else:
+ raise Exception('Spatial Database Creation canceled.')
+ else:
+ raise Exception('Unknown error occurred in creating database: %s' % output)
+
+def create_spatial_db(test=False, verbosity=1, autoclobber=False, interactive=False):
+ "Creates a spatial database based on the settings."
+
+ # Making sure we're using PostgreSQL and psycopg2
+ if settings.DATABASE_ENGINE != 'postgresql_psycopg2':
+ raise Exception('Spatial database creation only supported postgresql_psycopg2 platform.')
+
+ # Getting the spatial database name
+ if test:
+ db_name = get_spatial_db(test=True)
+ _create_with_cursor(db_name, verbosity=verbosity, autoclobber=autoclobber)
+ else:
+ db_name = get_spatial_db()
+ _create_with_shell(db_name, verbosity=verbosity, autoclobber=autoclobber)
+
+ # Creating the db language, does not need to be done on NT platforms
+ # since the PostGIS installer enables this capability.
+ if os.name != 'nt':
+ create_lang(db_name, verbosity=verbosity)
+
+ # Now adding in the PostGIS routines.
+ load_postgis_sql(db_name, verbosity=verbosity)
+
+ if verbosity >= 1: print 'Creation of spatial database %s successful.' % db_name
+
+ # Closing the connection
+ connection.close()
+ settings.DATABASE_NAME = db_name
+
+ # Syncing the database
+ call_command('syncdb', verbosity=verbosity, interactive=interactive)
+
+def drop_db(db_name=False, test=False):
+ """
+ Drops the given database (defaults to what is returned from
+ get_spatial_db()). All exceptions are propagated up to the caller.
+ """
+ if not db_name: db_name = get_spatial_db(test=test)
+ cursor = connection.cursor()
+ cursor.execute('DROP DATABASE %s' % connection.ops.quote_name(db_name))
+
+def get_cmd_options(db_name):
+ "Obtains the command-line PostgreSQL connection options for shell commands."
+ # The db_name parameter is optional
+ options = ''
+ if db_name:
+ options += '-d %s ' % db_name
+ if settings.DATABASE_USER:
+ options += '-U %s ' % settings.DATABASE_USER
+ if settings.DATABASE_HOST:
+ options += '-h %s ' % settings.DATABASE_HOST
+ if settings.DATABASE_PORT:
+ options += '-p %s ' % settings.DATABASE_PORT
+ return options
+
+def get_spatial_db(test=False):
+ """
+ Returns the name of the spatial database. The 'test' keyword may be set
+ to return the test spatial database name.
+ """
+ if test:
+ if settings.TEST_DATABASE_NAME:
+ test_db_name = settings.TEST_DATABASE_NAME
+ else:
+ test_db_name = TEST_DATABASE_PREFIX + settings.DATABASE_NAME
+ return test_db_name
+ else:
+ if not settings.DATABASE_NAME:
+ raise Exception('must configure DATABASE_NAME in settings.py')
+ return settings.DATABASE_NAME
+
+def load_postgis_sql(db_name, verbosity=1):
+ """
+ This routine loads up the PostGIS SQL files lwpostgis.sql and
+ spatial_ref_sys.sql.
+ """
+
+ # Getting the path to the PostGIS SQL
+ try:
+ # POSTGIS_SQL_PATH may be placed in settings to tell GeoDjango where the
+ # PostGIS SQL files are located. This is especially useful on Win32
+ # platforms since the output of pg_config looks like "C:/PROGRA~1/..".
+ sql_path = settings.POSTGIS_SQL_PATH
+ except AttributeError:
+ status, sql_path = getstatusoutput('pg_config --sharedir')
+ if status:
+ sql_path = '/usr/local/share'
+
+ # The PostGIS SQL post-creation files.
+ lwpostgis_file = os.path.join(sql_path, 'lwpostgis.sql')
+ srefsys_file = os.path.join(sql_path, 'spatial_ref_sys.sql')
+ if not os.path.isfile(lwpostgis_file):
+ raise Exception('Could not find PostGIS function definitions in %s' % lwpostgis_file)
+ if not os.path.isfile(srefsys_file):
+ raise Exception('Could not find PostGIS spatial reference system definitions in %s' % srefsys_file)
+
+ # Getting the psql command-line options, and command format.
+ options = get_cmd_options(db_name)
+ cmd_fmt = 'psql %s-f "%%s"' % options
+
+ # Now trying to load up the PostGIS functions
+ cmd = cmd_fmt % lwpostgis_file
+ if verbosity >= 1: print cmd
+ status, output = getstatusoutput(cmd)
+ if status:
+ raise Exception('Error in loading PostGIS lwgeometry routines.')
+
+ # Now trying to load up the Spatial Reference System table
+ cmd = cmd_fmt % srefsys_file
+ if verbosity >= 1: print cmd
+ status, output = getstatusoutput(cmd)
+ if status:
+ raise Exception('Error in loading PostGIS spatial_ref_sys table.')
+
+ # Setting the permissions because on Windows platforms the owner
+ # of the spatial_ref_sys and geometry_columns tables is always
+ # the postgres user, regardless of how the db is created.
+ if os.name == 'nt': set_permissions(db_name)
+
+def set_permissions(db_name):
+ """
+ Sets the permissions on the given database to that of the user specified
+ in the settings. Needed specifically for PostGIS on Win32 platforms.
+ """
+ cursor = connection.cursor()
+ user = settings.DATABASE_USER
+ cursor.execute('ALTER TABLE geometry_columns OWNER TO %s' % user)
+ cursor.execute('ALTER TABLE spatial_ref_sys OWNER TO %s' % user)
diff --git a/webapp/django/contrib/gis/db/backend/postgis/field.py b/webapp/django/contrib/gis/db/backend/postgis/field.py
new file mode 100644
index 0000000000..9d6c0fad24
--- /dev/null
+++ b/webapp/django/contrib/gis/db/backend/postgis/field.py
@@ -0,0 +1,95 @@
+from django.db import connection
+from django.db.models.fields import Field # Django base Field class
+from django.contrib.gis.db.backend.util import gqn
+from django.contrib.gis.db.backend.postgis.query import TRANSFORM
+
+# Quotename & geographic quotename, respectively
+qn = connection.ops.quote_name
+
+class PostGISField(Field):
+ """
+ The backend-specific geographic field for PostGIS.
+ """
+
+ def _add_geom(self, style, db_table):
+ """
+ Constructs the addition of the geometry to the table using the
+ AddGeometryColumn(...) PostGIS (and OGC standard) stored procedure.
+
+ Takes the style object (provides syntax highlighting) and the
+ database table as parameters.
+ """
+ sql = style.SQL_KEYWORD('SELECT ') + \
+ style.SQL_TABLE('AddGeometryColumn') + '(' + \
+ style.SQL_TABLE(gqn(db_table)) + ', ' + \
+ style.SQL_FIELD(gqn(self.column)) + ', ' + \
+ style.SQL_FIELD(str(self._srid)) + ', ' + \
+ style.SQL_COLTYPE(gqn(self._geom)) + ', ' + \
+ style.SQL_KEYWORD(str(self._dim)) + ');'
+
+ if not self.null:
+ # Add a NOT NULL constraint to the field
+ sql += '\n' + \
+ style.SQL_KEYWORD('ALTER TABLE ') + \
+ style.SQL_TABLE(qn(db_table)) + \
+ style.SQL_KEYWORD(' ALTER ') + \
+ style.SQL_FIELD(qn(self.column)) + \
+ style.SQL_KEYWORD(' SET NOT NULL') + ';'
+ return sql
+
+ def _geom_index(self, style, db_table,
+ index_type='GIST', index_opts='GIST_GEOMETRY_OPS'):
+ "Creates a GiST index for this geometry field."
+ sql = style.SQL_KEYWORD('CREATE INDEX ') + \
+ style.SQL_TABLE(qn('%s_%s_id' % (db_table, self.column))) + \
+ style.SQL_KEYWORD(' ON ') + \
+ style.SQL_TABLE(qn(db_table)) + \
+ style.SQL_KEYWORD(' USING ') + \
+ style.SQL_COLTYPE(index_type) + ' ( ' + \
+ style.SQL_FIELD(qn(self.column)) + ' ' + \
+ style.SQL_KEYWORD(index_opts) + ' );'
+ return sql
+
+ def post_create_sql(self, style, db_table):
+ """
+ Returns SQL that will be executed after the model has been
+ created. Geometry columns must be added after creation with the
+ PostGIS AddGeometryColumn() function.
+ """
+
+ # Getting the AddGeometryColumn() SQL necessary to create a PostGIS
+ # geometry field.
+ post_sql = self._add_geom(style, db_table)
+
+ # If the user wants to index this data, then get the indexing SQL as well.
+ if self._index:
+ return (post_sql, self._geom_index(style, db_table))
+ else:
+ return (post_sql,)
+
+ def _post_delete_sql(self, style, db_table):
+ "Drops the geometry column."
+ sql = style.SQL_KEYWORD('SELECT ') + \
+ style.SQL_KEYWORD('DropGeometryColumn') + '(' + \
+ style.SQL_TABLE(gqn(db_table)) + ', ' + \
+ style.SQL_FIELD(gqn(self.column)) + ');'
+ return sql
+
+ def db_type(self):
+ """
+ PostGIS geometry columns are added by stored procedures, should be
+ None.
+ """
+ return None
+
+ def get_placeholder(self, value):
+ """
+ Provides a proper substitution value for Geometries that are not in the
+ SRID of the field. Specifically, this routine will substitute in the
+ ST_Transform() function call.
+ """
+ if value is None or value.srid == self._srid:
+ return '%s'
+ else:
+ # Adding Transform() to the SQL placeholder.
+ return '%s(%%s, %s)' % (TRANSFORM, self._srid)
diff --git a/webapp/django/contrib/gis/db/backend/postgis/management.py b/webapp/django/contrib/gis/db/backend/postgis/management.py
new file mode 100644
index 0000000000..c1cb32a04f
--- /dev/null
+++ b/webapp/django/contrib/gis/db/backend/postgis/management.py
@@ -0,0 +1,54 @@
+"""
+ This utility module is for obtaining information about the PostGIS
+ installation.
+
+ See PostGIS docs at Ch. 6.2.1 for more information on these functions.
+"""
+import re
+
+def _get_postgis_func(func):
+ "Helper routine for calling PostGIS functions and returning their result."
+ from django.db import connection
+ cursor = connection.cursor()
+ cursor.execute('SELECT %s()' % func)
+ row = cursor.fetchone()
+ cursor.close()
+ return row[0]
+
+### PostGIS management functions ###
+def postgis_geos_version():
+ "Returns the version of the GEOS library used with PostGIS."
+ return _get_postgis_func('postgis_geos_version')
+
+def postgis_lib_version():
+ "Returns the version number of the PostGIS library used with PostgreSQL."
+ return _get_postgis_func('postgis_lib_version')
+
+def postgis_proj_version():
+ "Returns the version of the PROJ.4 library used with PostGIS."
+ return _get_postgis_func('postgis_proj_version')
+
+def postgis_version():
+ "Returns PostGIS version number and compile-time options."
+ return _get_postgis_func('postgis_version')
+
+def postgis_full_version():
+ "Returns PostGIS version number and compile-time options."
+ return _get_postgis_func('postgis_full_version')
+
+### Routines for parsing output of management functions. ###
+version_regex = re.compile('^(?P<major>\d)\.(?P<minor1>\d)\.(?P<minor2>\d+)')
+def postgis_version_tuple():
+ "Returns the PostGIS version as a tuple."
+
+ # Getting the PostGIS version
+ version = postgis_lib_version()
+ m = version_regex.match(version)
+ if m:
+ major = int(m.group('major'))
+ minor1 = int(m.group('minor1'))
+ minor2 = int(m.group('minor2'))
+ else:
+ raise Exception('Could not parse PostGIS version string: %s' % version)
+
+ return (version, major, minor1, minor2)
diff --git a/webapp/django/contrib/gis/db/backend/postgis/models.py b/webapp/django/contrib/gis/db/backend/postgis/models.py
new file mode 100644
index 0000000000..e032da4d89
--- /dev/null
+++ b/webapp/django/contrib/gis/db/backend/postgis/models.py
@@ -0,0 +1,58 @@
+"""
+ The GeometryColumns and SpatialRefSys models for the PostGIS backend.
+"""
+from django.db import models
+from django.contrib.gis.models import SpatialRefSysMixin
+
+# Checking for the presence of GDAL (needed for the SpatialReference object)
+from django.contrib.gis.gdal import HAS_GDAL
+if HAS_GDAL:
+ from django.contrib.gis.gdal import SpatialReference
+
+class GeometryColumns(models.Model):
+ """
+ The 'geometry_columns' table from the PostGIS. See the PostGIS
+ documentation at Ch. 4.2.2.
+ """
+ f_table_catalog = models.CharField(max_length=256)
+ f_table_schema = models.CharField(max_length=256)
+ f_table_name = models.CharField(max_length=256)
+ f_geometry_column = models.CharField(max_length=256)
+ coord_dimension = models.IntegerField()
+ srid = models.IntegerField(primary_key=True)
+ type = models.CharField(max_length=30)
+
+ class Meta:
+ db_table = 'geometry_columns'
+
+ @classmethod
+ def table_name_col(cls):
+ "Class method for returning the table name column for this model."
+ return 'f_table_name'
+
+ def __unicode__(self):
+ return "%s.%s - %dD %s field (SRID: %d)" % \
+ (self.f_table_name, self.f_geometry_column,
+ self.coord_dimension, self.type, self.srid)
+
+class SpatialRefSys(models.Model, SpatialRefSysMixin):
+ """
+ The 'spatial_ref_sys' table from PostGIS. See the PostGIS
+ documentaiton at Ch. 4.2.1.
+ """
+ srid = models.IntegerField(primary_key=True)
+ auth_name = models.CharField(max_length=256)
+ auth_srid = models.IntegerField()
+ srtext = models.CharField(max_length=2048)
+ proj4text = models.CharField(max_length=2048)
+
+ class Meta:
+ db_table = 'spatial_ref_sys'
+
+ @property
+ def wkt(self):
+ return self.srtext
+
+ @classmethod
+ def wkt_col(cls):
+ return 'srtext'
diff --git a/webapp/django/contrib/gis/db/backend/postgis/query.py b/webapp/django/contrib/gis/db/backend/postgis/query.py
new file mode 100644
index 0000000000..8780780402
--- /dev/null
+++ b/webapp/django/contrib/gis/db/backend/postgis/query.py
@@ -0,0 +1,287 @@
+"""
+ This module contains the spatial lookup types, and the get_geo_where_clause()
+ routine for PostGIS.
+"""
+import re
+from decimal import Decimal
+from django.db import connection
+from django.contrib.gis.measure import Distance
+from django.contrib.gis.db.backend.postgis.management import postgis_version_tuple
+from django.contrib.gis.db.backend.util import SpatialOperation, SpatialFunction
+qn = connection.ops.quote_name
+
+# Getting the PostGIS version information
+POSTGIS_VERSION, MAJOR_VERSION, MINOR_VERSION1, MINOR_VERSION2 = postgis_version_tuple()
+
+# The supported PostGIS versions.
+# TODO: Confirm tests with PostGIS versions 1.1.x -- should work.
+# Versions <= 1.0.x do not use GEOS C API, and will not be supported.
+if MAJOR_VERSION != 1 or (MAJOR_VERSION == 1 and MINOR_VERSION1 < 1):
+ raise Exception('PostGIS version %s not supported.' % POSTGIS_VERSION)
+
+# Versions of PostGIS >= 1.2.2 changed their naming convention to be
+# 'SQL-MM-centric' to conform with the ISO standard. Practically, this
+# means that 'ST_' prefixes geometry function names.
+GEOM_FUNC_PREFIX = ''
+if MAJOR_VERSION >= 1:
+ if (MINOR_VERSION1 > 2 or
+ (MINOR_VERSION1 == 2 and MINOR_VERSION2 >= 2)):
+ GEOM_FUNC_PREFIX = 'ST_'
+
+ def get_func(func): return '%s%s' % (GEOM_FUNC_PREFIX, func)
+
+ # Custom selection not needed for PostGIS because GEOS geometries are
+ # instantiated directly from the HEXEWKB returned by default. If
+ # WKT is needed for some reason in the future, this value may be changed,
+ # e.g,, 'AsText(%s)'.
+ GEOM_SELECT = None
+
+ # Functions used by the GeoManager & GeoQuerySet
+ AREA = get_func('Area')
+ ASKML = get_func('AsKML')
+ ASGML = get_func('AsGML')
+ ASSVG = get_func('AsSVG')
+ CENTROID = get_func('Centroid')
+ DIFFERENCE = get_func('Difference')
+ DISTANCE = get_func('Distance')
+ DISTANCE_SPHERE = get_func('distance_sphere')
+ DISTANCE_SPHEROID = get_func('distance_spheroid')
+ ENVELOPE = get_func('Envelope')
+ EXTENT = get_func('extent')
+ GEOM_FROM_TEXT = get_func('GeomFromText')
+ GEOM_FROM_WKB = get_func('GeomFromWKB')
+ INTERSECTION = get_func('Intersection')
+ LENGTH = get_func('Length')
+ LENGTH_SPHEROID = get_func('length_spheroid')
+ MAKE_LINE = get_func('MakeLine')
+ MEM_SIZE = get_func('mem_size')
+ NUM_GEOM = get_func('NumGeometries')
+ NUM_POINTS = get_func('npoints')
+ PERIMETER = get_func('Perimeter')
+ POINT_ON_SURFACE = get_func('PointOnSurface')
+ SCALE = get_func('Scale')
+ SYM_DIFFERENCE = get_func('SymDifference')
+ TRANSFORM = get_func('Transform')
+ TRANSLATE = get_func('Translate')
+
+ # Special cases for union and KML methods.
+ if MINOR_VERSION1 < 3:
+ UNIONAGG = 'GeomUnion'
+ UNION = 'Union'
+ else:
+ UNIONAGG = 'ST_Union'
+ UNION = 'ST_Union'
+
+ if MINOR_VERSION1 == 1:
+ ASKML = False
+else:
+ raise NotImplementedError('PostGIS versions < 1.0 are not supported.')
+
+#### Classes used in constructing PostGIS spatial SQL ####
+class PostGISOperator(SpatialOperation):
+ "For PostGIS operators (e.g. `&&`, `~`)."
+ def __init__(self, operator):
+ super(PostGISOperator, self).__init__(operator=operator, beg_subst='%s %s %%s')
+
+class PostGISFunction(SpatialFunction):
+ "For PostGIS function calls (e.g., `ST_Contains(table, geom)`)."
+ def __init__(self, function, **kwargs):
+ super(PostGISFunction, self).__init__(get_func(function), **kwargs)
+
+class PostGISFunctionParam(PostGISFunction):
+ "For PostGIS functions that take another parameter (e.g. DWithin, Relate)."
+ def __init__(self, func):
+ super(PostGISFunctionParam, self).__init__(func, end_subst=', %%s)')
+
+class PostGISDistance(PostGISFunction):
+ "For PostGIS distance operations."
+ dist_func = 'Distance'
+ def __init__(self, operator):
+ super(PostGISDistance, self).__init__(self.dist_func, end_subst=') %s %s',
+ operator=operator, result='%%s')
+
+class PostGISSpheroidDistance(PostGISFunction):
+ "For PostGIS spherical distance operations (using the spheroid)."
+ dist_func = 'distance_spheroid'
+ def __init__(self, operator):
+ # An extra parameter in `end_subst` is needed for the spheroid string.
+ super(PostGISSpheroidDistance, self).__init__(self.dist_func,
+ beg_subst='%s(%s, %%s, %%s',
+ end_subst=') %s %s',
+ operator=operator, result='%%s')
+
+class PostGISSphereDistance(PostGISFunction):
+ "For PostGIS spherical distance operations."
+ dist_func = 'distance_sphere'
+ def __init__(self, operator):
+ super(PostGISSphereDistance, self).__init__(self.dist_func, end_subst=') %s %s',
+ operator=operator, result='%%s')
+
+class PostGISRelate(PostGISFunctionParam):
+ "For PostGIS Relate(<geom>, <pattern>) calls."
+ pattern_regex = re.compile(r'^[012TF\*]{9}$')
+ def __init__(self, pattern):
+ if not self.pattern_regex.match(pattern):
+ raise ValueError('Invalid intersection matrix pattern "%s".' % pattern)
+ super(PostGISRelate, self).__init__('Relate')
+
+#### Lookup type mapping dictionaries of PostGIS operations. ####
+
+# PostGIS-specific operators. The commented descriptions of these
+# operators come from Section 6.2.2 of the official PostGIS documentation.
+POSTGIS_OPERATORS = {
+ # The "&<" operator returns true if A's bounding box overlaps or
+ # is to the left of B's bounding box.
+ 'overlaps_left' : PostGISOperator('&<'),
+ # The "&>" operator returns true if A's bounding box overlaps or
+ # is to the right of B's bounding box.
+ 'overlaps_right' : PostGISOperator('&>'),
+ # The "<<" operator returns true if A's bounding box is strictly
+ # to the left of B's bounding box.
+ 'left' : PostGISOperator('<<'),
+ # The ">>" operator returns true if A's bounding box is strictly
+ # to the right of B's bounding box.
+ 'right' : PostGISOperator('>>'),
+ # The "&<|" operator returns true if A's bounding box overlaps or
+ # is below B's bounding box.
+ 'overlaps_below' : PostGISOperator('&<|'),
+ # The "|&>" operator returns true if A's bounding box overlaps or
+ # is above B's bounding box.
+ 'overlaps_above' : PostGISOperator('|&>'),
+ # The "<<|" operator returns true if A's bounding box is strictly
+ # below B's bounding box.
+ 'strictly_below' : PostGISOperator('<<|'),
+ # The "|>>" operator returns true if A's bounding box is strictly
+ # above B's bounding box.
+ 'strictly_above' : PostGISOperator('|>>'),
+ # The "~=" operator is the "same as" operator. It tests actual
+ # geometric equality of two features. So if A and B are the same feature,
+ # vertex-by-vertex, the operator returns true.
+ 'same_as' : PostGISOperator('~='),
+ 'exact' : PostGISOperator('~='),
+ # The "@" operator returns true if A's bounding box is completely contained
+ # by B's bounding box.
+ 'contained' : PostGISOperator('@'),
+ # The "~" operator returns true if A's bounding box completely contains
+ # by B's bounding box.
+ 'bbcontains' : PostGISOperator('~'),
+ # The "&&" operator returns true if A's bounding box overlaps
+ # B's bounding box.
+ 'bboverlaps' : PostGISOperator('&&'),
+ }
+
+# For PostGIS >= 1.2.2 the following lookup types will do a bounding box query
+# first before calling the more computationally expensive GEOS routines (called
+# "inline index magic"):
+# 'touches', 'crosses', 'contains', 'intersects', 'within', 'overlaps', and
+# 'covers'.
+POSTGIS_GEOMETRY_FUNCTIONS = {
+ 'equals' : PostGISFunction('Equals'),
+ 'disjoint' : PostGISFunction('Disjoint'),
+ 'touches' : PostGISFunction('Touches'),
+ 'crosses' : PostGISFunction('Crosses'),
+ 'within' : PostGISFunction('Within'),
+ 'overlaps' : PostGISFunction('Overlaps'),
+ 'contains' : PostGISFunction('Contains'),
+ 'intersects' : PostGISFunction('Intersects'),
+ 'relate' : (PostGISRelate, basestring),
+ }
+
+# Valid distance types and substitutions
+dtypes = (Decimal, Distance, float, int, long)
+def get_dist_ops(operator):
+ "Returns operations for both regular and spherical distances."
+ return (PostGISDistance(operator), PostGISSphereDistance(operator), PostGISSpheroidDistance(operator))
+DISTANCE_FUNCTIONS = {
+ 'distance_gt' : (get_dist_ops('>'), dtypes),
+ 'distance_gte' : (get_dist_ops('>='), dtypes),
+ 'distance_lt' : (get_dist_ops('<'), dtypes),
+ 'distance_lte' : (get_dist_ops('<='), dtypes),
+ }
+
+if GEOM_FUNC_PREFIX == 'ST_':
+ # The ST_DWithin, ST_CoveredBy, and ST_Covers routines become available in 1.2.2+
+ POSTGIS_GEOMETRY_FUNCTIONS.update(
+ {'coveredby' : PostGISFunction('CoveredBy'),
+ 'covers' : PostGISFunction('Covers'),
+ })
+ DISTANCE_FUNCTIONS['dwithin'] = (PostGISFunctionParam('DWithin'), dtypes)
+
+# Distance functions are a part of PostGIS geometry functions.
+POSTGIS_GEOMETRY_FUNCTIONS.update(DISTANCE_FUNCTIONS)
+
+# Any other lookup types that do not require a mapping.
+MISC_TERMS = ['isnull']
+
+# These are the PostGIS-customized QUERY_TERMS -- a list of the lookup types
+# allowed for geographic queries.
+POSTGIS_TERMS = POSTGIS_OPERATORS.keys() # Getting the operators first
+POSTGIS_TERMS += POSTGIS_GEOMETRY_FUNCTIONS.keys() # Adding on the Geometry Functions
+POSTGIS_TERMS += MISC_TERMS # Adding any other miscellaneous terms (e.g., 'isnull')
+POSTGIS_TERMS = dict((term, None) for term in POSTGIS_TERMS) # Making a dictionary for fast lookups
+
+# For checking tuple parameters -- not very pretty but gets job done.
+def exactly_two(val): return val == 2
+def two_to_three(val): return val >= 2 and val <=3
+def num_params(lookup_type, val):
+ if lookup_type in DISTANCE_FUNCTIONS and lookup_type != 'dwithin': return two_to_three(val)
+ else: return exactly_two(val)
+
+#### The `get_geo_where_clause` function for PostGIS. ####
+def get_geo_where_clause(table_alias, name, lookup_type, geo_annot):
+ "Returns the SQL WHERE clause for use in PostGIS SQL construction."
+ # Getting the quoted field as `geo_col`.
+ geo_col = '%s.%s' % (qn(table_alias), qn(name))
+ if lookup_type in POSTGIS_OPERATORS:
+ # See if a PostGIS operator matches the lookup type.
+ return POSTGIS_OPERATORS[lookup_type].as_sql(geo_col)
+ elif lookup_type in POSTGIS_GEOMETRY_FUNCTIONS:
+ # See if a PostGIS geometry function matches the lookup type.
+ tmp = POSTGIS_GEOMETRY_FUNCTIONS[lookup_type]
+
+ # Lookup types that are tuples take tuple arguments, e.g., 'relate' and
+ # distance lookups.
+ if isinstance(tmp, tuple):
+ # First element of tuple is the PostGISOperation instance, and the
+ # second element is either the type or a tuple of acceptable types
+ # that may passed in as further parameters for the lookup type.
+ op, arg_type = tmp
+
+ # Ensuring that a tuple _value_ was passed in from the user
+ if not isinstance(geo_annot.value, (tuple, list)):
+ raise TypeError('Tuple required for `%s` lookup type.' % lookup_type)
+
+ # Number of valid tuple parameters depends on the lookup type.
+ nparams = len(geo_annot.value)
+ if not num_params(lookup_type, nparams):
+ raise ValueError('Incorrect number of parameters given for `%s` lookup type.' % lookup_type)
+
+ # Ensuring the argument type matches what we expect.
+ if not isinstance(geo_annot.value[1], arg_type):
+ raise TypeError('Argument type should be %s, got %s instead.' % (arg_type, type(geo_annot.value[1])))
+
+ # For lookup type `relate`, the op instance is not yet created (has
+ # to be instantiated here to check the pattern parameter).
+ if lookup_type == 'relate':
+ op = op(geo_annot.value[1])
+ elif lookup_type in DISTANCE_FUNCTIONS and lookup_type != 'dwithin':
+ if geo_annot.geodetic:
+ # Geodetic distances are only availble from Points to PointFields.
+ if geo_annot.geom_type != 'POINT':
+ raise TypeError('PostGIS spherical operations are only valid on PointFields.')
+ if geo_annot.value[0].geom_typeid != 0:
+ raise TypeError('PostGIS geometry distance parameter is required to be of type Point.')
+ # Setting up the geodetic operation appropriately.
+ if nparams == 3 and geo_annot.value[2] == 'spheroid': op = op[2]
+ else: op = op[1]
+ else:
+ op = op[0]
+ else:
+ op = tmp
+ # Calling the `as_sql` function on the operation instance.
+ return op.as_sql(geo_col)
+ elif lookup_type == 'isnull':
+ # Handling 'isnull' lookup type
+ return "%s IS %sNULL" % (geo_col, (not geo_annot.value and 'NOT ' or ''))
+
+ raise TypeError("Got invalid lookup_type: %s" % repr(lookup_type))
diff --git a/webapp/django/contrib/gis/db/backend/util.py b/webapp/django/contrib/gis/db/backend/util.py
new file mode 100644
index 0000000000..a19dd975c1
--- /dev/null
+++ b/webapp/django/contrib/gis/db/backend/util.py
@@ -0,0 +1,52 @@
+from types import UnicodeType
+
+def gqn(val):
+ """
+ The geographic quote name function; used for quoting tables and
+ geometries (they use single rather than the double quotes of the
+ backend quotename function).
+ """
+ if isinstance(val, basestring):
+ if isinstance(val, UnicodeType): val = val.encode('ascii')
+ return "'%s'" % val
+ else:
+ return str(val)
+
+class SpatialOperation(object):
+ """
+ Base class for generating spatial SQL.
+ """
+ def __init__(self, function='', operator='', result='', beg_subst='', end_subst=''):
+ self.function = function
+ self.operator = operator
+ self.result = result
+ self.beg_subst = beg_subst
+ try:
+ # Try and put the operator and result into to the
+ # end substitution.
+ self.end_subst = end_subst % (operator, result)
+ except TypeError:
+ self.end_subst = end_subst
+
+ @property
+ def sql_subst(self):
+ return ''.join([self.beg_subst, self.end_subst])
+
+ def as_sql(self, geo_col):
+ return self.sql_subst % self.params(geo_col)
+
+ def params(self, geo_col):
+ return (geo_col, self.operator)
+
+class SpatialFunction(SpatialOperation):
+ """
+ Base class for generating spatial SQL related to a function.
+ """
+ def __init__(self, func, beg_subst='%s(%s, %%s', end_subst=')', result='', operator=''):
+ # Getting the function prefix.
+ kwargs = {'function' : func, 'operator' : operator, 'result' : result,
+ 'beg_subst' : beg_subst, 'end_subst' : end_subst,}
+ super(SpatialFunction, self).__init__(**kwargs)
+
+ def params(self, geo_col):
+ return (self.function, geo_col)
diff --git a/webapp/django/contrib/gis/db/models/__init__.py b/webapp/django/contrib/gis/db/models/__init__.py
new file mode 100644
index 0000000000..02a2c5318e
--- /dev/null
+++ b/webapp/django/contrib/gis/db/models/__init__.py
@@ -0,0 +1,17 @@
+# Want to get everything from the 'normal' models package.
+from django.db.models import *
+
+# The GeoManager
+from django.contrib.gis.db.models.manager import GeoManager
+
+# The GeoQ object
+from django.contrib.gis.db.models.query import GeoQ
+
+# The geographic-enabled fields.
+from django.contrib.gis.db.models.fields import \
+ GeometryField, PointField, LineStringField, PolygonField, \
+ MultiPointField, MultiLineStringField, MultiPolygonField, \
+ GeometryCollectionField
+
+# The geographic mixin class.
+from mixin import GeoMixin
diff --git a/webapp/django/contrib/gis/db/models/fields/__init__.py b/webapp/django/contrib/gis/db/models/fields/__init__.py
new file mode 100644
index 0000000000..a1dfa23eb4
--- /dev/null
+++ b/webapp/django/contrib/gis/db/models/fields/__init__.py
@@ -0,0 +1,211 @@
+from django.contrib.gis import forms
+# Getting the SpatialBackend container and the geographic quoting method.
+from django.contrib.gis.db.backend import SpatialBackend, gqn
+# GeometryProxy, GEOS, and Distance imports.
+from django.contrib.gis.db.models.proxy import GeometryProxy
+from django.contrib.gis.measure import Distance
+# The `get_srid_info` function gets SRID information from the spatial
+# reference system table w/o using the ORM.
+from django.contrib.gis.models import get_srid_info
+
+#TODO: Flesh out widgets; consider adding support for OGR Geometry proxies.
+class GeometryField(SpatialBackend.Field):
+ "The base GIS field -- maps to the OpenGIS Specification Geometry type."
+
+ # The OpenGIS Geometry name.
+ _geom = 'GEOMETRY'
+
+ # Geodetic units.
+ geodetic_units = ('Decimal Degree', 'degree')
+
+ def __init__(self, verbose_name=None, srid=4326, spatial_index=True, dim=2, **kwargs):
+ """
+ The initialization function for geometry fields. Takes the following
+ as keyword arguments:
+
+ srid:
+ The spatial reference system identifier, an OGC standard.
+ Defaults to 4326 (WGS84).
+
+ spatial_index:
+ Indicates whether to create a spatial index. Defaults to True.
+ Set this instead of 'db_index' for geographic fields since index
+ creation is different for geometry columns.
+
+ dim:
+ The number of dimensions for this geometry. Defaults to 2.
+ """
+
+ # Setting the index flag with the value of the `spatial_index` keyword.
+ self._index = spatial_index
+
+ # Setting the SRID and getting the units. Unit information must be
+ # easily available in the field instance for distance queries.
+ self._srid = srid
+ self._unit, self._unit_name, self._spheroid = get_srid_info(srid)
+
+ # Setting the dimension of the geometry field.
+ self._dim = dim
+
+ # Setting the verbose_name keyword argument with the positional
+ # first parameter, so this works like normal fields.
+ kwargs['verbose_name'] = verbose_name
+
+ super(GeometryField, self).__init__(**kwargs) # Calling the parent initializtion function
+
+ ### Routines specific to GeometryField ###
+ @property
+ def geodetic(self):
+ """
+ Returns true if this field's SRID corresponds with a coordinate
+ system that uses non-projected units (e.g., latitude/longitude).
+ """
+ return self._unit_name in self.geodetic_units
+
+ def get_distance(self, dist_val, lookup_type):
+ """
+ Returns a distance number in units of the field. For example, if
+ `D(km=1)` was passed in and the units of the field were in meters,
+ then 1000 would be returned.
+ """
+ # Getting the distance parameter and any options.
+ if len(dist_val) == 1: dist, option = dist_val[0], None
+ else: dist, option = dist_val
+
+ if isinstance(dist, Distance):
+ if self.geodetic:
+ # Won't allow Distance objects w/DWithin lookups on PostGIS.
+ if SpatialBackend.postgis and lookup_type == 'dwithin':
+ raise TypeError('Only numeric values of degree units are allowed on geographic DWithin queries.')
+ # Spherical distance calculation parameter should be in meters.
+ dist_param = dist.m
+ else:
+ dist_param = getattr(dist, Distance.unit_attname(self._unit_name))
+ else:
+ # Assuming the distance is in the units of the field.
+ dist_param = dist
+
+ if SpatialBackend.postgis and self.geodetic and lookup_type != 'dwithin' and option == 'spheroid':
+ # On PostGIS, by default `ST_distance_sphere` is used; but if the
+ # accuracy of `ST_distance_spheroid` is needed than the spheroid
+ # needs to be passed to the SQL stored procedure.
+ return [gqn(self._spheroid), dist_param]
+ else:
+ return [dist_param]
+
+ def get_geometry(self, value):
+ """
+ Retrieves the geometry, setting the default SRID from the given
+ lookup parameters.
+ """
+ if isinstance(value, (tuple, list)):
+ geom = value[0]
+ else:
+ geom = value
+
+ # When the input is not a GEOS geometry, attempt to construct one
+ # from the given string input.
+ if isinstance(geom, SpatialBackend.Geometry):
+ pass
+ elif isinstance(geom, basestring):
+ try:
+ geom = SpatialBackend.Geometry(geom)
+ except SpatialBackend.GeometryException:
+ raise ValueError('Could not create geometry from lookup value: %s' % str(value))
+ else:
+ raise TypeError('Cannot use parameter of `%s` type as lookup parameter.' % type(value))
+
+ # Assigning the SRID value.
+ geom.srid = self.get_srid(geom)
+
+ return geom
+
+ def get_srid(self, geom):
+ """
+ Returns the default SRID for the given geometry, taking into account
+ the SRID set for the field. For example, if the input geometry
+ has no SRID, then that of the field will be returned.
+ """
+ gsrid = geom.srid # SRID of given geometry.
+ if gsrid is None or self._srid == -1 or (gsrid == -1 and self._srid != -1):
+ return self._srid
+ else:
+ return gsrid
+
+ ### Routines overloaded from Field ###
+ def contribute_to_class(self, cls, name):
+ super(GeometryField, self).contribute_to_class(cls, name)
+
+ # Setup for lazy-instantiated Geometry object.
+ setattr(cls, self.attname, GeometryProxy(SpatialBackend.Geometry, self))
+
+ def formfield(self, **kwargs):
+ defaults = {'form_class' : forms.GeometryField,
+ 'geom_type' : self._geom,
+ 'null' : self.null,
+ }
+ defaults.update(kwargs)
+ return super(GeometryField, self).formfield(**defaults)
+
+ def get_db_prep_lookup(self, lookup_type, value):
+ """
+ Returns the spatial WHERE clause and associated parameters for the
+ given lookup type and value. The value will be prepared for database
+ lookup (e.g., spatial transformation SQL will be added if necessary).
+ """
+ if lookup_type in SpatialBackend.gis_terms:
+ # special case for isnull lookup
+ if lookup_type == 'isnull': return [], []
+
+ # Get the geometry with SRID; defaults SRID to that of the field
+ # if it is None.
+ geom = self.get_geometry(value)
+
+ # Getting the WHERE clause list and the associated params list. The params
+ # list is populated with the Adaptor wrapping the Geometry for the
+ # backend. The WHERE clause list contains the placeholder for the adaptor
+ # (e.g. any transformation SQL).
+ where = [self.get_placeholder(geom)]
+ params = [SpatialBackend.Adaptor(geom)]
+
+ if isinstance(value, (tuple, list)):
+ if lookup_type in SpatialBackend.distance_functions:
+ # Getting the distance parameter in the units of the field.
+ where += self.get_distance(value[1:], lookup_type)
+ elif lookup_type in SpatialBackend.limited_where:
+ pass
+ else:
+ # Otherwise, making sure any other parameters are properly quoted.
+ where += map(gqn, value[1:])
+ return where, params
+ else:
+ raise TypeError("Field has invalid lookup: %s" % lookup_type)
+
+ def get_db_prep_save(self, value):
+ "Prepares the value for saving in the database."
+ if value is None:
+ return None
+ else:
+ return SpatialBackend.Adaptor(self.get_geometry(value))
+
+# The OpenGIS Geometry Type Fields
+class PointField(GeometryField):
+ _geom = 'POINT'
+
+class LineStringField(GeometryField):
+ _geom = 'LINESTRING'
+
+class PolygonField(GeometryField):
+ _geom = 'POLYGON'
+
+class MultiPointField(GeometryField):
+ _geom = 'MULTIPOINT'
+
+class MultiLineStringField(GeometryField):
+ _geom = 'MULTILINESTRING'
+
+class MultiPolygonField(GeometryField):
+ _geom = 'MULTIPOLYGON'
+
+class GeometryCollectionField(GeometryField):
+ _geom = 'GEOMETRYCOLLECTION'
diff --git a/webapp/django/contrib/gis/db/models/manager.py b/webapp/django/contrib/gis/db/models/manager.py
new file mode 100644
index 0000000000..602d11251a
--- /dev/null
+++ b/webapp/django/contrib/gis/db/models/manager.py
@@ -0,0 +1,82 @@
+from django.db.models.manager import Manager
+from django.contrib.gis.db.models.query import GeoQuerySet
+
+class GeoManager(Manager):
+ "Overrides Manager to return Geographic QuerySets."
+
+ # This manager should be used for queries on related fields
+ # so that geometry columns on Oracle and MySQL are selected
+ # properly.
+ use_for_related_fields = True
+
+ def get_query_set(self):
+ return GeoQuerySet(model=self.model)
+
+ def area(self, *args, **kwargs):
+ return self.get_query_set().area(*args, **kwargs)
+
+ def centroid(self, *args, **kwargs):
+ return self.get_query_set().centroid(*args, **kwargs)
+
+ def difference(self, *args, **kwargs):
+ return self.get_query_set().difference(*args, **kwargs)
+
+ def distance(self, *args, **kwargs):
+ return self.get_query_set().distance(*args, **kwargs)
+
+ def envelope(self, *args, **kwargs):
+ return self.get_query_set().envelope(*args, **kwargs)
+
+ def extent(self, *args, **kwargs):
+ return self.get_query_set().extent(*args, **kwargs)
+
+ def gml(self, *args, **kwargs):
+ return self.get_query_set().gml(*args, **kwargs)
+
+ def intersection(self, *args, **kwargs):
+ return self.get_query_set().intersection(*args, **kwargs)
+
+ def kml(self, *args, **kwargs):
+ return self.get_query_set().kml(*args, **kwargs)
+
+ def length(self, *args, **kwargs):
+ return self.get_query_set().length(*args, **kwargs)
+
+ def make_line(self, *args, **kwargs):
+ return self.get_query_set().make_line(*args, **kwargs)
+
+ def mem_size(self, *args, **kwargs):
+ return self.get_query_set().mem_size(*args, **kwargs)
+
+ def num_geom(self, *args, **kwargs):
+ return self.get_query_set().num_geom(*args, **kwargs)
+
+ def num_points(self, *args, **kwargs):
+ return self.get_query_set().num_points(*args, **kwargs)
+
+ def perimeter(self, *args, **kwargs):
+ return self.get_query_set().perimeter(*args, **kwargs)
+
+ def point_on_surface(self, *args, **kwargs):
+ return self.get_query_set().point_on_surface(*args, **kwargs)
+
+ def scale(self, *args, **kwargs):
+ return self.get_query_set().scale(*args, **kwargs)
+
+ def svg(self, *args, **kwargs):
+ return self.get_query_set().svg(*args, **kwargs)
+
+ def sym_difference(self, *args, **kwargs):
+ return self.get_query_set().sym_difference(*args, **kwargs)
+
+ def transform(self, *args, **kwargs):
+ return self.get_query_set().transform(*args, **kwargs)
+
+ def translate(self, *args, **kwargs):
+ return self.get_query_set().translate(*args, **kwargs)
+
+ def union(self, *args, **kwargs):
+ return self.get_query_set().union(*args, **kwargs)
+
+ def unionagg(self, *args, **kwargs):
+ return self.get_query_set().unionagg(*args, **kwargs)
diff --git a/webapp/django/contrib/gis/db/models/mixin.py b/webapp/django/contrib/gis/db/models/mixin.py
new file mode 100644
index 0000000000..475a053b8f
--- /dev/null
+++ b/webapp/django/contrib/gis/db/models/mixin.py
@@ -0,0 +1,11 @@
+# Until model subclassing is a possibility, a mixin class is used to add
+# the necessary functions that may be contributed for geographic objects.
+class GeoMixin:
+ """
+ The Geographic Mixin class provides routines for geographic objects,
+ however, it is no longer necessary, since all of its previous functions
+ may now be accessed via the GeometryProxy. This mixin is only provided
+ for backwards-compatibility purposes, and will be eventually removed
+ (unless the need arises again).
+ """
+ pass
diff --git a/webapp/django/contrib/gis/db/models/proxy.py b/webapp/django/contrib/gis/db/models/proxy.py
new file mode 100644
index 0000000000..34276a6d63
--- /dev/null
+++ b/webapp/django/contrib/gis/db/models/proxy.py
@@ -0,0 +1,62 @@
+"""
+ The GeometryProxy object, allows for lazy-geometries. The proxy uses
+ Python descriptors for instantiating and setting Geometry objects
+ corresponding to geographic model fields.
+
+ Thanks to Robert Coup for providing this functionality (see #4322).
+"""
+
+from types import NoneType, StringType, UnicodeType
+
+class GeometryProxy(object):
+ def __init__(self, klass, field):
+ """
+ Proxy initializes on the given Geometry class (not an instance) and
+ the GeometryField.
+ """
+ self._field = field
+ self._klass = klass
+
+ def __get__(self, obj, type=None):
+ """
+ This accessor retrieves the geometry, initializing it using the geometry
+ class specified during initialization and the HEXEWKB value of the field.
+ Currently, only GEOS or OGR geometries are supported.
+ """
+ # Getting the value of the field.
+ geom_value = obj.__dict__[self._field.attname]
+
+ if isinstance(geom_value, self._klass):
+ geom = geom_value
+ elif (geom_value is None) or (geom_value==''):
+ geom = None
+ else:
+ # Otherwise, a Geometry object is built using the field's contents,
+ # and the model's corresponding attribute is set.
+ geom = self._klass(geom_value)
+ setattr(obj, self._field.attname, geom)
+ return geom
+
+ def __set__(self, obj, value):
+ """
+ This accessor sets the proxied geometry with the geometry class
+ specified during initialization. Values of None, HEXEWKB, or WKT may
+ be used to set the geometry as well.
+ """
+ # The OGC Geometry type of the field.
+ gtype = self._field._geom
+
+ # The geometry type must match that of the field -- unless the
+ # general GeometryField is used.
+ if isinstance(value, self._klass) and (str(value.geom_type).upper() == gtype or gtype == 'GEOMETRY'):
+ # Assigning the SRID to the geometry.
+ if value.srid is None: value.srid = self._field._srid
+ elif isinstance(value, (NoneType, StringType, UnicodeType)):
+ # Set with None, WKT, or HEX
+ pass
+ else:
+ raise TypeError('cannot set %s GeometryProxy with value of type: %s' % (obj.__class__.__name__, type(value)))
+
+ # Setting the objects dictionary with the value, and returning.
+ obj.__dict__[self._field.attname] = value
+ return value
diff --git a/webapp/django/contrib/gis/db/models/query.py b/webapp/django/contrib/gis/db/models/query.py
new file mode 100644
index 0000000000..8efc720333
--- /dev/null
+++ b/webapp/django/contrib/gis/db/models/query.py
@@ -0,0 +1,617 @@
+from django.core.exceptions import ImproperlyConfigured
+from django.db import connection
+from django.db.models.query import sql, QuerySet, Q
+
+from django.contrib.gis.db.backend import SpatialBackend
+from django.contrib.gis.db.models.fields import GeometryField, PointField
+from django.contrib.gis.db.models.sql import AreaField, DistanceField, GeomField, GeoQuery, GeoWhereNode
+from django.contrib.gis.measure import Area, Distance
+from django.contrib.gis.models import get_srid_info
+qn = connection.ops.quote_name
+
+# For backwards-compatibility; Q object should work just fine
+# after queryset-refactor.
+class GeoQ(Q): pass
+
+class GeomSQL(object):
+ "Simple wrapper object for geometric SQL."
+ def __init__(self, geo_sql):
+ self.sql = geo_sql
+
+ def as_sql(self, *args, **kwargs):
+ return self.sql
+
+class GeoQuerySet(QuerySet):
+ "The Geographic QuerySet."
+
+ def __init__(self, model=None, query=None):
+ super(GeoQuerySet, self).__init__(model=model, query=query)
+ self.query = query or GeoQuery(self.model, connection)
+
+ def area(self, tolerance=0.05, **kwargs):
+ """
+ Returns the area of the geographic field in an `area` attribute on
+ each element of this GeoQuerySet.
+ """
+ # Peforming setup here rather than in `_spatial_attribute` so that
+ # we can get the units for `AreaField`.
+ procedure_args, geo_field = self._spatial_setup('area', field_name=kwargs.get('field_name', None))
+ s = {'procedure_args' : procedure_args,
+ 'geo_field' : geo_field,
+ 'setup' : False,
+ }
+ if SpatialBackend.oracle:
+ s['procedure_fmt'] = '%(geo_col)s,%(tolerance)s'
+ s['procedure_args']['tolerance'] = tolerance
+ s['select_field'] = AreaField('sq_m') # Oracle returns area in units of meters.
+ elif SpatialBackend.postgis:
+ if not geo_field.geodetic:
+ # Getting the area units of the geographic field.
+ s['select_field'] = AreaField(Area.unit_attname(geo_field._unit_name))
+ else:
+ # TODO: Do we want to support raw number areas for geodetic fields?
+ raise Exception('Area on geodetic coordinate systems not supported.')
+ return self._spatial_attribute('area', s, **kwargs)
+
+ def centroid(self, **kwargs):
+ """
+ Returns the centroid of the geographic field in a `centroid`
+ attribute on each element of this GeoQuerySet.
+ """
+ return self._geom_attribute('centroid', **kwargs)
+
+ def difference(self, geom, **kwargs):
+ """
+ Returns the spatial difference of the geographic field in a `difference`
+ attribute on each element of this GeoQuerySet.
+ """
+ return self._geomset_attribute('difference', geom, **kwargs)
+
+ def distance(self, geom, **kwargs):
+ """
+ Returns the distance from the given geographic field name to the
+ given geometry in a `distance` attribute on each element of the
+ GeoQuerySet.
+
+ Keyword Arguments:
+ `spheroid` => If the geometry field is geodetic and PostGIS is
+ the spatial database, then the more accurate
+ spheroid calculation will be used instead of the
+ quicker sphere calculation.
+
+ `tolerance` => Used only for Oracle. The tolerance is
+ in meters -- a default of 5 centimeters (0.05)
+ is used.
+ """
+ return self._distance_attribute('distance', geom, **kwargs)
+
+ def envelope(self, **kwargs):
+ """
+ Returns a Geometry representing the bounding box of the
+ Geometry field in an `envelope` attribute on each element of
+ the GeoQuerySet.
+ """
+ return self._geom_attribute('envelope', **kwargs)
+
+ def extent(self, **kwargs):
+ """
+ Returns the extent (aggregate) of the features in the GeoQuerySet. The
+ extent will be returned as a 4-tuple, consisting of (xmin, ymin, xmax, ymax).
+ """
+ convert_extent = None
+ if SpatialBackend.postgis:
+ def convert_extent(box, geo_field):
+ # TODO: Parsing of BOX3D, Oracle support (patches welcome!)
+ # Box text will be something like "BOX(-90.0 30.0, -85.0 40.0)";
+ # parsing out and returning as a 4-tuple.
+ ll, ur = box[4:-1].split(',')
+ xmin, ymin = map(float, ll.split())
+ xmax, ymax = map(float, ur.split())
+ return (xmin, ymin, xmax, ymax)
+ elif SpatialBackend.oracle:
+ def convert_extent(wkt, geo_field):
+ raise NotImplementedError
+ return self._spatial_aggregate('extent', convert_func=convert_extent, **kwargs)
+
+ def gml(self, precision=8, version=2, **kwargs):
+ """
+ Returns GML representation of the given field in a `gml` attribute
+ on each element of the GeoQuerySet.
+ """
+ s = {'desc' : 'GML', 'procedure_args' : {'precision' : precision}}
+ if SpatialBackend.postgis:
+ # PostGIS AsGML() aggregate function parameter order depends on the
+ # version -- uggh.
+ major, minor1, minor2 = SpatialBackend.version
+ if major >= 1 and (minor1 > 3 or (minor1 == 3 and minor2 > 1)):
+ procedure_fmt = '%(version)s,%(geo_col)s,%(precision)s'
+ else:
+ procedure_fmt = '%(geo_col)s,%(precision)s,%(version)s'
+ s['procedure_args'] = {'precision' : precision, 'version' : version}
+
+ return self._spatial_attribute('gml', s, **kwargs)
+
+ def intersection(self, geom, **kwargs):
+ """
+ Returns the spatial intersection of the Geometry field in
+ an `intersection` attribute on each element of this
+ GeoQuerySet.
+ """
+ return self._geomset_attribute('intersection', geom, **kwargs)
+
+ def kml(self, **kwargs):
+ """
+ Returns KML representation of the geometry field in a `kml`
+ attribute on each element of this GeoQuerySet.
+ """
+ s = {'desc' : 'KML',
+ 'procedure_fmt' : '%(geo_col)s,%(precision)s',
+ 'procedure_args' : {'precision' : kwargs.pop('precision', 8)},
+ }
+ return self._spatial_attribute('kml', s, **kwargs)
+
+ def length(self, **kwargs):
+ """
+ Returns the length of the geometry field as a `Distance` object
+ stored in a `length` attribute on each element of this GeoQuerySet.
+ """
+ return self._distance_attribute('length', None, **kwargs)
+
+ def make_line(self, **kwargs):
+ """
+ Creates a linestring from all of the PointField geometries in the
+ this GeoQuerySet and returns it. This is a spatial aggregate
+ method, and thus returns a geometry rather than a GeoQuerySet.
+ """
+ kwargs['geo_field_type'] = PointField
+ kwargs['agg_field'] = GeometryField
+ return self._spatial_aggregate('make_line', **kwargs)
+
+ def mem_size(self, **kwargs):
+ """
+ Returns the memory size (number of bytes) that the geometry field takes
+ in a `mem_size` attribute on each element of this GeoQuerySet.
+ """
+ return self._spatial_attribute('mem_size', {}, **kwargs)
+
+ def num_geom(self, **kwargs):
+ """
+ Returns the number of geometries if the field is a
+ GeometryCollection or Multi* Field in a `num_geom`
+ attribute on each element of this GeoQuerySet; otherwise
+ the sets with None.
+ """
+ return self._spatial_attribute('num_geom', {}, **kwargs)
+
+ def num_points(self, **kwargs):
+ """
+ Returns the number of points in the first linestring in the
+ Geometry field in a `num_points` attribute on each element of
+ this GeoQuerySet; otherwise sets with None.
+ """
+ return self._spatial_attribute('num_points', {}, **kwargs)
+
+ def perimeter(self, **kwargs):
+ """
+ Returns the perimeter of the geometry field as a `Distance` object
+ stored in a `perimeter` attribute on each element of this GeoQuerySet.
+ """
+ return self._distance_attribute('perimeter', None, **kwargs)
+
+ def point_on_surface(self, **kwargs):
+ """
+ Returns a Point geometry guaranteed to lie on the surface of the
+ Geometry field in a `point_on_surface` attribute on each element
+ of this GeoQuerySet; otherwise sets with None.
+ """
+ return self._geom_attribute('point_on_surface', **kwargs)
+
+ def scale(self, x, y, z=0.0, **kwargs):
+ """
+ Scales the geometry to a new size by multiplying the ordinates
+ with the given x,y,z scale factors.
+ """
+ s = {'procedure_fmt' : '%(geo_col)s,%(x)s,%(y)s,%(z)s',
+ 'procedure_args' : {'x' : x, 'y' : y, 'z' : z},
+ 'select_field' : GeomField(),
+ }
+ return self._spatial_attribute('scale', s, **kwargs)
+
+ def svg(self, **kwargs):
+ """
+ Returns SVG representation of the geographic field in a `svg`
+ attribute on each element of this GeoQuerySet.
+ """
+ s = {'desc' : 'SVG',
+ 'procedure_fmt' : '%(geo_col)s,%(rel)s,%(precision)s',
+ 'procedure_args' : {'rel' : int(kwargs.pop('relative', 0)),
+ 'precision' : kwargs.pop('precision', 8)},
+ }
+ return self._spatial_attribute('svg', s, **kwargs)
+
+ def sym_difference(self, geom, **kwargs):
+ """
+ Returns the symmetric difference of the geographic field in a
+ `sym_difference` attribute on each element of this GeoQuerySet.
+ """
+ return self._geomset_attribute('sym_difference', geom, **kwargs)
+
+ def translate(self, x, y, z=0.0, **kwargs):
+ """
+ Translates the geometry to a new location using the given numeric
+ parameters as offsets.
+ """
+ s = {'procedure_fmt' : '%(geo_col)s,%(x)s,%(y)s,%(z)s',
+ 'procedure_args' : {'x' : x, 'y' : y, 'z' : z},
+ 'select_field' : GeomField(),
+ }
+ return self._spatial_attribute('translate', s, **kwargs)
+
+ def transform(self, srid=4326, **kwargs):
+ """
+ Transforms the given geometry field to the given SRID. If no SRID is
+ provided, the transformation will default to using 4326 (WGS84).
+ """
+ if not isinstance(srid, (int, long)):
+ raise TypeError('An integer SRID must be provided.')
+ field_name = kwargs.get('field_name', None)
+ tmp, geo_field = self._spatial_setup('transform', field_name=field_name)
+
+ # Getting the selection SQL for the given geographic field.
+ field_col = self._geocol_select(geo_field, field_name)
+
+ # Why cascading substitutions? Because spatial backends like
+ # Oracle and MySQL already require a function call to convert to text, thus
+ # when there's also a transformation we need to cascade the substitutions.
+ # For example, 'SDO_UTIL.TO_WKTGEOMETRY(SDO_CS.TRANSFORM( ... )'
+ geo_col = self.query.custom_select.get(geo_field, field_col)
+
+ # Setting the key for the field's column with the custom SELECT SQL to
+ # override the geometry column returned from the database.
+ custom_sel = '%s(%s, %s)' % (SpatialBackend.transform, geo_col, srid)
+ # TODO: Should we have this as an alias?
+ # custom_sel = '(%s(%s, %s)) AS %s' % (SpatialBackend.transform, geo_col, srid, qn(geo_field.name))
+ self.query.transformed_srid = srid # So other GeoQuerySet methods
+ self.query.custom_select[geo_field] = custom_sel
+ return self._clone()
+
+ def union(self, geom, **kwargs):
+ """
+ Returns the union of the geographic field with the given
+ Geometry in a `union` attribute on each element of this GeoQuerySet.
+ """
+ return self._geomset_attribute('union', geom, **kwargs)
+
+ def unionagg(self, **kwargs):
+ """
+ Performs an aggregate union on the given geometry field. Returns
+ None if the GeoQuerySet is empty. The `tolerance` keyword is for
+ Oracle backends only.
+ """
+ kwargs['agg_field'] = GeometryField
+ return self._spatial_aggregate('unionagg', **kwargs)
+
+ ### Private API -- Abstracted DRY routines. ###
+ def _spatial_setup(self, att, aggregate=False, desc=None, field_name=None, geo_field_type=None):
+ """
+ Performs set up for executing the spatial function.
+ """
+ # Does the spatial backend support this?
+ func = getattr(SpatialBackend, att, False)
+ if desc is None: desc = att
+ if not func: raise ImproperlyConfigured('%s stored procedure not available.' % desc)
+
+ # Initializing the procedure arguments.
+ procedure_args = {'function' : func}
+
+ # Is there a geographic field in the model to perform this
+ # operation on?
+ geo_field = self.query._geo_field(field_name)
+ if not geo_field:
+ raise TypeError('%s output only available on GeometryFields.' % func)
+
+ # If the `geo_field_type` keyword was used, then enforce that
+ # type limitation.
+ if not geo_field_type is None and not isinstance(geo_field, geo_field_type):
+ raise TypeError('"%s" stored procedures may only be called on %ss.' % (func, geo_field_type.__name__))
+
+ # Setting the procedure args.
+ procedure_args['geo_col'] = self._geocol_select(geo_field, field_name, aggregate)
+
+ return procedure_args, geo_field
+
+ def _spatial_aggregate(self, att, field_name=None,
+ agg_field=None, convert_func=None,
+ geo_field_type=None, tolerance=0.0005):
+ """
+ DRY routine for calling aggregate spatial stored procedures and
+ returning their result to the caller of the function.
+ """
+ # Constructing the setup keyword arguments.
+ setup_kwargs = {'aggregate' : True,
+ 'field_name' : field_name,
+ 'geo_field_type' : geo_field_type,
+ }
+ procedure_args, geo_field = self._spatial_setup(att, **setup_kwargs)
+
+ if SpatialBackend.oracle:
+ procedure_args['tolerance'] = tolerance
+ # Adding in selection SQL for Oracle geometry columns.
+ if agg_field is GeometryField:
+ agg_sql = '%s' % SpatialBackend.select
+ else:
+ agg_sql = '%s'
+ agg_sql = agg_sql % ('%(function)s(SDOAGGRTYPE(%(geo_col)s,%(tolerance)s))' % procedure_args)
+ else:
+ agg_sql = '%(function)s(%(geo_col)s)' % procedure_args
+
+ # Wrapping our selection SQL in `GeomSQL` to bypass quoting, and
+ # specifying the type of the aggregate field.
+ self.query.select = [GeomSQL(agg_sql)]
+ self.query.select_fields = [agg_field]
+
+ try:
+ # `asql` => not overriding `sql` module.
+ asql, params = self.query.as_sql()
+ except sql.datastructures.EmptyResultSet:
+ return None
+
+ # Getting a cursor, executing the query, and extracting the returned
+ # value from the aggregate function.
+ cursor = connection.cursor()
+ cursor.execute(asql, params)
+ result = cursor.fetchone()[0]
+
+ # If the `agg_field` is specified as a GeometryField, then autmatically
+ # set up the conversion function.
+ if agg_field is GeometryField and not callable(convert_func):
+ if SpatialBackend.postgis:
+ def convert_geom(hex, geo_field):
+ if hex: return SpatialBackend.Geometry(hex)
+ else: return None
+ elif SpatialBackend.oracle:
+ def convert_geom(clob, geo_field):
+ if clob: return SpatialBackend.Geometry(clob.read(), geo_field._srid)
+ else: return None
+ convert_func = convert_geom
+
+ # Returning the callback function evaluated on the result culled
+ # from the executed cursor.
+ if callable(convert_func):
+ return convert_func(result, geo_field)
+ else:
+ return result
+
+ def _spatial_attribute(self, att, settings, field_name=None, model_att=None):
+ """
+ DRY routine for calling a spatial stored procedure on a geometry column
+ and attaching its output as an attribute of the model.
+
+ Arguments:
+ att:
+ The name of the spatial attribute that holds the spatial
+ SQL function to call.
+
+ settings:
+ Dictonary of internal settings to customize for the spatial procedure.
+
+ Public Keyword Arguments:
+
+ field_name:
+ The name of the geographic field to call the spatial
+ function on. May also be a lookup to a geometry field
+ as part of a foreign key relation.
+
+ model_att:
+ The name of the model attribute to attach the output of
+ the spatial function to.
+ """
+ # Default settings.
+ settings.setdefault('desc', None)
+ settings.setdefault('geom_args', ())
+ settings.setdefault('geom_field', None)
+ settings.setdefault('procedure_args', {})
+ settings.setdefault('procedure_fmt', '%(geo_col)s')
+ settings.setdefault('select_params', [])
+
+ # Performing setup for the spatial column, unless told not to.
+ if settings.get('setup', True):
+ default_args, geo_field = self._spatial_setup(att, desc=settings['desc'], field_name=field_name)
+ for k, v in default_args.iteritems(): settings['procedure_args'].setdefault(k, v)
+ else:
+ geo_field = settings['geo_field']
+
+ # The attribute to attach to the model.
+ if not isinstance(model_att, basestring): model_att = att
+
+ # Special handling for any argument that is a geometry.
+ for name in settings['geom_args']:
+ # Using the field's get_db_prep_lookup() to get any needed
+ # transformation SQL -- we pass in a 'dummy' `contains` lookup.
+ where, params = geo_field.get_db_prep_lookup('contains', settings['procedure_args'][name])
+ # Replacing the procedure format with that of any needed
+ # transformation SQL.
+ old_fmt = '%%(%s)s' % name
+ new_fmt = where[0] % '%%s'
+ settings['procedure_fmt'] = settings['procedure_fmt'].replace(old_fmt, new_fmt)
+ settings['select_params'].extend(params)
+
+ # Getting the format for the stored procedure.
+ fmt = '%%(function)s(%s)' % settings['procedure_fmt']
+
+ # If the result of this function needs to be converted.
+ if settings.get('select_field', False):
+ sel_fld = settings['select_field']
+ if isinstance(sel_fld, GeomField) and SpatialBackend.select:
+ self.query.custom_select[model_att] = SpatialBackend.select
+ self.query.extra_select_fields[model_att] = sel_fld
+
+ # Finally, setting the extra selection attribute with
+ # the format string expanded with the stored procedure
+ # arguments.
+ return self.extra(select={model_att : fmt % settings['procedure_args']},
+ select_params=settings['select_params'])
+
+ def _distance_attribute(self, func, geom=None, tolerance=0.05, spheroid=False, **kwargs):
+ """
+ DRY routine for GeoQuerySet distance attribute routines.
+ """
+ # Setting up the distance procedure arguments.
+ procedure_args, geo_field = self._spatial_setup(func, field_name=kwargs.get('field_name', None))
+
+ # If geodetic defaulting distance attribute to meters (Oracle and
+ # PostGIS spherical distances return meters). Otherwise, use the
+ # units of the geometry field.
+ if geo_field.geodetic:
+ dist_att = 'm'
+ else:
+ dist_att = Distance.unit_attname(geo_field._unit_name)
+
+ # Shortcut booleans for what distance function we're using.
+ distance = func == 'distance'
+ length = func == 'length'
+ perimeter = func == 'perimeter'
+ if not (distance or length or perimeter):
+ raise ValueError('Unknown distance function: %s' % func)
+
+ # The field's get_db_prep_lookup() is used to get any
+ # extra distance parameters. Here we set up the
+ # parameters that will be passed in to field's function.
+ lookup_params = [geom or 'POINT (0 0)', 0]
+
+ # If the spheroid calculation is desired, either by the `spheroid`
+ # keyword or wehn calculating the length of geodetic field, make
+ # sure the 'spheroid' distance setting string is passed in so we
+ # get the correct spatial stored procedure.
+ if spheroid or (SpatialBackend.postgis and geo_field.geodetic and length):
+ lookup_params.append('spheroid')
+ where, params = geo_field.get_db_prep_lookup('distance_lte', lookup_params)
+
+ # The `geom_args` flag is set to true if a geometry parameter was
+ # passed in.
+ geom_args = bool(geom)
+
+ if SpatialBackend.oracle:
+ if distance:
+ procedure_fmt = '%(geo_col)s,%(geom)s,%(tolerance)s'
+ elif length or perimeter:
+ procedure_fmt = '%(geo_col)s,%(tolerance)s'
+ procedure_args['tolerance'] = tolerance
+ else:
+ # Getting whether this field is in units of degrees since the field may have
+ # been transformed via the `transform` GeoQuerySet method.
+ if self.query.transformed_srid:
+ u, unit_name, s = get_srid_info(self.query.transformed_srid)
+ geodetic = unit_name in geo_field.geodetic_units
+ else:
+ geodetic = geo_field.geodetic
+
+ if distance:
+ if self.query.transformed_srid:
+ # Setting the `geom_args` flag to false because we want to handle
+ # transformation SQL here, rather than the way done by default
+ # (which will transform to the original SRID of the field rather
+ # than to what was transformed to).
+ geom_args = False
+ procedure_fmt = '%s(%%(geo_col)s, %s)' % (SpatialBackend.transform, self.query.transformed_srid)
+ if geom.srid is None or geom.srid == self.query.transformed_srid:
+ # If the geom parameter srid is None, it is assumed the coordinates
+ # are in the transformed units. A placeholder is used for the
+ # geometry parameter.
+ procedure_fmt += ', %%s'
+ else:
+ # We need to transform the geom to the srid specified in `transform()`,
+ # so wrapping the geometry placeholder in transformation SQL.
+ procedure_fmt += ', %s(%%%%s, %s)' % (SpatialBackend.transform, self.query.transformed_srid)
+ else:
+ # `transform()` was not used on this GeoQuerySet.
+ procedure_fmt = '%(geo_col)s,%(geom)s'
+
+ if geodetic:
+ # Spherical distance calculation is needed (because the geographic
+ # field is geodetic). However, the PostGIS ST_distance_sphere/spheroid()
+ # procedures may only do queries from point columns to point geometries
+ # some error checking is required.
+ if not isinstance(geo_field, PointField):
+ raise TypeError('Spherical distance calculation only supported on PointFields.')
+ if not str(SpatialBackend.Geometry(buffer(params[0].wkb)).geom_type) == 'Point':
+ raise TypeError('Spherical distance calculation only supported with Point Geometry parameters')
+ # The `function` procedure argument needs to be set differently for
+ # geodetic distance calculations.
+ if spheroid:
+ # Call to distance_spheroid() requires spheroid param as well.
+ procedure_fmt += ',%(spheroid)s'
+ procedure_args.update({'function' : SpatialBackend.distance_spheroid, 'spheroid' : where[1]})
+ else:
+ procedure_args.update({'function' : SpatialBackend.distance_sphere})
+ elif length or perimeter:
+ procedure_fmt = '%(geo_col)s'
+ if geodetic and length:
+ # There's no `length_sphere`
+ procedure_fmt += ',%(spheroid)s'
+ procedure_args.update({'function' : SpatialBackend.length_spheroid, 'spheroid' : where[1]})
+
+ # Setting up the settings for `_spatial_attribute`.
+ s = {'select_field' : DistanceField(dist_att),
+ 'setup' : False,
+ 'geo_field' : geo_field,
+ 'procedure_args' : procedure_args,
+ 'procedure_fmt' : procedure_fmt,
+ }
+ if geom_args:
+ s['geom_args'] = ('geom',)
+ s['procedure_args']['geom'] = geom
+ elif geom:
+ # The geometry is passed in as a parameter because we handled
+ # transformation conditions in this routine.
+ s['select_params'] = [SpatialBackend.Adaptor(geom)]
+ return self._spatial_attribute(func, s, **kwargs)
+
+ def _geom_attribute(self, func, tolerance=0.05, **kwargs):
+ """
+ DRY routine for setting up a GeoQuerySet method that attaches a
+ Geometry attribute (e.g., `centroid`, `point_on_surface`).
+ """
+ s = {'select_field' : GeomField(),}
+ if SpatialBackend.oracle:
+ s['procedure_fmt'] = '%(geo_col)s,%(tolerance)s'
+ s['procedure_args'] = {'tolerance' : tolerance}
+ return self._spatial_attribute(func, s, **kwargs)
+
+ def _geomset_attribute(self, func, geom, tolerance=0.05, **kwargs):
+ """
+ DRY routine for setting up a GeoQuerySet method that attaches a
+ Geometry attribute and takes a Geoemtry parameter. This is used
+ for geometry set-like operations (e.g., intersection, difference,
+ union, sym_difference).
+ """
+ s = {'geom_args' : ('geom',),
+ 'select_field' : GeomField(),
+ 'procedure_fmt' : '%(geo_col)s,%(geom)s',
+ 'procedure_args' : {'geom' : geom},
+ }
+ if SpatialBackend.oracle:
+ s['procedure_fmt'] += ',%(tolerance)s'
+ s['procedure_args']['tolerance'] = tolerance
+ return self._spatial_attribute(func, s, **kwargs)
+
+ def _geocol_select(self, geo_field, field_name, aggregate=False):
+ """
+ Helper routine for constructing the SQL to select the geographic
+ column. Takes into account if the geographic field is in a
+ ForeignKey relation to the current model.
+ """
+ # If this is an aggregate spatial query, the flag needs to be
+ # set on the `GeoQuery` object of this queryset.
+ if aggregate: self.query.aggregate = True
+
+ # Is this operation going to be on a related geographic field?
+ if not geo_field in self.model._meta.fields:
+ # If so, it'll have to be added to the select related information
+ # (e.g., if 'location__point' was given as the field name).
+ self.query.add_select_related([field_name])
+ self.query.pre_sql_setup()
+ rel_table, rel_col = self.query.related_select_cols[self.query.related_select_fields.index(geo_field)]
+ return self.query._field_column(geo_field, rel_table)
+ else:
+ return self.query._field_column(geo_field)
diff --git a/webapp/django/contrib/gis/db/models/sql/__init__.py b/webapp/django/contrib/gis/db/models/sql/__init__.py
new file mode 100644
index 0000000000..4a66b41664
--- /dev/null
+++ b/webapp/django/contrib/gis/db/models/sql/__init__.py
@@ -0,0 +1,2 @@
+from django.contrib.gis.db.models.sql.query import AreaField, DistanceField, GeomField, GeoQuery
+from django.contrib.gis.db.models.sql.where import GeoWhereNode
diff --git a/webapp/django/contrib/gis/db/models/sql/query.py b/webapp/django/contrib/gis/db/models/sql/query.py
new file mode 100644
index 0000000000..f3e9fb25ca
--- /dev/null
+++ b/webapp/django/contrib/gis/db/models/sql/query.py
@@ -0,0 +1,327 @@
+from itertools import izip
+from django.db.models.query import sql
+from django.db.models.fields import FieldDoesNotExist
+from django.db.models.fields.related import ForeignKey
+
+from django.contrib.gis.db.backend import SpatialBackend
+from django.contrib.gis.db.models.fields import GeometryField
+from django.contrib.gis.db.models.sql.where import GeoWhereNode
+from django.contrib.gis.measure import Area, Distance
+
+# Valid GIS query types.
+ALL_TERMS = sql.constants.QUERY_TERMS.copy()
+ALL_TERMS.update(SpatialBackend.gis_terms)
+
+class GeoQuery(sql.Query):
+ """
+ A single spatial SQL query.
+ """
+ # Overridding the valid query terms.
+ query_terms = ALL_TERMS
+
+ #### Methods overridden from the base Query class ####
+ def __init__(self, model, conn):
+ super(GeoQuery, self).__init__(model, conn, where=GeoWhereNode)
+ # The following attributes are customized for the GeoQuerySet.
+ # The GeoWhereNode and SpatialBackend classes contain backend-specific
+ # routines and functions.
+ self.aggregate = False
+ self.custom_select = {}
+ self.transformed_srid = None
+ self.extra_select_fields = {}
+
+ def clone(self, *args, **kwargs):
+ obj = super(GeoQuery, self).clone(*args, **kwargs)
+ # Customized selection dictionary and transformed srid flag have
+ # to also be added to obj.
+ obj.aggregate = self.aggregate
+ obj.custom_select = self.custom_select.copy()
+ obj.transformed_srid = self.transformed_srid
+ obj.extra_select_fields = self.extra_select_fields.copy()
+ return obj
+
+ def get_columns(self, with_aliases=False):
+ """
+ Return the list of columns to use in the select statement. If no
+ columns have been specified, returns all columns relating to fields in
+ the model.
+
+ If 'with_aliases' is true, any column names that are duplicated
+ (without the table names) are given unique aliases. This is needed in
+ some cases to avoid ambiguitity with nested queries.
+
+ This routine is overridden from Query to handle customized selection of
+ geometry columns.
+ """
+ qn = self.quote_name_unless_alias
+ qn2 = self.connection.ops.quote_name
+ result = ['(%s) AS %s' % (self.get_extra_select_format(alias) % col[0], qn2(alias))
+ for alias, col in self.extra_select.iteritems()]
+ aliases = set(self.extra_select.keys())
+ if with_aliases:
+ col_aliases = aliases.copy()
+ else:
+ col_aliases = set()
+ if self.select:
+ # This loop customized for GeoQuery.
+ for col, field in izip(self.select, self.select_fields):
+ if isinstance(col, (list, tuple)):
+ r = self.get_field_select(field, col[0])
+ if with_aliases and col[1] in col_aliases:
+ c_alias = 'Col%d' % len(col_aliases)
+ result.append('%s AS %s' % (r, c_alias))
+ aliases.add(c_alias)
+ col_aliases.add(c_alias)
+ else:
+ result.append(r)
+ aliases.add(r)
+ col_aliases.add(col[1])
+ else:
+ result.append(col.as_sql(quote_func=qn))
+ if hasattr(col, 'alias'):
+ aliases.add(col.alias)
+ col_aliases.add(col.alias)
+ elif self.default_cols:
+ cols, new_aliases = self.get_default_columns(with_aliases,
+ col_aliases)
+ result.extend(cols)
+ aliases.update(new_aliases)
+ # This loop customized for GeoQuery.
+ if not self.aggregate:
+ for (table, col), field in izip(self.related_select_cols, self.related_select_fields):
+ r = self.get_field_select(field, table)
+ if with_aliases and col in col_aliases:
+ c_alias = 'Col%d' % len(col_aliases)
+ result.append('%s AS %s' % (r, c_alias))
+ aliases.add(c_alias)
+ col_aliases.add(c_alias)
+ else:
+ result.append(r)
+ aliases.add(r)
+ col_aliases.add(col)
+
+ self._select_aliases = aliases
+ return result
+
+ def get_default_columns(self, with_aliases=False, col_aliases=None,
+ start_alias=None, opts=None, as_pairs=False):
+ """
+ Computes the default columns for selecting every field in the base
+ model.
+
+ Returns a list of strings, quoted appropriately for use in SQL
+ directly, as well as a set of aliases used in the select statement.
+
+ This routine is overridden from Query to handle customized selection of
+ geometry columns.
+ """
+ result = []
+ if opts is None:
+ opts = self.model._meta
+ if start_alias:
+ table_alias = start_alias
+ else:
+ table_alias = self.tables[0]
+ root_pk = self.model._meta.pk.column
+ seen = {None: table_alias}
+ aliases = set()
+ for field, model in opts.get_fields_with_model():
+ try:
+ alias = seen[model]
+ except KeyError:
+ alias = self.join((table_alias, model._meta.db_table,
+ root_pk, model._meta.pk.column))
+ seen[model] = alias
+ if as_pairs:
+ result.append((alias, field.column))
+ continue
+ # This part of the function is customized for GeoQuery. We
+ # see if there was any custom selection specified in the
+ # dictionary, and set up the selection format appropriately.
+ field_sel = self.get_field_select(field, alias)
+ if with_aliases and field.column in col_aliases:
+ c_alias = 'Col%d' % len(col_aliases)
+ result.append('%s AS %s' % (field_sel, c_alias))
+ col_aliases.add(c_alias)
+ aliases.add(c_alias)
+ else:
+ r = field_sel
+ result.append(r)
+ aliases.add(r)
+ if with_aliases:
+ col_aliases.add(field.column)
+ if as_pairs:
+ return result, None
+ return result, aliases
+
+ def get_ordering(self):
+ """
+ This routine is overridden to disable ordering for aggregate
+ spatial queries.
+ """
+ if not self.aggregate:
+ return super(GeoQuery, self).get_ordering()
+ else:
+ return ()
+
+ def resolve_columns(self, row, fields=()):
+ """
+ This routine is necessary so that distances and geometries returned
+ from extra selection SQL get resolved appropriately into Python
+ objects.
+ """
+ values = []
+ aliases = self.extra_select.keys()
+ index_start = len(aliases)
+ values = [self.convert_values(v, self.extra_select_fields.get(a, None))
+ for v, a in izip(row[:index_start], aliases)]
+ if SpatialBackend.oracle:
+ # This is what happens normally in Oracle's `resolve_columns`.
+ for value, field in izip(row[index_start:], fields):
+ values.append(self.convert_values(value, field))
+ else:
+ values.extend(row[index_start:])
+ return values
+
+ def convert_values(self, value, field):
+ """
+ Using the same routines that Oracle does we can convert our
+ extra selection objects into Geometry and Distance objects.
+ TODO: Laziness.
+ """
+ if SpatialBackend.oracle:
+ # Running through Oracle's first.
+ value = super(GeoQuery, self).convert_values(value, field)
+ if isinstance(field, DistanceField):
+ # Using the field's distance attribute, can instantiate
+ # `Distance` with the right context.
+ value = Distance(**{field.distance_att : value})
+ elif isinstance(field, AreaField):
+ value = Area(**{field.area_att : value})
+ elif isinstance(field, GeomField):
+ value = SpatialBackend.Geometry(value)
+ return value
+
+ #### Routines unique to GeoQuery ####
+ def get_extra_select_format(self, alias):
+ sel_fmt = '%s'
+ if alias in self.custom_select:
+ sel_fmt = sel_fmt % self.custom_select[alias]
+ return sel_fmt
+
+ def get_field_select(self, fld, alias=None):
+ """
+ Returns the SELECT SQL string for the given field. Figures out
+ if any custom selection SQL is needed for the column The `alias`
+ keyword may be used to manually specify the database table where
+ the column exists, if not in the model associated with this
+ `GeoQuery`.
+ """
+ sel_fmt = self.get_select_format(fld)
+ if fld in self.custom_select:
+ field_sel = sel_fmt % self.custom_select[fld]
+ else:
+ field_sel = sel_fmt % self._field_column(fld, alias)
+ return field_sel
+
+ def get_select_format(self, fld):
+ """
+ Returns the selection format string, depending on the requirements
+ of the spatial backend. For example, Oracle and MySQL require custom
+ selection formats in order to retrieve geometries in OGC WKT. For all
+ other fields a simple '%s' format string is returned.
+ """
+ if SpatialBackend.select and hasattr(fld, '_geom'):
+ # This allows operations to be done on fields in the SELECT,
+ # overriding their values -- used by the Oracle and MySQL
+ # spatial backends to get database values as WKT, and by the
+ # `transform` method.
+ sel_fmt = SpatialBackend.select
+
+ # Because WKT doesn't contain spatial reference information,
+ # the SRID is prefixed to the returned WKT to ensure that the
+ # transformed geometries have an SRID different than that of the
+ # field -- this is only used by `transform` for Oracle backends.
+ if self.transformed_srid and SpatialBackend.oracle:
+ sel_fmt = "'SRID=%d;'||%s" % (self.transformed_srid, sel_fmt)
+ else:
+ sel_fmt = '%s'
+ return sel_fmt
+
+ # Private API utilities, subject to change.
+ def _check_geo_field(self, model, name_param):
+ """
+ Recursive utility routine for checking the given name parameter
+ on the given model. Initially, the name parameter is a string,
+ of the field on the given model e.g., 'point', 'the_geom'.
+ Related model field strings like 'address__point', may also be
+ used.
+
+ If a GeometryField exists according to the given name parameter
+ it will be returned, otherwise returns False.
+ """
+ if isinstance(name_param, basestring):
+ # This takes into account the situation where the name is a
+ # lookup to a related geographic field, e.g., 'address__point'.
+ name_param = name_param.split(sql.constants.LOOKUP_SEP)
+ name_param.reverse() # Reversing so list operates like a queue of related lookups.
+ elif not isinstance(name_param, list):
+ raise TypeError
+ try:
+ # Getting the name of the field for the model (by popping the first
+ # name from the `name_param` list created above).
+ fld, mod, direct, m2m = model._meta.get_field_by_name(name_param.pop())
+ except (FieldDoesNotExist, IndexError):
+ return False
+ # TODO: ManyToManyField?
+ if isinstance(fld, GeometryField):
+ return fld # A-OK.
+ elif isinstance(fld, ForeignKey):
+ # ForeignKey encountered, return the output of this utility called
+ # on the _related_ model with the remaining name parameters.
+ return self._check_geo_field(fld.rel.to, name_param) # Recurse to check ForeignKey relation.
+ else:
+ return False
+
+ def _field_column(self, field, table_alias=None):
+ """
+ Helper function that returns the database column for the given field.
+ The table and column are returned (quoted) in the proper format, e.g.,
+ `"geoapp_city"."point"`. If `table_alias` is not specified, the
+ database table associated with the model of this `GeoQuery` will be
+ used.
+ """
+ if table_alias is None: table_alias = self.model._meta.db_table
+ return "%s.%s" % (self.quote_name_unless_alias(table_alias),
+ self.connection.ops.quote_name(field.column))
+
+ def _geo_field(self, field_name=None):
+ """
+ Returns the first Geometry field encountered; or specified via the
+ `field_name` keyword. The `field_name` may be a string specifying
+ the geometry field on this GeoQuery's model, or a lookup string
+ to a geometry field via a ForeignKey relation.
+ """
+ if field_name is None:
+ # Incrementing until the first geographic field is found.
+ for fld in self.model._meta.fields:
+ if isinstance(fld, GeometryField): return fld
+ return False
+ else:
+ # Otherwise, check by the given field name -- which may be
+ # a lookup to a _related_ geographic field.
+ return self._check_geo_field(self.model, field_name)
+
+### Field Classes for `convert_values` ####
+class AreaField(object):
+ def __init__(self, area_att):
+ self.area_att = area_att
+
+class DistanceField(object):
+ def __init__(self, distance_att):
+ self.distance_att = distance_att
+
+# Rather than use GeometryField (which requires a SQL query
+# upon instantiation), use this lighter weight class.
+class GeomField(object):
+ pass
diff --git a/webapp/django/contrib/gis/db/models/sql/where.py b/webapp/django/contrib/gis/db/models/sql/where.py
new file mode 100644
index 0000000000..a1a28d9511
--- /dev/null
+++ b/webapp/django/contrib/gis/db/models/sql/where.py
@@ -0,0 +1,64 @@
+import datetime
+from django.db.models.fields import Field
+from django.db.models.sql.where import WhereNode
+from django.contrib.gis.db.backend import get_geo_where_clause, SpatialBackend
+
+class GeoAnnotation(object):
+ """
+ The annotation used for GeometryFields; basically a placeholder
+ for metadata needed by the `get_geo_where_clause` of the spatial
+ backend.
+ """
+ def __init__(self, field, value, where):
+ self.geodetic = field.geodetic
+ self.geom_type = field._geom
+ self.value = value
+ self.where = tuple(where)
+
+class GeoWhereNode(WhereNode):
+ """
+ Used to represent the SQL where-clause for spatial databases --
+ these are tied to the GeoQuery class that created it.
+ """
+ def add(self, data, connector):
+ """
+ This is overridden from the regular WhereNode to handle the
+ peculiarties of GeometryFields, because they need a special
+ annotation object that contains the spatial metadata from the
+ field to generate the spatial SQL.
+ """
+ if not isinstance(data, (list, tuple)):
+ return super(WhereNode, self).add(data, connector)
+ alias, col, field, lookup_type, value = data
+ if not hasattr(field, "_geom"):
+ # Not a geographic field, so call `WhereNode.add`.
+ return super(GeoWhereNode, self).add(data, connector)
+ else:
+ # `GeometryField.get_db_prep_lookup` returns a where clause
+ # substitution array in addition to the parameters.
+ where, params = field.get_db_prep_lookup(lookup_type, value)
+
+ # The annotation will be a `GeoAnnotation` object that
+ # will contain the necessary geometry field metadata for
+ # the `get_geo_where_clause` to construct the appropriate
+ # spatial SQL when `make_atom` is called.
+ annotation = GeoAnnotation(field, value, where)
+ return super(WhereNode, self).add((alias, col, field.db_type(), lookup_type,
+ annotation, params), connector)
+
+ def make_atom(self, child, qn):
+ table_alias, name, db_type, lookup_type, value_annot, params = child
+
+ if isinstance(value_annot, GeoAnnotation):
+ if lookup_type in SpatialBackend.gis_terms:
+ # Getting the geographic where clause; substitution parameters
+ # will be populated in the GeoFieldSQL object returned by the
+ # GeometryField.
+ gwc = get_geo_where_clause(table_alias, name, lookup_type, value_annot)
+ return gwc % value_annot.where, params
+ else:
+ raise TypeError('Invalid lookup type: %r' % lookup_type)
+ else:
+ # If not a GeometryField, call the `make_atom` from the
+ # base class.
+ return super(GeoWhereNode, self).make_atom(child, qn)