Refactored and cleaned up parts of the spatial database backend. Changes 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
This commit is contained in:
parent
a61c0b7949
commit
867e71501c
|
@ -9,10 +9,12 @@ from django.contrib.gis.db.backend.util import gqn
|
|||
|
||||
# Retrieving the necessary settings from the backend.
|
||||
if settings.DATABASE_ENGINE == 'postgresql_psycopg2':
|
||||
from django.contrib.gis.db.backend.postgis import create_spatial_db, get_geo_where_clause, SpatialBackend
|
||||
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)
|
||||
|
|
|
@ -23,7 +23,4 @@ class BaseSpatialBackend(object):
|
|||
return self.__dict__[name]
|
||||
except KeyError:
|
||||
return False
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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 *
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 @@ class MySQLGeoField(Field):
|
|||
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
|
||||
|
|
|
@ -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 @@ SpatialBackend = BaseSpatialBackend(name='oracle', oracle=True,
|
|||
union=UNION,
|
||||
Adaptor=OracleSpatialAdaptor,
|
||||
Field=OracleSpatialField,
|
||||
GeometryColumns=GeometryColumns,
|
||||
SpatialRefSys=SpatialRefSys,
|
||||
)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -32,26 +32,25 @@ class OracleSpatialField(Field):
|
|||
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 @@ class OracleSpatialField(Field):
|
|||
# Getting the index name, Oracle doesn't allow object
|
||||
# names > 30 characters.
|
||||
idx_name = truncate_name('%s_%s_id' % (db_table, self.column), 30)
|
||||
|
||||
sql = style.SQL_KEYWORD('CREATE INDEX ') + \
|
||||
style.SQL_TABLE(qn(idx_name)) + \
|
||||
style.SQL_KEYWORD(' ON ') + \
|
||||
style.SQL_TABLE(qn(db_table)) + '(' + \
|
||||
style.SQL_FIELD(qn(self.column)) + ') ' + \
|
||||
style.SQL_KEYWORD('INDEXTYPE IS ') + \
|
||||
style.SQL_TABLE('MDSYS.SPATIAL_INDEX') + ';'
|
||||
|
||||
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 @@ class OracleSpatialField(Field):
|
|||
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 @@ class OracleSpatialField(Field):
|
|||
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 @@ class OracleSpatialField(Field):
|
|||
"""
|
||||
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
|
||||
|
|
|
@ -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 GeometryColumns(models.Model):
|
|||
@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 @@ class GeometryColumns(models.Model):
|
|||
@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 @@ class GeometryColumns(models.Model):
|
|||
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):
|
||||
|
|
|
@ -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 @@ SpatialBackend = BaseSpatialBackend(name='postgis', postgis=True,
|
|||
version=(MAJOR_VERSION, MINOR_VERSION1, MINOR_VERSION2),
|
||||
Adaptor=PostGISAdaptor,
|
||||
Field=PostGISField,
|
||||
GeometryColumns=GeometryColumns,
|
||||
SpatialRefSys=SpatialRefSys,
|
||||
)
|
||||
|
|
|
@ -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'):
|
||||
|
|
|
@ -19,35 +19,35 @@ class PostGISField(Field):
|
|||
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 @@ class PostGISField(Field):
|
|||
post_sql = self._add_geom(style, db_table)
|
||||
|
||||
# If the user wants to index this data, then get the indexing SQL as well.
|
||||
if self._index:
|
||||
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 @@ class PostGISField(Field):
|
|||
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)
|
||||
|
|
|
@ -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 GeometryColumns(models.Model):
|
|||
@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 @@ class GeometryColumns(models.Model):
|
|||
@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 @@ class GeometryColumns(models.Model):
|
|||
(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
|
||||
|
|
|
@ -42,6 +42,7 @@ if MAJOR_VERSION >= 1:
|
|||
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')
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -7,9 +7,6 @@ from django.contrib.gis.db.models.aggregates import *
|
|||
# The GeoManager
|
||||
from django.contrib.gis.db.models.manager import GeoManager
|
||||
|
||||
# The GeoQ object
|
||||
from django.contrib.gis.db.models.query import GeoQ
|
||||
|
||||
# The geographic-enabled fields.
|
||||
from django.contrib.gis.db.models.fields import \
|
||||
GeometryField, PointField, LineStringField, PolygonField, \
|
||||
|
|
|
@ -5,7 +5,7 @@ from django.contrib.gis.db.models.sql import GeomField
|
|||
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 @@ class GeoAggregate(Aggregate):
|
|||
|
||||
super(GeoAggregate, self).add_to_query(query, alias, col, source, is_summary)
|
||||
|
||||
class Collect(GeoAggregate):
|
||||
name = 'Collect'
|
||||
|
||||
class Extent(GeoAggregate):
|
||||
name = 'Extent'
|
||||
|
||||
|
|
|
@ -8,12 +8,16 @@ from django.contrib.gis.measure import Distance
|
|||
# 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 @@ class GeometryField(SpatialBackend.Field):
|
|||
"""
|
||||
|
||||
# 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 @@ class GeometryField(SpatialBackend.Field):
|
|||
|
||||
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 @@ class GeometryField(SpatialBackend.Field):
|
|||
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 @@ class GeometryField(SpatialBackend.Field):
|
|||
# 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 @@ class GeometryField(SpatialBackend.Field):
|
|||
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 @@ class GeometryField(SpatialBackend.Field):
|
|||
|
||||
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 @@ class GeometryField(SpatialBackend.Field):
|
|||
|
||||
# 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'
|
||||
|
|
|
@ -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
|
|
@ -44,13 +44,13 @@ class GeometryProxy(object):
|
|||
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
|
||||
|
|
|
@ -9,10 +9,6 @@ from django.contrib.gis.db.models.sql import AreaField, DistanceField, GeomField
|
|||
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 @@ class GeoQuerySet(QuerySet):
|
|||
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 @@ class GeoQuerySet(QuerySet):
|
|||
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 @@ class GeoQuerySet(QuerySet):
|
|||
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 @@ class GeoQuerySet(QuerySet):
|
|||
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 @@ class GeoQuerySet(QuerySet):
|
|||
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 @@ class GeoQuerySet(QuerySet):
|
|||
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 @@ class GeoQuerySet(QuerySet):
|
|||
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 @@ class GeoQuerySet(QuerySet):
|
|||
# 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:
|
||||
|
|
|
@ -44,8 +44,17 @@ elif SpatialBackend.oracle:
|
|||
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 @@ class GeoAggregate(Aggregate):
|
|||
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
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -208,13 +208,14 @@ class GeoQuery(sql.Query):
|
|||
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 @@ class GeoQuery(sql.Query):
|
|||
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 @@ class GeoQuery(sql.Query):
|
|||
# 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'
|
||||
|
|
|
@ -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 @@ class GeoWhereNode(WhereNode):
|
|||
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 @@ class GeoWhereNode(WhereNode):
|
|||
# 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 @@ class GeoWhereNode(WhereNode):
|
|||
|
||||
# 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'
|
||||
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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 @@ class SpatialRefSysMixin(object):
|
|||
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 @@ class SpatialRefSysMixin(object):
|
|||
"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 @@ class SpatialRefSysMixin(object):
|
|||
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 @@ class SpatialRefSysMixin(object):
|
|||
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 @@ class SpatialRefSysMixin(object):
|
|||
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.
|
||||
# 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.postgis.models import GeometryColumns, SpatialRefSys
|
||||
# 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
|
||||
|
||||
# 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:
|
||||
_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.
|
||||
try:
|
||||
from django.contrib.gis.db.backend.oracle.models import GeometryColumns, SpatialRefSys
|
||||
except:
|
||||
_srid_info = False
|
||||
else:
|
||||
_srid_info = False
|
||||
|
||||
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
|
||||
|
||||
pass
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -31,3 +31,6 @@ stx_zips = (('77002', 'POLYGON ((-95.365015 29.772327, -95.362415 29.772327, -95
|
|||
|
||||
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)'),
|
||||
)
|
||||
|
|
|
@ -37,6 +37,13 @@ class SouthTexasZipcode(models.Model):
|
|||
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
|
||||
|
|
|
@ -5,11 +5,11 @@ from django.db.models import Q
|
|||
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 @@ class DistanceTest(unittest.TestCase):
|
|||
# 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 @@ class DistanceTest(unittest.TestCase):
|
|||
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 @@ class DistanceTest(unittest.TestCase):
|
|||
self.assertEqual(4, CensusZipcode.objects.count())
|
||||
|
||||
# Loading up the Interstates.
|
||||
for name, wkt in interstates:
|
||||
Interstate(name=name, line=GEOSGeometry(wkt, srid=4326)).save()
|
||||
self.assertEqual(1, Interstate.objects.count())
|
||||
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 @@ class DistanceTest(unittest.TestCase):
|
|||
# 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 @@ class DistanceTest(unittest.TestCase):
|
|||
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 @@ class DistanceTest(unittest.TestCase):
|
|||
# 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 @@ class DistanceTest(unittest.TestCase):
|
|||
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 @@ class DistanceTest(unittest.TestCase):
|
|||
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 @@ class DistanceTest(unittest.TestCase):
|
|||
"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 @@ class DistanceTest(unittest.TestCase):
|
|||
# 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 @@ class DistanceTest(unittest.TestCase):
|
|||
# (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 @@ class DistanceTest(unittest.TestCase):
|
|||
# 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 @@ class DistanceTest(unittest.TestCase):
|
|||
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 @@ class DistanceTest(unittest.TestCase):
|
|||
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 @@ class DistanceTest(unittest.TestCase):
|
|||
"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:
|
||||
|
|
|
@ -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 @@ class Country(models.Model):
|
|||
|
||||
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()
|
||||
|
|
|
@ -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 @@ class GeoModelTest(unittest.TestCase):
|
|||
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 @@ class GeoModelTest(unittest.TestCase):
|
|||
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 @@ class GeoModelTest(unittest.TestCase):
|
|||
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 @@ class GeoModelTest(unittest.TestCase):
|
|||
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 @@ class GeoModelTest(unittest.TestCase):
|
|||
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 @@ class GeoModelTest(unittest.TestCase):
|
|||
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 @@ class GeoModelTest(unittest.TestCase):
|
|||
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 @@ class GeoModelTest(unittest.TestCase):
|
|||
# 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 @@ class GeoModelTest(unittest.TestCase):
|
|||
|
||||
# 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 @@ class GeoModelTest(unittest.TestCase):
|
|||
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 @@ class GeoModelTest(unittest.TestCase):
|
|||
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 @@ class GeoModelTest(unittest.TestCase):
|
|||
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 @@ class GeoModelTest(unittest.TestCase):
|
|||
"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 @@ class GeoModelTest(unittest.TestCase):
|
|||
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():
|
||||
self.assertEqual(ref[cntry.name], cntry.point_on_surface)
|
||||
if SpatialBackend.spatialite:
|
||||
# XXX This seems to be a WKT-translation-related precision issue?
|
||||
tol = 0.00001
|
||||
else: tol = 0.000000001
|
||||
self.assertEqual(True, ref[cntry.name].equals_exact(cntry.point_on_surface, tol))
|
||||
|
||||
@no_oracle
|
||||
def test21_scale(self):
|
||||
|
@ -512,8 +542,9 @@ class GeoModelTest(unittest.TestCase):
|
|||
for p1, p2 in zip(c.mpoly, c.scaled):
|
||||
for r1, r2 in zip(p1, p2):
|
||||
for c1, c2 in zip(r1.coords, r2.coords):
|
||||
self.assertEqual(c1[0] * xfac, c2[0])
|
||||
self.assertEqual(c1[1] * yfac, c2[1])
|
||||
# XXX The low precision is for SpatiaLite
|
||||
self.assertAlmostEqual(c1[0] * xfac, c2[0], 5)
|
||||
self.assertAlmostEqual(c1[1] * yfac, c2[1], 5)
|
||||
|
||||
@no_oracle
|
||||
def test22_translate(self):
|
||||
|
@ -525,8 +556,9 @@ class GeoModelTest(unittest.TestCase):
|
|||
for p1, p2 in zip(c.mpoly, c.translated):
|
||||
for r1, r2 in zip(p1, p2):
|
||||
for c1, c2 in zip(r1.coords, r2.coords):
|
||||
self.assertEqual(c1[0] + xfac, c2[0])
|
||||
self.assertEqual(c1[1] + yfac, c2[1])
|
||||
# XXX The low precision is for SpatiaLite
|
||||
self.assertAlmostEqual(c1[0] + xfac, c2[0], 5)
|
||||
self.assertAlmostEqual(c1[1] + yfac, c2[1], 5)
|
||||
|
||||
def test23_numgeom(self):
|
||||
"Testing the `num_geom` GeoQuerySet method."
|
||||
|
@ -539,27 +571,46 @@ class GeoModelTest(unittest.TestCase):
|
|||
if SpatialBackend.postgis: self.assertEqual(None, c.num_geom)
|
||||
else: self.assertEqual(1, c.num_geom)
|
||||
|
||||
@no_spatialite # SpatiaLite can only count vertices in LineStrings
|
||||
def test24_numpoints(self):
|
||||
"Testing the `num_points` GeoQuerySet method."
|
||||
if DISABLE: return
|
||||
for c in Country.objects.num_points(): self.assertEqual(c.mpoly.num_points, c.num_points)
|
||||
if SpatialBackend.postgis:
|
||||
for c in Country.objects.num_points():
|
||||
self.assertEqual(c.mpoly.num_points, c.num_points)
|
||||
if not SpatialBackend.oracle:
|
||||
# Oracle cannot count vertices in Point geometries.
|
||||
for c in City.objects.num_points(): self.assertEqual(1, c.num_points)
|
||||
|
||||
@no_oracle
|
||||
def test25_geoset(self):
|
||||
"Testing the `difference`, `intersection`, `sym_difference`, and `union` GeoQuerySet methods."
|
||||
if DISABLE: return
|
||||
geom = Point(5, 23)
|
||||
for c in Country.objects.all().intersection(geom).difference(geom).sym_difference(geom).union(geom):
|
||||
self.assertEqual(c.mpoly.difference(geom), c.difference)
|
||||
self.assertEqual(c.mpoly.intersection(geom), c.intersection)
|
||||
self.assertEqual(c.mpoly.sym_difference(geom), c.sym_difference)
|
||||
self.assertEqual(c.mpoly.union(geom), c.union)
|
||||
tol = 1
|
||||
qs = Country.objects.all().difference(geom).sym_difference(geom).union(geom)
|
||||
|
||||
# XXX For some reason SpatiaLite does something screwey with the Texas geometry here. Also,
|
||||
# XXX it doesn't like the null intersection.
|
||||
if SpatialBackend.spatialite:
|
||||
qs = qs.exclude(name='Texas')
|
||||
else:
|
||||
qs = qs.intersection(geom)
|
||||
|
||||
for c in qs:
|
||||
if SpatialBackend.oracle:
|
||||
# Should be able to execute the queries; however, they won't be the same
|
||||
# as GEOS (because Oracle doesn't use GEOS internally like PostGIS or
|
||||
# SpatiaLite).
|
||||
pass
|
||||
else:
|
||||
self.assertEqual(c.mpoly.difference(geom), c.difference)
|
||||
if not SpatialBackend.spatialite:
|
||||
self.assertEqual(c.mpoly.intersection(geom), c.intersection)
|
||||
self.assertEqual(c.mpoly.sym_difference(geom), c.sym_difference)
|
||||
self.assertEqual(c.mpoly.union(geom), c.union)
|
||||
|
||||
def test26_inherited_geofields(self):
|
||||
"Test GeoQuerySet methods on inherited Geometry fields."
|
||||
if DISABLE: return
|
||||
# Creating a Pennsylvanian city.
|
||||
mansfield = PennsylvaniaCity.objects.create(name='Mansfield', county='Tioga', point='POINT(-77.071445 41.823881)')
|
||||
|
||||
|
|
|
@ -7,9 +7,9 @@ from django.contrib.gis.utils.layermapping import LayerMapping, LayerMapError, I
|
|||
from django.contrib.gis.gdal import DataSource
|
||||
|
||||
shp_path = os.path.dirname(__file__)
|
||||
city_shp = os.path.join(shp_path, 'cities/cities.shp')
|
||||
co_shp = os.path.join(shp_path, 'counties/counties.shp')
|
||||
inter_shp = os.path.join(shp_path, 'interstates/interstates.shp')
|
||||
city_shp = os.path.join(shp_path, '../data/cities/cities.shp')
|
||||
co_shp = os.path.join(shp_path, '../data/counties/counties.shp')
|
||||
inter_shp = os.path.join(shp_path, '../data/interstates/interstates.shp')
|
||||
|
||||
# Dictionaries to hold what's expected in the county shapefile.
|
||||
NAMES = ['Bexar', 'Galveston', 'Harris', 'Honolulu', 'Pueblo']
|
||||
|
@ -53,7 +53,6 @@ class LayerMapTest(unittest.TestCase):
|
|||
|
||||
def test02_simple_layermap(self):
|
||||
"Test LayerMapping import of a simple point shapefile."
|
||||
|
||||
# Setting up for the LayerMapping.
|
||||
lm = LayerMapping(City, city_shp, city_mapping)
|
||||
lm.save()
|
||||
|
@ -78,7 +77,6 @@ class LayerMapTest(unittest.TestCase):
|
|||
|
||||
def test03_layermap_strict(self):
|
||||
"Testing the `strict` keyword, and import of a LineString shapefile."
|
||||
|
||||
# When the `strict` keyword is set an error encountered will force
|
||||
# the importation to stop.
|
||||
try:
|
||||
|
@ -118,7 +116,6 @@ class LayerMapTest(unittest.TestCase):
|
|||
|
||||
def county_helper(self, county_feat=True):
|
||||
"Helper function for ensuring the integrity of the mapped County models."
|
||||
|
||||
for name, n, st in zip(NAMES, NUMS, STATES):
|
||||
# Should only be one record b/c of `unique` keyword.
|
||||
c = County.objects.get(name=name)
|
||||
|
@ -198,7 +195,6 @@ class LayerMapTest(unittest.TestCase):
|
|||
|
||||
def test05_test_fid_range_step(self):
|
||||
"Tests the `fid_range` keyword and the `step` keyword of .save()."
|
||||
|
||||
# Function for clearing out all the counties before testing.
|
||||
def clear_counties(): County.objects.all().delete()
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ import os, unittest
|
|||
from django.contrib.gis.geos import *
|
||||
from django.contrib.gis.db.backend import SpatialBackend
|
||||
from django.contrib.gis.db.models import F, Extent, Union
|
||||
from django.contrib.gis.tests.utils import no_mysql, no_oracle
|
||||
from django.contrib.gis.tests.utils import no_mysql, no_oracle, no_spatialite
|
||||
from django.conf import settings
|
||||
from models import City, Location, DirectoryEntry, Parcel
|
||||
|
||||
|
@ -12,7 +12,7 @@ cities = (('Aurora', 'TX', -97.516111, 33.058333),
|
|||
)
|
||||
|
||||
class RelatedGeoModelTest(unittest.TestCase):
|
||||
|
||||
|
||||
def test01_setup(self):
|
||||
"Setting up for related model tests."
|
||||
for name, state, lon, lat in cities:
|
||||
|
@ -32,7 +32,7 @@ class RelatedGeoModelTest(unittest.TestCase):
|
|||
self.assertEqual(nm, c.name)
|
||||
self.assertEqual(st, c.state)
|
||||
self.assertEqual(Point(lon, lat), c.location.point)
|
||||
|
||||
|
||||
@no_mysql
|
||||
@no_oracle # Pagination problem is implicated in this test as well.
|
||||
def test03_transform_related(self):
|
||||
|
@ -43,7 +43,7 @@ class RelatedGeoModelTest(unittest.TestCase):
|
|||
tol = 3
|
||||
else:
|
||||
tol = 0
|
||||
|
||||
|
||||
def check_pnt(ref, pnt):
|
||||
self.assertAlmostEqual(ref.x, pnt.x, tol)
|
||||
self.assertAlmostEqual(ref.y, pnt.y, tol)
|
||||
|
@ -59,14 +59,14 @@ class RelatedGeoModelTest(unittest.TestCase):
|
|||
# Doing this implicitly sets `select_related` select the location.
|
||||
# TODO: Fix why this breaks on Oracle.
|
||||
qs = list(City.objects.filter(name=name).transform(srid, field_name='location__point'))
|
||||
check_pnt(GEOSGeometry(wkt, srid), qs[0].location.point)
|
||||
check_pnt(GEOSGeometry(wkt, srid), qs[0].location.point)
|
||||
|
||||
@no_mysql
|
||||
def test04_related_aggregate(self):
|
||||
"Testing the `extent` and `unionagg` GeoQuerySet aggregates on related geographic models."
|
||||
|
||||
@no_spatialite
|
||||
def test04a_related_extent_aggregate(self):
|
||||
"Testing the `extent` GeoQuerySet aggregates on related geographic models."
|
||||
# This combines the Extent and Union aggregates into one query
|
||||
aggs = City.objects.aggregate(Extent('location__point'), Union('location__point'))
|
||||
aggs = City.objects.aggregate(Extent('location__point'))
|
||||
|
||||
# One for all locations, one that excludes Roswell.
|
||||
all_extent = (-104.528060913086, 33.0583305358887,-79.4607315063477, 40.1847610473633)
|
||||
|
@ -81,14 +81,20 @@ class RelatedGeoModelTest(unittest.TestCase):
|
|||
for ref, e in [(all_extent, e1), (txpa_extent, e2), (all_extent, e3)]:
|
||||
for ref_val, e_val in zip(ref, e): self.assertAlmostEqual(ref_val, e_val, tol)
|
||||
|
||||
@no_mysql
|
||||
def test04b_related_union_aggregate(self):
|
||||
"Testing the `unionagg` GeoQuerySet aggregates on related geographic models."
|
||||
# This combines the Extent and Union aggregates into one query
|
||||
aggs = City.objects.aggregate(Union('location__point'))
|
||||
|
||||
# These are the points that are components of the aggregate geographic
|
||||
# union that is returned.
|
||||
p1 = Point(-104.528056, 33.387222)
|
||||
p2 = Point(-97.516111, 33.058333)
|
||||
p3 = Point(-79.460734, 40.18476)
|
||||
|
||||
|
||||
# Creating the reference union geometry depending on the spatial backend,
|
||||
# as Oracle will have a different internal ordering of the component
|
||||
# as Oracle will have a different internal ordering of the component
|
||||
# geometries than PostGIS. The second union aggregate is for a union
|
||||
# query that includes limiting information in the WHERE clause (in other
|
||||
# words a `.filter()` precedes the call to `.unionagg()`).
|
||||
|
@ -98,7 +104,7 @@ class RelatedGeoModelTest(unittest.TestCase):
|
|||
else:
|
||||
ref_u1 = MultiPoint(p1, p2, p3, srid=4326)
|
||||
ref_u2 = MultiPoint(p2, p3, srid=4326)
|
||||
|
||||
|
||||
u1 = City.objects.unionagg(field_name='location__point')
|
||||
u2 = City.objects.exclude(name='Roswell').unionagg(field_name='location__point')
|
||||
u3 = aggs['location__point__union']
|
||||
|
@ -106,7 +112,7 @@ class RelatedGeoModelTest(unittest.TestCase):
|
|||
self.assertEqual(ref_u1, u1)
|
||||
self.assertEqual(ref_u2, u2)
|
||||
self.assertEqual(ref_u1, u3)
|
||||
|
||||
|
||||
def test05_select_related_fk_to_subclass(self):
|
||||
"Testing that calling select_related on a query over a model with an FK to a model subclass works"
|
||||
# Regression test for #9752.
|
||||
|
@ -140,14 +146,14 @@ class RelatedGeoModelTest(unittest.TestCase):
|
|||
qs = Parcel.objects.filter(center1__within=F('border1'))
|
||||
self.assertEqual(1, len(qs))
|
||||
self.assertEqual('P2', qs[0].name)
|
||||
|
||||
|
||||
if not SpatialBackend.mysql:
|
||||
# This time center2 is in a different coordinate system and needs
|
||||
# to be wrapped in transformation SQL.
|
||||
qs = Parcel.objects.filter(center2__within=F('border1'))
|
||||
self.assertEqual(1, len(qs))
|
||||
self.assertEqual('P2', qs[0].name)
|
||||
|
||||
|
||||
# Should return the first Parcel, which has the center point equal
|
||||
# to the point in the City ForeignKey.
|
||||
qs = Parcel.objects.filter(center1=F('city__location__point'))
|
||||
|
|
|
@ -15,8 +15,10 @@ def no_backend(test_func, backend):
|
|||
def no_oracle(func): return no_backend(func, 'oracle')
|
||||
def no_postgis(func): return no_backend(func, 'postgresql_psycopg2')
|
||||
def no_mysql(func): return no_backend(func, 'mysql')
|
||||
def no_spatialite(func): return no_backend(func, 'sqlite3')
|
||||
|
||||
# Shortcut booleans to omit only portions of tests.
|
||||
oracle = settings.DATABASE_ENGINE == 'oracle'
|
||||
postgis = settings.DATABASE_ENGINE == 'postgresql_psycopg2'
|
||||
mysql = settings.DATABASE_ENGINE == 'mysql'
|
||||
spatialite = settings.DATABASE_ENGINE == 'sqlite3'
|
||||
|
|
Loading…
Reference in New Issue