Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
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
@jbronn 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
View
8 django/contrib/gis/db/backend/__init__.py
@@ -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)
View
5 django/contrib/gis/db/backend/base.py
@@ -23,7 +23,4 @@ def __getattr__(self, name):
return self.__dict__[name]
except KeyError:
return False
-
-
-
-
+
View
4 django/contrib/gis/db/backend/mysql/__init__.py
@@ -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 *
View
4 django/contrib/gis/db/backend/mysql/creation.py
@@ -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)
View
24 django/contrib/gis/db/backend/mysql/field.py
@@ -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
View
7 django/contrib/gis/db/backend/oracle/__init__.py
@@ -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,
)
View
3  django/contrib/gis/db/backend/oracle/creation.py
@@ -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)
View
53 django/contrib/gis/db/backend/oracle/field.py
@@ -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
View
13 django/contrib/gis/db/backend/oracle/models.py
@@ -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):
View
8 django/contrib/gis/db/backend/postgis/__init__.py
@@ -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,
)
View
27 django/contrib/gis/db/backend/postgis/creation.py
@@ -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'):
View
58 django/contrib/gis/db/backend/postgis/field.py
@@ -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)
View
13 django/contrib/gis/db/backend/postgis/models.py
@@ -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
View
1  django/contrib/gis/db/backend/postgis/query.py
@@ -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')
View
21 django/contrib/gis/db/backend/util.py
@@ -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)
View
3  django/contrib/gis/db/models/__init__.py
@@ -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, \
View
5 django/contrib/gis/db/models/aggregates.py
@@ -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'
View
64 django/contrib/gis/db/models/fields/__init__.py
@@ -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'
View
11 django/contrib/gis/db/models/mixin.py
@@ -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
View
4 django/contrib/gis/db/models/proxy.py
@@ -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
View
67 django/contrib/gis/db/models/query.py
@@ -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:
View
17 django/contrib/gis/db/models/sql/aggregates.py
@@ -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
View
4 django/contrib/gis/db/models/sql/conversion.py
@@ -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):
View
11 django/contrib/gis/db/models/sql/query.py
@@ -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'
View
10 django/contrib/gis/db/models/sql/where.py
@@ -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'
View
1  django/contrib/gis/forms/fields.py
@@ -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):
View
180 django/contrib/gis/models.py
@@ -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
View
73 django/contrib/gis/tests/__init__.py
@@ -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)
View
0  ...trib/gis/tests/layermap/cities/cities.dbf → .../contrib/gis/tests/data/cities/cities.dbf
File renamed without changes
View
0  ...trib/gis/tests/layermap/cities/cities.prj → .../contrib/gis/tests/data/cities/cities.prj
File renamed without changes
View
0  ...trib/gis/tests/layermap/cities/cities.shp → .../contrib/gis/tests/data/cities/cities.shp
File renamed without changes
View
0  ...trib/gis/tests/layermap/cities/cities.shx → .../contrib/gis/tests/data/cities/cities.shx
File renamed without changes
View
0  .../gis/tests/layermap/counties/counties.dbf → ...trib/gis/tests/data/counties/counties.dbf
File renamed without changes
View
0  .../gis/tests/layermap/counties/counties.shp → ...trib/gis/tests/data/counties/counties.shp
File renamed without changes
View
0  .../gis/tests/layermap/counties/counties.shx → ...trib/gis/tests/data/counties/counties.shx
File renamed without changes
View
0  ...ests/layermap/interstates/interstates.dbf → ...is/tests/data/interstates/interstates.dbf
File renamed without changes
View
0  ...ests/layermap/interstates/interstates.prj → ...is/tests/data/interstates/interstates.prj
File renamed without changes
View
0  ...ests/layermap/interstates/interstates.shp → ...is/tests/data/interstates/interstates.shp
File renamed without changes
View
0  ...ests/layermap/interstates/interstates.shx → ...is/tests/data/interstates/interstates.shx
File renamed without changes
View
3  django/contrib/gis/tests/distapp/data.py
@@ -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)'),
+ )
View
9 django/contrib/gis/tests/distapp/models.py
@@ -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
View
106 django/contrib/gis/tests/distapp/tests.py
@@ -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:
View
21 django/contrib/gis/tests/geoapp/models.py
@@ -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()
View
117 django/contrib/gis/tests/geoapp/tests.py
@@ -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
+ #