Kaydet (Commit) 867e7150 authored tarafından Justin Bronn's avatar Justin Bronn

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
üst a61c0b79
......@@ -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
__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 *
......
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
__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,
)
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):
......
__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,
)
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')
......
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'
# 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):
......
This diff is collapsed.
......@@ -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
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()
......@@ -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'
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment