Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Refactored and cleaned up parts of the spatial database backend. Chan…

…ges include:

* Laid foundations for SpatiaLite support in `GeoQuerySet`, `GeoWhereNode` and the tests.
* Added the `Collect` aggregate for PostGIS (still needs tests).
* Oracle now goes to 11.
* The backend-specific `SpatialRefSys` and `GeometryColumns` models are now attributes of `SpatialBackend`.
* Renamed `GeometryField` attributes to be public that were private (e.g., `_srid` -> `srid` and `_geom` -> `geom_type`).
* Renamed `create_test_db` to `create_test_spatial_db`.
* Removed the legacy classes `GeoMixin` and `GeoQ`.
* Removed evil `\` from spatial backend fields.
* Moved shapefile data from `tests/layermap` to `tests/data`.

Fixed #9794.  Refs #9686.


git-svn-id: http://code.djangoproject.com/svn/django/trunk@10197 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
commit 867e71501c3273ba4a0b3ac13847cd895f0da663 1 parent a61c0b7
Justin Bronn jbronn authored
Showing with 594 additions and 409 deletions.
  1. +5 −3 django/contrib/gis/db/backend/__init__.py
  2. +1 −4 django/contrib/gis/db/backend/base.py
  3. +2 −2 django/contrib/gis/db/backend/mysql/__init__.py
  4. +2 −2 django/contrib/gis/db/backend/mysql/creation.py
  5. +12 −12 django/contrib/gis/db/backend/mysql/field.py
  6. +5 −2 django/contrib/gis/db/backend/oracle/__init__.py
  7. +1 −2  django/contrib/gis/db/backend/oracle/creation.py
  8. +26 −27 django/contrib/gis/db/backend/oracle/field.py
  9. +6 −7 django/contrib/gis/db/backend/oracle/models.py
  10. +6 −2 django/contrib/gis/db/backend/postgis/__init__.py
  11. +5 −22 django/contrib/gis/db/backend/postgis/creation.py
  12. +29 −29 django/contrib/gis/db/backend/postgis/field.py
  13. +4 −9 django/contrib/gis/db/backend/postgis/models.py
  14. +1 −0  django/contrib/gis/db/backend/postgis/query.py
  15. +19 −2 django/contrib/gis/db/backend/util.py
  16. +0 −3  django/contrib/gis/db/models/__init__.py
  17. +4 −1 django/contrib/gis/db/models/aggregates.py
  18. +46 −18 django/contrib/gis/db/models/fields/__init__.py
  19. +0 −11 django/contrib/gis/db/models/mixin.py
  20. +2 −2 django/contrib/gis/db/models/proxy.py
  21. +46 −21 django/contrib/gis/db/models/query.py
  22. +15 −2 django/contrib/gis/db/models/sql/aggregates.py
  23. +4 −0 django/contrib/gis/db/models/sql/conversion.py
  24. +7 −4 django/contrib/gis/db/models/sql/query.py
  25. +5 −5 django/contrib/gis/db/models/sql/where.py
  26. +1 −0  django/contrib/gis/forms/fields.py
  27. +97 −83 django/contrib/gis/models.py
  28. +41 −32 django/contrib/gis/tests/__init__.py
  29. 0  django/contrib/gis/tests/{layermap → data}/cities/cities.dbf
  30. 0  django/contrib/gis/tests/{layermap → data}/cities/cities.prj
  31. 0  django/contrib/gis/tests/{layermap → data}/cities/cities.shp
  32. 0  django/contrib/gis/tests/{layermap → data}/cities/cities.shx
  33. 0  django/contrib/gis/tests/{layermap → data}/counties/counties.dbf
  34. 0  django/contrib/gis/tests/{layermap → data}/counties/counties.shp
  35. 0  django/contrib/gis/tests/{layermap → data}/counties/counties.shx
  36. 0  django/contrib/gis/tests/{layermap → data}/interstates/interstates.dbf
  37. 0  django/contrib/gis/tests/{layermap → data}/interstates/interstates.prj
  38. 0  django/contrib/gis/tests/{layermap → data}/interstates/interstates.shp
  39. 0  django/contrib/gis/tests/{layermap → data}/interstates/interstates.shx
  40. +3 −0  django/contrib/gis/tests/distapp/data.py
  41. +8 −1 django/contrib/gis/tests/distapp/models.py
  42. +70 −36 django/contrib/gis/tests/distapp/tests.py
  43. +11 −10 django/contrib/gis/tests/geoapp/models.py
  44. +84 −33 django/contrib/gis/tests/geoapp/tests.py
  45. +3 −7 django/contrib/gis/tests/layermap/tests.py
  46. +21 −15 django/contrib/gis/tests/relatedapp/tests.py
  47. +2 −0  django/contrib/gis/tests/utils.py
8 django/contrib/gis/db/backend/__init__.py
View
@@ -9,10 +9,12 @@
# 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
+ from django.contrib.gis.db.backend.postgis import create_test_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
+ from django.contrib.gis.db.backend.oracle import create_test_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
+ from django.contrib.gis.db.backend.mysql import create_test_spatial_db, get_geo_where_clause, SpatialBackend
+elif settings.DATABASE_ENGINE == 'sqlite3':
+ from django.contrib.gis.db.backend.spatialite import create_test_spatial_db, get_geo_where_clause, SpatialBackend
else:
raise NotImplementedError('No Geographic Backend exists for %s' % settings.DATABASE_ENGINE)
5 django/contrib/gis/db/backend/base.py
View
@@ -23,7 +23,4 @@ def __getattr__(self, name):
return self.__dict__[name]
except KeyError:
return False
-
-
-
-
+
4 django/contrib/gis/db/backend/mysql/__init__.py
View
@@ -1,8 +1,8 @@
-__all__ = ['create_spatial_db', 'get_geo_where_clause', 'SpatialBackend']
+__all__ = ['create_test_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.creation import create_test_spatial_db
from django.contrib.gis.db.backend.mysql.field import MySQLGeoField
from django.contrib.gis.db.backend.mysql.query import *
4 django/contrib/gis/db/backend/mysql/creation.py
View
@@ -1,5 +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')
+def create_test_spatial_db(verbosity=1, autoclobber=False):
+ "A wrapper over the MySQL `create_test_db` method."
from django.db import connection
connection.creation.create_test_db(verbosity, autoclobber)
24 django/contrib/gis/db/backend/mysql/field.py
View
@@ -13,20 +13,20 @@ class MySQLGeoField(Field):
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.
+ 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.
+ (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)) + ');'
+
+ 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):
@@ -35,19 +35,19 @@ def post_create_sql(self, style, db_table):
created.
"""
# Getting the geometric index for this Geometry column.
- if self._index:
+ if self.spatial_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
+ return self.geom_type
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
+ 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
7 django/contrib/gis/db/backend/oracle/__init__.py
View
@@ -1,9 +1,10 @@
-__all__ = ['create_spatial_db', 'get_geo_where_clause', 'SpatialBackend']
+__all__ = ['create_test_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.creation import create_test_spatial_db
from django.contrib.gis.db.backend.oracle.field import OracleSpatialField
+from django.contrib.gis.db.backend.oracle.models import GeometryColumns, SpatialRefSys
from django.contrib.gis.db.backend.oracle.query import *
SpatialBackend = BaseSpatialBackend(name='oracle', oracle=True,
@@ -29,4 +30,6 @@
union=UNION,
Adaptor=OracleSpatialAdaptor,
Field=OracleSpatialField,
+ GeometryColumns=GeometryColumns,
+ SpatialRefSys=SpatialRefSys,
)
3  django/contrib/gis/db/backend/oracle/creation.py
View
@@ -1,6 +1,5 @@
-def create_spatial_db(test=True, verbosity=1, autoclobber=False):
+def create_test_spatial_db(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)
53 django/contrib/gis/db/backend/oracle/field.py
View
@@ -32,26 +32,25 @@ 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:
+ 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
+ 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):
@@ -60,14 +59,14 @@ def _geom_index(self, style, db_table):
# 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') + ';'
+
+ 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):
@@ -79,7 +78,7 @@ def post_create_sql(self, style, db_table):
post_sql = self._add_geom(style, db_table)
# Getting the geometric index for this Geometry column.
- if self._index:
+ if self.spatial_index:
return (post_sql, self._geom_index(style, db_table))
else:
return (post_sql,)
@@ -87,7 +86,7 @@ def post_create_sql(self, style, db_table):
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
@@ -96,8 +95,8 @@ def get_placeholder(self, value):
"""
if value is None:
return '%s'
- elif value.srid != self._srid:
+ elif value.srid != self.srid:
# Adding Transform() to the SQL placeholder.
- return '%s(SDO_GEOMETRY(%%s, %s), %s)' % (TRANSFORM, value.srid, self._srid)
+ return '%s(SDO_GEOMETRY(%%s, %s), %s)' % (TRANSFORM, value.srid, self.srid)
else:
- return 'SDO_GEOMETRY(%%s, %s)' % self._srid
+ return 'SDO_GEOMETRY(%%s, %s)' % self.srid
13 django/contrib/gis/db/backend/oracle/models.py
View
@@ -8,7 +8,6 @@
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."
@@ -22,7 +21,7 @@ class Meta:
@classmethod
def table_name_col(cls):
"""
- Returns the name of the metadata column used to store the
+ Returns the name of the metadata column used to store the
the feature table name.
"""
return 'table_name'
@@ -30,7 +29,7 @@ def table_name_col(cls):
@classmethod
def geom_col_name(cls):
"""
- Returns the name of the metadata column used to store the
+ Returns the name of the metadata column used to store the
the feature geometry column.
"""
return 'column_name'
@@ -38,19 +37,19 @@ def geom_col_name(cls):
def __unicode__(self):
return '%s - %s (SRID: %s)' % (self.table_name, self.column_name, self.srid)
-class SpatialRefSys(models.Model, SpatialRefSysMixin):
+class SpatialRefSys(models.Model):
"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()
+ #cs_bounds = models.GeometryField() # TODO
class Meta:
- # TODO: Figure out way to have this be MDSYS.CS_SRS without
- # having django's quoting mess up the SQL.
+ abstract = True
db_table = 'CS_SRS'
+ app_label = '_mdsys' # Hack so that syncdb won't try to create "CS_SRS" table.
@property
def wkt(self):
8 django/contrib/gis/db/backend/postgis/__init__.py
View
@@ -1,14 +1,16 @@
-__all__ = ['create_spatial_db', 'get_geo_where_clause', 'SpatialBackend']
+__all__ = ['create_test_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.creation import create_test_spatial_db
from django.contrib.gis.db.backend.postgis.field import PostGISField
+from django.contrib.gis.db.backend.postgis.models import GeometryColumns, SpatialRefSys
from django.contrib.gis.db.backend.postgis.query import *
SpatialBackend = BaseSpatialBackend(name='postgis', postgis=True,
area=AREA,
centroid=CENTROID,
+ collect=COLLECT,
difference=DIFFERENCE,
distance=DISTANCE,
distance_functions=DISTANCE_FUNCTIONS,
@@ -39,4 +41,6 @@
version=(MAJOR_VERSION, MINOR_VERSION1, MINOR_VERSION2),
Adaptor=PostGISAdaptor,
Field=PostGISField,
+ GeometryColumns=GeometryColumns,
+ SpatialRefSys=SpatialRefSys,
)
27 django/contrib/gis/db/backend/postgis/creation.py
View
@@ -1,23 +1,10 @@
import os, re, sys
-from subprocess import Popen, PIPE
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):
- """
- Executes a shell command on the platform using subprocess.Popen and
- return a tuple of the status and stdout output.
- """
- # Set stdout and stderr to PIPE because we want to capture stdout and
- # prevent stderr from displaying.
- p = Popen(cmd, shell=True, stdout=PIPE, stderr=PIPE)
- # We use p.communicate() instead of p.wait() to avoid deadlocks if the
- # output buffers exceed POSIX buffer size.
- stdout, stderr = p.communicate()
- return p.returncode, stdout.strip()
+from django.contrib.gis.db.backend.util import getstatusoutput
def create_lang(db_name, verbosity=1):
"Sets up the pl/pgsql language on the given database."
@@ -110,20 +97,16 @@ def _create_with_shell(db_name, verbosity=1, autoclobber=False):
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."
+def create_test_spatial_db(verbosity=1, autoclobber=False, interactive=False):
+ "Creates a test 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)
+ db_name = get_spatial_db(test=True)
+ _create_with_cursor(db_name, verbosity=verbosity, autoclobber=autoclobber)
# If a template database is used, then don't need to do any of the following.
if not hasattr(settings, 'POSTGIS_TEMPLATE'):
58 django/contrib/gis/db/backend/postgis/field.py
View
@@ -19,35 +19,35 @@ def _add_geom(self, style, db_table):
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)) + ');'
+ 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_type)) + ', ' +
+ 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') + ';'
+ 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) + ' );'
+ 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):
@@ -62,17 +62,17 @@ def post_create_sql(self, style, db_table):
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:
+ if self.spatial_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)) + ');'
+ 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):
@@ -88,8 +88,8 @@ def get_placeholder(self, value):
SRID of the field. Specifically, this routine will substitute in the
ST_Transform() function call.
"""
- if value is None or value.srid == self._srid:
+ 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)
+ return '%s(%%s, %s)' % (TRANSFORM, self.srid)
13 django/contrib/gis/db/backend/postgis/models.py
View
@@ -2,12 +2,6 @@
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):
"""
@@ -28,7 +22,7 @@ class Meta:
@classmethod
def table_name_col(cls):
"""
- Returns the name of the metadata column used to store the
+ Returns the name of the metadata column used to store the
the feature table name.
"""
return 'f_table_name'
@@ -36,7 +30,7 @@ def table_name_col(cls):
@classmethod
def geom_col_name(cls):
"""
- Returns the name of the metadata column used to store the
+ Returns the name of the metadata column used to store the
the feature geometry column.
"""
return 'f_geometry_column'
@@ -46,7 +40,7 @@ def __unicode__(self):
(self.f_table_name, self.f_geometry_column,
self.coord_dimension, self.type, self.srid)
-class SpatialRefSys(models.Model, SpatialRefSysMixin):
+class SpatialRefSys(models.Model):
"""
The 'spatial_ref_sys' table from PostGIS. See the PostGIS
documentaiton at Ch. 4.2.1.
@@ -58,6 +52,7 @@ class SpatialRefSys(models.Model, SpatialRefSysMixin):
proj4text = models.CharField(max_length=2048)
class Meta:
+ abstract = True
db_table = 'spatial_ref_sys'
@property
1  django/contrib/gis/db/backend/postgis/query.py
View
@@ -42,6 +42,7 @@ def get_func(func): return '%s%s' % (GEOM_FUNC_PREFIX, func)
ASGML = get_func('AsGML')
ASSVG = get_func('AsSVG')
CENTROID = get_func('Centroid')
+ COLLECT = get_func('Collect')
DIFFERENCE = get_func('Difference')
DISTANCE = get_func('Distance')
DISTANCE_SPHERE = get_func('distance_sphere')
21 django/contrib/gis/db/backend/util.py
View
@@ -1,4 +1,21 @@
-from types import UnicodeType
+"""
+A collection of utility routines and classes used by the spatial
+backends.
+"""
+
+def getstatusoutput(cmd):
+ """
+ Executes a shell command on the platform using subprocess.Popen and
+ return a tuple of the status and stdout output.
+ """
+ from subprocess import Popen, PIPE
+ # Set stdout and stderr to PIPE because we want to capture stdout and
+ # prevent stderr from displaying.
+ p = Popen(cmd, shell=True, stdout=PIPE, stderr=PIPE)
+ # We use p.communicate() instead of p.wait() to avoid deadlocks if the
+ # output buffers exceed POSIX buffer size.
+ stdout, stderr = p.communicate()
+ return p.returncode, stdout.strip()
def gqn(val):
"""
@@ -7,7 +24,7 @@ def gqn(val):
backend quotename function).
"""
if isinstance(val, basestring):
- if isinstance(val, UnicodeType): val = val.encode('ascii')
+ if isinstance(val, unicode): val = val.encode('ascii')
return "'%s'" % val
else:
return str(val)
3  django/contrib/gis/db/models/__init__.py
View
@@ -7,9 +7,6 @@
# 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, \
5 django/contrib/gis/db/models/aggregates.py
View
@@ -5,7 +5,7 @@
class GeoAggregate(Aggregate):
def add_to_query(self, query, alias, col, source, is_summary):
- if hasattr(source, '_geom'):
+ if hasattr(source, 'geom_type'):
# Doing additional setup on the Query object for spatial aggregates.
aggregate = getattr(query.aggregates_module, self.name)
@@ -18,6 +18,9 @@ def add_to_query(self, query, alias, col, source, is_summary):
super(GeoAggregate, self).add_to_query(query, alias, col, source, is_summary)
+class Collect(GeoAggregate):
+ name = 'Collect'
+
class Extent(GeoAggregate):
name = 'Extent'
64 django/contrib/gis/db/models/fields/__init__.py
View
@@ -8,12 +8,16 @@
# 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.
+def deprecated_property(func):
+ from warnings import warn
+ warn('This attribute has been deprecated, pleas use "%s" instead.' % func.__name__[1:])
+ return property(func)
+
class GeometryField(SpatialBackend.Field):
"The base GIS field -- maps to the OpenGIS Specification Geometry type."
# The OpenGIS Geometry name.
- _geom = 'GEOMETRY'
+ geom_type = 'GEOMETRY'
# Geodetic units.
geodetic_units = ('Decimal Degree', 'degree')
@@ -37,15 +41,15 @@ def __init__(self, verbose_name=None, srid=4326, spatial_index=True, dim=2, **kw
"""
# Setting the index flag with the value of the `spatial_index` keyword.
- self._index = spatial_index
+ self.spatial_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)
+ self.srid = srid
+ self.units, self.units_name, self._spheroid = get_srid_info(srid)
# Setting the dimension of the geometry field.
- self._dim = dim
+ self.dim = dim
# Setting the verbose_name keyword argument with the positional
# first parameter, so this works like normal fields.
@@ -53,6 +57,29 @@ def __init__(self, verbose_name=None, srid=4326, spatial_index=True, dim=2, **kw
super(GeometryField, self).__init__(**kwargs) # Calling the parent initializtion function
+ # The following properties are for formerly private variables that are now
+ # public for GeometryField. Because of their use by third-party applications,
+ # a deprecation warning is issued to notify them to use new attribute name.
+ def _deprecated_warning(self, old_name, new_name):
+ from warnings import warn
+ warn('The `%s` attribute name is deprecated, please update your code to use `%s` instead.' %
+ (old_name, new_name))
+
+ @property
+ def _geom(self):
+ self._deprecated_warning('_geom', 'geom_type')
+ return self.geom_type
+
+ @property
+ def _index(self):
+ self._deprecated_warning('_index', 'spatial_index')
+ return self.spatial_index
+
+ @property
+ def _srid(self):
+ self._deprecated_warning('_srid', 'srid')
+ return self.srid
+
### Routines specific to GeometryField ###
@property
def geodetic(self):
@@ -60,7 +87,7 @@ 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
+ return self.units_name in self.geodetic_units
def get_distance(self, dist_val, lookup_type):
"""
@@ -80,7 +107,7 @@ def get_distance(self, dist_val, lookup_type):
# Spherical distance calculation parameter should be in meters.
dist_param = dist.m
else:
- dist_param = getattr(dist, Distance.unit_attname(self._unit_name))
+ dist_param = getattr(dist, Distance.unit_attname(self.units_name))
else:
# Assuming the distance is in the units of the field.
dist_param = dist
@@ -127,8 +154,8 @@ def get_srid(self, geom):
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
+ if gsrid is None or self.srid == -1 or (gsrid == -1 and self.srid != -1):
+ return self.srid
else:
return gsrid
@@ -141,8 +168,9 @@ def contribute_to_class(self, cls, name):
def formfield(self, **kwargs):
defaults = {'form_class' : forms.GeometryField,
- 'geom_type' : self._geom,
'null' : self.null,
+ 'geom_type' : self.geom_type,
+ 'srid' : self.srid,
}
defaults.update(kwargs)
return super(GeometryField, self).formfield(**defaults)
@@ -190,22 +218,22 @@ def get_db_prep_save(self, value):
# The OpenGIS Geometry Type Fields
class PointField(GeometryField):
- _geom = 'POINT'
+ geom_type = 'POINT'
class LineStringField(GeometryField):
- _geom = 'LINESTRING'
+ geom_type = 'LINESTRING'
class PolygonField(GeometryField):
- _geom = 'POLYGON'
+ geom_type = 'POLYGON'
class MultiPointField(GeometryField):
- _geom = 'MULTIPOINT'
+ geom_type = 'MULTIPOINT'
class MultiLineStringField(GeometryField):
- _geom = 'MULTILINESTRING'
+ geom_type = 'MULTILINESTRING'
class MultiPolygonField(GeometryField):
- _geom = 'MULTIPOLYGON'
+ geom_type = 'MULTIPOLYGON'
class GeometryCollectionField(GeometryField):
- _geom = 'GEOMETRYCOLLECTION'
+ geom_type = 'GEOMETRYCOLLECTION'
11 django/contrib/gis/db/models/mixin.py
View
@@ -1,11 +0,0 @@
-# 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
4 django/contrib/gis/db/models/proxy.py
View
@@ -44,13 +44,13 @@ def __set__(self, obj, value):
be used to set the geometry as well.
"""
# The OGC Geometry type of the field.
- gtype = self._field._geom
+ gtype = self._field.geom_type
# 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
+ if value.srid is None: value.srid = self._field.srid
elif isinstance(value, (NoneType, StringType, UnicodeType)):
# Set with None, WKT, or HEX
pass
67 django/contrib/gis/db/models/query.py
View
@@ -9,10 +9,6 @@
from django.contrib.gis.measure import Area, Distance
from django.contrib.gis.models import get_srid_info
-# 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):
@@ -44,10 +40,10 @@ def area(self, tolerance=0.05, **kwargs):
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:
+ elif SpatialBackend.postgis or SpatialBackend.spatialite:
if not geo_field.geodetic:
# Getting the area units of the geographic field.
- s['select_field'] = AreaField(Area.unit_attname(geo_field._unit_name))
+ s['select_field'] = AreaField(Area.unit_attname(geo_field.units_name))
else:
# TODO: Do we want to support raw number areas for geodetic fields?
raise Exception('Area on geodetic coordinate systems not supported.')
@@ -196,10 +192,18 @@ 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(),
- }
+ if SpatialBackend.spatialite:
+ if z != 0.0:
+ raise NotImplementedError('SpatiaLite does not support 3D scaling.')
+ s = {'procedure_fmt' : '%(geo_col)s,%(x)s,%(y)s',
+ 'procedure_args' : {'x' : x, 'y' : y},
+ 'select_field' : GeomField(),
+ }
+ else:
+ 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):
@@ -226,10 +230,18 @@ 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(),
- }
+ if SpatialBackend.spatialite:
+ if z != 0.0:
+ raise NotImplementedError('SpatiaLite does not support 3D translation.')
+ s = {'procedure_fmt' : '%(geo_col)s,%(x)s,%(y)s',
+ 'procedure_args' : {'x' : x, 'y' : y},
+ 'select_field' : GeomField(),
+ }
+ else:
+ 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):
@@ -415,7 +427,7 @@ def _distance_attribute(self, func, geom=None, tolerance=0.05, spheroid=False, *
if geo_field.geodetic:
dist_att = 'm'
else:
- dist_att = Distance.unit_attname(geo_field._unit_name)
+ dist_att = Distance.unit_attname(geo_field.units_name)
# Shortcut booleans for what distance function we're using.
distance = func == 'distance'
@@ -430,7 +442,7 @@ def _distance_attribute(self, func, geom=None, tolerance=0.05, spheroid=False, *
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
+ # keyword or when 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):
@@ -456,6 +468,9 @@ def _distance_attribute(self, func, geom=None, tolerance=0.05, spheroid=False, *
else:
geodetic = geo_field.geodetic
+ if SpatialBackend.spatialite and geodetic:
+ raise ValueError('SQLite does not support linear distance calculations on geodetic coordinate systems.')
+
if distance:
if self.query.transformed_srid:
# Setting the `geom_args` flag to false because we want to handle
@@ -467,12 +482,22 @@ def _distance_attribute(self, func, geom=None, tolerance=0.05, spheroid=False, *
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'
+ # geometry parameter. `GeomFromText` constructor is also needed
+ # to wrap geom placeholder for SpatiaLite.
+ if SpatialBackend.spatialite:
+ procedure_fmt += ', %s(%%%%s, %s)' % (SpatialBackend.from_text, self.query.transformed_srid)
+ else:
+ 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)
+ # SpatiaLite also needs geometry placeholder wrapped in `GeomFromText`
+ # constructor.
+ if SpatialBackend.spatialite:
+ procedure_fmt += ', %s(%s(%%%%s, %s), %s)' % (SpatialBackend.transform, SpatialBackend.from_text,
+ geom.srid, self.query.transformed_srid)
+ else:
+ 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'
@@ -483,9 +508,9 @@ def _distance_attribute(self, func, geom=None, tolerance=0.05, spheroid=False, *
# 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.')
+ raise ValueError('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')
+ raise ValueError('Spherical distance calculation only supported with Point Geometry parameters')
# The `function` procedure argument needs to be set differently for
# geodetic distance calculations.
if spheroid:
17 django/contrib/gis/db/models/sql/aggregates.py
View
@@ -44,8 +44,17 @@ def convert_extent(clob):
return None
def convert_geom(clob, geo_field):
- if clob: return SpatialBackend.Geometry(clob.read(), geo_field._srid)
- else: return None
+ if clob:
+ return SpatialBackend.Geometry(clob.read(), geo_field.srid)
+ else:
+ return None
+elif SpatialBackend.spatialite:
+ # SpatiaLite returns WKT.
+ def convert_geom(wkt, geo_field):
+ if wkt:
+ return SpatialBackend.Geometry(wkt, geo_field.srid)
+ else:
+ return None
class GeoAggregate(Aggregate):
# Overriding the SQL template with the geographic one.
@@ -71,6 +80,10 @@ def __init__(self, col, source=None, is_summary=False, **extra):
if not self.sql_function:
raise NotImplementedError('This aggregate functionality not implemented for your spatial backend.')
+class Collect(GeoAggregate):
+ conversion_class = GeomField
+ sql_function = SpatialBackend.collect
+
class Extent(GeoAggregate):
is_extent = True
sql_function = SpatialBackend.extent
4 django/contrib/gis/db/models/sql/conversion.py
View
@@ -2,11 +2,15 @@
This module holds simple classes used by GeoQuery.convert_values
to convert geospatial values from the database.
"""
+from django.contrib.gis.db.backend import SpatialBackend
+
class BaseField(object):
def get_internal_type(self):
"Overloaded method so OracleQuery.convert_values doesn't balk."
return None
+if SpatialBackend.oracle: BaseField.empty_strings_allowed = False
+
class AreaField(BaseField):
"Wrapper for Area values."
def __init__(self, area_att):
11 django/contrib/gis/db/models/sql/query.py
View
@@ -208,13 +208,14 @@ def convert_values(self, value, field):
if SpatialBackend.oracle:
# Running through Oracle's first.
value = super(GeoQuery, self).convert_values(value, field or GeomField())
+
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):
+ elif isinstance(field, GeomField) and value:
value = SpatialBackend.Geometry(value)
return value
@@ -260,7 +261,7 @@ def get_select_format(self, fld):
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'):
+ if SpatialBackend.select and hasattr(fld, 'geom_type'):
# 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
@@ -270,8 +271,10 @@ def get_select_format(self, fld):
# 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:
+ # field -- this is only used by `transform` for Oracle and
+ # SpatiaLite backends.
+ if self.transformed_srid and ( SpatialBackend.oracle or
+ SpatialBackend.spatialite ):
sel_fmt = "'SRID=%d;'||%s" % (self.transformed_srid, sel_fmt)
else:
sel_fmt = '%s'
10 django/contrib/gis/db/models/sql/where.py
View
@@ -15,7 +15,7 @@ class GeoAnnotation(object):
"""
def __init__(self, field, value, where):
self.geodetic = field.geodetic
- self.geom_type = field._geom
+ self.geom_type = field.geom_type
self.value = value
self.where = tuple(where)
@@ -37,7 +37,7 @@ def add(self, data, connector):
obj, lookup_type, value = data
alias, col, field = obj.alias, obj.col, obj.field
- if not hasattr(field, "_geom"):
+ if not hasattr(field, "geom_type"):
# Not a geographic field, so call `WhereNode.add`.
return super(GeoWhereNode, self).add(data, connector)
else:
@@ -50,7 +50,7 @@ def add(self, data, connector):
# Get the SRID of the geometry field that the expression was meant
# to operate on -- it's needed to determine whether transformation
# SQL is necessary.
- srid = geo_fld._srid
+ srid = geo_fld.srid
# Getting the quoted representation of the geometry column that
# the expression is operating on.
@@ -58,8 +58,8 @@ def add(self, data, connector):
# If it's in a different SRID, we'll need to wrap in
# transformation SQL.
- if not srid is None and srid != field._srid and SpatialBackend.transform:
- placeholder = '%s(%%s, %s)' % (SpatialBackend.transform, field._srid)
+ if not srid is None and srid != field.srid and SpatialBackend.transform:
+ placeholder = '%s(%%s, %s)' % (SpatialBackend.transform, field.srid)
else:
placeholder = '%s'
1  django/contrib/gis/forms/fields.py
View
@@ -19,6 +19,7 @@ class GeometryField(forms.Field):
def __init__(self, **kwargs):
self.null = kwargs.pop('null')
self.geom_type = kwargs.pop('geom_type')
+ self.srid = kwargs.pop('srid')
super(GeometryField, self).__init__(**kwargs)
def clean(self, value):
180 django/contrib/gis/models.py
View
@@ -15,24 +15,24 @@ class SpatialRefSysMixin(object):
The SpatialRefSysMixin is a class used by the database-dependent
SpatialRefSys objects to reduce redundnant code.
"""
-
# For pulling out the spheroid from the spatial reference string. This
# regular expression is used only if the user does not have GDAL installed.
- # TODO: Flattening not used in all ellipsoids, could also be a minor axis, or 'b'
- # parameter.
+ # TODO: Flattening not used in all ellipsoids, could also be a minor axis,
+ # or 'b' parameter.
spheroid_regex = re.compile(r'.+SPHEROID\[\"(?P<name>.+)\",(?P<major>\d+(\.\d+)?),(?P<flattening>\d{3}\.\d+),')
# For pulling out the units on platforms w/o GDAL installed.
# TODO: Figure out how to pull out angular units of projected coordinate system and
- # fix for LOCAL_CS types. GDAL should be highly recommended for performing
+ # fix for LOCAL_CS types. GDAL should be highly recommended for performing
# distance queries.
units_regex = re.compile(r'.+UNIT ?\["(?P<unit_name>[\w \'\(\)]+)", ?(?P<unit>[\d\.]+)(,AUTHORITY\["(?P<unit_auth_name>[\w \'\(\)]+)","(?P<unit_auth_val>\d+)"\])?\]([\w ]+)?(,AUTHORITY\["(?P<auth_name>[\w \'\(\)]+)","(?P<auth_val>\d+)"\])?\]$')
-
+
def srs(self):
"""
Returns a GDAL SpatialReference object, if GDAL is installed.
"""
if HAS_GDAL:
+ # TODO: Is caching really necessary here? Is complexity worth it?
if hasattr(self, '_srs'):
# Returning a clone of the cached SpatialReference object.
return self._srs.clone()
@@ -45,7 +45,13 @@ def srs(self):
return self.srs
except Exception, msg:
pass
-
+
+ try:
+ self._srs = SpatialReference(self.proj4text)
+ return self.srs
+ except Exception, msg:
+ pass
+
raise Exception('Could not get OSR SpatialReference from WKT: %s\nError:\n%s' % (self.wkt, msg))
else:
raise Exception('GDAL is not installed.')
@@ -107,7 +113,7 @@ def linear_name(self):
"Returns the linear units name."
if HAS_GDAL:
return self.srs.linear_name
- elif self.geographic:
+ elif self.geographic:
return None
else:
m = self.units_regex.match(self.wkt)
@@ -181,13 +187,13 @@ def get_spheroid(cls, wkt, string=True):
sphere_name = srs['spheroid']
else:
m = cls.spheroid_regex.match(wkt)
- if m:
+ if m:
sphere_params = (float(m.group('major')), float(m.group('flattening')))
sphere_name = m.group('name')
- else:
+ else:
return None
-
- if not string:
+
+ if not string:
return sphere_name, sphere_params
else:
# `string` parameter used to place in format acceptable by PostGIS
@@ -195,7 +201,7 @@ def get_spheroid(cls, wkt, string=True):
radius, flattening = sphere_params[0], sphere_params[2]
else:
radius, flattening = sphere_params
- return 'SPHEROID["%s",%s,%s]' % (sphere_name, radius, flattening)
+ return 'SPHEROID["%s",%s,%s]' % (sphere_name, radius, flattening)
get_spheroid = classmethod(get_spheroid)
def __unicode__(self):
@@ -208,76 +214,84 @@ def __unicode__(self):
except:
return unicode(self.wkt)
-# The SpatialRefSys and GeometryColumns models
-_srid_info = True
-if not PYTHON23 and settings.DATABASE_ENGINE == 'postgresql_psycopg2':
- # Because the PostGIS version is checked when initializing the spatial
- # backend a `ProgrammingError` will be raised if the PostGIS tables
- # and functions are not installed. We catch here so it won't be raised when
- # running the Django test suite. `ImportError` is also possible if no ctypes.
- try:
- from django.contrib.gis.db.backend.postgis.models import GeometryColumns, SpatialRefSys
- except:
- _srid_info = False
-elif not PYTHON23 and settings.DATABASE_ENGINE == 'oracle':
- # Same thing as above, except the GEOS library is attempted to be loaded for
- # `BaseSpatialBackend`, and an exception will be raised during the
- # Django test suite if it doesn't exist.
+# Defining dummy default first; if spatial db, will overrride.
+def get_srid_info(srid):
+ """
+ Dummy routine for the backends that do not have the OGC required
+ spatial metadata tables (like MySQL).
+ """
+ return None, None, None
+
+# Django test suite on 2.3 platforms will choke on code inside this
+# conditional.
+if not PYTHON23:
try:
- from django.contrib.gis.db.backend.oracle.models import GeometryColumns, SpatialRefSys
- except:
- _srid_info = False
-else:
- _srid_info = False
+ # try/except'ing the importation of SpatialBackend. Have to fail
+ # silently because this module may be inadvertently invoked by
+ # non-GeoDjango users (e.g., when the Django test suite executes
+ # the models.py of all contrib apps).
+ from django.contrib.gis.db.backend import SpatialBackend
+ if SpatialBackend.mysql: raise Exception
-if _srid_info:
- def get_srid_info(srid):
- """
- Returns the units, unit name, and spheroid WKT associated with the
- given SRID from the `spatial_ref_sys` (or equivalent) spatial database
- table. We use a database cursor to execute the query because this
- function is used when it is not possible to use the ORM (for example,
- during field initialization).
- """
- # SRID=-1 is a common convention for indicating the geometry has no
- # spatial reference information associated with it. Thus, we will
- # return all None values without raising an exception.
- if srid == -1: return None, None, None
-
- # Getting the spatial reference WKT associated with the SRID from the
- # `spatial_ref_sys` (or equivalent) spatial database table. This query
- # cannot be executed using the ORM because this information is needed
- # when the ORM cannot be used (e.g., during the initialization of
- # `GeometryField`).
- from django.db import connection
- cur = connection.cursor()
- qn = connection.ops.quote_name
- stmt = 'SELECT %(table)s.%(wkt_col)s FROM %(table)s WHERE (%(table)s.%(srid_col)s = %(srid)s)'
- stmt = stmt % {'table' : qn(SpatialRefSys._meta.db_table),
- 'wkt_col' : qn(SpatialRefSys.wkt_col()),
- 'srid_col' : qn('srid'),
- 'srid' : srid,
- }
- cur.execute(stmt)
-
- # Fetching the WKT from the cursor; if the query failed raise an Exception.
- fetched = cur.fetchone()
- if not fetched:
- raise ValueError('Failed to find spatial reference entry in "%s" corresponding to SRID=%s.' %
- (SpatialRefSys._meta.db_table, srid))
- srs_wkt = fetched[0]
-
- # Getting metadata associated with the spatial reference system identifier.
- # Specifically, getting the unit information and spheroid information
- # (both required for distance queries).
- unit, unit_name = SpatialRefSys.get_units(srs_wkt)
- spheroid = SpatialRefSys.get_spheroid(srs_wkt)
- return unit, unit_name, spheroid
-else:
- def get_srid_info(srid):
- """
- Dummy routine for the backends that do not have the OGC required
- spatial metadata tables (like MySQL).
- """
- return None, None, None
-
+ # Exposing the SpatialRefSys and GeometryColumns models.
+ class SpatialRefSys(SpatialBackend.SpatialRefSys, SpatialRefSysMixin):
+ pass
+ GeometryColumns = SpatialBackend.GeometryColumns
+
+ # Override `get_srid_info` with something real thing.
+ def get_srid_info(srid):
+ """
+ Returns the units, unit name, and spheroid WKT associated with the
+ given SRID from the `spatial_ref_sys` (or equivalent) spatial database
+ table. We use a database cursor to execute the query because this
+ function is used when it is not possible to use the ORM (for example,
+ during field initialization).
+ """
+ # SRID=-1 is a common convention for indicating the geometry has no
+ # spatial reference information associated with it. Thus, we will
+ # return all None values without raising an exception.
+ if srid == -1: return None, None, None
+
+ # Getting the spatial reference WKT associated with the SRID from the
+ # `spatial_ref_sys` (or equivalent) spatial database table. This query
+ # cannot be executed using the ORM because this information is needed
+ # when the ORM cannot be used (e.g., during the initialization of
+ # `GeometryField`).
+ from django.db import connection
+ cur = connection.cursor()
+ qn = connection.ops.quote_name
+ stmt = 'SELECT %(table)s.%(wkt_col)s FROM %(table)s WHERE (%(table)s.%(srid_col)s = %(srid)s)'
+ params = {'table' : qn(SpatialRefSys._meta.db_table),
+ 'srid_col' : qn('srid'),
+ 'srid' : srid,
+ }
+ if SpatialBackend.spatialite:
+ if not HAS_GDAL: raise Exception('GDAL is required to use the SpatiaLite backend.')
+ params['wkt_col'] = 'proj4text'
+ else:
+ params['wkt_col'] = qn(SpatialRefSys.wkt_col())
+
+ # Executing the SQL statement.
+ cur.execute(stmt % params)
+
+ # Fetching the WKT from the cursor; if the query failed raise an Exception.
+ fetched = cur.fetchone()
+ if not fetched:
+ raise ValueError('Failed to find spatial reference entry in "%s" corresponding to SRID=%s.' %
+ (SpatialRefSys._meta.db_table, srid))
+
+ if SpatialBackend.spatialite:
+ # Because the `spatial_ref_sys` table does _not_ contain a WKT column,
+ # we have to use GDAL to determine the units from the PROJ.4 string.
+ srs_wkt = SpatialReference(fetched[0]).wkt
+ else:
+ srs_wkt = fetched[0]
+
+ # Getting metadata associated with the spatial reference system identifier.
+ # Specifically, getting the unit information and spheroid information
+ # (both required for distance queries).
+ unit, unit_name = SpatialRefSys.get_units(srs_wkt)
+ spheroid = SpatialRefSys.get_spheroid(srs_wkt)
+ return unit, unit_name, spheroid
+ except:
+ pass
73 django/contrib/gis/tests/__init__.py
View
@@ -9,28 +9,26 @@ def geo_suite():
some backends).
"""
from django.conf import settings
- from django.contrib.gis.tests.utils import mysql, oracle, postgis
- from django.contrib.gis import gdal, utils
+ from django.contrib.gis.gdal import HAS_GDAL
+ from django.contrib.gis.utils import HAS_GEOIP
+ from django.contrib.gis.tests.utils import mysql
# The test suite.
s = unittest.TestSuite()
- # Adding the GEOS tests. (__future__)
- from django.contrib.gis.geos import tests as geos_tests
- s.addTest(geos_tests.suite())
-
- # Test apps that require use of a spatial database (e.g., creation of models)
+ # Tests that require use of a spatial database (e.g., creation of models)
test_apps = ['geoapp', 'relatedapp']
- if oracle or postgis:
- test_apps.append('distapp')
- # Tests that do not require setting up and tearing down a spatial database
- # and are modules in `django.contrib.gis.tests`.
+ # Tests that do not require setting up and tearing down a spatial database.
test_suite_names = [
'test_measure',
]
- if gdal.HAS_GDAL:
+ # Tests applications that require a test spatial db.
+ if not mysql:
+ test_apps.append('distapp')
+
+ if HAS_GDAL:
# These tests require GDAL.
test_suite_names.append('test_spatialrefsys')
test_apps.append('layermap')
@@ -39,14 +37,25 @@ def geo_suite():
from django.contrib.gis.gdal import tests as gdal_tests
s.addTest(gdal_tests.suite())
else:
- print >>sys.stderr, "GDAL not available - no GDAL tests will be run."
+ print >>sys.stderr, "GDAL not available - no tests requiring GDAL will be run."
- if utils.HAS_GEOIP and hasattr(settings, 'GEOIP_PATH'):
+ if HAS_GEOIP and hasattr(settings, 'GEOIP_PATH'):
test_suite_names.append('test_geoip')
+ # Adding the rest of the suites from the modules specified
+ # in the `test_suite_names`.
for suite_name in test_suite_names:
tsuite = import_module('django.contrib.gis.tests.' + suite_name)
s.addTest(tsuite.suite())
+
+ # Adding the GEOS tests _last_. Doing this because if suite starts
+ # immediately with this test while after running syncdb, it will cause a
+ # segmentation fault. My initial guess is that SpatiaLite is still in
+ # critical areas of non thread-safe GEOS code when the test suite is run.
+ # TODO: Confirm my reasoning. Are there other consequences?
+ from django.contrib.gis.geos import tests as geos_tests
+ s.addTest(geos_tests.suite())
+
return s, test_apps
def run_gis_tests(test_labels, **kwargs):
@@ -84,8 +93,8 @@ def run_gis_tests(test_labels, **kwargs):
# Creating the test suite, adding the test models to INSTALLED_APPS, and
# adding the model test suites to our suite package.
gis_suite, test_apps = geo_suite()
- for test_app in test_apps:
- module_name = 'django.contrib.gis.tests.%s' % test_app
+ for test_model in test_apps:
+ module_name = 'django.contrib.gis.tests.%s' % test_model
if mysql:
test_module = 'tests_mysql'
else:
@@ -114,12 +123,12 @@ def run_gis_tests(test_labels, **kwargs):
def run_tests(test_labels, verbosity=1, interactive=True, extra_tests=[], suite=None):
"""
- This module allows users to run tests for GIS apps that require the creation
+ This module allows users to run tests for GIS apps that require the creation
of a spatial database. Currently, this is only required for PostgreSQL as
PostGIS needs extra overhead in test database creation.
- In order to create a PostGIS database, the DATABASE_USER (or
- TEST_DATABASE_USER, if defined) will require superuser priviliges.
+ In order to create a PostGIS database, the DATABASE_USER (or
+ TEST_DATABASE_USER, if defined) will require superuser priviliges.
To accomplish this outside the `postgres` user, you have a few options:
(A) Make your user a super user:
@@ -133,11 +142,11 @@ def run_tests(test_labels, verbosity=1, interactive=True, extra_tests=[], suite=
(B) Create your own PostgreSQL database as a local user:
1. Initialize database: `initdb -D /path/to/user/db`
2. If there's already a Postgres instance on the machine, it will need
- to use a different TCP port than 5432. Edit postgresql.conf (in
- /path/to/user/db) to change the database port (e.g. `port = 5433`).
+ to use a different TCP port than 5432. Edit postgresql.conf (in
+ /path/to/user/db) to change the database port (e.g. `port = 5433`).
3. Start this database `pg_ctl -D /path/to/user/db start`
- (C) On Windows platforms the pgAdmin III utility may also be used as
+ (C) On Windows platforms the pgAdmin III utility may also be used as
a simple way to add superuser privileges to your database user.
The TEST_RUNNER needs to be set in your settings like so:
@@ -145,10 +154,10 @@ def run_tests(test_labels, verbosity=1, interactive=True, extra_tests=[], suite=
TEST_RUNNER='django.contrib.gis.tests.run_tests'
Note: This test runner assumes that the PostGIS SQL files ('lwpostgis.sql'
- and 'spatial_ref_sys.sql') are installed in the directory specified by
+ and 'spatial_ref_sys.sql') are installed in the directory specified by
`pg_config --sharedir` (and defaults to /usr/local/share if that fails).
This behavior is overridden if POSTGIS_SQL_PATH is set in your settings.
-
+
Windows users should set POSTGIS_SQL_PATH manually because the output
of `pg_config` uses paths like 'C:/PROGRA~1/POSTGR~1/..'.
@@ -160,18 +169,21 @@ def run_tests(test_labels, verbosity=1, interactive=True, extra_tests=[], suite=
from django.test.simple import build_suite, build_test
from django.test.utils import setup_test_environment, teardown_test_environment
- # The `create_spatial_db` routine abstracts away all the steps needed
+ # The `create_test_spatial_db` routine abstracts away all the steps needed
# to properly construct a spatial database for the backend.
- from django.contrib.gis.db.backend import create_spatial_db
+ from django.contrib.gis.db.backend import create_test_spatial_db
# Setting up for testing.
setup_test_environment()
settings.DEBUG = False
old_name = settings.DATABASE_NAME
+ # Creating the test spatial database.
+ create_test_spatial_db(verbosity=verbosity)
+
# The suite may be passed in manually, e.g., when we run the GeoDjango test,
- # we want to build it and pass it in due to some customizations. Otherwise,
- # the normal test suite creation process from `django.test.simple.run_tests`
+ # we want to build it and pass it in due to some customizations. Otherwise,
+ # the normal test suite creation process from `django.test.simple.run_tests`
# is used to create the test suite.
if suite is None:
suite = unittest.TestSuite()
@@ -185,13 +197,10 @@ def run_tests(test_labels, verbosity=1, interactive=True, extra_tests=[], suite=
else:
for app in get_apps():
suite.addTest(build_suite(app))
-
+
for test in extra_tests:
suite.addTest(test)
- # Creating the test spatial database.
- create_spatial_db(test=True, verbosity=verbosity)
-
# Executing the tests (including the model tests), and destorying the
# test database after the tests have completed.
result = unittest.TextTestRunner(verbosity=verbosity).run(suite)
0  django/contrib/gis/tests/layermap/cities/cities.dbf → django/contrib/gis/tests/data/cities/cities.dbf
View
File renamed without changes
0  django/contrib/gis/tests/layermap/cities/cities.prj → django/contrib/gis/tests/data/cities/cities.prj
View
File renamed without changes
0  django/contrib/gis/tests/layermap/cities/cities.shp → django/contrib/gis/tests/data/cities/cities.shp
View
File renamed without changes
0  django/contrib/gis/tests/layermap/cities/cities.shx → django/contrib/gis/tests/data/cities/cities.shx
View
File renamed without changes
0  .../contrib/gis/tests/layermap/counties/counties.dbf → django/contrib/gis/tests/data/counties/counties.dbf
View
File renamed without changes
0  .../contrib/gis/tests/layermap/counties/counties.shp → django/contrib/gis/tests/data/counties/counties.shp
View
File renamed without changes
0  .../contrib/gis/tests/layermap/counties/counties.shx → django/contrib/gis/tests/data/counties/counties.shx
View
File renamed without changes
0  ...ib/gis/tests/layermap/interstates/interstates.dbf → ...ontrib/gis/tests/data/interstates/interstates.dbf
View
File renamed without changes
0  ...ib/gis/tests/layermap/interstates/interstates.prj → ...ontrib/gis/tests/data/interstates/interstates.prj
View
File renamed without changes
0  ...ib/gis/tests/layermap/interstates/interstates.shp → ...ontrib/gis/tests/data/interstates/interstates.shp
View
File renamed without changes
0  ...ib/gis/tests/layermap/interstates/interstates.shx → ...ontrib/gis/tests/data/interstates/interstates.shx
View
File renamed without changes
3  django/contrib/gis/tests/distapp/data.py
View
@@ -31,3 +31,6 @@
interstates = (('I-25', 'LINESTRING(-104.4780170766108 36.66698791870694, -104.4468522338495 36.79925409393386, -104.46212692626 36.9372149776075, -104.5126119783768 37.08163268820887, -104.5247764602161 37.29300499892048, -104.7084397427668 37.49150259925398, -104.8126599016282 37.69514285621863, -104.8452887035466 37.87613395659479, -104.7160169341003 38.05951763337799, -104.6165437927668 38.30432045855106, -104.6437227858174 38.53979986564737, -104.7596170387259 38.7322907594295, -104.8380078676822 38.89998460604341, -104.8501253693506 39.09980189213358, -104.8791648316464 39.24368776457503, -104.8635041274215 39.3785278162751, -104.8894471170052 39.5929228239605, -104.9721242843344 39.69528482419685, -105.0112104500356 39.7273080432394, -105.0010368577104 39.76677607811571, -104.981835619 39.81466504121967, -104.9858891550477 39.88806911250832, -104.9873548059578 39.98117234571016, -104.9766220487419 40.09796423450692, -104.9818565932953 40.36056530662884, -104.9912746373997 40.74904484447656)'),
)
+
+stx_interstates = (('I-10', 'LINESTRING(924952.5 4220931.6,925065.3 4220931.6,929568.4 4221057.8)'),
+ )
9 django/contrib/gis/tests/distapp/models.py
View
@@ -37,6 +37,13 @@ def __unicode__(self): return self.name
class Interstate(models.Model):
"Geodetic model for U.S. Interstates."
name = models.CharField(max_length=10)
- line = models.LineStringField()
+ path = models.LineStringField()
+ objects = models.GeoManager()
+ def __unicode__(self): return self.name
+
+class SouthTexasInterstate(models.Model):
+ "Projected model for South Texas Interstates."
+ name = models.CharField(max_length=10)
+ path = models.LineStringField(srid=32140)
objects = models.GeoManager()
def __unicode__(self): return self.name
106 django/contrib/gis/tests/distapp/tests.py
View
@@ -5,11 +5,11 @@
from django.contrib.gis.gdal import DataSource
from django.contrib.gis.geos import GEOSGeometry, Point, LineString
from django.contrib.gis.measure import D # alias for Distance
-from django.contrib.gis.db.models import GeoQ
-from django.contrib.gis.tests.utils import oracle, postgis, no_oracle
+from django.contrib.gis.tests.utils import oracle, postgis, spatialite, no_oracle, no_spatialite
-from models import AustraliaCity, Interstate, SouthTexasCity, SouthTexasCityFt, CensusZipcode, SouthTexasZipcode
-from data import au_cities, interstates, stx_cities, stx_zips
+from models import AustraliaCity, Interstate, SouthTexasInterstate, \
+ SouthTexasCity, SouthTexasCityFt, CensusZipcode, SouthTexasZipcode
+from data import au_cities, interstates, stx_interstates, stx_cities, stx_zips
class DistanceTest(unittest.TestCase):
@@ -32,9 +32,12 @@ def test01_init(self):
# Loading up the cities.
def load_cities(city_model, data_tup):
for name, x, y in data_tup:
- c = city_model(name=name, point=Point(x, y, srid=4326))
- c.save()
-
+ city_model(name=name, point=Point(x, y, srid=4326)).save()
+
+ def load_interstates(imodel, data_tup):
+ for name, wkt in data_tup:
+ imodel(name=name, path=wkt).save()
+
load_cities(SouthTexasCity, stx_cities)
load_cities(SouthTexasCityFt, stx_cities)
load_cities(AustraliaCity, au_cities)
@@ -42,7 +45,7 @@ def load_cities(city_model, data_tup):
self.assertEqual(9, SouthTexasCity.objects.count())
self.assertEqual(9, SouthTexasCityFt.objects.count())
self.assertEqual(11, AustraliaCity.objects.count())
-
+
# Loading up the South Texas Zip Codes.
for name, wkt in stx_zips:
poly = GEOSGeometry(wkt, srid=4269)
@@ -52,10 +55,13 @@ def load_cities(city_model, data_tup):
self.assertEqual(4, CensusZipcode.objects.count())
# Loading up the Interstates.
- for name, wkt in interstates:
- Interstate(name=name, line=GEOSGeometry(wkt, srid=4326)).save()
+ load_interstates(Interstate, interstates)
+ load_interstates(SouthTexasInterstate, stx_interstates)
+
self.assertEqual(1, Interstate.objects.count())
+ self.assertEqual(1, SouthTexasInterstate.objects.count())
+ @no_spatialite
def test02_dwithin(self):
"Testing the `dwithin` lookup type."
# Distances -- all should be equal (except for the
@@ -63,7 +69,7 @@ def test02_dwithin(self):
# approximate).
tx_dists = [(7000, 22965.83), D(km=7), D(mi=4.349)]
au_dists = [(0.5, 32000), D(km=32), D(mi=19.884)]
-
+
# Expected cities for Australia and Texas.
tx_cities = ['Downtown Houston', 'Southside Place']
au_cities = ['Mittagong', 'Shellharbour', 'Thirroul', 'Wollongong']
@@ -86,27 +92,29 @@ def test02_dwithin(self):
if isinstance(dist, tuple):
if oracle: dist = dist[1]
else: dist = dist[0]
-
+
# Creating the query set.
qs = AustraliaCity.objects.order_by('name')
if type_error:
# A TypeError should be raised on PostGIS when trying to pass
- # Distance objects into a DWithin query using a geodetic field.
+ # Distance objects into a DWithin query using a geodetic field.
self.assertRaises(TypeError, AustraliaCity.objects.filter, point__dwithin=(self.au_pnt, dist))
else:
self.assertEqual(au_cities, self.get_names(qs.filter(point__dwithin=(self.au_pnt, dist))))
-
+
def test03a_distance_method(self):
"Testing the `distance` GeoQuerySet method on projected coordinate systems."
# The point for La Grange, TX
lagrange = GEOSGeometry('POINT(-96.876369 29.905320)', 4326)
- # Reference distances in feet and in meters. Got these values from
+ # Reference distances in feet and in meters. Got these values from
# using the provided raw SQL statements.
# SELECT ST_Distance(point, ST_Transform(ST_GeomFromText('POINT(-96.876369 29.905320)', 4326), 32140)) FROM distapp_southtexascity;
m_distances = [147075.069813, 139630.198056, 140888.552826,
138809.684197, 158309.246259, 212183.594374,
70870.188967, 165337.758878, 139196.085105]
# SELECT ST_Distance(point, ST_Transform(ST_GeomFromText('POINT(-96.876369 29.905320)', 4326), 2278)) FROM distapp_southtexascityft;
+ # Oracle 11 thinks this is not a projected coordinate system, so it's s
+ # not tested.
ft_distances = [482528.79154625, 458103.408123001, 462231.860397575,
455411.438904354, 519386.252102563, 696139.009211594,
232513.278304279, 542445.630586414, 456679.155883207]
@@ -115,8 +123,12 @@ def test03a_distance_method(self):
# with different projected coordinate systems.
dist1 = SouthTexasCity.objects.distance(lagrange, field_name='point')
dist2 = SouthTexasCity.objects.distance(lagrange) # Using GEOSGeometry parameter
- dist3 = SouthTexasCityFt.objects.distance(lagrange.ewkt) # Using EWKT string parameter.
- dist4 = SouthTexasCityFt.objects.distance(lagrange)
+ if spatialite or oracle:
+ dist_qs = [dist1, dist2]
+ else:
+ dist3 = SouthTexasCityFt.objects.distance(lagrange.ewkt) # Using EWKT string parameter.
+ dist4 = SouthTexasCityFt.objects.distance(lagrange)
+ dist_qs = [dist1, dist2, dist3, dist4]
# Original query done on PostGIS, have to adjust AlmostEqual tolerance
# for Oracle.
@@ -124,11 +136,12 @@ def test03a_distance_method(self):
else: tol = 5
# Ensuring expected distances are returned for each distance queryset.
- for qs in [dist1, dist2, dist3, dist4]:
+ for qs in dist_qs:
for i, c in enumerate(qs):
self.assertAlmostEqual(m_distances[i], c.distance.m, tol)
self.assertAlmostEqual(ft_distances[i], c.distance.survey_ft, tol)
+ @no_spatialite
def test03b_distance_method(self):
"Testing the `distance` GeoQuerySet method on geodetic coordnate systems."
if oracle: tol = 2
@@ -139,8 +152,8 @@ def test03b_distance_method(self):
if not oracle:
# PostGIS is limited to disance queries only to/from point geometries,
# ensuring a TypeError is raised if something else is put in.
- self.assertRaises(TypeError, AustraliaCity.objects.distance, 'LINESTRING(0 0, 1 1)')
- self.assertRaises(TypeError, AustraliaCity.objects.distance, LineString((0, 0), (1, 1)))
+ self.assertRaises(ValueError, AustraliaCity.objects.distance, 'LINESTRING(0 0, 1 1)')
+ self.assertRaises(ValueError, AustraliaCity.objects.distance, LineString((0, 0), (1, 1)))
# Got the reference distances using the raw SQL statements:
# SELECT ST_distance_spheroid(point, ST_GeomFromText('POINT(151.231341 -33.952685)', 4326), 'SPHEROID["WGS 84",6378137.0,298.257223563]') FROM distapp_australiacity WHERE (NOT (id = 11));
@@ -163,8 +176,8 @@ def test03c_distance_method(self):
"Testing the `distance` GeoQuerySet method used with `transform` on a geographic field."
# Normally you can't compute distances from a geometry field
# that is not a PointField (on PostGIS).
- self.assertRaises(TypeError, CensusZipcode.objects.distance, self.stx_pnt)
-
+ self.assertRaises(ValueError, CensusZipcode.objects.distance, self.stx_pnt)
+
# We'll be using a Polygon (created by buffering the centroid
# of 77005 to 100m) -- which aren't allowed in geographic distance
# queries normally, however our field has been transformed to
@@ -182,9 +195,11 @@ def test03c_distance_method(self):
# however.
buf1 = z.poly.centroid.buffer(100)
buf2 = buf1.transform(4269, clone=True)
+ ref_zips = ['77002', '77025', '77401']
+
for buf in [buf1, buf2]:
qs = CensusZipcode.objects.exclude(name='77005').transform(32140).distance(buf)
- self.assertEqual(['77002', '77025', '77401'], self.get_names(qs))
+ self.assertEqual(ref_zips, self.get_names(qs))
for i, z in enumerate(qs):
self.assertAlmostEqual(z.distance.m, dists_m[i], 5)
@@ -194,8 +209,16 @@ def test04_distance_lookups(self):
# (thus, Houston and Southside place will be excluded as tested in
# the `test02_dwithin` above).
qs1 = SouthTexasCity.objects.filter(point__distance_gte=(self.stx_pnt, D(km=7))).filter(point__distance_lte=(self.stx_pnt, D(km=20)))
- qs2 = SouthTexasCityFt.objects.filter(point__distance_gte=(self.stx_pnt, D(km=7))).filter(point__distance_lte=(self.stx_pnt, D(km=20)))
- for qs in qs1, qs2:
+
+ # Can't determine the units on SpatiaLite from PROJ.4 string, and
+ # Oracle 11 incorrectly thinks it is not projected.
+ if spatialite or oracle:
+ dist_qs = (qs1,)
+ else:
+ qs2 = SouthTexasCityFt.objects.filter(point__distance_gte=(self.stx_pnt, D(km=7))).filter(point__distance_lte=(self.stx_pnt, D(km=20)))
+ dist_qs = (qs1, qs2)
+
+ for qs in dist_qs:
cities = self.get_names(qs)
self.assertEqual(cities, ['Bellaire', 'Pearland', 'West University Place'])
@@ -206,7 +229,8 @@ def test04_distance_lookups(self):
# If we add a little more distance 77002 should be included.
qs = SouthTexasZipcode.objects.exclude(name='77005').filter(poly__distance_lte=(z.poly, D(m=300)))
self.assertEqual(['77002', '77025', '77401'], self.get_names(qs))
-
+
+ @no_spatialite
def test05_geodetic_distance_lookups(self):
"Testing distance lookups on geodetic coordinate systems."
if not oracle:
@@ -216,7 +240,7 @@ def test05_geodetic_distance_lookups(self):
self.assertRaises(TypeError,
AustraliaCity.objects.filter(point__distance_lte=(mp, D(km=100))))
# Too many params (4 in this case) should raise a ValueError.
- self.assertRaises(ValueError,
+ self.assertRaises(ValueError,
AustraliaCity.objects.filter, point__distance_lte=('POINT(5 23)', D(km=100), 'spheroid', '4'))
# Not enough params should raise a ValueError.
@@ -235,14 +259,13 @@ def test05_geodetic_distance_lookups(self):
d1, d2 = D(yd=19500), D(nm=400) # Yards (~17km) & Nautical miles.
# Normal geodetic distance lookup (uses `distance_sphere` on PostGIS.
- gq1 = GeoQ(point__distance_lte=(wollongong.point, d1))
- gq2 = GeoQ(point__distance_gte=(wollongong.point, d2))
+ gq1 = Q(point__distance_lte=(wollongong.point, d1))
+ gq2 = Q(point__distance_gte=(wollongong.point, d2))
qs1 = AustraliaCity.objects.exclude(name='Wollongong').filter(gq1 | gq2)
# Geodetic distance lookup but telling GeoDjango to use `distance_spheroid`
# instead (we should get the same results b/c accuracy variance won't matter
- # in this test case). Using `Q` instead of `GeoQ` to be different (post-qsrf
- # it doesn't matter).
+ # in this test case).
if postgis:
gq3 = Q(point__distance_lte=(wollongong.point, d1, 'spheroid'))
gq4 = Q(point__distance_gte=(wollongong.point, d2, 'spheroid'))
@@ -270,12 +293,23 @@ def test07_length(self):
"Testing the `length` GeoQuerySet method."
# Reference query (should use `length_spheroid`).
# SELECT ST_length_spheroid(ST_GeomFromText('<wkt>', 4326) 'SPHEROID["WGS 84",6378137,298.257223563, AUTHORITY["EPSG","7030"]]');
- len_m = 473504.769553813
- qs = Interstate.objects.length()
- if oracle: tol = 2
- else: tol = 5
- self.assertAlmostEqual(len_m, qs[0].length.m, tol)
+ len_m1 = 473504.769553813
+ len_m2 = 4617.668
+
+ if spatialite:
+ # Does not support geodetic coordinate systems.
+ self.assertRaises(ValueError, Interstate.objects.length)
+ else:
+ qs = Interstate.objects.length()
+ if oracle: tol = 2
+ else: tol = 5
+ self.assertAlmostEqual(len_m1, qs[0].length.m, tol)
+
+ # Now doing length on a projected coordinate system.
+ i10 = SouthTexasInterstate.objects.length().get(name='I-10')
+ self.assertAlmostEqual(len_m2, i10.length.m, 2)
+ @no_spatialite
def test08_perimeter(self):
"Testing the `perimeter` GeoQuerySet method."
# Reference query:
21 django/contrib/gis/tests/geoapp/models.py
View
@@ -1,5 +1,5 @@
from django.contrib.gis.db import models
-from django.contrib.gis.tests.utils import mysql
+from django.contrib.gis.tests.utils import mysql, spatialite
# MySQL spatial indices can't handle NULL geometries.
null_flag = not mysql
@@ -12,7 +12,7 @@ def __unicode__(self): return self.name
class City(models.Model):
name = models.CharField(max_length=30)
- point = models.PointField()
+ point = models.PointField()
objects = models.GeoManager()
def __unicode__(self): return self.name
@@ -27,12 +27,13 @@ class State(models.Model):
objects = models.GeoManager()
def __unicode__(self): return self.name
-class Feature(models.Model):
- name = models.CharField(max_length=20)
- geom = models.GeometryField()
- objects = models.GeoManager()
- def __unicode__(self): return self.name
+if not spatialite:
+ class Feature(models.Model):
+ name = models.CharField(max_length=20)
+ geom = models.GeometryField()
+ objects = models.GeoManager()
+ def __unicode__(self): return self.name
-class MinusOneSRID(models.Model):
- geom = models.PointField(srid=-1) # Minus one SRID.
- objects = models.GeoManager()
+ class MinusOneSRID(models.Model):
+ geom = models.PointField(srid=-1) # Minus one SRID.
+ objects = models.GeoManager()
117 django/contrib/gis/tests/geoapp/tests.py
View
@@ -1,10 +1,13 @@
import os, unittest
-from models import Country, City, PennsylvaniaCity, State, Feature, MinusOneSRID
from django.contrib.gis import gdal
from django.contrib.gis.db.backend import SpatialBackend
from django.contrib.gis.geos import *
from django.contrib.gis.measure import Distance
-from django.contrib.gis.tests.utils import no_oracle, no_postgis
+from django.contrib.gis.tests.utils import no_oracle, no_postgis, no_spatialite
+from models import Country, City, PennsylvaniaCity, State
+
+if not SpatialBackend.spatialite:
+ from models import Feature, MinusOneSRID
# TODO: Some tests depend on the success/failure of previous tests, these should
# be decoupled. This flag is an artifact of this problem, and makes debugging easier;
@@ -37,9 +40,11 @@ def get_file(wkt_file):
self.assertEqual(2, Country.objects.count())
self.assertEqual(8, City.objects.count())
- # Oracle cannot handle NULL geometry values w/certain queries.
- if SpatialBackend.oracle: n_state = 2
- else: n_state = 3
+ # Only PostGIS can handle NULL geometries
+ if SpatialBackend.postgis or SpatialBackend.spatialite:
+ n_state = 3
+ else:
+ n_state = 2
self.assertEqual(n_state, State.objects.count())
def test02_proxy(self):
@@ -112,6 +117,7 @@ def test02_proxy(self):
ns.delete()
@no_oracle # Oracle does not support KML.
+ @no_spatialite # SpatiaLite does not support KML.
def test03a_kml(self):
"Testing KML output from the database using GeoManager.kml()."
if DISABLE: return
@@ -137,6 +143,7 @@ def test03a_kml(self):
for ptown in [ptown1, ptown2]:
self.assertEqual(ref_kml, ptown.kml)
+ @no_spatialite # SpatiaLite does not support GML.
def test03b_gml(self):
"Testing GML output from the database using GeoManager.gml()."
if DISABLE: return
@@ -150,7 +157,7 @@ def test03b_gml(self):
if SpatialBackend.oracle:
# No precision parameter for Oracle :-/
import re
- gml_regex = re.compile(r'<gml:Point srsName="SDO:4326" xmlns:gml="http://www.opengis.net/gml"><gml:coordinates decimal="\." cs="," ts=" ">-104.60925199\d+,38.25500\d+ </gml:coordinates></gml:Point>')
+ gml_regex = re.compile(r'<gml:Point srsName="SDO:4326" xmlns:gml="http://www.opengis.net/gml"><gml:coordinates decimal="\." cs="," ts=" ">-104.60925\d+,38.25500\d+ </gml:coordinates></gml:Point>')
for ptown in [ptown1, ptown2]:
self.assertEqual(True, bool(gml_regex.match(ptown.gml)))
else:
@@ -180,7 +187,7 @@ def test04_transform(self):
self.assertAlmostEqual(ptown.x, p.point.x, prec)
self.assertAlmostEqual(ptown.y, p.point.y, prec)
- @no_oracle # Most likely can do this in Oracle, however, it is not yet implemented (patches welcome!)
+ @no_spatialite # SpatiaLite does not have an Extent function
def test05_extent(self):
"Testing the `extent` GeoQuerySet method."
if DISABLE: return
@@ -193,9 +200,10 @@ def test05_extent(self):
extent = qs.extent()
for val, exp in zip(extent, expected):
- self.assertAlmostEqual(exp, val, 8)
+ self.assertAlmostEqual(exp, val, 4)
@no_oracle
+ @no_spatialite # SpatiaLite does not have a MakeLine function
def test06_make_line(self):
"Testing the `make_line` GeoQuerySet method."
if DISABLE: return
@@ -214,10 +222,13 @@ def test09_disjoint(self):
qs1 = City.objects.filter(point__disjoint=ptown.point)
self.assertEqual(7, qs1.count())
- if not SpatialBackend.postgis:
+ if not (SpatialBackend.postgis or SpatialBackend.spatialite):
# TODO: Do NULL columns bork queries on PostGIS? The following
# error is encountered:
# psycopg2.ProgrammingError: invalid memory alloc request size 4294957297
+ #
+ # Similarly, on SpatiaLite Puerto Rico is also returned (could be a
+ # manifestation of
qs2 = State.objects.filter(poly__disjoint=ptown.point)
self.assertEqual(1, qs2.count())
self.assertEqual('Kansas', qs2[0].name)
@@ -248,10 +259,13 @@ def test10_contains_contained(self):
# Houston and Wellington.
tx = Country.objects.get(mpoly__contains=houston.point) # Query w/GEOSGeometry
nz = Country.objects.get(mpoly__contains=wellington.point.hex) # Query w/EWKBHEX
- ks = State.objects.get(poly__contains=lawrence.point)
self.assertEqual('Texas', tx.name)
self.assertEqual('New Zealand', nz.name)
- self.assertEqual('Kansas', ks.name)
+
+ # Spatialite 2.3 thinks that Lawrence is in Puerto Rico (a NULL geometry).
+ if not SpatialBackend.spatialite:
+ ks = State.objects.get(poly__contains=lawrence.point)
+ self.assertEqual('Kansas', ks.name)
# Pueblo and Oklahoma City (even though OK City is within the bounding box of Texas)
# are not contained in Texas or New Zealand.
@@ -304,13 +318,16 @@ def test11_lookup_insert_transform(self):
# If the GeometryField SRID is -1, then we shouldn't perform any
# transformation if the SRID of the input geometry is different.
- m1 = MinusOneSRID(geom=Point(17, 23, srid=4326))
- m1.save()
- self.assertEqual(-1, m1.geom.srid)
+ # SpatiaLite does not support missing SRID values.
+ if not SpatialBackend.spatialite:
+ m1 = MinusOneSRID(geom=Point(17, 23, srid=4326))
+ m1.save()
+ self.assertEqual(-1, m1.geom.srid)
# Oracle does not support NULL geometries in its spatial index for
# some routines (e.g., SDO_GEOM.RELATE).
@no_oracle
+ @no_spatialite
def test12_null_geometries(self):
"Testing NULL geometry support, and the `isnull` lookup type."
if DISABLE: return
@@ -334,6 +351,7 @@ def test12_null_geometries(self):
State(name='Northern Mariana Islands', poly=None).save()
@no_oracle # No specific `left` or `right` operators in Oracle.
+ @no_spatialite # No `left` or `right` operators in SpatiaLite.
def test13_left_right(self):
"Testing the 'left' and 'right' lookup types."
if DISABLE: return
@@ -398,7 +416,7 @@ def test15_relate(self):
self.assertRaises(e, qs.count)
# Relate works differently for the different backends.
- if SpatialBackend.postgis:
+ if SpatialBackend.postgis or SpatialBackend.spatialite:
contains_mask = 'T*T***FF*'
within_mask = 'T*F**F***'
intersects_mask = 'T********'
@@ -449,9 +467,12 @@ def test17_unionagg(self):
union = union1
self.assertEqual(True, union.equals_exact(u1, tol))
self.assertEqual(True, union.equals_exact(u2, tol))
- qs = City.objects.filter(name='NotACity')
- self.assertEqual(None, qs.unionagg(field_name='point'))
+ # SpatiaLite will segfault trying to union a NULL geometry.
+ if not SpatialBackend.spatialite:
+ qs = City.objects.filter(name='NotACity')
+ self.assertEqual(None, qs.unionagg(field_name='point'))
+ @no_spatialite # SpatiaLite does not support abstract geometry columns
def test18_geometryfield(self):
"Testing GeometryField."
if DISABLE: return
@@ -479,8 +500,12 @@ def test19_centroid(self):
"Testing the `centroid` GeoQuerySet method."
if DISABLE: return
qs = State.objects.exclude(poly__isnull=True).centroid()
- if SpatialBackend.oracle: tol = 0.1
- else: tol = 0.000000001
+ if SpatialBackend.oracle:
+ tol = 0.1
+ elif SpatialBackend.spatialite:
+ tol = 0.000001
+ else:
+ tol = 0.000000001
for s in qs:
self.assertEqual(True, s.poly.centroid.equals_exact(s.centroid, tol))
@@ -493,14 +518,19 @@ def test20_pointonsurface(self):
ref = {'New Zealand' : fromstr('POINT (174.616364 -36.100861)', srid=4326),
'Texas' : fromstr('POINT (-103.002434 36.500397)', srid=4326),
}
- elif SpatialBackend.postgis:
+
+ elif SpatialBackend.postgis or SpatialBackend.spatialite:
# Using GEOSGeometry to compute the reference point on surface values
# -- since PostGIS also uses GEOS these should be the same.
ref = {'New Zealand' : Country.objects.get(name='New Zealand').mpoly.point_on_surface,
'Texas' : Country.objects.get(name='Texas').mpoly.point_on_surface
}
for cntry in Country.objects.point_on_surface():