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.
|
# Retrieving the necessary settings from the backend.
|
||||||
if settings.DATABASE_ENGINE == 'postgresql_psycopg2':
|
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':
|
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':
|
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:
|
else:
|
||||||
raise NotImplementedError('No Geographic Backend exists for %s' % settings.DATABASE_ENGINE)
|
raise NotImplementedError('No Geographic Backend exists for %s' % settings.DATABASE_ENGINE)
|
||||||
|
|
|
@ -24,6 +24,3 @@ class BaseSpatialBackend(object):
|
||||||
except KeyError:
|
except KeyError:
|
||||||
return False
|
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.base import BaseSpatialBackend
|
||||||
from django.contrib.gis.db.backend.adaptor import WKTAdaptor
|
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.field import MySQLGeoField
|
||||||
from django.contrib.gis.db.backend.mysql.query import *
|
from django.contrib.gis.db.backend.mysql.query import *
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
|
|
||||||
def create_spatial_db(test=True, verbosity=1, autoclobber=False):
|
def create_test_spatial_db(verbosity=1, autoclobber=False):
|
||||||
if not test: raise NotImplementedError('This uses `create_test_db` from test/utils.py')
|
"A wrapper over the MySQL `create_test_db` method."
|
||||||
from django.db import connection
|
from django.db import connection
|
||||||
connection.creation.create_test_db(verbosity, autoclobber)
|
connection.creation.create_test_db(verbosity, autoclobber)
|
||||||
|
|
|
@ -22,11 +22,11 @@ class MySQLGeoField(Field):
|
||||||
# Getting the index name.
|
# Getting the index name.
|
||||||
idx_name = '%s_%s_id' % (db_table, self.column)
|
idx_name = '%s_%s_id' % (db_table, self.column)
|
||||||
|
|
||||||
sql = style.SQL_KEYWORD('CREATE SPATIAL INDEX ') + \
|
sql = (style.SQL_KEYWORD('CREATE SPATIAL INDEX ') +
|
||||||
style.SQL_TABLE(qn(idx_name)) + \
|
style.SQL_TABLE(qn(idx_name)) +
|
||||||
style.SQL_KEYWORD(' ON ') + \
|
style.SQL_KEYWORD(' ON ') +
|
||||||
style.SQL_TABLE(qn(db_table)) + '(' + \
|
style.SQL_TABLE(qn(db_table)) + '(' +
|
||||||
style.SQL_FIELD(qn(self.column)) + ');'
|
style.SQL_FIELD(qn(self.column)) + ');')
|
||||||
return sql
|
return sql
|
||||||
|
|
||||||
def post_create_sql(self, style, db_table):
|
def post_create_sql(self, style, db_table):
|
||||||
|
@ -35,14 +35,14 @@ class MySQLGeoField(Field):
|
||||||
created.
|
created.
|
||||||
"""
|
"""
|
||||||
# Getting the geometric index for this Geometry column.
|
# Getting the geometric index for this Geometry column.
|
||||||
if self._index:
|
if self.spatial_index:
|
||||||
return (self._geom_index(style, db_table),)
|
return (self._geom_index(style, db_table),)
|
||||||
else:
|
else:
|
||||||
return ()
|
return ()
|
||||||
|
|
||||||
def db_type(self):
|
def db_type(self):
|
||||||
"The OpenGIS name is returned for the MySQL database column type."
|
"The OpenGIS name is returned for the MySQL database column type."
|
||||||
return self._geom
|
return self.geom_type
|
||||||
|
|
||||||
def get_placeholder(self, value):
|
def get_placeholder(self, value):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -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.base import BaseSpatialBackend
|
||||||
from django.contrib.gis.db.backend.oracle.adaptor import OracleSpatialAdaptor
|
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.field import OracleSpatialField
|
||||||
|
from django.contrib.gis.db.backend.oracle.models import GeometryColumns, SpatialRefSys
|
||||||
from django.contrib.gis.db.backend.oracle.query import *
|
from django.contrib.gis.db.backend.oracle.query import *
|
||||||
|
|
||||||
SpatialBackend = BaseSpatialBackend(name='oracle', oracle=True,
|
SpatialBackend = BaseSpatialBackend(name='oracle', oracle=True,
|
||||||
|
@ -29,4 +30,6 @@ SpatialBackend = BaseSpatialBackend(name='oracle', oracle=True,
|
||||||
union=UNION,
|
union=UNION,
|
||||||
Adaptor=OracleSpatialAdaptor,
|
Adaptor=OracleSpatialAdaptor,
|
||||||
Field=OracleSpatialField,
|
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."
|
"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
|
from django.db import connection
|
||||||
connection.creation.create_test_db(verbosity, autoclobber)
|
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
|
Adds this geometry column into the Oracle USER_SDO_GEOM_METADATA
|
||||||
table.
|
table.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Checking the dimensions.
|
# Checking the dimensions.
|
||||||
# TODO: Add support for 3D geometries.
|
# 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.')
|
raise Exception('3D geometries not yet supported on Oracle Spatial backend.')
|
||||||
|
|
||||||
# Constructing the SQL that will be used to insert information about
|
# Constructing the SQL that will be used to insert information about
|
||||||
# the geometry column into the USER_GSDO_GEOM_METADATA table.
|
# the geometry column into the USER_GSDO_GEOM_METADATA table.
|
||||||
meta_sql = style.SQL_KEYWORD('INSERT INTO ') + \
|
meta_sql = (style.SQL_KEYWORD('INSERT INTO ') +
|
||||||
style.SQL_TABLE('USER_SDO_GEOM_METADATA') + \
|
style.SQL_TABLE('USER_SDO_GEOM_METADATA') +
|
||||||
' (%s, %s, %s, %s)\n ' % tuple(map(qn, ['TABLE_NAME', 'COLUMN_NAME', 'DIMINFO', 'SRID'])) + \
|
' (%s, %s, %s, %s)\n ' % tuple(map(qn, ['TABLE_NAME', 'COLUMN_NAME', 'DIMINFO', 'SRID'])) +
|
||||||
style.SQL_KEYWORD(' VALUES ') + '(\n ' + \
|
style.SQL_KEYWORD(' VALUES ') + '(\n ' +
|
||||||
style.SQL_TABLE(gqn(db_table)) + ',\n ' + \
|
style.SQL_TABLE(gqn(db_table)) + ',\n ' +
|
||||||
style.SQL_FIELD(gqn(self.column)) + ',\n ' + \
|
style.SQL_FIELD(gqn(self.column)) + ',\n ' +
|
||||||
style.SQL_KEYWORD("MDSYS.SDO_DIM_ARRAY") + '(\n ' + \
|
style.SQL_KEYWORD("MDSYS.SDO_DIM_ARRAY") + '(\n ' +
|
||||||
style.SQL_KEYWORD("MDSYS.SDO_DIM_ELEMENT") + \
|
style.SQL_KEYWORD("MDSYS.SDO_DIM_ELEMENT") +
|
||||||
("('LONG', %s, %s, %s),\n " % (self._extent[0], self._extent[2], self._tolerance)) + \
|
("('LONG', %s, %s, %s),\n " % (self._extent[0], self._extent[2], self._tolerance)) +
|
||||||
style.SQL_KEYWORD("MDSYS.SDO_DIM_ELEMENT") + \
|
style.SQL_KEYWORD("MDSYS.SDO_DIM_ELEMENT") +
|
||||||
("('LAT', %s, %s, %s)\n ),\n" % (self._extent[1], self._extent[3], self._tolerance)) + \
|
("('LAT', %s, %s, %s)\n ),\n" % (self._extent[1], self._extent[3], self._tolerance)) +
|
||||||
' %s\n );' % self._srid
|
' %s\n );' % self.srid)
|
||||||
return meta_sql
|
return meta_sql
|
||||||
|
|
||||||
def _geom_index(self, style, db_table):
|
def _geom_index(self, style, db_table):
|
||||||
|
@ -61,13 +60,13 @@ class OracleSpatialField(Field):
|
||||||
# names > 30 characters.
|
# names > 30 characters.
|
||||||
idx_name = truncate_name('%s_%s_id' % (db_table, self.column), 30)
|
idx_name = truncate_name('%s_%s_id' % (db_table, self.column), 30)
|
||||||
|
|
||||||
sql = style.SQL_KEYWORD('CREATE INDEX ') + \
|
sql = (style.SQL_KEYWORD('CREATE INDEX ') +
|
||||||
style.SQL_TABLE(qn(idx_name)) + \
|
style.SQL_TABLE(qn(idx_name)) +
|
||||||
style.SQL_KEYWORD(' ON ') + \
|
style.SQL_KEYWORD(' ON ') +
|
||||||
style.SQL_TABLE(qn(db_table)) + '(' + \
|
style.SQL_TABLE(qn(db_table)) + '(' +
|
||||||
style.SQL_FIELD(qn(self.column)) + ') ' + \
|
style.SQL_FIELD(qn(self.column)) + ') ' +
|
||||||
style.SQL_KEYWORD('INDEXTYPE IS ') + \
|
style.SQL_KEYWORD('INDEXTYPE IS ') +
|
||||||
style.SQL_TABLE('MDSYS.SPATIAL_INDEX') + ';'
|
style.SQL_TABLE('MDSYS.SPATIAL_INDEX') + ';')
|
||||||
return sql
|
return sql
|
||||||
|
|
||||||
def post_create_sql(self, style, db_table):
|
def post_create_sql(self, style, db_table):
|
||||||
|
@ -79,7 +78,7 @@ class OracleSpatialField(Field):
|
||||||
post_sql = self._add_geom(style, db_table)
|
post_sql = self._add_geom(style, db_table)
|
||||||
|
|
||||||
# Getting the geometric index for this Geometry column.
|
# Getting the geometric index for this Geometry column.
|
||||||
if self._index:
|
if self.spatial_index:
|
||||||
return (post_sql, self._geom_index(style, db_table))
|
return (post_sql, self._geom_index(style, db_table))
|
||||||
else:
|
else:
|
||||||
return (post_sql,)
|
return (post_sql,)
|
||||||
|
@ -96,8 +95,8 @@ class OracleSpatialField(Field):
|
||||||
"""
|
"""
|
||||||
if value is None:
|
if value is None:
|
||||||
return '%s'
|
return '%s'
|
||||||
elif value.srid != self._srid:
|
elif value.srid != self.srid:
|
||||||
# Adding Transform() to the SQL placeholder.
|
# 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:
|
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.
|
model and the `SDO_COORD_REF_SYS` is used for the SpatialRefSys model.
|
||||||
"""
|
"""
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.contrib.gis.models import SpatialRefSysMixin
|
|
||||||
|
|
||||||
class GeometryColumns(models.Model):
|
class GeometryColumns(models.Model):
|
||||||
"Maps to the Oracle USER_SDO_GEOM_METADATA table."
|
"Maps to the Oracle USER_SDO_GEOM_METADATA table."
|
||||||
|
@ -38,19 +37,19 @@ class GeometryColumns(models.Model):
|
||||||
def __unicode__(self):
|
def __unicode__(self):
|
||||||
return '%s - %s (SRID: %s)' % (self.table_name, self.column_name, self.srid)
|
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."
|
"Maps to the Oracle MDSYS.CS_SRS table."
|
||||||
cs_name = models.CharField(max_length=68)
|
cs_name = models.CharField(max_length=68)
|
||||||
srid = models.IntegerField(primary_key=True)
|
srid = models.IntegerField(primary_key=True)
|
||||||
auth_srid = models.IntegerField()
|
auth_srid = models.IntegerField()
|
||||||
auth_name = models.CharField(max_length=256)
|
auth_name = models.CharField(max_length=256)
|
||||||
wktext = models.CharField(max_length=2046)
|
wktext = models.CharField(max_length=2046)
|
||||||
#cs_bounds = models.GeometryField()
|
#cs_bounds = models.GeometryField() # TODO
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
# TODO: Figure out way to have this be MDSYS.CS_SRS without
|
abstract = True
|
||||||
# having django's quoting mess up the SQL.
|
|
||||||
db_table = 'CS_SRS'
|
db_table = 'CS_SRS'
|
||||||
|
app_label = '_mdsys' # Hack so that syncdb won't try to create "CS_SRS" table.
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def wkt(self):
|
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.base import BaseSpatialBackend
|
||||||
from django.contrib.gis.db.backend.postgis.adaptor import PostGISAdaptor
|
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.field import PostGISField
|
||||||
|
from django.contrib.gis.db.backend.postgis.models import GeometryColumns, SpatialRefSys
|
||||||
from django.contrib.gis.db.backend.postgis.query import *
|
from django.contrib.gis.db.backend.postgis.query import *
|
||||||
|
|
||||||
SpatialBackend = BaseSpatialBackend(name='postgis', postgis=True,
|
SpatialBackend = BaseSpatialBackend(name='postgis', postgis=True,
|
||||||
area=AREA,
|
area=AREA,
|
||||||
centroid=CENTROID,
|
centroid=CENTROID,
|
||||||
|
collect=COLLECT,
|
||||||
difference=DIFFERENCE,
|
difference=DIFFERENCE,
|
||||||
distance=DISTANCE,
|
distance=DISTANCE,
|
||||||
distance_functions=DISTANCE_FUNCTIONS,
|
distance_functions=DISTANCE_FUNCTIONS,
|
||||||
|
@ -39,4 +41,6 @@ SpatialBackend = BaseSpatialBackend(name='postgis', postgis=True,
|
||||||
version=(MAJOR_VERSION, MINOR_VERSION1, MINOR_VERSION2),
|
version=(MAJOR_VERSION, MINOR_VERSION1, MINOR_VERSION2),
|
||||||
Adaptor=PostGISAdaptor,
|
Adaptor=PostGISAdaptor,
|
||||||
Field=PostGISField,
|
Field=PostGISField,
|
||||||
|
GeometryColumns=GeometryColumns,
|
||||||
|
SpatialRefSys=SpatialRefSys,
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,23 +1,10 @@
|
||||||
import os, re, sys
|
import os, re, sys
|
||||||
from subprocess import Popen, PIPE
|
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core.management import call_command
|
from django.core.management import call_command
|
||||||
from django.db import connection
|
from django.db import connection
|
||||||
from django.db.backends.creation import TEST_DATABASE_PREFIX
|
from django.db.backends.creation import TEST_DATABASE_PREFIX
|
||||||
|
from django.contrib.gis.db.backend.util import getstatusoutput
|
||||||
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()
|
|
||||||
|
|
||||||
def create_lang(db_name, verbosity=1):
|
def create_lang(db_name, verbosity=1):
|
||||||
"Sets up the pl/pgsql language on the given database."
|
"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:
|
else:
|
||||||
raise Exception('Unknown error occurred in creating database: %s' % output)
|
raise Exception('Unknown error occurred in creating database: %s' % output)
|
||||||
|
|
||||||
def create_spatial_db(test=False, verbosity=1, autoclobber=False, interactive=False):
|
def create_test_spatial_db(verbosity=1, autoclobber=False, interactive=False):
|
||||||
"Creates a spatial database based on the settings."
|
"Creates a test spatial database based on the settings."
|
||||||
|
|
||||||
# Making sure we're using PostgreSQL and psycopg2
|
# Making sure we're using PostgreSQL and psycopg2
|
||||||
if settings.DATABASE_ENGINE != 'postgresql_psycopg2':
|
if settings.DATABASE_ENGINE != 'postgresql_psycopg2':
|
||||||
raise Exception('Spatial database creation only supported postgresql_psycopg2 platform.')
|
raise Exception('Spatial database creation only supported postgresql_psycopg2 platform.')
|
||||||
|
|
||||||
# Getting the spatial database name
|
# Getting the spatial database name
|
||||||
if test:
|
|
||||||
db_name = get_spatial_db(test=True)
|
db_name = get_spatial_db(test=True)
|
||||||
_create_with_cursor(db_name, verbosity=verbosity, autoclobber=autoclobber)
|
_create_with_cursor(db_name, verbosity=verbosity, autoclobber=autoclobber)
|
||||||
else:
|
|
||||||
db_name = get_spatial_db()
|
|
||||||
_create_with_shell(db_name, verbosity=verbosity, autoclobber=autoclobber)
|
|
||||||
|
|
||||||
# If a template database is used, then don't need to do any of the following.
|
# If a template database is used, then don't need to do any of the following.
|
||||||
if not hasattr(settings, 'POSTGIS_TEMPLATE'):
|
if not hasattr(settings, 'POSTGIS_TEMPLATE'):
|
||||||
|
|
|
@ -19,35 +19,35 @@ class PostGISField(Field):
|
||||||
Takes the style object (provides syntax highlighting) and the
|
Takes the style object (provides syntax highlighting) and the
|
||||||
database table as parameters.
|
database table as parameters.
|
||||||
"""
|
"""
|
||||||
sql = style.SQL_KEYWORD('SELECT ') + \
|
sql = (style.SQL_KEYWORD('SELECT ') +
|
||||||
style.SQL_TABLE('AddGeometryColumn') + '(' + \
|
style.SQL_TABLE('AddGeometryColumn') + '(' +
|
||||||
style.SQL_TABLE(gqn(db_table)) + ', ' + \
|
style.SQL_TABLE(gqn(db_table)) + ', ' +
|
||||||
style.SQL_FIELD(gqn(self.column)) + ', ' + \
|
style.SQL_FIELD(gqn(self.column)) + ', ' +
|
||||||
style.SQL_FIELD(str(self._srid)) + ', ' + \
|
style.SQL_FIELD(str(self.srid)) + ', ' +
|
||||||
style.SQL_COLTYPE(gqn(self._geom)) + ', ' + \
|
style.SQL_COLTYPE(gqn(self.geom_type)) + ', ' +
|
||||||
style.SQL_KEYWORD(str(self._dim)) + ');'
|
style.SQL_KEYWORD(str(self.dim)) + ');')
|
||||||
|
|
||||||
if not self.null:
|
if not self.null:
|
||||||
# Add a NOT NULL constraint to the field
|
# Add a NOT NULL constraint to the field
|
||||||
sql += '\n' + \
|
sql += ('\n' +
|
||||||
style.SQL_KEYWORD('ALTER TABLE ') + \
|
style.SQL_KEYWORD('ALTER TABLE ') +
|
||||||
style.SQL_TABLE(qn(db_table)) + \
|
style.SQL_TABLE(qn(db_table)) +
|
||||||
style.SQL_KEYWORD(' ALTER ') + \
|
style.SQL_KEYWORD(' ALTER ') +
|
||||||
style.SQL_FIELD(qn(self.column)) + \
|
style.SQL_FIELD(qn(self.column)) +
|
||||||
style.SQL_KEYWORD(' SET NOT NULL') + ';'
|
style.SQL_KEYWORD(' SET NOT NULL') + ';')
|
||||||
return sql
|
return sql
|
||||||
|
|
||||||
def _geom_index(self, style, db_table,
|
def _geom_index(self, style, db_table,
|
||||||
index_type='GIST', index_opts='GIST_GEOMETRY_OPS'):
|
index_type='GIST', index_opts='GIST_GEOMETRY_OPS'):
|
||||||
"Creates a GiST index for this geometry field."
|
"Creates a GiST index for this geometry field."
|
||||||
sql = style.SQL_KEYWORD('CREATE INDEX ') + \
|
sql = (style.SQL_KEYWORD('CREATE INDEX ') +
|
||||||
style.SQL_TABLE(qn('%s_%s_id' % (db_table, self.column))) + \
|
style.SQL_TABLE(qn('%s_%s_id' % (db_table, self.column))) +
|
||||||
style.SQL_KEYWORD(' ON ') + \
|
style.SQL_KEYWORD(' ON ') +
|
||||||
style.SQL_TABLE(qn(db_table)) + \
|
style.SQL_TABLE(qn(db_table)) +
|
||||||
style.SQL_KEYWORD(' USING ') + \
|
style.SQL_KEYWORD(' USING ') +
|
||||||
style.SQL_COLTYPE(index_type) + ' ( ' + \
|
style.SQL_COLTYPE(index_type) + ' ( ' +
|
||||||
style.SQL_FIELD(qn(self.column)) + ' ' + \
|
style.SQL_FIELD(qn(self.column)) + ' ' +
|
||||||
style.SQL_KEYWORD(index_opts) + ' );'
|
style.SQL_KEYWORD(index_opts) + ' );')
|
||||||
return sql
|
return sql
|
||||||
|
|
||||||
def post_create_sql(self, style, db_table):
|
def post_create_sql(self, style, db_table):
|
||||||
|
@ -62,17 +62,17 @@ class PostGISField(Field):
|
||||||
post_sql = self._add_geom(style, db_table)
|
post_sql = self._add_geom(style, db_table)
|
||||||
|
|
||||||
# If the user wants to index this data, then get the indexing SQL as well.
|
# If 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))
|
return (post_sql, self._geom_index(style, db_table))
|
||||||
else:
|
else:
|
||||||
return (post_sql,)
|
return (post_sql,)
|
||||||
|
|
||||||
def _post_delete_sql(self, style, db_table):
|
def _post_delete_sql(self, style, db_table):
|
||||||
"Drops the geometry column."
|
"Drops the geometry column."
|
||||||
sql = style.SQL_KEYWORD('SELECT ') + \
|
sql = (style.SQL_KEYWORD('SELECT ') +
|
||||||
style.SQL_KEYWORD('DropGeometryColumn') + '(' + \
|
style.SQL_KEYWORD('DropGeometryColumn') + '(' +
|
||||||
style.SQL_TABLE(gqn(db_table)) + ', ' + \
|
style.SQL_TABLE(gqn(db_table)) + ', ' +
|
||||||
style.SQL_FIELD(gqn(self.column)) + ');'
|
style.SQL_FIELD(gqn(self.column)) + ');')
|
||||||
return sql
|
return sql
|
||||||
|
|
||||||
def db_type(self):
|
def db_type(self):
|
||||||
|
@ -88,8 +88,8 @@ class PostGISField(Field):
|
||||||
SRID of the field. Specifically, this routine will substitute in the
|
SRID of the field. Specifically, this routine will substitute in the
|
||||||
ST_Transform() function call.
|
ST_Transform() function call.
|
||||||
"""
|
"""
|
||||||
if value is None or value.srid == self._srid:
|
if value is None or value.srid == self.srid:
|
||||||
return '%s'
|
return '%s'
|
||||||
else:
|
else:
|
||||||
# Adding Transform() to the SQL placeholder.
|
# 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.
|
The GeometryColumns and SpatialRefSys models for the PostGIS backend.
|
||||||
"""
|
"""
|
||||||
from django.db import models
|
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):
|
class GeometryColumns(models.Model):
|
||||||
"""
|
"""
|
||||||
|
@ -46,7 +40,7 @@ class GeometryColumns(models.Model):
|
||||||
(self.f_table_name, self.f_geometry_column,
|
(self.f_table_name, self.f_geometry_column,
|
||||||
self.coord_dimension, self.type, self.srid)
|
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
|
The 'spatial_ref_sys' table from PostGIS. See the PostGIS
|
||||||
documentaiton at Ch. 4.2.1.
|
documentaiton at Ch. 4.2.1.
|
||||||
|
@ -58,6 +52,7 @@ class SpatialRefSys(models.Model, SpatialRefSysMixin):
|
||||||
proj4text = models.CharField(max_length=2048)
|
proj4text = models.CharField(max_length=2048)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
abstract = True
|
||||||
db_table = 'spatial_ref_sys'
|
db_table = 'spatial_ref_sys'
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|
|
@ -42,6 +42,7 @@ if MAJOR_VERSION >= 1:
|
||||||
ASGML = get_func('AsGML')
|
ASGML = get_func('AsGML')
|
||||||
ASSVG = get_func('AsSVG')
|
ASSVG = get_func('AsSVG')
|
||||||
CENTROID = get_func('Centroid')
|
CENTROID = get_func('Centroid')
|
||||||
|
COLLECT = get_func('Collect')
|
||||||
DIFFERENCE = get_func('Difference')
|
DIFFERENCE = get_func('Difference')
|
||||||
DISTANCE = get_func('Distance')
|
DISTANCE = get_func('Distance')
|
||||||
DISTANCE_SPHERE = get_func('distance_sphere')
|
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):
|
def gqn(val):
|
||||||
"""
|
"""
|
||||||
|
@ -7,7 +24,7 @@ def gqn(val):
|
||||||
backend quotename function).
|
backend quotename function).
|
||||||
"""
|
"""
|
||||||
if isinstance(val, basestring):
|
if isinstance(val, basestring):
|
||||||
if isinstance(val, UnicodeType): val = val.encode('ascii')
|
if isinstance(val, unicode): val = val.encode('ascii')
|
||||||
return "'%s'" % val
|
return "'%s'" % val
|
||||||
else:
|
else:
|
||||||
return str(val)
|
return str(val)
|
||||||
|
|
|
@ -7,9 +7,6 @@ from django.contrib.gis.db.models.aggregates import *
|
||||||
# The GeoManager
|
# The GeoManager
|
||||||
from django.contrib.gis.db.models.manager import 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.
|
# The geographic-enabled fields.
|
||||||
from django.contrib.gis.db.models.fields import \
|
from django.contrib.gis.db.models.fields import \
|
||||||
GeometryField, PointField, LineStringField, PolygonField, \
|
GeometryField, PointField, LineStringField, PolygonField, \
|
||||||
|
|
|
@ -5,7 +5,7 @@ from django.contrib.gis.db.models.sql import GeomField
|
||||||
class GeoAggregate(Aggregate):
|
class GeoAggregate(Aggregate):
|
||||||
|
|
||||||
def add_to_query(self, query, alias, col, source, is_summary):
|
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.
|
# Doing additional setup on the Query object for spatial aggregates.
|
||||||
aggregate = getattr(query.aggregates_module, self.name)
|
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)
|
super(GeoAggregate, self).add_to_query(query, alias, col, source, is_summary)
|
||||||
|
|
||||||
|
class Collect(GeoAggregate):
|
||||||
|
name = 'Collect'
|
||||||
|
|
||||||
class Extent(GeoAggregate):
|
class Extent(GeoAggregate):
|
||||||
name = 'Extent'
|
name = 'Extent'
|
||||||
|
|
||||||
|
|
|
@ -8,12 +8,16 @@ from django.contrib.gis.measure import Distance
|
||||||
# reference system table w/o using the ORM.
|
# reference system table w/o using the ORM.
|
||||||
from django.contrib.gis.models import get_srid_info
|
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):
|
class GeometryField(SpatialBackend.Field):
|
||||||
"The base GIS field -- maps to the OpenGIS Specification Geometry type."
|
"The base GIS field -- maps to the OpenGIS Specification Geometry type."
|
||||||
|
|
||||||
# The OpenGIS Geometry name.
|
# The OpenGIS Geometry name.
|
||||||
_geom = 'GEOMETRY'
|
geom_type = 'GEOMETRY'
|
||||||
|
|
||||||
# Geodetic units.
|
# Geodetic units.
|
||||||
geodetic_units = ('Decimal Degree', 'degree')
|
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.
|
# 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
|
# Setting the SRID and getting the units. Unit information must be
|
||||||
# easily available in the field instance for distance queries.
|
# easily available in the field instance for distance queries.
|
||||||
self._srid = srid
|
self.srid = srid
|
||||||
self._unit, self._unit_name, self._spheroid = get_srid_info(srid)
|
self.units, self.units_name, self._spheroid = get_srid_info(srid)
|
||||||
|
|
||||||
# Setting the dimension of the geometry field.
|
# Setting the dimension of the geometry field.
|
||||||
self._dim = dim
|
self.dim = dim
|
||||||
|
|
||||||
# Setting the verbose_name keyword argument with the positional
|
# Setting the verbose_name keyword argument with the positional
|
||||||
# first parameter, so this works like normal fields.
|
# 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
|
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 ###
|
### Routines specific to GeometryField ###
|
||||||
@property
|
@property
|
||||||
def geodetic(self):
|
def geodetic(self):
|
||||||
|
@ -60,7 +87,7 @@ class GeometryField(SpatialBackend.Field):
|
||||||
Returns true if this field's SRID corresponds with a coordinate
|
Returns true if this field's SRID corresponds with a coordinate
|
||||||
system that uses non-projected units (e.g., latitude/longitude).
|
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):
|
def get_distance(self, dist_val, lookup_type):
|
||||||
"""
|
"""
|
||||||
|
@ -80,7 +107,7 @@ class GeometryField(SpatialBackend.Field):
|
||||||
# Spherical distance calculation parameter should be in meters.
|
# Spherical distance calculation parameter should be in meters.
|
||||||
dist_param = dist.m
|
dist_param = dist.m
|
||||||
else:
|
else:
|
||||||
dist_param = getattr(dist, Distance.unit_attname(self._unit_name))
|
dist_param = getattr(dist, Distance.unit_attname(self.units_name))
|
||||||
else:
|
else:
|
||||||
# Assuming the distance is in the units of the field.
|
# Assuming the distance is in the units of the field.
|
||||||
dist_param = dist
|
dist_param = dist
|
||||||
|
@ -127,8 +154,8 @@ class GeometryField(SpatialBackend.Field):
|
||||||
has no SRID, then that of the field will be returned.
|
has no SRID, then that of the field will be returned.
|
||||||
"""
|
"""
|
||||||
gsrid = geom.srid # SRID of given geometry.
|
gsrid = geom.srid # SRID of given geometry.
|
||||||
if gsrid is None or self._srid == -1 or (gsrid == -1 and self._srid != -1):
|
if gsrid is None or self.srid == -1 or (gsrid == -1 and self.srid != -1):
|
||||||
return self._srid
|
return self.srid
|
||||||
else:
|
else:
|
||||||
return gsrid
|
return gsrid
|
||||||
|
|
||||||
|
@ -141,8 +168,9 @@ class GeometryField(SpatialBackend.Field):
|
||||||
|
|
||||||
def formfield(self, **kwargs):
|
def formfield(self, **kwargs):
|
||||||
defaults = {'form_class' : forms.GeometryField,
|
defaults = {'form_class' : forms.GeometryField,
|
||||||
'geom_type' : self._geom,
|
|
||||||
'null' : self.null,
|
'null' : self.null,
|
||||||
|
'geom_type' : self.geom_type,
|
||||||
|
'srid' : self.srid,
|
||||||
}
|
}
|
||||||
defaults.update(kwargs)
|
defaults.update(kwargs)
|
||||||
return super(GeometryField, self).formfield(**defaults)
|
return super(GeometryField, self).formfield(**defaults)
|
||||||
|
@ -190,22 +218,22 @@ class GeometryField(SpatialBackend.Field):
|
||||||
|
|
||||||
# The OpenGIS Geometry Type Fields
|
# The OpenGIS Geometry Type Fields
|
||||||
class PointField(GeometryField):
|
class PointField(GeometryField):
|
||||||
_geom = 'POINT'
|
geom_type = 'POINT'
|
||||||
|
|
||||||
class LineStringField(GeometryField):
|
class LineStringField(GeometryField):
|
||||||
_geom = 'LINESTRING'
|
geom_type = 'LINESTRING'
|
||||||
|
|
||||||
class PolygonField(GeometryField):
|
class PolygonField(GeometryField):
|
||||||
_geom = 'POLYGON'
|
geom_type = 'POLYGON'
|
||||||
|
|
||||||
class MultiPointField(GeometryField):
|
class MultiPointField(GeometryField):
|
||||||
_geom = 'MULTIPOINT'
|
geom_type = 'MULTIPOINT'
|
||||||
|
|
||||||
class MultiLineStringField(GeometryField):
|
class MultiLineStringField(GeometryField):
|
||||||
_geom = 'MULTILINESTRING'
|
geom_type = 'MULTILINESTRING'
|
||||||
|
|
||||||
class MultiPolygonField(GeometryField):
|
class MultiPolygonField(GeometryField):
|
||||||
_geom = 'MULTIPOLYGON'
|
geom_type = 'MULTIPOLYGON'
|
||||||
|
|
||||||
class GeometryCollectionField(GeometryField):
|
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.
|
be used to set the geometry as well.
|
||||||
"""
|
"""
|
||||||
# The OGC Geometry type of the field.
|
# 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
|
# The geometry type must match that of the field -- unless the
|
||||||
# general GeometryField is used.
|
# general GeometryField is used.
|
||||||
if isinstance(value, self._klass) and (str(value.geom_type).upper() == gtype or gtype == 'GEOMETRY'):
|
if isinstance(value, self._klass) and (str(value.geom_type).upper() == gtype or gtype == 'GEOMETRY'):
|
||||||
# Assigning the SRID to the 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)):
|
elif isinstance(value, (NoneType, StringType, UnicodeType)):
|
||||||
# Set with None, WKT, or HEX
|
# Set with None, WKT, or HEX
|
||||||
pass
|
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.measure import Area, Distance
|
||||||
from django.contrib.gis.models import get_srid_info
|
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):
|
class GeomSQL(object):
|
||||||
"Simple wrapper object for geometric SQL."
|
"Simple wrapper object for geometric SQL."
|
||||||
def __init__(self, geo_sql):
|
def __init__(self, geo_sql):
|
||||||
|
@ -44,10 +40,10 @@ class GeoQuerySet(QuerySet):
|
||||||
s['procedure_fmt'] = '%(geo_col)s,%(tolerance)s'
|
s['procedure_fmt'] = '%(geo_col)s,%(tolerance)s'
|
||||||
s['procedure_args']['tolerance'] = tolerance
|
s['procedure_args']['tolerance'] = tolerance
|
||||||
s['select_field'] = AreaField('sq_m') # Oracle returns area in units of meters.
|
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:
|
if not geo_field.geodetic:
|
||||||
# Getting the area units of the geographic field.
|
# 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:
|
else:
|
||||||
# TODO: Do we want to support raw number areas for geodetic fields?
|
# TODO: Do we want to support raw number areas for geodetic fields?
|
||||||
raise Exception('Area on geodetic coordinate systems not supported.')
|
raise Exception('Area on geodetic coordinate systems not supported.')
|
||||||
|
@ -196,6 +192,14 @@ class GeoQuerySet(QuerySet):
|
||||||
Scales the geometry to a new size by multiplying the ordinates
|
Scales the geometry to a new size by multiplying the ordinates
|
||||||
with the given x,y,z scale factors.
|
with the given x,y,z scale factors.
|
||||||
"""
|
"""
|
||||||
|
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',
|
s = {'procedure_fmt' : '%(geo_col)s,%(x)s,%(y)s,%(z)s',
|
||||||
'procedure_args' : {'x' : x, 'y' : y, 'z' : z},
|
'procedure_args' : {'x' : x, 'y' : y, 'z' : z},
|
||||||
'select_field' : GeomField(),
|
'select_field' : GeomField(),
|
||||||
|
@ -226,6 +230,14 @@ class GeoQuerySet(QuerySet):
|
||||||
Translates the geometry to a new location using the given numeric
|
Translates the geometry to a new location using the given numeric
|
||||||
parameters as offsets.
|
parameters as offsets.
|
||||||
"""
|
"""
|
||||||
|
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',
|
s = {'procedure_fmt' : '%(geo_col)s,%(x)s,%(y)s,%(z)s',
|
||||||
'procedure_args' : {'x' : x, 'y' : y, 'z' : z},
|
'procedure_args' : {'x' : x, 'y' : y, 'z' : z},
|
||||||
'select_field' : GeomField(),
|
'select_field' : GeomField(),
|
||||||
|
@ -415,7 +427,7 @@ class GeoQuerySet(QuerySet):
|
||||||
if geo_field.geodetic:
|
if geo_field.geodetic:
|
||||||
dist_att = 'm'
|
dist_att = 'm'
|
||||||
else:
|
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.
|
# Shortcut booleans for what distance function we're using.
|
||||||
distance = func == 'distance'
|
distance = func == 'distance'
|
||||||
|
@ -430,7 +442,7 @@ class GeoQuerySet(QuerySet):
|
||||||
lookup_params = [geom or 'POINT (0 0)', 0]
|
lookup_params = [geom or 'POINT (0 0)', 0]
|
||||||
|
|
||||||
# If the spheroid calculation is desired, either by the `spheroid`
|
# 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
|
# sure the 'spheroid' distance setting string is passed in so we
|
||||||
# get the correct spatial stored procedure.
|
# get the correct spatial stored procedure.
|
||||||
if spheroid or (SpatialBackend.postgis and geo_field.geodetic and length):
|
if spheroid or (SpatialBackend.postgis and geo_field.geodetic and length):
|
||||||
|
@ -456,6 +468,9 @@ class GeoQuerySet(QuerySet):
|
||||||
else:
|
else:
|
||||||
geodetic = geo_field.geodetic
|
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 distance:
|
||||||
if self.query.transformed_srid:
|
if self.query.transformed_srid:
|
||||||
# Setting the `geom_args` flag to false because we want to handle
|
# Setting the `geom_args` flag to false because we want to handle
|
||||||
|
@ -467,11 +482,21 @@ class GeoQuerySet(QuerySet):
|
||||||
if geom.srid is None or geom.srid == self.query.transformed_srid:
|
if geom.srid is None or geom.srid == self.query.transformed_srid:
|
||||||
# If the geom parameter srid is None, it is assumed the coordinates
|
# If the geom parameter srid is None, it is assumed the coordinates
|
||||||
# are in the transformed units. A placeholder is used for the
|
# are in the transformed units. A placeholder is used for the
|
||||||
# geometry parameter.
|
# 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'
|
procedure_fmt += ', %%s'
|
||||||
else:
|
else:
|
||||||
# We need to transform the geom to the srid specified in `transform()`,
|
# We need to transform the geom to the srid specified in `transform()`,
|
||||||
# so wrapping the geometry placeholder in transformation SQL.
|
# so wrapping the geometry placeholder in transformation SQL.
|
||||||
|
# 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)
|
procedure_fmt += ', %s(%%%%s, %s)' % (SpatialBackend.transform, self.query.transformed_srid)
|
||||||
else:
|
else:
|
||||||
# `transform()` was not used on this GeoQuerySet.
|
# `transform()` was not used on this GeoQuerySet.
|
||||||
|
@ -483,9 +508,9 @@ class GeoQuerySet(QuerySet):
|
||||||
# procedures may only do queries from point columns to point geometries
|
# procedures may only do queries from point columns to point geometries
|
||||||
# some error checking is required.
|
# some error checking is required.
|
||||||
if not isinstance(geo_field, PointField):
|
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':
|
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
|
# The `function` procedure argument needs to be set differently for
|
||||||
# geodetic distance calculations.
|
# geodetic distance calculations.
|
||||||
if spheroid:
|
if spheroid:
|
||||||
|
|
|
@ -44,8 +44,17 @@ elif SpatialBackend.oracle:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def convert_geom(clob, geo_field):
|
def convert_geom(clob, geo_field):
|
||||||
if clob: return SpatialBackend.Geometry(clob.read(), geo_field._srid)
|
if clob:
|
||||||
else: return None
|
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):
|
class GeoAggregate(Aggregate):
|
||||||
# Overriding the SQL template with the geographic one.
|
# Overriding the SQL template with the geographic one.
|
||||||
|
@ -71,6 +80,10 @@ class GeoAggregate(Aggregate):
|
||||||
if not self.sql_function:
|
if not self.sql_function:
|
||||||
raise NotImplementedError('This aggregate functionality not implemented for your spatial backend.')
|
raise NotImplementedError('This aggregate functionality not implemented for your spatial backend.')
|
||||||
|
|
||||||
|
class Collect(GeoAggregate):
|
||||||
|
conversion_class = GeomField
|
||||||
|
sql_function = SpatialBackend.collect
|
||||||
|
|
||||||
class Extent(GeoAggregate):
|
class Extent(GeoAggregate):
|
||||||
is_extent = True
|
is_extent = True
|
||||||
sql_function = SpatialBackend.extent
|
sql_function = SpatialBackend.extent
|
||||||
|
|
|
@ -2,11 +2,15 @@
|
||||||
This module holds simple classes used by GeoQuery.convert_values
|
This module holds simple classes used by GeoQuery.convert_values
|
||||||
to convert geospatial values from the database.
|
to convert geospatial values from the database.
|
||||||
"""
|
"""
|
||||||
|
from django.contrib.gis.db.backend import SpatialBackend
|
||||||
|
|
||||||
class BaseField(object):
|
class BaseField(object):
|
||||||
def get_internal_type(self):
|
def get_internal_type(self):
|
||||||
"Overloaded method so OracleQuery.convert_values doesn't balk."
|
"Overloaded method so OracleQuery.convert_values doesn't balk."
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
if SpatialBackend.oracle: BaseField.empty_strings_allowed = False
|
||||||
|
|
||||||
class AreaField(BaseField):
|
class AreaField(BaseField):
|
||||||
"Wrapper for Area values."
|
"Wrapper for Area values."
|
||||||
def __init__(self, area_att):
|
def __init__(self, area_att):
|
||||||
|
|
|
@ -208,13 +208,14 @@ class GeoQuery(sql.Query):
|
||||||
if SpatialBackend.oracle:
|
if SpatialBackend.oracle:
|
||||||
# Running through Oracle's first.
|
# Running through Oracle's first.
|
||||||
value = super(GeoQuery, self).convert_values(value, field or GeomField())
|
value = super(GeoQuery, self).convert_values(value, field or GeomField())
|
||||||
|
|
||||||
if isinstance(field, DistanceField):
|
if isinstance(field, DistanceField):
|
||||||
# Using the field's distance attribute, can instantiate
|
# Using the field's distance attribute, can instantiate
|
||||||
# `Distance` with the right context.
|
# `Distance` with the right context.
|
||||||
value = Distance(**{field.distance_att : value})
|
value = Distance(**{field.distance_att : value})
|
||||||
elif isinstance(field, AreaField):
|
elif isinstance(field, AreaField):
|
||||||
value = Area(**{field.area_att : value})
|
value = Area(**{field.area_att : value})
|
||||||
elif isinstance(field, GeomField):
|
elif isinstance(field, GeomField) and value:
|
||||||
value = SpatialBackend.Geometry(value)
|
value = SpatialBackend.Geometry(value)
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
@ -260,7 +261,7 @@ class GeoQuery(sql.Query):
|
||||||
selection formats in order to retrieve geometries in OGC WKT. For all
|
selection formats in order to retrieve geometries in OGC WKT. For all
|
||||||
other fields a simple '%s' format string is returned.
|
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,
|
# This allows operations to be done on fields in the SELECT,
|
||||||
# overriding their values -- used by the Oracle and MySQL
|
# overriding their values -- used by the Oracle and MySQL
|
||||||
# spatial backends to get database values as WKT, and by the
|
# 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,
|
# Because WKT doesn't contain spatial reference information,
|
||||||
# the SRID is prefixed to the returned WKT to ensure that the
|
# the SRID is prefixed to the returned WKT to ensure that the
|
||||||
# transformed geometries have an SRID different than that of the
|
# transformed geometries have an SRID different than that of the
|
||||||
# field -- this is only used by `transform` for Oracle backends.
|
# field -- this is only used by `transform` for Oracle and
|
||||||
if self.transformed_srid and SpatialBackend.oracle:
|
# SpatiaLite backends.
|
||||||
|
if self.transformed_srid and ( SpatialBackend.oracle or
|
||||||
|
SpatialBackend.spatialite ):
|
||||||
sel_fmt = "'SRID=%d;'||%s" % (self.transformed_srid, sel_fmt)
|
sel_fmt = "'SRID=%d;'||%s" % (self.transformed_srid, sel_fmt)
|
||||||
else:
|
else:
|
||||||
sel_fmt = '%s'
|
sel_fmt = '%s'
|
||||||
|
|
|
@ -15,7 +15,7 @@ class GeoAnnotation(object):
|
||||||
"""
|
"""
|
||||||
def __init__(self, field, value, where):
|
def __init__(self, field, value, where):
|
||||||
self.geodetic = field.geodetic
|
self.geodetic = field.geodetic
|
||||||
self.geom_type = field._geom
|
self.geom_type = field.geom_type
|
||||||
self.value = value
|
self.value = value
|
||||||
self.where = tuple(where)
|
self.where = tuple(where)
|
||||||
|
|
||||||
|
@ -37,7 +37,7 @@ class GeoWhereNode(WhereNode):
|
||||||
obj, lookup_type, value = data
|
obj, lookup_type, value = data
|
||||||
alias, col, field = obj.alias, obj.col, obj.field
|
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`.
|
# Not a geographic field, so call `WhereNode.add`.
|
||||||
return super(GeoWhereNode, self).add(data, connector)
|
return super(GeoWhereNode, self).add(data, connector)
|
||||||
else:
|
else:
|
||||||
|
@ -50,7 +50,7 @@ class GeoWhereNode(WhereNode):
|
||||||
# Get the SRID of the geometry field that the expression was meant
|
# Get the SRID of the geometry field that the expression was meant
|
||||||
# to operate on -- it's needed to determine whether transformation
|
# to operate on -- it's needed to determine whether transformation
|
||||||
# SQL is necessary.
|
# SQL is necessary.
|
||||||
srid = geo_fld._srid
|
srid = geo_fld.srid
|
||||||
|
|
||||||
# Getting the quoted representation of the geometry column that
|
# Getting the quoted representation of the geometry column that
|
||||||
# the expression is operating on.
|
# 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
|
# If it's in a different SRID, we'll need to wrap in
|
||||||
# transformation SQL.
|
# transformation SQL.
|
||||||
if not srid is None and srid != field._srid and SpatialBackend.transform:
|
if not srid is None and srid != field.srid and SpatialBackend.transform:
|
||||||
placeholder = '%s(%%s, %s)' % (SpatialBackend.transform, field._srid)
|
placeholder = '%s(%%s, %s)' % (SpatialBackend.transform, field.srid)
|
||||||
else:
|
else:
|
||||||
placeholder = '%s'
|
placeholder = '%s'
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,7 @@ class GeometryField(forms.Field):
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
self.null = kwargs.pop('null')
|
self.null = kwargs.pop('null')
|
||||||
self.geom_type = kwargs.pop('geom_type')
|
self.geom_type = kwargs.pop('geom_type')
|
||||||
|
self.srid = kwargs.pop('srid')
|
||||||
super(GeometryField, self).__init__(**kwargs)
|
super(GeometryField, self).__init__(**kwargs)
|
||||||
|
|
||||||
def clean(self, value):
|
def clean(self, value):
|
||||||
|
|
|
@ -15,11 +15,10 @@ class SpatialRefSysMixin(object):
|
||||||
The SpatialRefSysMixin is a class used by the database-dependent
|
The SpatialRefSysMixin is a class used by the database-dependent
|
||||||
SpatialRefSys objects to reduce redundnant code.
|
SpatialRefSys objects to reduce redundnant code.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# For pulling out the spheroid from the spatial reference string. This
|
# For pulling out the spheroid from the spatial reference string. This
|
||||||
# regular expression is used only if the user does not have GDAL installed.
|
# 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'
|
# TODO: Flattening not used in all ellipsoids, could also be a minor axis,
|
||||||
# parameter.
|
# or 'b' parameter.
|
||||||
spheroid_regex = re.compile(r'.+SPHEROID\[\"(?P<name>.+)\",(?P<major>\d+(\.\d+)?),(?P<flattening>\d{3}\.\d+),')
|
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.
|
# For pulling out the units on platforms w/o GDAL installed.
|
||||||
|
@ -33,6 +32,7 @@ class SpatialRefSysMixin(object):
|
||||||
Returns a GDAL SpatialReference object, if GDAL is installed.
|
Returns a GDAL SpatialReference object, if GDAL is installed.
|
||||||
"""
|
"""
|
||||||
if HAS_GDAL:
|
if HAS_GDAL:
|
||||||
|
# TODO: Is caching really necessary here? Is complexity worth it?
|
||||||
if hasattr(self, '_srs'):
|
if hasattr(self, '_srs'):
|
||||||
# Returning a clone of the cached SpatialReference object.
|
# Returning a clone of the cached SpatialReference object.
|
||||||
return self._srs.clone()
|
return self._srs.clone()
|
||||||
|
@ -46,6 +46,12 @@ class SpatialRefSysMixin(object):
|
||||||
except Exception, msg:
|
except Exception, msg:
|
||||||
pass
|
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))
|
raise Exception('Could not get OSR SpatialReference from WKT: %s\nError:\n%s' % (self.wkt, msg))
|
||||||
else:
|
else:
|
||||||
raise Exception('GDAL is not installed.')
|
raise Exception('GDAL is not installed.')
|
||||||
|
@ -208,29 +214,31 @@ class SpatialRefSysMixin(object):
|
||||||
except:
|
except:
|
||||||
return unicode(self.wkt)
|
return unicode(self.wkt)
|
||||||
|
|
||||||
# The SpatialRefSys and GeometryColumns models
|
# Defining dummy default first; if spatial db, will overrride.
|
||||||
_srid_info = True
|
def get_srid_info(srid):
|
||||||
if not PYTHON23 and settings.DATABASE_ENGINE == 'postgresql_psycopg2':
|
"""
|
||||||
# Because the PostGIS version is checked when initializing the spatial
|
Dummy routine for the backends that do not have the OGC required
|
||||||
# backend a `ProgrammingError` will be raised if the PostGIS tables
|
spatial metadata tables (like MySQL).
|
||||||
# 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.
|
return None, None, None
|
||||||
try:
|
|
||||||
from django.contrib.gis.db.backend.postgis.models import GeometryColumns, SpatialRefSys
|
|
||||||
except:
|
|
||||||
_srid_info = False
|
|
||||||
elif not PYTHON23 and settings.DATABASE_ENGINE == 'oracle':
|
|
||||||
# Same thing as above, except the GEOS library is attempted to be loaded for
|
|
||||||
# `BaseSpatialBackend`, and an exception will be raised during the
|
|
||||||
# Django test suite if it doesn't exist.
|
|
||||||
try:
|
|
||||||
from django.contrib.gis.db.backend.oracle.models import GeometryColumns, SpatialRefSys
|
|
||||||
except:
|
|
||||||
_srid_info = False
|
|
||||||
else:
|
|
||||||
_srid_info = False
|
|
||||||
|
|
||||||
if _srid_info:
|
# Django test suite on 2.3 platforms will choke on code inside this
|
||||||
|
# conditional.
|
||||||
|
if not PYTHON23:
|
||||||
|
try:
|
||||||
|
# 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):
|
def get_srid_info(srid):
|
||||||
"""
|
"""
|
||||||
Returns the units, unit name, and spheroid WKT associated with the
|
Returns the units, unit name, and spheroid WKT associated with the
|
||||||
|
@ -253,18 +261,30 @@ if _srid_info:
|
||||||
cur = connection.cursor()
|
cur = connection.cursor()
|
||||||
qn = connection.ops.quote_name
|
qn = connection.ops.quote_name
|
||||||
stmt = 'SELECT %(table)s.%(wkt_col)s FROM %(table)s WHERE (%(table)s.%(srid_col)s = %(srid)s)'
|
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),
|
params = {'table' : qn(SpatialRefSys._meta.db_table),
|
||||||
'wkt_col' : qn(SpatialRefSys.wkt_col()),
|
|
||||||
'srid_col' : qn('srid'),
|
'srid_col' : qn('srid'),
|
||||||
'srid' : srid,
|
'srid' : srid,
|
||||||
}
|
}
|
||||||
cur.execute(stmt)
|
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.
|
# Fetching the WKT from the cursor; if the query failed raise an Exception.
|
||||||
fetched = cur.fetchone()
|
fetched = cur.fetchone()
|
||||||
if not fetched:
|
if not fetched:
|
||||||
raise ValueError('Failed to find spatial reference entry in "%s" corresponding to SRID=%s.' %
|
raise ValueError('Failed to find spatial reference entry in "%s" corresponding to SRID=%s.' %
|
||||||
(SpatialRefSys._meta.db_table, srid))
|
(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]
|
srs_wkt = fetched[0]
|
||||||
|
|
||||||
# Getting metadata associated with the spatial reference system identifier.
|
# Getting metadata associated with the spatial reference system identifier.
|
||||||
|
@ -273,11 +293,5 @@ if _srid_info:
|
||||||
unit, unit_name = SpatialRefSys.get_units(srs_wkt)
|
unit, unit_name = SpatialRefSys.get_units(srs_wkt)
|
||||||
spheroid = SpatialRefSys.get_spheroid(srs_wkt)
|
spheroid = SpatialRefSys.get_spheroid(srs_wkt)
|
||||||
return unit, unit_name, spheroid
|
return unit, unit_name, spheroid
|
||||||
else:
|
except:
|
||||||
def get_srid_info(srid):
|
pass
|
||||||
"""
|
|
||||||
Dummy routine for the backends that do not have the OGC required
|
|
||||||
spatial metadata tables (like MySQL).
|
|
||||||
"""
|
|
||||||
return None, None, None
|
|
||||||
|
|
||||||
|
|
|
@ -9,28 +9,26 @@ def geo_suite():
|
||||||
some backends).
|
some backends).
|
||||||
"""
|
"""
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.gis.tests.utils import mysql, oracle, postgis
|
from django.contrib.gis.gdal import HAS_GDAL
|
||||||
from django.contrib.gis import gdal, utils
|
from django.contrib.gis.utils import HAS_GEOIP
|
||||||
|
from django.contrib.gis.tests.utils import mysql
|
||||||
|
|
||||||
# The test suite.
|
# The test suite.
|
||||||
s = unittest.TestSuite()
|
s = unittest.TestSuite()
|
||||||
|
|
||||||
# Adding the GEOS tests. (__future__)
|
# Tests that require use of a spatial database (e.g., creation of models)
|
||||||
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)
|
|
||||||
test_apps = ['geoapp', 'relatedapp']
|
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
|
# Tests that do not require setting up and tearing down a spatial database.
|
||||||
# and are modules in `django.contrib.gis.tests`.
|
|
||||||
test_suite_names = [
|
test_suite_names = [
|
||||||
'test_measure',
|
'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.
|
# These tests require GDAL.
|
||||||
test_suite_names.append('test_spatialrefsys')
|
test_suite_names.append('test_spatialrefsys')
|
||||||
test_apps.append('layermap')
|
test_apps.append('layermap')
|
||||||
|
@ -39,14 +37,25 @@ def geo_suite():
|
||||||
from django.contrib.gis.gdal import tests as gdal_tests
|
from django.contrib.gis.gdal import tests as gdal_tests
|
||||||
s.addTest(gdal_tests.suite())
|
s.addTest(gdal_tests.suite())
|
||||||
else:
|
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')
|
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:
|
for suite_name in test_suite_names:
|
||||||
tsuite = import_module('django.contrib.gis.tests.' + suite_name)
|
tsuite = import_module('django.contrib.gis.tests.' + suite_name)
|
||||||
s.addTest(tsuite.suite())
|
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
|
return s, test_apps
|
||||||
|
|
||||||
def run_gis_tests(test_labels, **kwargs):
|
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
|
# Creating the test suite, adding the test models to INSTALLED_APPS, and
|
||||||
# adding the model test suites to our suite package.
|
# adding the model test suites to our suite package.
|
||||||
gis_suite, test_apps = geo_suite()
|
gis_suite, test_apps = geo_suite()
|
||||||
for test_app in test_apps:
|
for test_model in test_apps:
|
||||||
module_name = 'django.contrib.gis.tests.%s' % test_app
|
module_name = 'django.contrib.gis.tests.%s' % test_model
|
||||||
if mysql:
|
if mysql:
|
||||||
test_module = 'tests_mysql'
|
test_module = 'tests_mysql'
|
||||||
else:
|
else:
|
||||||
|
@ -160,15 +169,18 @@ def run_tests(test_labels, verbosity=1, interactive=True, extra_tests=[], suite=
|
||||||
from django.test.simple import build_suite, build_test
|
from django.test.simple import build_suite, build_test
|
||||||
from django.test.utils import setup_test_environment, teardown_test_environment
|
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.
|
# 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.
|
# Setting up for testing.
|
||||||
setup_test_environment()
|
setup_test_environment()
|
||||||
settings.DEBUG = False
|
settings.DEBUG = False
|
||||||
old_name = settings.DATABASE_NAME
|
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,
|
# 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,
|
# 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`
|
# the normal test suite creation process from `django.test.simple.run_tests`
|
||||||
|
@ -189,9 +201,6 @@ def run_tests(test_labels, verbosity=1, interactive=True, extra_tests=[], suite=
|
||||||
for test in extra_tests:
|
for test in extra_tests:
|
||||||
suite.addTest(test)
|
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
|
# Executing the tests (including the model tests), and destorying the
|
||||||
# test database after the tests have completed.
|
# test database after the tests have completed.
|
||||||
result = unittest.TextTestRunner(verbosity=verbosity).run(suite)
|
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)'),
|
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):
|
class Interstate(models.Model):
|
||||||
"Geodetic model for U.S. Interstates."
|
"Geodetic model for U.S. Interstates."
|
||||||
name = models.CharField(max_length=10)
|
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()
|
objects = models.GeoManager()
|
||||||
def __unicode__(self): return self.name
|
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.gdal import DataSource
|
||||||
from django.contrib.gis.geos import GEOSGeometry, Point, LineString
|
from django.contrib.gis.geos import GEOSGeometry, Point, LineString
|
||||||
from django.contrib.gis.measure import D # alias for Distance
|
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, spatialite, no_oracle, no_spatialite
|
||||||
from django.contrib.gis.tests.utils import oracle, postgis, no_oracle
|
|
||||||
|
|
||||||
from models import AustraliaCity, Interstate, SouthTexasCity, SouthTexasCityFt, CensusZipcode, SouthTexasZipcode
|
from models import AustraliaCity, Interstate, SouthTexasInterstate, \
|
||||||
from data import au_cities, interstates, stx_cities, stx_zips
|
SouthTexasCity, SouthTexasCityFt, CensusZipcode, SouthTexasZipcode
|
||||||
|
from data import au_cities, interstates, stx_interstates, stx_cities, stx_zips
|
||||||
|
|
||||||
class DistanceTest(unittest.TestCase):
|
class DistanceTest(unittest.TestCase):
|
||||||
|
|
||||||
|
@ -32,8 +32,11 @@ class DistanceTest(unittest.TestCase):
|
||||||
# Loading up the cities.
|
# Loading up the cities.
|
||||||
def load_cities(city_model, data_tup):
|
def load_cities(city_model, data_tup):
|
||||||
for name, x, y in data_tup:
|
for name, x, y in data_tup:
|
||||||
c = city_model(name=name, point=Point(x, y, srid=4326))
|
city_model(name=name, point=Point(x, y, srid=4326)).save()
|
||||||
c.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(SouthTexasCity, stx_cities)
|
||||||
load_cities(SouthTexasCityFt, stx_cities)
|
load_cities(SouthTexasCityFt, stx_cities)
|
||||||
|
@ -52,10 +55,13 @@ class DistanceTest(unittest.TestCase):
|
||||||
self.assertEqual(4, CensusZipcode.objects.count())
|
self.assertEqual(4, CensusZipcode.objects.count())
|
||||||
|
|
||||||
# Loading up the Interstates.
|
# Loading up the Interstates.
|
||||||
for name, wkt in interstates:
|
load_interstates(Interstate, interstates)
|
||||||
Interstate(name=name, line=GEOSGeometry(wkt, srid=4326)).save()
|
load_interstates(SouthTexasInterstate, stx_interstates)
|
||||||
self.assertEqual(1, Interstate.objects.count())
|
|
||||||
|
|
||||||
|
self.assertEqual(1, Interstate.objects.count())
|
||||||
|
self.assertEqual(1, SouthTexasInterstate.objects.count())
|
||||||
|
|
||||||
|
@no_spatialite
|
||||||
def test02_dwithin(self):
|
def test02_dwithin(self):
|
||||||
"Testing the `dwithin` lookup type."
|
"Testing the `dwithin` lookup type."
|
||||||
# Distances -- all should be equal (except for the
|
# Distances -- all should be equal (except for the
|
||||||
|
@ -107,6 +113,8 @@ class DistanceTest(unittest.TestCase):
|
||||||
138809.684197, 158309.246259, 212183.594374,
|
138809.684197, 158309.246259, 212183.594374,
|
||||||
70870.188967, 165337.758878, 139196.085105]
|
70870.188967, 165337.758878, 139196.085105]
|
||||||
# SELECT ST_Distance(point, ST_Transform(ST_GeomFromText('POINT(-96.876369 29.905320)', 4326), 2278)) FROM distapp_southtexascityft;
|
# 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,
|
ft_distances = [482528.79154625, 458103.408123001, 462231.860397575,
|
||||||
455411.438904354, 519386.252102563, 696139.009211594,
|
455411.438904354, 519386.252102563, 696139.009211594,
|
||||||
232513.278304279, 542445.630586414, 456679.155883207]
|
232513.278304279, 542445.630586414, 456679.155883207]
|
||||||
|
@ -115,8 +123,12 @@ class DistanceTest(unittest.TestCase):
|
||||||
# with different projected coordinate systems.
|
# with different projected coordinate systems.
|
||||||
dist1 = SouthTexasCity.objects.distance(lagrange, field_name='point')
|
dist1 = SouthTexasCity.objects.distance(lagrange, field_name='point')
|
||||||
dist2 = SouthTexasCity.objects.distance(lagrange) # Using GEOSGeometry parameter
|
dist2 = SouthTexasCity.objects.distance(lagrange) # Using GEOSGeometry parameter
|
||||||
|
if spatialite or oracle:
|
||||||
|
dist_qs = [dist1, dist2]
|
||||||
|
else:
|
||||||
dist3 = SouthTexasCityFt.objects.distance(lagrange.ewkt) # Using EWKT string parameter.
|
dist3 = SouthTexasCityFt.objects.distance(lagrange.ewkt) # Using EWKT string parameter.
|
||||||
dist4 = SouthTexasCityFt.objects.distance(lagrange)
|
dist4 = SouthTexasCityFt.objects.distance(lagrange)
|
||||||
|
dist_qs = [dist1, dist2, dist3, dist4]
|
||||||
|
|
||||||
# Original query done on PostGIS, have to adjust AlmostEqual tolerance
|
# Original query done on PostGIS, have to adjust AlmostEqual tolerance
|
||||||
# for Oracle.
|
# for Oracle.
|
||||||
|
@ -124,11 +136,12 @@ class DistanceTest(unittest.TestCase):
|
||||||
else: tol = 5
|
else: tol = 5
|
||||||
|
|
||||||
# Ensuring expected distances are returned for each distance queryset.
|
# 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):
|
for i, c in enumerate(qs):
|
||||||
self.assertAlmostEqual(m_distances[i], c.distance.m, tol)
|
self.assertAlmostEqual(m_distances[i], c.distance.m, tol)
|
||||||
self.assertAlmostEqual(ft_distances[i], c.distance.survey_ft, tol)
|
self.assertAlmostEqual(ft_distances[i], c.distance.survey_ft, tol)
|
||||||
|
|
||||||
|
@no_spatialite
|
||||||
def test03b_distance_method(self):
|
def test03b_distance_method(self):
|
||||||
"Testing the `distance` GeoQuerySet method on geodetic coordnate systems."
|
"Testing the `distance` GeoQuerySet method on geodetic coordnate systems."
|
||||||
if oracle: tol = 2
|
if oracle: tol = 2
|
||||||
|
@ -139,8 +152,8 @@ class DistanceTest(unittest.TestCase):
|
||||||
if not oracle:
|
if not oracle:
|
||||||
# PostGIS is limited to disance queries only to/from point geometries,
|
# PostGIS is limited to disance queries only to/from point geometries,
|
||||||
# ensuring a TypeError is raised if something else is put in.
|
# ensuring a TypeError is raised if something else is put in.
|
||||||
self.assertRaises(TypeError, AustraliaCity.objects.distance, 'LINESTRING(0 0, 1 1)')
|
self.assertRaises(ValueError, 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)))
|
||||||
|
|
||||||
# Got the reference distances using the raw SQL statements:
|
# 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));
|
# 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,7 +176,7 @@ class DistanceTest(unittest.TestCase):
|
||||||
"Testing the `distance` GeoQuerySet method used with `transform` on a geographic field."
|
"Testing the `distance` GeoQuerySet method used with `transform` on a geographic field."
|
||||||
# Normally you can't compute distances from a geometry field
|
# Normally you can't compute distances from a geometry field
|
||||||
# that is not a PointField (on PostGIS).
|
# 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
|
# We'll be using a Polygon (created by buffering the centroid
|
||||||
# of 77005 to 100m) -- which aren't allowed in geographic distance
|
# of 77005 to 100m) -- which aren't allowed in geographic distance
|
||||||
|
@ -182,9 +195,11 @@ class DistanceTest(unittest.TestCase):
|
||||||
# however.
|
# however.
|
||||||
buf1 = z.poly.centroid.buffer(100)
|
buf1 = z.poly.centroid.buffer(100)
|
||||||
buf2 = buf1.transform(4269, clone=True)
|
buf2 = buf1.transform(4269, clone=True)
|
||||||
|
ref_zips = ['77002', '77025', '77401']
|
||||||
|
|
||||||
for buf in [buf1, buf2]:
|
for buf in [buf1, buf2]:
|
||||||
qs = CensusZipcode.objects.exclude(name='77005').transform(32140).distance(buf)
|
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):
|
for i, z in enumerate(qs):
|
||||||
self.assertAlmostEqual(z.distance.m, dists_m[i], 5)
|
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
|
# (thus, Houston and Southside place will be excluded as tested in
|
||||||
# the `test02_dwithin` above).
|
# 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)))
|
qs1 = SouthTexasCity.objects.filter(point__distance_gte=(self.stx_pnt, D(km=7))).filter(point__distance_lte=(self.stx_pnt, D(km=20)))
|
||||||
|
|
||||||
|
# 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)))
|
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:
|
dist_qs = (qs1, qs2)
|
||||||
|
|
||||||
|
for qs in dist_qs:
|
||||||
cities = self.get_names(qs)
|
cities = self.get_names(qs)
|
||||||
self.assertEqual(cities, ['Bellaire', 'Pearland', 'West University Place'])
|
self.assertEqual(cities, ['Bellaire', 'Pearland', 'West University Place'])
|
||||||
|
|
||||||
|
@ -207,6 +230,7 @@ class DistanceTest(unittest.TestCase):
|
||||||
qs = SouthTexasZipcode.objects.exclude(name='77005').filter(poly__distance_lte=(z.poly, D(m=300)))
|
qs = SouthTexasZipcode.objects.exclude(name='77005').filter(poly__distance_lte=(z.poly, D(m=300)))
|
||||||
self.assertEqual(['77002', '77025', '77401'], self.get_names(qs))
|
self.assertEqual(['77002', '77025', '77401'], self.get_names(qs))
|
||||||
|
|
||||||
|
@no_spatialite
|
||||||
def test05_geodetic_distance_lookups(self):
|
def test05_geodetic_distance_lookups(self):
|
||||||
"Testing distance lookups on geodetic coordinate systems."
|
"Testing distance lookups on geodetic coordinate systems."
|
||||||
if not oracle:
|
if not oracle:
|
||||||
|
@ -235,14 +259,13 @@ class DistanceTest(unittest.TestCase):
|
||||||
d1, d2 = D(yd=19500), D(nm=400) # Yards (~17km) & Nautical miles.
|
d1, d2 = D(yd=19500), D(nm=400) # Yards (~17km) & Nautical miles.
|
||||||
|
|
||||||
# Normal geodetic distance lookup (uses `distance_sphere` on PostGIS.
|
# Normal geodetic distance lookup (uses `distance_sphere` on PostGIS.
|
||||||
gq1 = GeoQ(point__distance_lte=(wollongong.point, d1))
|
gq1 = Q(point__distance_lte=(wollongong.point, d1))
|
||||||
gq2 = GeoQ(point__distance_gte=(wollongong.point, d2))
|
gq2 = Q(point__distance_gte=(wollongong.point, d2))
|
||||||
qs1 = AustraliaCity.objects.exclude(name='Wollongong').filter(gq1 | gq2)
|
qs1 = AustraliaCity.objects.exclude(name='Wollongong').filter(gq1 | gq2)
|
||||||
|
|
||||||
# Geodetic distance lookup but telling GeoDjango to use `distance_spheroid`
|
# Geodetic distance lookup but telling GeoDjango to use `distance_spheroid`
|
||||||
# instead (we should get the same results b/c accuracy variance won't matter
|
# 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
|
# in this test case).
|
||||||
# it doesn't matter).
|
|
||||||
if postgis:
|
if postgis:
|
||||||
gq3 = Q(point__distance_lte=(wollongong.point, d1, 'spheroid'))
|
gq3 = Q(point__distance_lte=(wollongong.point, d1, 'spheroid'))
|
||||||
gq4 = Q(point__distance_gte=(wollongong.point, d2, 'spheroid'))
|
gq4 = Q(point__distance_gte=(wollongong.point, d2, 'spheroid'))
|
||||||
|
@ -270,12 +293,23 @@ class DistanceTest(unittest.TestCase):
|
||||||
"Testing the `length` GeoQuerySet method."
|
"Testing the `length` GeoQuerySet method."
|
||||||
# Reference query (should use `length_spheroid`).
|
# Reference query (should use `length_spheroid`).
|
||||||
# SELECT ST_length_spheroid(ST_GeomFromText('<wkt>', 4326) 'SPHEROID["WGS 84",6378137,298.257223563, AUTHORITY["EPSG","7030"]]');
|
# SELECT ST_length_spheroid(ST_GeomFromText('<wkt>', 4326) 'SPHEROID["WGS 84",6378137,298.257223563, AUTHORITY["EPSG","7030"]]');
|
||||||
len_m = 473504.769553813
|
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()
|
qs = Interstate.objects.length()
|
||||||
if oracle: tol = 2
|
if oracle: tol = 2
|
||||||
else: tol = 5
|
else: tol = 5
|
||||||
self.assertAlmostEqual(len_m, qs[0].length.m, tol)
|
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):
|
def test08_perimeter(self):
|
||||||
"Testing the `perimeter` GeoQuerySet method."
|
"Testing the `perimeter` GeoQuerySet method."
|
||||||
# Reference query:
|
# Reference query:
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
from django.contrib.gis.db import models
|
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.
|
# MySQL spatial indices can't handle NULL geometries.
|
||||||
null_flag = not mysql
|
null_flag = not mysql
|
||||||
|
@ -27,12 +27,13 @@ class State(models.Model):
|
||||||
objects = models.GeoManager()
|
objects = models.GeoManager()
|
||||||
def __unicode__(self): return self.name
|
def __unicode__(self): return self.name
|
||||||
|
|
||||||
class Feature(models.Model):
|
if not spatialite:
|
||||||
|
class Feature(models.Model):
|
||||||
name = models.CharField(max_length=20)
|
name = models.CharField(max_length=20)
|
||||||
geom = models.GeometryField()
|
geom = models.GeometryField()
|
||||||
objects = models.GeoManager()
|
objects = models.GeoManager()
|
||||||
def __unicode__(self): return self.name
|
def __unicode__(self): return self.name
|
||||||
|
|
||||||
class MinusOneSRID(models.Model):
|
class MinusOneSRID(models.Model):
|
||||||
geom = models.PointField(srid=-1) # Minus one SRID.
|
geom = models.PointField(srid=-1) # Minus one SRID.
|
||||||
objects = models.GeoManager()
|
objects = models.GeoManager()
|
||||||
|
|
|
@ -1,10 +1,13 @@
|
||||||
import os, unittest
|
import os, unittest
|
||||||
from models import Country, City, PennsylvaniaCity, State, Feature, MinusOneSRID
|
|
||||||
from django.contrib.gis import gdal
|
from django.contrib.gis import gdal
|
||||||
from django.contrib.gis.db.backend import SpatialBackend
|
from django.contrib.gis.db.backend import SpatialBackend
|
||||||
from django.contrib.gis.geos import *
|
from django.contrib.gis.geos import *
|
||||||
from django.contrib.gis.measure import Distance
|
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
|
# 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;
|
# 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(2, Country.objects.count())
|
||||||
self.assertEqual(8, City.objects.count())
|
self.assertEqual(8, City.objects.count())
|
||||||
|
|
||||||
# Oracle cannot handle NULL geometry values w/certain queries.
|
# Only PostGIS can handle NULL geometries
|
||||||
if SpatialBackend.oracle: n_state = 2
|
if SpatialBackend.postgis or SpatialBackend.spatialite:
|
||||||
else: n_state = 3
|
n_state = 3
|
||||||
|
else:
|
||||||
|
n_state = 2
|
||||||
self.assertEqual(n_state, State.objects.count())
|
self.assertEqual(n_state, State.objects.count())
|
||||||
|
|
||||||
def test02_proxy(self):
|
def test02_proxy(self):
|
||||||
|
@ -112,6 +117,7 @@ class GeoModelTest(unittest.TestCase):
|
||||||
ns.delete()
|
ns.delete()
|
||||||
|
|
||||||
@no_oracle # Oracle does not support KML.
|
@no_oracle # Oracle does not support KML.
|
||||||
|
@no_spatialite # SpatiaLite does not support KML.
|
||||||
def test03a_kml(self):
|
def test03a_kml(self):
|
||||||
"Testing KML output from the database using GeoManager.kml()."
|
"Testing KML output from the database using GeoManager.kml()."
|
||||||
if DISABLE: return
|
if DISABLE: return
|
||||||
|
@ -137,6 +143,7 @@ class GeoModelTest(unittest.TestCase):
|
||||||
for ptown in [ptown1, ptown2]:
|
for ptown in [ptown1, ptown2]:
|
||||||
self.assertEqual(ref_kml, ptown.kml)
|
self.assertEqual(ref_kml, ptown.kml)
|
||||||
|
|
||||||
|
@no_spatialite # SpatiaLite does not support GML.
|
||||||
def test03b_gml(self):
|
def test03b_gml(self):
|
||||||
"Testing GML output from the database using GeoManager.gml()."
|
"Testing GML output from the database using GeoManager.gml()."
|
||||||
if DISABLE: return
|
if DISABLE: return
|
||||||
|
@ -150,7 +157,7 @@ class GeoModelTest(unittest.TestCase):
|
||||||
if SpatialBackend.oracle:
|
if SpatialBackend.oracle:
|
||||||
# No precision parameter for Oracle :-/
|
# No precision parameter for Oracle :-/
|
||||||
import re
|
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]:
|
for ptown in [ptown1, ptown2]:
|
||||||
self.assertEqual(True, bool(gml_regex.match(ptown.gml)))
|
self.assertEqual(True, bool(gml_regex.match(ptown.gml)))
|
||||||
else:
|
else:
|
||||||
|
@ -180,7 +187,7 @@ class GeoModelTest(unittest.TestCase):
|
||||||
self.assertAlmostEqual(ptown.x, p.point.x, prec)
|
self.assertAlmostEqual(ptown.x, p.point.x, prec)
|
||||||
self.assertAlmostEqual(ptown.y, p.point.y, 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):
|
def test05_extent(self):
|
||||||
"Testing the `extent` GeoQuerySet method."
|
"Testing the `extent` GeoQuerySet method."
|
||||||
if DISABLE: return
|
if DISABLE: return
|
||||||
|
@ -193,9 +200,10 @@ class GeoModelTest(unittest.TestCase):
|
||||||
extent = qs.extent()
|
extent = qs.extent()
|
||||||
|
|
||||||
for val, exp in zip(extent, expected):
|
for val, exp in zip(extent, expected):
|
||||||
self.assertAlmostEqual(exp, val, 8)
|
self.assertAlmostEqual(exp, val, 4)
|
||||||
|
|
||||||
@no_oracle
|
@no_oracle
|
||||||
|
@no_spatialite # SpatiaLite does not have a MakeLine function
|
||||||
def test06_make_line(self):
|
def test06_make_line(self):
|
||||||
"Testing the `make_line` GeoQuerySet method."
|
"Testing the `make_line` GeoQuerySet method."
|
||||||
if DISABLE: return
|
if DISABLE: return
|
||||||
|
@ -214,10 +222,13 @@ class GeoModelTest(unittest.TestCase):
|
||||||
qs1 = City.objects.filter(point__disjoint=ptown.point)
|
qs1 = City.objects.filter(point__disjoint=ptown.point)
|
||||||
self.assertEqual(7, qs1.count())
|
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
|
# TODO: Do NULL columns bork queries on PostGIS? The following
|
||||||
# error is encountered:
|
# error is encountered:
|
||||||
# psycopg2.ProgrammingError: invalid memory alloc request size 4294957297
|
# 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)
|
qs2 = State.objects.filter(poly__disjoint=ptown.point)
|
||||||
self.assertEqual(1, qs2.count())
|
self.assertEqual(1, qs2.count())
|
||||||
self.assertEqual('Kansas', qs2[0].name)
|
self.assertEqual('Kansas', qs2[0].name)
|
||||||
|
@ -248,9 +259,12 @@ class GeoModelTest(unittest.TestCase):
|
||||||
# Houston and Wellington.
|
# Houston and Wellington.
|
||||||
tx = Country.objects.get(mpoly__contains=houston.point) # Query w/GEOSGeometry
|
tx = Country.objects.get(mpoly__contains=houston.point) # Query w/GEOSGeometry
|
||||||
nz = Country.objects.get(mpoly__contains=wellington.point.hex) # Query w/EWKBHEX
|
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('Texas', tx.name)
|
||||||
self.assertEqual('New Zealand', nz.name)
|
self.assertEqual('New Zealand', nz.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)
|
self.assertEqual('Kansas', ks.name)
|
||||||
|
|
||||||
# Pueblo and Oklahoma City (even though OK City is within the bounding box of Texas)
|
# Pueblo and Oklahoma City (even though OK City is within the bounding box of Texas)
|
||||||
|
@ -304,6 +318,8 @@ class GeoModelTest(unittest.TestCase):
|
||||||
|
|
||||||
# If the GeometryField SRID is -1, then we shouldn't perform any
|
# If the GeometryField SRID is -1, then we shouldn't perform any
|
||||||
# transformation if the SRID of the input geometry is different.
|
# transformation if the SRID of the input geometry is different.
|
||||||
|
# SpatiaLite does not support missing SRID values.
|
||||||
|
if not SpatialBackend.spatialite:
|
||||||
m1 = MinusOneSRID(geom=Point(17, 23, srid=4326))
|
m1 = MinusOneSRID(geom=Point(17, 23, srid=4326))
|
||||||
m1.save()
|
m1.save()
|
||||||
self.assertEqual(-1, m1.geom.srid)
|
self.assertEqual(-1, m1.geom.srid)
|
||||||
|
@ -311,6 +327,7 @@ class GeoModelTest(unittest.TestCase):
|
||||||
# Oracle does not support NULL geometries in its spatial index for
|
# Oracle does not support NULL geometries in its spatial index for
|
||||||
# some routines (e.g., SDO_GEOM.RELATE).
|
# some routines (e.g., SDO_GEOM.RELATE).
|
||||||
@no_oracle
|
@no_oracle
|
||||||
|
@no_spatialite
|
||||||
def test12_null_geometries(self):
|
def test12_null_geometries(self):
|
||||||
"Testing NULL geometry support, and the `isnull` lookup type."
|
"Testing NULL geometry support, and the `isnull` lookup type."
|
||||||
if DISABLE: return
|
if DISABLE: return
|
||||||
|
@ -334,6 +351,7 @@ class GeoModelTest(unittest.TestCase):
|
||||||
State(name='Northern Mariana Islands', poly=None).save()
|
State(name='Northern Mariana Islands', poly=None).save()
|
||||||
|
|
||||||
@no_oracle # No specific `left` or `right` operators in Oracle.
|
@no_oracle # No specific `left` or `right` operators in Oracle.
|
||||||
|
@no_spatialite # No `left` or `right` operators in SpatiaLite.
|
||||||
def test13_left_right(self):
|
def test13_left_right(self):
|
||||||
"Testing the 'left' and 'right' lookup types."
|
"Testing the 'left' and 'right' lookup types."
|
||||||
if DISABLE: return
|
if DISABLE: return
|
||||||
|
@ -398,7 +416,7 @@ class GeoModelTest(unittest.TestCase):
|
||||||
self.assertRaises(e, qs.count)
|
self.assertRaises(e, qs.count)
|
||||||
|
|
||||||
# Relate works differently for the different backends.
|
# Relate works differently for the different backends.
|
||||||
if SpatialBackend.postgis:
|
if SpatialBackend.postgis or SpatialBackend.spatialite:
|
||||||
contains_mask = 'T*T***FF*'
|
contains_mask = 'T*T***FF*'
|
||||||
within_mask = 'T*F**F***'
|
within_mask = 'T*F**F***'
|
||||||
intersects_mask = 'T********'
|
intersects_mask = 'T********'
|
||||||
|
@ -449,9 +467,12 @@ class GeoModelTest(unittest.TestCase):
|
||||||
union = union1
|
union = union1
|
||||||
self.assertEqual(True, union.equals_exact(u1, tol))
|
self.assertEqual(True, union.equals_exact(u1, tol))
|
||||||
self.assertEqual(True, union.equals_exact(u2, tol))
|
self.assertEqual(True, union.equals_exact(u2, tol))
|
||||||
|
# SpatiaLite will segfault trying to union a NULL geometry.
|
||||||
|
if not SpatialBackend.spatialite:
|
||||||
qs = City.objects.filter(name='NotACity')
|
qs = City.objects.filter(name='NotACity')
|
||||||
self.assertEqual(None, qs.unionagg(field_name='point'))
|
self.assertEqual(None, qs.unionagg(field_name='point'))
|
||||||
|
|
||||||
|
@no_spatialite # SpatiaLite does not support abstract geometry columns
|
||||||
def test18_geometryfield(self):
|
def test18_geometryfield(self):
|
||||||
"Testing GeometryField."
|
"Testing GeometryField."
|
||||||
if DISABLE: return
|
if DISABLE: return
|
||||||
|
@ -479,8 +500,12 @@ class GeoModelTest(unittest.TestCase):
|
||||||
"Testing the `centroid` GeoQuerySet method."
|
"Testing the `centroid` GeoQuerySet method."
|
||||||
if DISABLE: return
|
if DISABLE: return
|
||||||
qs = State.objects.exclude(poly__isnull=True).centroid()
|
qs = State.objects.exclude(poly__isnull=True).centroid()
|
||||||
if SpatialBackend.oracle: tol = 0.1
|
if SpatialBackend.oracle:
|
||||||
else: tol = 0.000000001
|
tol = 0.1
|
||||||
|
elif SpatialBackend.spatialite:
|
||||||
|
tol = 0.000001
|
||||||
|
else:
|
||||||
|
tol = 0.000000001
|
||||||
for s in qs:
|
for s in qs:
|
||||||
self.assertEqual(True, s.poly.centroid.equals_exact(s.centroid, tol))
|
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),
|
ref = {'New Zealand' : fromstr('POINT (174.616364 -36.100861)', srid=4326),
|
||||||
'Texas' : fromstr('POINT (-103.002434 36.500397)', 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
|
# Using GEOSGeometry to compute the reference point on surface values
|
||||||
# -- since PostGIS also uses GEOS these should be the same.
|
# -- since PostGIS also uses GEOS these should be the same.
|
||||||
ref = {'New Zealand' : Country.objects.get(name='New Zealand').mpoly.point_on_surface,
|
ref = {'New Zealand' : Country.objects.get(name='New Zealand').mpoly.point_on_surface,
|
||||||
'Texas' : Country.objects.get(name='Texas').mpoly.point_on_surface
|
'Texas' : Country.objects.get(name='Texas').mpoly.point_on_surface
|
||||||
}
|
}
|
||||||
for cntry in Country.objects.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
|
@no_oracle
|
||||||
def test21_scale(self):
|
def test21_scale(self):
|
||||||
|
@ -512,8 +542,9 @@ class GeoModelTest(unittest.TestCase):
|
||||||
for p1, p2 in zip(c.mpoly, c.scaled):
|
for p1, p2 in zip(c.mpoly, c.scaled):
|
||||||
for r1, r2 in zip(p1, p2):
|
for r1, r2 in zip(p1, p2):
|
||||||
for c1, c2 in zip(r1.coords, r2.coords):
|
for c1, c2 in zip(r1.coords, r2.coords):
|
||||||
self.assertEqual(c1[0] * xfac, c2[0])
|
# XXX The low precision is for SpatiaLite
|
||||||
self.assertEqual(c1[1] * yfac, c2[1])
|
self.assertAlmostEqual(c1[0] * xfac, c2[0], 5)
|
||||||
|
self.assertAlmostEqual(c1[1] * yfac, c2[1], 5)
|
||||||
|
|
||||||
@no_oracle
|
@no_oracle
|
||||||
def test22_translate(self):
|
def test22_translate(self):
|
||||||
|
@ -525,8 +556,9 @@ class GeoModelTest(unittest.TestCase):
|
||||||
for p1, p2 in zip(c.mpoly, c.translated):
|
for p1, p2 in zip(c.mpoly, c.translated):
|
||||||
for r1, r2 in zip(p1, p2):
|
for r1, r2 in zip(p1, p2):
|
||||||
for c1, c2 in zip(r1.coords, r2.coords):
|
for c1, c2 in zip(r1.coords, r2.coords):
|
||||||
self.assertEqual(c1[0] + xfac, c2[0])
|
# XXX The low precision is for SpatiaLite
|
||||||
self.assertEqual(c1[1] + yfac, c2[1])
|
self.assertAlmostEqual(c1[0] + xfac, c2[0], 5)
|
||||||
|
self.assertAlmostEqual(c1[1] + yfac, c2[1], 5)
|
||||||
|
|
||||||
def test23_numgeom(self):
|
def test23_numgeom(self):
|
||||||
"Testing the `num_geom` GeoQuerySet method."
|
"Testing the `num_geom` GeoQuerySet method."
|
||||||
|
@ -539,27 +571,46 @@ class GeoModelTest(unittest.TestCase):
|
||||||
if SpatialBackend.postgis: self.assertEqual(None, c.num_geom)
|
if SpatialBackend.postgis: self.assertEqual(None, c.num_geom)
|
||||||
else: self.assertEqual(1, c.num_geom)
|
else: self.assertEqual(1, c.num_geom)
|
||||||
|
|
||||||
|
@no_spatialite # SpatiaLite can only count vertices in LineStrings
|
||||||
def test24_numpoints(self):
|
def test24_numpoints(self):
|
||||||
"Testing the `num_points` GeoQuerySet method."
|
"Testing the `num_points` GeoQuerySet method."
|
||||||
if DISABLE: return
|
if DISABLE: return
|
||||||
for c in Country.objects.num_points(): self.assertEqual(c.mpoly.num_points, c.num_points)
|
for c in Country.objects.num_points():
|
||||||
if SpatialBackend.postgis:
|
self.assertEqual(c.mpoly.num_points, c.num_points)
|
||||||
|
if not SpatialBackend.oracle:
|
||||||
# Oracle cannot count vertices in Point geometries.
|
# Oracle cannot count vertices in Point geometries.
|
||||||
for c in City.objects.num_points(): self.assertEqual(1, c.num_points)
|
for c in City.objects.num_points(): self.assertEqual(1, c.num_points)
|
||||||
|
|
||||||
@no_oracle
|
|
||||||
def test25_geoset(self):
|
def test25_geoset(self):
|
||||||
"Testing the `difference`, `intersection`, `sym_difference`, and `union` GeoQuerySet methods."
|
"Testing the `difference`, `intersection`, `sym_difference`, and `union` GeoQuerySet methods."
|
||||||
if DISABLE: return
|
if DISABLE: return
|
||||||
geom = Point(5, 23)
|
geom = Point(5, 23)
|
||||||
for c in Country.objects.all().intersection(geom).difference(geom).sym_difference(geom).union(geom):
|
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)
|
self.assertEqual(c.mpoly.difference(geom), c.difference)
|
||||||
|
if not SpatialBackend.spatialite:
|
||||||
self.assertEqual(c.mpoly.intersection(geom), c.intersection)
|
self.assertEqual(c.mpoly.intersection(geom), c.intersection)
|
||||||
self.assertEqual(c.mpoly.sym_difference(geom), c.sym_difference)
|
self.assertEqual(c.mpoly.sym_difference(geom), c.sym_difference)
|
||||||
self.assertEqual(c.mpoly.union(geom), c.union)
|
self.assertEqual(c.mpoly.union(geom), c.union)
|
||||||
|
|
||||||
def test26_inherited_geofields(self):
|
def test26_inherited_geofields(self):
|
||||||
"Test GeoQuerySet methods on inherited Geometry fields."
|
"Test GeoQuerySet methods on inherited Geometry fields."
|
||||||
|
if DISABLE: return
|
||||||
# Creating a Pennsylvanian city.
|
# Creating a Pennsylvanian city.
|
||||||
mansfield = PennsylvaniaCity.objects.create(name='Mansfield', county='Tioga', point='POINT(-77.071445 41.823881)')
|
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
|
from django.contrib.gis.gdal import DataSource
|
||||||
|
|
||||||
shp_path = os.path.dirname(__file__)
|
shp_path = os.path.dirname(__file__)
|
||||||
city_shp = os.path.join(shp_path, 'cities/cities.shp')
|
city_shp = os.path.join(shp_path, '../data/cities/cities.shp')
|
||||||
co_shp = os.path.join(shp_path, 'counties/counties.shp')
|
co_shp = os.path.join(shp_path, '../data/counties/counties.shp')
|
||||||
inter_shp = os.path.join(shp_path, 'interstates/interstates.shp')
|
inter_shp = os.path.join(shp_path, '../data/interstates/interstates.shp')
|
||||||
|
|
||||||
# Dictionaries to hold what's expected in the county shapefile.
|
# Dictionaries to hold what's expected in the county shapefile.
|
||||||
NAMES = ['Bexar', 'Galveston', 'Harris', 'Honolulu', 'Pueblo']
|
NAMES = ['Bexar', 'Galveston', 'Harris', 'Honolulu', 'Pueblo']
|
||||||
|
@ -53,7 +53,6 @@ class LayerMapTest(unittest.TestCase):
|
||||||
|
|
||||||
def test02_simple_layermap(self):
|
def test02_simple_layermap(self):
|
||||||
"Test LayerMapping import of a simple point shapefile."
|
"Test LayerMapping import of a simple point shapefile."
|
||||||
|
|
||||||
# Setting up for the LayerMapping.
|
# Setting up for the LayerMapping.
|
||||||
lm = LayerMapping(City, city_shp, city_mapping)
|
lm = LayerMapping(City, city_shp, city_mapping)
|
||||||
lm.save()
|
lm.save()
|
||||||
|
@ -78,7 +77,6 @@ class LayerMapTest(unittest.TestCase):
|
||||||
|
|
||||||
def test03_layermap_strict(self):
|
def test03_layermap_strict(self):
|
||||||
"Testing the `strict` keyword, and import of a LineString shapefile."
|
"Testing the `strict` keyword, and import of a LineString shapefile."
|
||||||
|
|
||||||
# When the `strict` keyword is set an error encountered will force
|
# When the `strict` keyword is set an error encountered will force
|
||||||
# the importation to stop.
|
# the importation to stop.
|
||||||
try:
|
try:
|
||||||
|
@ -118,7 +116,6 @@ class LayerMapTest(unittest.TestCase):
|
||||||
|
|
||||||
def county_helper(self, county_feat=True):
|
def county_helper(self, county_feat=True):
|
||||||
"Helper function for ensuring the integrity of the mapped County models."
|
"Helper function for ensuring the integrity of the mapped County models."
|
||||||
|
|
||||||
for name, n, st in zip(NAMES, NUMS, STATES):
|
for name, n, st in zip(NAMES, NUMS, STATES):
|
||||||
# Should only be one record b/c of `unique` keyword.
|
# Should only be one record b/c of `unique` keyword.
|
||||||
c = County.objects.get(name=name)
|
c = County.objects.get(name=name)
|
||||||
|
@ -198,7 +195,6 @@ class LayerMapTest(unittest.TestCase):
|
||||||
|
|
||||||
def test05_test_fid_range_step(self):
|
def test05_test_fid_range_step(self):
|
||||||
"Tests the `fid_range` keyword and the `step` keyword of .save()."
|
"Tests the `fid_range` keyword and the `step` keyword of .save()."
|
||||||
|
|
||||||
# Function for clearing out all the counties before testing.
|
# Function for clearing out all the counties before testing.
|
||||||
def clear_counties(): County.objects.all().delete()
|
def clear_counties(): County.objects.all().delete()
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@ import os, unittest
|
||||||
from django.contrib.gis.geos import *
|
from django.contrib.gis.geos import *
|
||||||
from django.contrib.gis.db.backend import SpatialBackend
|
from django.contrib.gis.db.backend import SpatialBackend
|
||||||
from django.contrib.gis.db.models import F, Extent, Union
|
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 django.conf import settings
|
||||||
from models import City, Location, DirectoryEntry, Parcel
|
from models import City, Location, DirectoryEntry, Parcel
|
||||||
|
|
||||||
|
@ -62,11 +62,11 @@ class RelatedGeoModelTest(unittest.TestCase):
|
||||||
check_pnt(GEOSGeometry(wkt, srid), qs[0].location.point)
|
check_pnt(GEOSGeometry(wkt, srid), qs[0].location.point)
|
||||||
|
|
||||||
@no_mysql
|
@no_mysql
|
||||||
def test04_related_aggregate(self):
|
@no_spatialite
|
||||||
"Testing the `extent` and `unionagg` GeoQuerySet aggregates on related geographic models."
|
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
|
# 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.
|
# One for all locations, one that excludes Roswell.
|
||||||
all_extent = (-104.528060913086, 33.0583305358887,-79.4607315063477, 40.1847610473633)
|
all_extent = (-104.528060913086, 33.0583305358887,-79.4607315063477, 40.1847610473633)
|
||||||
|
@ -81,6 +81,12 @@ class RelatedGeoModelTest(unittest.TestCase):
|
||||||
for ref, e in [(all_extent, e1), (txpa_extent, e2), (all_extent, e3)]:
|
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)
|
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
|
# These are the points that are components of the aggregate geographic
|
||||||
# union that is returned.
|
# union that is returned.
|
||||||
p1 = Point(-104.528056, 33.387222)
|
p1 = Point(-104.528056, 33.387222)
|
||||||
|
|
|
@ -15,8 +15,10 @@ def no_backend(test_func, backend):
|
||||||
def no_oracle(func): return no_backend(func, 'oracle')
|
def no_oracle(func): return no_backend(func, 'oracle')
|
||||||
def no_postgis(func): return no_backend(func, 'postgresql_psycopg2')
|
def no_postgis(func): return no_backend(func, 'postgresql_psycopg2')
|
||||||
def no_mysql(func): return no_backend(func, 'mysql')
|
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.
|
# Shortcut booleans to omit only portions of tests.
|
||||||
oracle = settings.DATABASE_ENGINE == 'oracle'
|
oracle = settings.DATABASE_ENGINE == 'oracle'
|
||||||
postgis = settings.DATABASE_ENGINE == 'postgresql_psycopg2'
|
postgis = settings.DATABASE_ENGINE == 'postgresql_psycopg2'
|
||||||
mysql = settings.DATABASE_ENGINE == 'mysql'
|
mysql = settings.DATABASE_ENGINE == 'mysql'
|
||||||
|
spatialite = settings.DATABASE_ENGINE == 'sqlite3'
|
||||||
|
|
Loading…
Reference in New Issue