mirror of
https://github.com/django/django.git
synced 2025-07-06 02:39:12 +00:00
[soc2009/multidb] Fixed #11741 -- Updates to the spatial backends (e.g., re-enabled POSTGIS_VERSION setting); added geometry backend module. Patch from Justin Bronn.
git-svn-id: http://code.djangoproject.com/svn/django/branches/soc2009/multidb@11872 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
parent
11c00d632d
commit
05b4d2f67b
@ -1,5 +1,6 @@
|
|||||||
"""
|
"""
|
||||||
|
Base/mixin classes for the spatial backend database operations and the
|
||||||
|
`SpatialRefSys` model the backend.
|
||||||
"""
|
"""
|
||||||
import re
|
import re
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
@ -14,8 +15,9 @@ class BaseSpatialOperations(object):
|
|||||||
distance_functions = {}
|
distance_functions = {}
|
||||||
geometry_functions = {}
|
geometry_functions = {}
|
||||||
geometry_operators = {}
|
geometry_operators = {}
|
||||||
|
geography_operators = {}
|
||||||
|
geography_functions = {}
|
||||||
gis_terms = {}
|
gis_terms = {}
|
||||||
limited_where = {}
|
|
||||||
|
|
||||||
# Quick booleans for the type of this spatial backend, and
|
# Quick booleans for the type of this spatial backend, and
|
||||||
# an attribute for the spatial database version tuple (if applicable)
|
# an attribute for the spatial database version tuple (if applicable)
|
||||||
@ -28,6 +30,9 @@ class BaseSpatialOperations(object):
|
|||||||
# How the geometry column should be selected.
|
# How the geometry column should be selected.
|
||||||
select = None
|
select = None
|
||||||
|
|
||||||
|
# Does the spatial database have a geography type?
|
||||||
|
geography = False
|
||||||
|
|
||||||
area = False
|
area = False
|
||||||
centroid = False
|
centroid = False
|
||||||
difference = False
|
difference = False
|
||||||
@ -37,11 +42,13 @@ class BaseSpatialOperations(object):
|
|||||||
envelope = False
|
envelope = False
|
||||||
force_rhr = False
|
force_rhr = False
|
||||||
mem_size = False
|
mem_size = False
|
||||||
|
bounding_circle = False
|
||||||
num_geom = False
|
num_geom = False
|
||||||
num_points = False
|
num_points = False
|
||||||
perimeter = False
|
perimeter = False
|
||||||
perimeter3d = False
|
perimeter3d = False
|
||||||
point_on_surface = False
|
point_on_surface = False
|
||||||
|
polygonize = False
|
||||||
scale = False
|
scale = False
|
||||||
snap_to_grid = False
|
snap_to_grid = False
|
||||||
sym_difference = False
|
sym_difference = False
|
||||||
@ -67,11 +74,6 @@ class BaseSpatialOperations(object):
|
|||||||
from_text = False
|
from_text = False
|
||||||
from_wkb = False
|
from_wkb = False
|
||||||
|
|
||||||
def geo_quote_name(self, name):
|
|
||||||
if isinstance(name, unicode):
|
|
||||||
name = name.encode('ascii')
|
|
||||||
return "'%s'" % name
|
|
||||||
|
|
||||||
# Default conversion functions for aggregates; will be overridden if implemented
|
# Default conversion functions for aggregates; will be overridden if implemented
|
||||||
# for the spatial backend.
|
# for the spatial backend.
|
||||||
def convert_extent(self, box):
|
def convert_extent(self, box):
|
||||||
@ -83,6 +85,37 @@ class BaseSpatialOperations(object):
|
|||||||
def convert_geom(self, geom_val, geom_field):
|
def convert_geom(self, geom_val, geom_field):
|
||||||
raise NotImplementedError('Aggregate method not implemented for this spatial backend.')
|
raise NotImplementedError('Aggregate method not implemented for this spatial backend.')
|
||||||
|
|
||||||
|
# For quoting column values, rather than columns.
|
||||||
|
def geo_quote_name(self, name):
|
||||||
|
if isinstance(name, unicode):
|
||||||
|
name = name.encode('ascii')
|
||||||
|
return "'%s'" % name
|
||||||
|
|
||||||
|
# GeometryField operations
|
||||||
|
def geo_db_type(self, f):
|
||||||
|
"""
|
||||||
|
Returns the database column type for the geometry field on
|
||||||
|
the spatial backend.
|
||||||
|
"""
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def get_distance(self, f, value, lookup_type):
|
||||||
|
"""
|
||||||
|
Returns the distance parameters for the given geometry field,
|
||||||
|
lookup value, and lookup type.
|
||||||
|
"""
|
||||||
|
raise NotImplementedError('Distance operations not available on this spatial backend.')
|
||||||
|
|
||||||
|
def get_geom_placeholder(self, f, value):
|
||||||
|
"""
|
||||||
|
Returns the placeholder for the given geometry field with the given
|
||||||
|
value. Depending on the spatial backend, the placeholder may contain a
|
||||||
|
stored procedure call to the transformation function of the spatial
|
||||||
|
backend.
|
||||||
|
"""
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
# Spatial SQL Construction
|
||||||
def spatial_aggregate_sql(self, agg):
|
def spatial_aggregate_sql(self, agg):
|
||||||
raise NotImplementedError('Aggregate support not implemented for this spatial backend.')
|
raise NotImplementedError('Aggregate support not implemented for this spatial backend.')
|
||||||
|
|
||||||
|
@ -31,6 +31,9 @@ class MySQLOperations(DatabaseOperations, BaseSpatialOperations):
|
|||||||
|
|
||||||
gis_terms = dict([(term, None) for term in geometry_functions.keys() + ['isnull']])
|
gis_terms = dict([(term, None) for term in geometry_functions.keys() + ['isnull']])
|
||||||
|
|
||||||
|
def geo_db_type(self, f):
|
||||||
|
return f.geom_type
|
||||||
|
|
||||||
def get_geom_placeholder(self, value, srid):
|
def get_geom_placeholder(self, value, srid):
|
||||||
"""
|
"""
|
||||||
The placeholder here has to include MySQL's WKT constructor. Because
|
The placeholder here has to include MySQL's WKT constructor. Because
|
||||||
@ -43,8 +46,7 @@ class MySQLOperations(DatabaseOperations, BaseSpatialOperations):
|
|||||||
placeholder = '%s(%%s)' % self.from_text
|
placeholder = '%s(%%s)' % self.from_text
|
||||||
return placeholder
|
return placeholder
|
||||||
|
|
||||||
def spatial_lookup_sql(self, lvalue, lookup_type, value, field):
|
def spatial_lookup_sql(self, lvalue, lookup_type, value, field, qn):
|
||||||
qn = self.quote_name
|
|
||||||
alias, col, db_type = lvalue
|
alias, col, db_type = lvalue
|
||||||
|
|
||||||
geo_col = '%s.%s' % (qn(alias), qn(col))
|
geo_col = '%s.%s' % (qn(alias), qn(col))
|
||||||
|
@ -7,4 +7,4 @@ class DatabaseWrapper(OracleDatabaseWrapper):
|
|||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super(DatabaseWrapper, self).__init__(*args, **kwargs)
|
super(DatabaseWrapper, self).__init__(*args, **kwargs)
|
||||||
self.creation = OracleCreation(self)
|
self.creation = OracleCreation(self)
|
||||||
self.ops = OracleOperations()
|
self.ops = OracleOperations(self)
|
||||||
|
@ -7,7 +7,29 @@ class GeoSQLCompiler(BaseGeoSQLCompiler, SQLCompiler):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
class SQLInsertCompiler(compiler.SQLInsertCompiler, GeoSQLCompiler):
|
class SQLInsertCompiler(compiler.SQLInsertCompiler, GeoSQLCompiler):
|
||||||
pass
|
def placeholder(self, field, val):
|
||||||
|
if field is None:
|
||||||
|
# A field value of None means the value is raw.
|
||||||
|
return val
|
||||||
|
elif hasattr(field, 'get_placeholder'):
|
||||||
|
# Some fields (e.g. geo fields) need special munging before
|
||||||
|
# they can be inserted.
|
||||||
|
ph = field.get_placeholder(val, self.connection)
|
||||||
|
if ph == 'NULL':
|
||||||
|
# If the placeholder returned is 'NULL', then we need to
|
||||||
|
# to remove None from the Query parameters. Specifically,
|
||||||
|
# cx_Oracle will assume a CHAR type when a placeholder ('%s')
|
||||||
|
# is used for columns of MDSYS.SDO_GEOMETRY. Thus, we use
|
||||||
|
# 'NULL' for the value, and remove None from the query params.
|
||||||
|
# See also #10888.
|
||||||
|
param_idx = self.query.columns.index(field.column)
|
||||||
|
params = list(self.query.params)
|
||||||
|
params.pop(param_idx)
|
||||||
|
self.query.params = tuple(params)
|
||||||
|
return ph
|
||||||
|
else:
|
||||||
|
# Return the common case for the placeholder
|
||||||
|
return '%s'
|
||||||
|
|
||||||
class SQLDeleteCompiler(compiler.SQLDeleteCompiler, GeoSQLCompiler):
|
class SQLDeleteCompiler(compiler.SQLDeleteCompiler, GeoSQLCompiler):
|
||||||
pass
|
pass
|
||||||
|
@ -14,7 +14,7 @@ from django.db.backends.oracle.base import DatabaseOperations
|
|||||||
from django.contrib.gis.db.backends.base import BaseSpatialOperations
|
from django.contrib.gis.db.backends.base import BaseSpatialOperations
|
||||||
from django.contrib.gis.db.backends.oracle.adapter import OracleSpatialAdapter
|
from django.contrib.gis.db.backends.oracle.adapter import OracleSpatialAdapter
|
||||||
from django.contrib.gis.db.backends.util import SpatialFunction
|
from django.contrib.gis.db.backends.util import SpatialFunction
|
||||||
from django.contrib.gis.geometry import Geometry
|
from django.contrib.gis.geometry.backend import Geometry
|
||||||
from django.contrib.gis.measure import Distance
|
from django.contrib.gis.measure import Distance
|
||||||
|
|
||||||
class SDOOperation(SpatialFunction):
|
class SDOOperation(SpatialFunction):
|
||||||
@ -91,7 +91,7 @@ class OracleOperations(DatabaseOperations, BaseSpatialOperations):
|
|||||||
sym_difference = 'SDO_GEOM.SDO_XOR'
|
sym_difference = 'SDO_GEOM.SDO_XOR'
|
||||||
transform = 'SDO_CS.TRANSFORM'
|
transform = 'SDO_CS.TRANSFORM'
|
||||||
union = 'SDO_GEOM.SDO_UNION'
|
union = 'SDO_GEOM.SDO_UNION'
|
||||||
unionagg = 'SDO_AGGR_UNION'
|
unionagg = 'SDO_AGGR_UNION'
|
||||||
|
|
||||||
# We want to get SDO Geometries as WKT because it is much easier to
|
# We want to get SDO Geometries as WKT because it is much easier to
|
||||||
# instantiate GEOS proxies from WKT than SDO_GEOMETRY(...) strings.
|
# instantiate GEOS proxies from WKT than SDO_GEOMETRY(...) strings.
|
||||||
@ -128,6 +128,10 @@ class OracleOperations(DatabaseOperations, BaseSpatialOperations):
|
|||||||
gis_terms += geometry_functions.keys()
|
gis_terms += geometry_functions.keys()
|
||||||
gis_terms = dict([(term, None) for term in gis_terms])
|
gis_terms = dict([(term, None) for term in gis_terms])
|
||||||
|
|
||||||
|
def __init__(self, connection):
|
||||||
|
super(OracleOperations, self).__init__()
|
||||||
|
self.connection = connection
|
||||||
|
|
||||||
def convert_extent(self, clob):
|
def convert_extent(self, clob):
|
||||||
if clob:
|
if clob:
|
||||||
# Generally, Oracle returns a polygon for the extent -- however,
|
# Generally, Oracle returns a polygon for the extent -- however,
|
||||||
@ -156,7 +160,40 @@ class OracleOperations(DatabaseOperations, BaseSpatialOperations):
|
|||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def get_geom_placeholder(self, value, srid):
|
def geo_db_type(self, f):
|
||||||
|
"""
|
||||||
|
Returns the geometry database type for Oracle. Unlike other spatial
|
||||||
|
backends, no stored procedure is necessary and it's the same for all
|
||||||
|
geometry types.
|
||||||
|
"""
|
||||||
|
return 'MDSYS.SDO_GEOMETRY'
|
||||||
|
|
||||||
|
def get_distance(self, f, value, lookup_type):
|
||||||
|
"""
|
||||||
|
Returns the distance parameters given the value and the lookup type.
|
||||||
|
On Oracle, geometry columns with a geodetic coordinate system behave
|
||||||
|
implicitly like a geography column, and thus meters will be used as
|
||||||
|
the distance parameter on them.
|
||||||
|
"""
|
||||||
|
if not value:
|
||||||
|
return []
|
||||||
|
value = value[0]
|
||||||
|
if isinstance(value, Distance):
|
||||||
|
if f.geodetic(self.connection):
|
||||||
|
dist_param = value.m
|
||||||
|
else:
|
||||||
|
dist_param = getattr(value, Distance.unit_attname(f.units_name(self.connection)))
|
||||||
|
else:
|
||||||
|
dist_param = value
|
||||||
|
|
||||||
|
# dwithin lookups on oracle require a special string parameter
|
||||||
|
# that starts with "distance=".
|
||||||
|
if lookup_type == 'dwithin':
|
||||||
|
dist_param = 'distance=%s' % dist_param
|
||||||
|
|
||||||
|
return [dist_param]
|
||||||
|
|
||||||
|
def get_geom_placeholder(self, f, value):
|
||||||
"""
|
"""
|
||||||
Provides a proper substitution value for Geometries that are not in the
|
Provides a proper substitution value for Geometries that are not in the
|
||||||
SRID of the field. Specifically, this routine will substitute in the
|
SRID of the field. Specifically, this routine will substitute in the
|
||||||
@ -165,26 +202,25 @@ class OracleOperations(DatabaseOperations, BaseSpatialOperations):
|
|||||||
if value is None:
|
if value is None:
|
||||||
return 'NULL'
|
return 'NULL'
|
||||||
|
|
||||||
def transform_value(value, srid):
|
def transform_value(val, srid):
|
||||||
return value.srid != srid
|
return val.srid != srid
|
||||||
|
|
||||||
if hasattr(value, 'expression'):
|
if hasattr(value, 'expression'):
|
||||||
if transform_value(value, srid):
|
if transform_value(value, f.srid):
|
||||||
placeholder = '%s(%%s, %s)' % (self.transform, srid)
|
placeholder = '%s(%%s, %s)' % (self.transform, f.srid)
|
||||||
else:
|
else:
|
||||||
placeholder = '%s'
|
placeholder = '%s'
|
||||||
# No geometry value used for F expression, substitue in
|
# No geometry value used for F expression, substitue in
|
||||||
# the column name instead.
|
# the column name instead.
|
||||||
return placeholder % '%s.%s' % tuple(map(self.quote_name, value.cols[value.expression]))
|
return placeholder % '%s.%s' % tuple(map(self.quote_name, value.cols[value.expression]))
|
||||||
else:
|
else:
|
||||||
if transform_value(value, srid):
|
if transform_value(value, f.srid):
|
||||||
return '%s(SDO_GEOMETRY(%%s, %s), %s)' % (self.transform, value.srid, srid)
|
return '%s(SDO_GEOMETRY(%%s, %s), %s)' % (self.transform, value.srid, f.srid)
|
||||||
else:
|
else:
|
||||||
return 'SDO_GEOMETRY(%%s, %s)' % srid
|
return 'SDO_GEOMETRY(%%s, %s)' % f.srid
|
||||||
|
|
||||||
def spatial_lookup_sql(self, lvalue, lookup_type, value, field):
|
def spatial_lookup_sql(self, lvalue, lookup_type, value, field, qn):
|
||||||
"Returns the SQL WHERE clause for use in Oracle spatial SQL construction."
|
"Returns the SQL WHERE clause for use in Oracle spatial SQL construction."
|
||||||
qn = self.quote_name
|
|
||||||
alias, col, db_type = lvalue
|
alias, col, db_type = lvalue
|
||||||
|
|
||||||
# Getting the quoted table name as `geo_col`.
|
# Getting the quoted table name as `geo_col`.
|
||||||
@ -214,15 +250,15 @@ class OracleOperations(DatabaseOperations, BaseSpatialOperations):
|
|||||||
if lookup_type == 'relate':
|
if lookup_type == 'relate':
|
||||||
# The SDORelate class handles construction for these queries,
|
# The SDORelate class handles construction for these queries,
|
||||||
# and verifies the mask argument.
|
# and verifies the mask argument.
|
||||||
return sdo_op(value[1]).as_sql(geo_col, self.get_geom_placeholder(geom, field.srid))
|
return sdo_op(value[1]).as_sql(geo_col, self.get_geom_placeholder(field, geom))
|
||||||
else:
|
else:
|
||||||
# Otherwise, just call the `as_sql` method on the SDOOperation instance.
|
# Otherwise, just call the `as_sql` method on the SDOOperation instance.
|
||||||
return sdo_op.as_sql(geo_col, self.get_geom_placeholder(geom, field.srid))
|
return sdo_op.as_sql(geo_col, self.get_geom_placeholder(field, geom))
|
||||||
else:
|
else:
|
||||||
# Lookup info is a SDOOperation instance, whose `as_sql` method returns
|
# Lookup info is a SDOOperation instance, whose `as_sql` method returns
|
||||||
# the SQL necessary for the geometry function call. For example:
|
# the SQL necessary for the geometry function call. For example:
|
||||||
# SDO_CONTAINS("geoapp_country"."poly", SDO_GEOMTRY('POINT(5 23)', 4326)) = 'TRUE'
|
# SDO_CONTAINS("geoapp_country"."poly", SDO_GEOMTRY('POINT(5 23)', 4326)) = 'TRUE'
|
||||||
return lookup_info.as_sql(geo_col, self.get_geom_placeholder(value, field.srid))
|
return lookup_info.as_sql(geo_col, self.get_geom_placeholder(field, value))
|
||||||
elif lookup_type == 'isnull':
|
elif lookup_type == 'isnull':
|
||||||
# Handling 'isnull' lookup type
|
# Handling 'isnull' lookup type
|
||||||
return "%s IS %sNULL" % (geo_col, (not value and 'NOT ' or ''))
|
return "%s IS %sNULL" % (geo_col, (not value and 'NOT ' or ''))
|
||||||
|
@ -16,32 +16,43 @@ class PostGISCreation(DatabaseCreation):
|
|||||||
qn = self.connection.ops.quote_name
|
qn = self.connection.ops.quote_name
|
||||||
db_table = model._meta.db_table
|
db_table = model._meta.db_table
|
||||||
|
|
||||||
output.append(style.SQL_KEYWORD('SELECT ') +
|
if f.geography:
|
||||||
style.SQL_TABLE('AddGeometryColumn') + '(' +
|
# Geogrophy columns are created normally.
|
||||||
style.SQL_TABLE(gqn(db_table)) + ', ' +
|
pass
|
||||||
style.SQL_FIELD(gqn(f.column)) + ', ' +
|
else:
|
||||||
style.SQL_FIELD(str(f.srid)) + ', ' +
|
# Geometry columns are created by `AddGeometryColumn`
|
||||||
style.SQL_COLTYPE(gqn(f.geom_type)) + ', ' +
|
# stored procedure.
|
||||||
style.SQL_KEYWORD(str(f.dim)) + ');')
|
output.append(style.SQL_KEYWORD('SELECT ') +
|
||||||
|
style.SQL_TABLE('AddGeometryColumn') + '(' +
|
||||||
|
style.SQL_TABLE(gqn(db_table)) + ', ' +
|
||||||
|
style.SQL_FIELD(gqn(f.column)) + ', ' +
|
||||||
|
style.SQL_FIELD(str(f.srid)) + ', ' +
|
||||||
|
style.SQL_COLTYPE(gqn(f.geom_type)) + ', ' +
|
||||||
|
style.SQL_KEYWORD(str(f.dim)) + ');')
|
||||||
|
|
||||||
if not f.null:
|
if not f.null:
|
||||||
# Add a NOT NULL constraint to the field
|
# Add a NOT NULL constraint to the field
|
||||||
output.append(style.SQL_KEYWORD('ALTER TABLE ') +
|
output.append(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(f.column)) +
|
style.SQL_FIELD(qn(f.column)) +
|
||||||
style.SQL_KEYWORD(' SET NOT NULL') + ';')
|
style.SQL_KEYWORD(' SET NOT NULL') + ';')
|
||||||
|
|
||||||
|
|
||||||
if f.spatial_index:
|
if f.spatial_index:
|
||||||
|
# Spatial indexes created the same way for both Geometry and
|
||||||
|
# Geography columns
|
||||||
|
if f.geography:
|
||||||
|
index_opts = ''
|
||||||
|
else:
|
||||||
|
index_opts = ' ' + style.SQL_KEYWORD(self.geom_index_opts)
|
||||||
output.append(style.SQL_KEYWORD('CREATE INDEX ') +
|
output.append(style.SQL_KEYWORD('CREATE INDEX ') +
|
||||||
style.SQL_TABLE(qn('%s_%s_id' % (db_table, f.column))) +
|
style.SQL_TABLE(qn('%s_%s_id' % (db_table, f.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(self.geom_index_type) + ' ( ' +
|
style.SQL_COLTYPE(self.geom_index_type) + ' ( ' +
|
||||||
style.SQL_FIELD(qn(f.column)) + ' ' +
|
style.SQL_FIELD(qn(f.column)) + index_opts + ' );')
|
||||||
style.SQL_KEYWORD(self.geom_index_opts) + ' );')
|
|
||||||
return output
|
return output
|
||||||
|
|
||||||
def sql_table_creation_suffix(self):
|
def sql_table_creation_suffix(self):
|
||||||
|
@ -1,12 +1,15 @@
|
|||||||
import re
|
import re
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
|
|
||||||
from django.db.backends.postgresql.operations import DatabaseOperations
|
from django.conf import settings
|
||||||
from django.contrib.gis.db.backends.base import BaseSpatialOperations
|
from django.contrib.gis.db.backends.base import BaseSpatialOperations
|
||||||
from django.contrib.gis.db.backends.util import SpatialOperation, SpatialFunction
|
from django.contrib.gis.db.backends.util import SpatialOperation, SpatialFunction
|
||||||
from django.contrib.gis.db.backends.postgis.adapter import PostGISAdapter
|
from django.contrib.gis.db.backends.postgis.adapter import PostGISAdapter
|
||||||
from django.contrib.gis.geometry import Geometry
|
from django.contrib.gis.geometry.backend import Geometry
|
||||||
from django.contrib.gis.measure import Distance
|
from django.contrib.gis.measure import Distance
|
||||||
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
|
from django.db.backends.postgresql.operations import DatabaseOperations
|
||||||
|
from django.db.backends.postgresql_psycopg2.base import Database
|
||||||
|
|
||||||
#### Classes used in constructing PostGIS spatial SQL ####
|
#### Classes used in constructing PostGIS spatial SQL ####
|
||||||
class PostGISOperator(SpatialOperation):
|
class PostGISOperator(SpatialOperation):
|
||||||
@ -68,23 +71,48 @@ class PostGISOperations(DatabaseOperations, BaseSpatialOperations):
|
|||||||
super(PostGISOperations, self).__init__(connection)
|
super(PostGISOperations, self).__init__(connection)
|
||||||
|
|
||||||
# Trying to get the PostGIS version because the function
|
# Trying to get the PostGIS version because the function
|
||||||
# signatures will depend on the version used.
|
# signatures will depend on the version used. The cost
|
||||||
|
# here is a database query to determine the version, which
|
||||||
|
# can be mitigated by setting `POSTGIS_VERSION` with a 3-tuple
|
||||||
|
# comprising user-supplied values for the major, minor, and
|
||||||
|
# subminor revision of PostGIS.
|
||||||
try:
|
try:
|
||||||
vtup = self.postgis_version_tuple()
|
if hasattr(settings, 'POSTGIS_VERSION'):
|
||||||
version = vtup[1:]
|
vtup = settings.POSTGIS_VERSION
|
||||||
|
if len(vtup) == 3:
|
||||||
|
# The user-supplied PostGIS version.
|
||||||
|
version = vtup
|
||||||
|
else:
|
||||||
|
# This was the old documented way, but it's stupid to
|
||||||
|
# include the string.
|
||||||
|
version = vtup[1:4]
|
||||||
|
else:
|
||||||
|
vtup = self.postgis_version_tuple()
|
||||||
|
version = vtup[1:]
|
||||||
|
|
||||||
|
# Getting the prefix -- even though we don't officially support
|
||||||
|
# PostGIS 1.2 anymore, keeping it anyway in case a prefix change
|
||||||
|
# for something else is necessary.
|
||||||
if version >= (1, 2, 2):
|
if version >= (1, 2, 2):
|
||||||
prefix = 'ST_'
|
prefix = 'ST_'
|
||||||
else:
|
else:
|
||||||
prefix = ''
|
prefix = ''
|
||||||
|
|
||||||
self.geom_func_prefix = prefix
|
self.geom_func_prefix = prefix
|
||||||
self.spatial_version = version
|
self.spatial_version = version
|
||||||
|
except Database.ProgrammingError:
|
||||||
|
raise ImproperlyConfigured('Cannot determine PostGIS version for database "%s". '
|
||||||
|
'GeoDjango requires at least PostGIS version 1.3. '
|
||||||
|
'Was the database created from a spatial database '
|
||||||
|
'template?' % self.connection.settings_dict['NAME']
|
||||||
|
)
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
# TODO: Plain raising right now.
|
# TODO: Raise helpful exceptions as they become known.
|
||||||
raise
|
raise
|
||||||
|
|
||||||
# PostGIS-specific operators. The commented descriptions of these
|
# PostGIS-specific operators. The commented descriptions of these
|
||||||
# operators come from Section 7.6 of the PostGIS 1.4 documentation.
|
# operators come from Section 7.6 of the PostGIS 1.4 documentation.
|
||||||
self.spatial_operators = {
|
self.geometry_operators = {
|
||||||
# The "&<" operator returns true if A's bounding box overlaps or
|
# The "&<" operator returns true if A's bounding box overlaps or
|
||||||
# is to the left of B's bounding box.
|
# is to the left of B's bounding box.
|
||||||
'overlaps_left' : PostGISOperator('&<'),
|
'overlaps_left' : PostGISOperator('&<'),
|
||||||
@ -166,19 +194,6 @@ class PostGISOperations(DatabaseOperations, BaseSpatialOperations):
|
|||||||
# Adding the distance functions to the geometries lookup.
|
# Adding the distance functions to the geometries lookup.
|
||||||
self.geometry_functions.update(self.distance_functions)
|
self.geometry_functions.update(self.distance_functions)
|
||||||
|
|
||||||
# ST_ContainsProperly and GeoHash serialization added in 1.4.
|
|
||||||
if version >= (1, 4, 0):
|
|
||||||
GEOHASH = 'ST_GeoHash'
|
|
||||||
self.geometry_functions['contains_properly'] = PostGISFunction(prefix, 'ContainsProperly')
|
|
||||||
else:
|
|
||||||
GEOHASH = False
|
|
||||||
|
|
||||||
# Creating a dictionary lookup of all GIS terms for PostGIS.
|
|
||||||
gis_terms = ['isnull']
|
|
||||||
gis_terms += self.spatial_operators.keys()
|
|
||||||
gis_terms += self.geometry_functions.keys()
|
|
||||||
self.gis_terms = dict([(term, None) for term in gis_terms])
|
|
||||||
|
|
||||||
# The union aggregate and topology operation use the same signature
|
# The union aggregate and topology operation use the same signature
|
||||||
# in versions 1.3+.
|
# in versions 1.3+.
|
||||||
if version < (1, 3, 0):
|
if version < (1, 3, 0):
|
||||||
@ -194,7 +209,40 @@ class PostGISOperations(DatabaseOperations, BaseSpatialOperations):
|
|||||||
else:
|
else:
|
||||||
GEOJSON = prefix + 'AsGeoJson'
|
GEOJSON = prefix + 'AsGeoJson'
|
||||||
|
|
||||||
|
# ST_ContainsProperly ST_MakeLine, and ST_GeoHash added in 1.4.
|
||||||
|
if version >= (1, 4, 0):
|
||||||
|
GEOHASH = 'ST_GeoHash'
|
||||||
|
MAKELINE = 'ST_MakeLine'
|
||||||
|
BOUNDINGCIRCLE = 'ST_MinimumBoundingCircle'
|
||||||
|
self.geometry_functions['contains_properly'] = PostGISFunction(prefix, 'ContainsProperly')
|
||||||
|
else:
|
||||||
|
GEOHASH, MAKELINE, BOUNDINGCIRCLE = False, False, False
|
||||||
|
|
||||||
|
# Geography type support added in 1.5.
|
||||||
|
if version >= (1, 5, 0):
|
||||||
|
self.geography = True
|
||||||
|
# Only a subset of the operators and functions are available
|
||||||
|
# for the geography type.
|
||||||
|
self.geography_functions = self.distance_functions.copy()
|
||||||
|
self.geography_functions.update({
|
||||||
|
'coveredby' : self.geometry_functions['coveredby'],
|
||||||
|
'covers' : self.geometry_functions['covers'],
|
||||||
|
'intersects' : self.geometry_functions['intersects'],
|
||||||
|
})
|
||||||
|
self.geography_operators = {
|
||||||
|
'bboverlaps' : PostGISOperator('&&'),
|
||||||
|
'exact' : PostGISOperator('~='),
|
||||||
|
'same_as' : PostGISOperator('~='),
|
||||||
|
}
|
||||||
|
|
||||||
|
# Creating a dictionary lookup of all GIS terms for PostGIS.
|
||||||
|
gis_terms = ['isnull']
|
||||||
|
gis_terms += self.geometry_operators.keys()
|
||||||
|
gis_terms += self.geometry_functions.keys()
|
||||||
|
self.gis_terms = dict([(term, None) for term in gis_terms])
|
||||||
|
|
||||||
self.area = prefix + 'Area'
|
self.area = prefix + 'Area'
|
||||||
|
self.bounding_circle = BOUNDINGCIRCLE
|
||||||
self.centroid = prefix + 'Centroid'
|
self.centroid = prefix + 'Centroid'
|
||||||
self.collect = prefix + 'Collect'
|
self.collect = prefix + 'Collect'
|
||||||
self.difference = prefix + 'Difference'
|
self.difference = prefix + 'Difference'
|
||||||
@ -212,13 +260,14 @@ class PostGISOperations(DatabaseOperations, BaseSpatialOperations):
|
|||||||
self.length = prefix + 'Length'
|
self.length = prefix + 'Length'
|
||||||
self.length3d = prefix + 'Length3D'
|
self.length3d = prefix + 'Length3D'
|
||||||
self.length_spheroid = prefix + 'length_spheroid'
|
self.length_spheroid = prefix + 'length_spheroid'
|
||||||
self.makeline = prefix + 'MakeLine'
|
self.makeline = MAKELINE
|
||||||
self.mem_size = prefix + 'mem_size'
|
self.mem_size = prefix + 'mem_size'
|
||||||
self.num_geom = prefix + 'NumGeometries'
|
self.num_geom = prefix + 'NumGeometries'
|
||||||
self.num_points =prefix + 'npoints'
|
self.num_points =prefix + 'npoints'
|
||||||
self.perimeter = prefix + 'Perimeter'
|
self.perimeter = prefix + 'Perimeter'
|
||||||
self.perimeter3d = prefix + 'Perimeter3D'
|
self.perimeter3d = prefix + 'Perimeter3D'
|
||||||
self.point_on_surface = prefix + 'PointOnSurface'
|
self.point_on_surface = prefix + 'PointOnSurface'
|
||||||
|
self.polygonize = prefix + 'Polygonize'
|
||||||
self.scale = prefix + 'Scale'
|
self.scale = prefix + 'Scale'
|
||||||
self.snap_to_grid = prefix + 'SnapToGrid'
|
self.snap_to_grid = prefix + 'SnapToGrid'
|
||||||
self.svg = prefix + 'AsSVG'
|
self.svg = prefix + 'AsSVG'
|
||||||
@ -237,16 +286,22 @@ class PostGISOperations(DatabaseOperations, BaseSpatialOperations):
|
|||||||
return agg_name in self.valid_aggregates
|
return agg_name in self.valid_aggregates
|
||||||
|
|
||||||
def convert_extent(self, box):
|
def convert_extent(self, box):
|
||||||
# Box text will be something like "BOX(-90.0 30.0, -85.0 40.0)";
|
"""
|
||||||
# parsing out and returning as a 4-tuple.
|
Returns a 4-tuple extent for the `Extent` aggregate by converting
|
||||||
|
the bounding box text returned by PostGIS (`box` argument), for
|
||||||
|
example: "BOX(-90.0 30.0, -85.0 40.0)".
|
||||||
|
"""
|
||||||
ll, ur = box[4:-1].split(',')
|
ll, ur = box[4:-1].split(',')
|
||||||
xmin, ymin = map(float, ll.split())
|
xmin, ymin = map(float, ll.split())
|
||||||
xmax, ymax = map(float, ur.split())
|
xmax, ymax = map(float, ur.split())
|
||||||
return (xmin, ymin, xmax, ymax)
|
return (xmin, ymin, xmax, ymax)
|
||||||
|
|
||||||
def convert_extent3d(self, box3d):
|
def convert_extent3d(self, box3d):
|
||||||
# Box text will be something like "BOX3D(-90.0 30.0 1, -85.0 40.0 2)";
|
"""
|
||||||
# parsing out and returning as a 4-tuple.
|
Returns a 6-tuple extent for the `Extent3D` aggregate by converting
|
||||||
|
the 3d bounding-box text returnded by PostGIS (`box3d` argument), for
|
||||||
|
example: "BOX3D(-90.0 30.0 1, -85.0 40.0 2)".
|
||||||
|
"""
|
||||||
ll, ur = box3d[6:-1].split(',')
|
ll, ur = box3d[6:-1].split(',')
|
||||||
xmin, ymin, zmin = map(float, ll.split())
|
xmin, ymin, zmin = map(float, ll.split())
|
||||||
xmax, ymax, zmax = map(float, ur.split())
|
xmax, ymax, zmax = map(float, ur.split())
|
||||||
@ -261,17 +316,78 @@ class PostGISOperations(DatabaseOperations, BaseSpatialOperations):
|
|||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def get_geom_placeholder(self, value, srid):
|
def geo_db_type(self, f):
|
||||||
|
"""
|
||||||
|
Return the database field type for the given geometry field.
|
||||||
|
Typically this is `None` because geometry columns are added via
|
||||||
|
the `AddGeometryColumn` stored procedure, unless the field
|
||||||
|
has been specified to be of geography type instead.
|
||||||
|
"""
|
||||||
|
if f.geography:
|
||||||
|
if not self.geography:
|
||||||
|
raise NotImplementedError('PostGIS 1.5 required for geography column support.')
|
||||||
|
|
||||||
|
if f.srid != 4326:
|
||||||
|
raise NotImplementedError('PostGIS 1.5 supports geography columns '
|
||||||
|
'only with an SRID of 4326.')
|
||||||
|
|
||||||
|
return 'geography(%s,%d)'% (f.geom_type, f.srid)
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_distance(self, f, dist_val, lookup_type):
|
||||||
|
"""
|
||||||
|
Retrieve the distance parameters for the given geometry field,
|
||||||
|
distance lookup value, and the distance lookup type.
|
||||||
|
|
||||||
|
This is the most complex implementation of the spatial backends due to
|
||||||
|
what is supported on geodetic geometry columns vs. what's available on
|
||||||
|
projected geometry columns. In addition, it has to take into account
|
||||||
|
the newly introduced geography column type introudced in PostGIS 1.5.
|
||||||
|
"""
|
||||||
|
# Getting the distance parameter and any options.
|
||||||
|
if len(dist_val) == 1:
|
||||||
|
value, option = dist_val[0], None
|
||||||
|
else:
|
||||||
|
value, option = dist_val
|
||||||
|
|
||||||
|
# Shorthand boolean flags.
|
||||||
|
geodetic = f.geodetic(self.connection)
|
||||||
|
geography = f.geography and self.geography
|
||||||
|
|
||||||
|
if isinstance(value, Distance):
|
||||||
|
if geography:
|
||||||
|
dist_param = value.m
|
||||||
|
elif geodetic:
|
||||||
|
if lookup_type == 'dwithin':
|
||||||
|
raise ValueError('Only numeric values of degree units are '
|
||||||
|
'allowed on geographic DWithin queries.')
|
||||||
|
dist_param = value.m
|
||||||
|
else:
|
||||||
|
dist_param = getattr(value, Distance.unit_attname(f.units_name(self.connection)))
|
||||||
|
else:
|
||||||
|
# Assuming the distance is in the units of the field.
|
||||||
|
dist_param = value
|
||||||
|
|
||||||
|
if (not geography and geodetic and lookup_type != 'dwithin'
|
||||||
|
and option == 'spheroid'):
|
||||||
|
# using distance_spheroid requires the spheroid of the field as
|
||||||
|
# a parameter.
|
||||||
|
return [f._spheroid, dist_param]
|
||||||
|
else:
|
||||||
|
return [dist_param]
|
||||||
|
|
||||||
|
def get_geom_placeholder(self, f, value):
|
||||||
"""
|
"""
|
||||||
Provides a proper substitution value for Geometries that are not in the
|
Provides a proper substitution value for Geometries that are not in the
|
||||||
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 == srid:
|
if value is None or value.srid == f.srid:
|
||||||
placeholder = '%s'
|
placeholder = '%s'
|
||||||
else:
|
else:
|
||||||
# Adding Transform() to the SQL placeholder.
|
# Adding Transform() to the SQL placeholder.
|
||||||
placeholder = '%s(%%s, %s)' % (self.transform, srid)
|
placeholder = '%s(%%s, %s)' % (self.transform, f.srid)
|
||||||
|
|
||||||
if hasattr(value, 'expression'):
|
if hasattr(value, 'expression'):
|
||||||
# If this is an F expression, then we don't really want
|
# If this is an F expression, then we don't really want
|
||||||
@ -290,7 +406,7 @@ class PostGISOperations(DatabaseOperations, BaseSpatialOperations):
|
|||||||
cursor.execute('SELECT %s()' % func)
|
cursor.execute('SELECT %s()' % func)
|
||||||
row = cursor.fetchone()
|
row = cursor.fetchone()
|
||||||
except:
|
except:
|
||||||
# TODO: raise helpful exception here.
|
# Responsibility of callers to perform error handling.
|
||||||
raise
|
raise
|
||||||
finally:
|
finally:
|
||||||
cursor.close()
|
cursor.close()
|
||||||
@ -334,32 +450,42 @@ class PostGISOperations(DatabaseOperations, BaseSpatialOperations):
|
|||||||
|
|
||||||
return (version, major, minor1, minor2)
|
return (version, major, minor1, minor2)
|
||||||
|
|
||||||
def num_params(self, lookup_type, val):
|
def num_params(self, lookup_type, num_param):
|
||||||
def exactly_two(val): return val == 2
|
"""
|
||||||
def two_to_three(val): return val >= 2 and val <=3
|
Helper routine that returns a boolean indicating whether the number of
|
||||||
|
parameters is correct for the lookup type.
|
||||||
|
"""
|
||||||
|
def exactly_two(np): return np == 2
|
||||||
|
def two_to_three(np): return np >= 2 and np <=3
|
||||||
if (lookup_type in self.distance_functions and
|
if (lookup_type in self.distance_functions and
|
||||||
lookup_type != 'dwithin'):
|
lookup_type != 'dwithin'):
|
||||||
return two_to_three(val)
|
return two_to_three(num_param)
|
||||||
else:
|
else:
|
||||||
return exactly_two(val)
|
return exactly_two(num_param)
|
||||||
|
|
||||||
def spatial_lookup_sql(self, lvalue, lookup_type, value, field):
|
def spatial_lookup_sql(self, lvalue, lookup_type, value, field, qn):
|
||||||
"""
|
"""
|
||||||
Constructs spatial SQL from the given lookup value tuple a
|
Constructs spatial SQL from the given lookup value tuple a
|
||||||
(alias, col, db_type), the lookup type string, lookup value, and
|
(alias, col, db_type), the lookup type string, lookup value, and
|
||||||
the geometry field.
|
the geometry field.
|
||||||
"""
|
"""
|
||||||
qn = self.quote_name
|
|
||||||
alias, col, db_type = lvalue
|
alias, col, db_type = lvalue
|
||||||
|
|
||||||
# Getting the quoted geometry column.
|
# Getting the quoted geometry column.
|
||||||
geo_col = '%s.%s' % (qn(alias), qn(col))
|
geo_col = '%s.%s' % (qn(alias), qn(col))
|
||||||
|
|
||||||
if lookup_type in self.spatial_operators:
|
if lookup_type in self.geometry_operators:
|
||||||
|
if field.geography and not lookup_type in self.geography_operators:
|
||||||
|
raise ValueError('PostGIS geography does not support the '
|
||||||
|
'"%s" lookup.' % lookup_type)
|
||||||
# Handling a PostGIS operator.
|
# Handling a PostGIS operator.
|
||||||
op = self.spatial_operators[lookup_type]
|
op = self.geometry_operators[lookup_type]
|
||||||
return op.as_sql(geo_col, self.get_geom_placeholder(value, field.srid))
|
return op.as_sql(geo_col, self.get_geom_placeholder(field, value))
|
||||||
elif lookup_type in self.geometry_functions:
|
elif lookup_type in self.geometry_functions:
|
||||||
|
if field.geography and not lookup_type in self.geography_functions:
|
||||||
|
raise ValueError('PostGIS geography type does not support the '
|
||||||
|
'"%s" lookup.' % lookup_type)
|
||||||
|
|
||||||
# See if a PostGIS geometry function matches the lookup type.
|
# See if a PostGIS geometry function matches the lookup type.
|
||||||
tmp = self.geometry_functions[lookup_type]
|
tmp = self.geometry_functions[lookup_type]
|
||||||
|
|
||||||
@ -392,7 +518,7 @@ class PostGISOperations(DatabaseOperations, BaseSpatialOperations):
|
|||||||
if lookup_type == 'relate':
|
if lookup_type == 'relate':
|
||||||
op = op(self.geom_func_prefix, value[1])
|
op = op(self.geom_func_prefix, value[1])
|
||||||
elif lookup_type in self.distance_functions and lookup_type != 'dwithin':
|
elif lookup_type in self.distance_functions and lookup_type != 'dwithin':
|
||||||
if field.geodetic(self.connection):
|
if not field.geography and field.geodetic(self.connection):
|
||||||
# Geodetic distances are only availble from Points to PointFields.
|
# Geodetic distances are only availble from Points to PointFields.
|
||||||
if field.geom_type != 'POINT':
|
if field.geom_type != 'POINT':
|
||||||
raise ValueError('PostGIS spherical operations are only valid on PointFields.')
|
raise ValueError('PostGIS spherical operations are only valid on PointFields.')
|
||||||
@ -412,7 +538,7 @@ class PostGISOperations(DatabaseOperations, BaseSpatialOperations):
|
|||||||
geom = value
|
geom = value
|
||||||
|
|
||||||
# Calling the `as_sql` function on the operation instance.
|
# Calling the `as_sql` function on the operation instance.
|
||||||
return op.as_sql(geo_col, self.get_geom_placeholder(geom, field.srid))
|
return op.as_sql(geo_col, self.get_geom_placeholder(field, geom))
|
||||||
|
|
||||||
elif lookup_type == 'isnull':
|
elif lookup_type == 'isnull':
|
||||||
# Handling 'isnull' lookup type
|
# Handling 'isnull' lookup type
|
||||||
|
@ -4,7 +4,7 @@ from decimal import Decimal
|
|||||||
from django.contrib.gis.db.backends.base import BaseSpatialOperations
|
from django.contrib.gis.db.backends.base import BaseSpatialOperations
|
||||||
from django.contrib.gis.db.backends.util import SpatialOperation, SpatialFunction
|
from django.contrib.gis.db.backends.util import SpatialOperation, SpatialFunction
|
||||||
from django.contrib.gis.db.backends.spatialite.adapter import SpatiaLiteAdapter
|
from django.contrib.gis.db.backends.spatialite.adapter import SpatiaLiteAdapter
|
||||||
from django.contrib.gis.geometry import Geometry
|
from django.contrib.gis.geometry.backend import Geometry
|
||||||
from django.contrib.gis.measure import Distance
|
from django.contrib.gis.measure import Distance
|
||||||
from django.core.exceptions import ImproperlyConfigured
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
from django.db.backends.sqlite3.base import DatabaseOperations
|
from django.db.backends.sqlite3.base import DatabaseOperations
|
||||||
@ -119,11 +119,17 @@ class SpatiaLiteOperations(DatabaseOperations, BaseSpatialOperations):
|
|||||||
try:
|
try:
|
||||||
vtup = self.spatialite_version_tuple()
|
vtup = self.spatialite_version_tuple()
|
||||||
version = vtup[1:]
|
version = vtup[1:]
|
||||||
self.spatial_version = version
|
|
||||||
if version < (2, 3, 1):
|
if version < (2, 3, 1):
|
||||||
raise Exception('GeoDjango only supports SpatiaLite versions 2.3.1+')
|
raise ImproperlyConfigured('GeoDjango only supports SpatiaLite versions '
|
||||||
except Exception, e:
|
'2.3.1 and above')
|
||||||
|
self.spatial_version = version
|
||||||
|
except ImproperlyConfigured:
|
||||||
raise
|
raise
|
||||||
|
except Exception, msg:
|
||||||
|
raise ImproperlyConfigured('Cannot determine the SpatiaLite version for the "%s" '
|
||||||
|
'database (error was "%s"). Was the SpatiaLite initialization '
|
||||||
|
'SQL loaded on this database?' %
|
||||||
|
(self.connection.settings_dict['NAME'], msg))
|
||||||
|
|
||||||
# Creating the GIS terms dictionary.
|
# Creating the GIS terms dictionary.
|
||||||
gis_terms = ['isnull']
|
gis_terms = ['isnull']
|
||||||
@ -147,7 +153,36 @@ class SpatiaLiteOperations(DatabaseOperations, BaseSpatialOperations):
|
|||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def get_geom_placeholder(self, value, srid):
|
def geo_db_type(self, f):
|
||||||
|
"""
|
||||||
|
Returns None because geometry columnas are added via the
|
||||||
|
`AddGeometryColumn` stored procedure on SpatiaLite.
|
||||||
|
"""
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_distance(self, f, value, lookup_type):
|
||||||
|
"""
|
||||||
|
Returns the distance parameters for the given geometry field,
|
||||||
|
lookup value, and lookup type. SpatiaLite only supports regular
|
||||||
|
cartesian-based queries (no spheroid/sphere calculations for point
|
||||||
|
geometries like PostGIS).
|
||||||
|
"""
|
||||||
|
if not value:
|
||||||
|
return []
|
||||||
|
value = value[0]
|
||||||
|
if isinstance(value, Distance):
|
||||||
|
if f.geodetic(self.connection):
|
||||||
|
raise ValueError('SpatiaLite does not support distance queries on '
|
||||||
|
'geometry fields with a geodetic coordinate system. '
|
||||||
|
'Distance objects; use a numeric value of your '
|
||||||
|
'distance in degrees instead.')
|
||||||
|
else:
|
||||||
|
dist_param = getattr(value, Distance.unit_attname(f.units_name(self.connection)))
|
||||||
|
else:
|
||||||
|
dist_param = value
|
||||||
|
return [dist_param]
|
||||||
|
|
||||||
|
def get_geom_placeholder(self, f, value):
|
||||||
"""
|
"""
|
||||||
Provides a proper substitution value for Geometries that are not in the
|
Provides a proper substitution value for Geometries that are not in the
|
||||||
SRID of the field. Specifically, this routine will substitute in the
|
SRID of the field. Specifically, this routine will substitute in the
|
||||||
@ -156,19 +191,19 @@ class SpatiaLiteOperations(DatabaseOperations, BaseSpatialOperations):
|
|||||||
def transform_value(value, srid):
|
def transform_value(value, srid):
|
||||||
return not (value is None or value.srid == srid)
|
return not (value is None or value.srid == srid)
|
||||||
if hasattr(value, 'expression'):
|
if hasattr(value, 'expression'):
|
||||||
if transform_value(value, srid):
|
if transform_value(value, f.srid):
|
||||||
placeholder = '%s(%%s, %s)' % (self.transform, srid)
|
placeholder = '%s(%%s, %s)' % (self.transform, f.srid)
|
||||||
else:
|
else:
|
||||||
placeholder = '%s'
|
placeholder = '%s'
|
||||||
# No geometry value used for F expression, substitue in
|
# No geometry value used for F expression, substitue in
|
||||||
# the column name instead.
|
# the column name instead.
|
||||||
return placeholder % '%s.%s' % tuple(map(self.quote_name, value.cols[value.expression]))
|
return placeholder % '%s.%s' % tuple(map(self.quote_name, value.cols[value.expression]))
|
||||||
else:
|
else:
|
||||||
if transform_value(value, srid):
|
if transform_value(value, f.srid):
|
||||||
# Adding Transform() to the SQL placeholder.
|
# Adding Transform() to the SQL placeholder.
|
||||||
return '%s(%s(%%s,%s), %s)' % (self.transform, self.from_text, value.srid, srid)
|
return '%s(%s(%%s,%s), %s)' % (self.transform, self.from_text, value.srid, f.srid)
|
||||||
else:
|
else:
|
||||||
return '%s(%%s,%s)' % (self.from_text, srid)
|
return '%s(%%s,%s)' % (self.from_text, f.srid)
|
||||||
|
|
||||||
def _get_spatialite_func(self, func):
|
def _get_spatialite_func(self, func):
|
||||||
"""
|
"""
|
||||||
@ -229,13 +264,12 @@ class SpatiaLiteOperations(DatabaseOperations, BaseSpatialOperations):
|
|||||||
sql_function = getattr(self, agg_name)
|
sql_function = getattr(self, agg_name)
|
||||||
return sql_template, sql_function
|
return sql_template, sql_function
|
||||||
|
|
||||||
def spatial_lookup_sql(self, lvalue, lookup_type, value, field):
|
def spatial_lookup_sql(self, lvalue, lookup_type, value, field, qn):
|
||||||
"""
|
"""
|
||||||
Returns the SpatiaLite-specific SQL for the given lookup value
|
Returns the SpatiaLite-specific SQL for the given lookup value
|
||||||
[a tuple of (alias, column, db_type)], lookup type, lookup
|
[a tuple of (alias, column, db_type)], lookup type, lookup
|
||||||
value, and the model field.
|
value, and the model field.
|
||||||
"""
|
"""
|
||||||
qn = self.quote_name
|
|
||||||
alias, col, db_type = lvalue
|
alias, col, db_type = lvalue
|
||||||
|
|
||||||
# Getting the quoted field as `geo_col`.
|
# Getting the quoted field as `geo_col`.
|
||||||
@ -278,7 +312,7 @@ class SpatiaLiteOperations(DatabaseOperations, BaseSpatialOperations):
|
|||||||
op = tmp
|
op = tmp
|
||||||
geom = value
|
geom = value
|
||||||
# Calling the `as_sql` function on the operation instance.
|
# Calling the `as_sql` function on the operation instance.
|
||||||
return op.as_sql(geo_col, self.get_geom_placeholder(geom, field.srid))
|
return op.as_sql(geo_col, self.get_geom_placeholder(field, geom))
|
||||||
elif lookup_type == 'isnull':
|
elif lookup_type == 'isnull':
|
||||||
# Handling 'isnull' lookup type
|
# Handling 'isnull' lookup type
|
||||||
return "%s IS %sNULL" % (geo_col, (not value and 'NOT ' or ''))
|
return "%s IS %sNULL" % (geo_col, (not value and 'NOT ' or ''))
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
from django.db.models.fields import Field
|
from django.db.models.fields import Field
|
||||||
from django.contrib.gis import forms
|
from django.contrib.gis import forms
|
||||||
from django.contrib.gis.db.models.proxy import GeometryProxy
|
from django.contrib.gis.db.models.proxy import GeometryProxy
|
||||||
from django.contrib.gis.geometry import Geometry, GeometryException
|
from django.contrib.gis.geometry.backend import Geometry, GeometryException
|
||||||
from django.contrib.gis.measure import Distance
|
from django.contrib.gis.measure import Distance
|
||||||
from django.db.models.sql.expressions import SQLEvaluator
|
from django.db.models.sql.expressions import SQLEvaluator
|
||||||
|
|
||||||
@ -40,8 +40,8 @@ def get_srid_info(srid, connection):
|
|||||||
|
|
||||||
return _srid_cache[name][srid]
|
return _srid_cache[name][srid]
|
||||||
|
|
||||||
class GeometryField(SpatialBackend.Field):
|
class GeometryField(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_type = 'GEOMETRY'
|
geom_type = 'GEOMETRY'
|
||||||
@ -50,7 +50,7 @@ class GeometryField(SpatialBackend.Field):
|
|||||||
geodetic_units = ('Decimal Degree', 'degree')
|
geodetic_units = ('Decimal Degree', 'degree')
|
||||||
|
|
||||||
def __init__(self, verbose_name=None, srid=4326, spatial_index=True, dim=2,
|
def __init__(self, verbose_name=None, srid=4326, spatial_index=True, dim=2,
|
||||||
**kwargs):
|
geography=False, **kwargs):
|
||||||
"""
|
"""
|
||||||
The initialization function for geometry fields. Takes the following
|
The initialization function for geometry fields. Takes the following
|
||||||
as keyword arguments:
|
as keyword arguments:
|
||||||
@ -67,8 +67,14 @@ class GeometryField(SpatialBackend.Field):
|
|||||||
dim:
|
dim:
|
||||||
The number of dimensions for this geometry. Defaults to 2.
|
The number of dimensions for this geometry. Defaults to 2.
|
||||||
|
|
||||||
Oracle-specific keywords:
|
extent:
|
||||||
extent, tolerance.
|
Customize the extent, in a 4-tuple of WGS 84 coordinates, for the
|
||||||
|
geometry field entry in the `USER_SDO_GEOM_METADATA` table. Defaults
|
||||||
|
to (-180.0, -90.0, 180.0, 90.0).
|
||||||
|
|
||||||
|
tolerance:
|
||||||
|
Define the tolerance, in meters, to use for the geometry field
|
||||||
|
entry in the `USER_SDO_GEOM_METADATA` table. Defaults to 0.05.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Setting the index flag with the value of the `spatial_index` keyword.
|
# Setting the index flag with the value of the `spatial_index` keyword.
|
||||||
@ -85,6 +91,9 @@ class GeometryField(SpatialBackend.Field):
|
|||||||
# first parameter, so this works like normal fields.
|
# first parameter, so this works like normal fields.
|
||||||
kwargs['verbose_name'] = verbose_name
|
kwargs['verbose_name'] = verbose_name
|
||||||
|
|
||||||
|
# Is this a geography rather than a geometry column?
|
||||||
|
self.geography = geography
|
||||||
|
|
||||||
# Oracle-specific private attributes for creating the entrie in
|
# Oracle-specific private attributes for creating the entrie in
|
||||||
# `USER_SDO_GEOM_METADATA`
|
# `USER_SDO_GEOM_METADATA`
|
||||||
self._extent = kwargs.pop('extent', (-180.0, -90.0, 180.0, 90.0))
|
self._extent = kwargs.pop('extent', (-180.0, -90.0, 180.0, 90.0))
|
||||||
@ -121,17 +130,13 @@ class GeometryField(SpatialBackend.Field):
|
|||||||
"""
|
"""
|
||||||
return self.units_name(connection) in self.geodetic_units
|
return self.units_name(connection) in self.geodetic_units
|
||||||
|
|
||||||
def get_distance(self, dist_val, lookup_type, connection):
|
def get_distance(self, value, lookup_type, connection):
|
||||||
"""
|
"""
|
||||||
Returns a distance number in units of the field. For example, if
|
Returns a distance number in units of the field. For example, if
|
||||||
`D(km=1)` was passed in and the units of the field were in meters,
|
`D(km=1)` was passed in and the units of the field were in meters,
|
||||||
then 1000 would be returned.
|
then 1000 would be returned.
|
||||||
"""
|
"""
|
||||||
# Getting the distance parameter and any options.
|
return connection.ops.get_distance(self, value, lookup_type)
|
||||||
if len(dist_val) == 1:
|
|
||||||
dist, option = dist_val[0], None
|
|
||||||
else:
|
|
||||||
dist, option = dist_val
|
|
||||||
|
|
||||||
if isinstance(dist, Distance):
|
if isinstance(dist, Distance):
|
||||||
if self.geodetic(connection):
|
if self.geodetic(connection):
|
||||||
@ -149,7 +154,7 @@ class GeometryField(SpatialBackend.Field):
|
|||||||
|
|
||||||
if connection.ops.oracle and lookup_type == 'dwithin':
|
if connection.ops.oracle and lookup_type == 'dwithin':
|
||||||
dist_param = 'distance=%s' % dist_param
|
dist_param = 'distance=%s' % dist_param
|
||||||
|
|
||||||
if connection.ops.postgis and self.geodetic(connection) and lookup_type != 'dwithin' and option == 'spheroid':
|
if connection.ops.postgis and self.geodetic(connection) and lookup_type != 'dwithin' and option == 'spheroid':
|
||||||
# On PostGIS, by default `ST_distance_sphere` is used; but if the
|
# On PostGIS, by default `ST_distance_sphere` is used; but if the
|
||||||
# accuracy of `ST_distance_spheroid` is needed than the spheroid
|
# accuracy of `ST_distance_spheroid` is needed than the spheroid
|
||||||
@ -179,11 +184,11 @@ class GeometryField(SpatialBackend.Field):
|
|||||||
# from the given string input.
|
# from the given string input.
|
||||||
if isinstance(geom, Geometry):
|
if isinstance(geom, Geometry):
|
||||||
pass
|
pass
|
||||||
elif isinstance(geom, basestring):
|
elif isinstance(geom, basestring) or hasattr(geom, '__geo_interface__'):
|
||||||
try:
|
try:
|
||||||
geom = Geometry(geom)
|
geom = Geometry(geom)
|
||||||
except GeometryException:
|
except GeometryException:
|
||||||
raise ValueError('Could not create geometry from lookup value: %s' % str(value))
|
raise ValueError('Could not create geometry from lookup value.')
|
||||||
else:
|
else:
|
||||||
raise ValueError('Cannot use parameter of `%s` type as lookup parameter.' % type(value))
|
raise ValueError('Cannot use parameter of `%s` type as lookup parameter.' % type(value))
|
||||||
|
|
||||||
@ -217,17 +222,7 @@ class GeometryField(SpatialBackend.Field):
|
|||||||
setattr(cls, self.attname, GeometryProxy(Geometry, self))
|
setattr(cls, self.attname, GeometryProxy(Geometry, self))
|
||||||
|
|
||||||
def db_type(self, connection):
|
def db_type(self, connection):
|
||||||
if (connection.ops.postgis or
|
return connection.ops.geo_db_type(self)
|
||||||
connection.ops.spatialite):
|
|
||||||
# Geometry columns on these spatial backends are initialized via
|
|
||||||
# the `AddGeometryColumn` stored procedure.
|
|
||||||
return None
|
|
||||||
elif connection.ops.mysql:
|
|
||||||
return self.geom_type
|
|
||||||
elif connection.ops.oracle:
|
|
||||||
return 'MDSYS.SDO_GEOMETRY'
|
|
||||||
else:
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
def formfield(self, **kwargs):
|
def formfield(self, **kwargs):
|
||||||
defaults = {'form_class' : forms.GeometryField,
|
defaults = {'form_class' : forms.GeometryField,
|
||||||
@ -240,7 +235,11 @@ class GeometryField(SpatialBackend.Field):
|
|||||||
|
|
||||||
def get_db_prep_lookup(self, lookup_type, value, connection, prepared=False):
|
def get_db_prep_lookup(self, lookup_type, value, connection, prepared=False):
|
||||||
"""
|
"""
|
||||||
XXX: Document me.
|
Prepare for the database lookup, and return any spatial parameters
|
||||||
|
necessary for the query. This includes wrapping any geometry
|
||||||
|
parameters with a backend-specific adapter and formatting any distance
|
||||||
|
parameters into the correct units for the coordinate system of the
|
||||||
|
field.
|
||||||
"""
|
"""
|
||||||
if lookup_type in connection.ops.gis_terms:
|
if lookup_type in connection.ops.gis_terms:
|
||||||
# special case for isnull lookup
|
# special case for isnull lookup
|
||||||
@ -254,8 +253,6 @@ class GeometryField(SpatialBackend.Field):
|
|||||||
if lookup_type in connection.ops.distance_functions:
|
if lookup_type in connection.ops.distance_functions:
|
||||||
# Getting the distance parameter in the units of the field.
|
# Getting the distance parameter in the units of the field.
|
||||||
params += self.get_distance(value[1:], lookup_type, connection)
|
params += self.get_distance(value[1:], lookup_type, connection)
|
||||||
elif lookup_type in connection.ops.limited_where:
|
|
||||||
pass
|
|
||||||
else:
|
else:
|
||||||
params += value[1:]
|
params += value[1:]
|
||||||
elif isinstance(value, SQLEvaluator):
|
elif isinstance(value, SQLEvaluator):
|
||||||
@ -281,33 +278,31 @@ class GeometryField(SpatialBackend.Field):
|
|||||||
return connection.ops.Adapter(self.get_prep_value(value))
|
return connection.ops.Adapter(self.get_prep_value(value))
|
||||||
|
|
||||||
def get_placeholder(self, value, connection):
|
def get_placeholder(self, value, connection):
|
||||||
return connection.ops.get_geom_placeholder(value, self.srid)
|
"""
|
||||||
|
Returns the placeholder for the geometry column for the
|
||||||
|
given value.
|
||||||
|
"""
|
||||||
|
return connection.ops.get_geom_placeholder(self, value)
|
||||||
|
|
||||||
# The OpenGIS Geometry Type Fields
|
# The OpenGIS Geometry Type Fields
|
||||||
class PointField(GeometryField):
|
class PointField(GeometryField):
|
||||||
"""Point"""
|
|
||||||
geom_type = 'POINT'
|
geom_type = 'POINT'
|
||||||
|
|
||||||
class LineStringField(GeometryField):
|
class LineStringField(GeometryField):
|
||||||
"""Line string"""
|
|
||||||
geom_type = 'LINESTRING'
|
geom_type = 'LINESTRING'
|
||||||
|
|
||||||
class PolygonField(GeometryField):
|
class PolygonField(GeometryField):
|
||||||
"""Polygon"""
|
|
||||||
geom_type = 'POLYGON'
|
geom_type = 'POLYGON'
|
||||||
|
|
||||||
class MultiPointField(GeometryField):
|
class MultiPointField(GeometryField):
|
||||||
"""Multi-point"""
|
|
||||||
geom_type = 'MULTIPOINT'
|
geom_type = 'MULTIPOINT'
|
||||||
|
|
||||||
class MultiLineStringField(GeometryField):
|
class MultiLineStringField(GeometryField):
|
||||||
"""Multi-line string"""
|
|
||||||
geom_type = 'MULTILINESTRING'
|
geom_type = 'MULTILINESTRING'
|
||||||
|
|
||||||
class MultiPolygonField(GeometryField):
|
class MultiPolygonField(GeometryField):
|
||||||
"""Multi polygon"""
|
|
||||||
geom_type = 'MULTIPOLYGON'
|
geom_type = 'MULTIPOLYGON'
|
||||||
|
|
||||||
class GeometryCollectionField(GeometryField):
|
class GeometryCollectionField(GeometryField):
|
||||||
"""Geometry collection"""
|
|
||||||
geom_type = 'GEOMETRYCOLLECTION'
|
geom_type = 'GEOMETRYCOLLECTION'
|
||||||
|
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
from django.db.models.manager import Manager
|
from django.db.models.manager import Manager
|
||||||
from django.contrib.gis.db.models.query import GeoQuerySet
|
from django.contrib.gis.db.models.query import GeoQuerySet
|
||||||
from django.contrib.gis.db.models.sql.subqueries import insert_query
|
|
||||||
|
|
||||||
class GeoManager(Manager):
|
class GeoManager(Manager):
|
||||||
"Overrides Manager to return Geographic QuerySets."
|
"Overrides Manager to return Geographic QuerySets."
|
||||||
@ -54,7 +53,7 @@ class GeoManager(Manager):
|
|||||||
|
|
||||||
def make_line(self, *args, **kwargs):
|
def make_line(self, *args, **kwargs):
|
||||||
return self.get_query_set().make_line(*args, **kwargs)
|
return self.get_query_set().make_line(*args, **kwargs)
|
||||||
|
|
||||||
def mem_size(self, *args, **kwargs):
|
def mem_size(self, *args, **kwargs):
|
||||||
return self.get_query_set().mem_size(*args, **kwargs)
|
return self.get_query_set().mem_size(*args, **kwargs)
|
||||||
|
|
||||||
@ -93,6 +92,3 @@ class GeoManager(Manager):
|
|||||||
|
|
||||||
def unionagg(self, *args, **kwargs):
|
def unionagg(self, *args, **kwargs):
|
||||||
return self.get_query_set().unionagg(*args, **kwargs)
|
return self.get_query_set().unionagg(*args, **kwargs)
|
||||||
|
|
||||||
def _insert(self, values, **kwargs):
|
|
||||||
return insert_query(self.model, values, **kwargs)
|
|
||||||
|
@ -4,7 +4,7 @@ from django.db.models.query import QuerySet, Q, ValuesQuerySet, ValuesListQueryS
|
|||||||
from django.contrib.gis.db.models import aggregates
|
from django.contrib.gis.db.models import aggregates
|
||||||
from django.contrib.gis.db.models.fields import get_srid_info, GeometryField, PointField
|
from django.contrib.gis.db.models.fields import get_srid_info, GeometryField, PointField
|
||||||
from django.contrib.gis.db.models.sql import AreaField, DistanceField, GeomField, GeoQuery, GeoWhereNode
|
from django.contrib.gis.db.models.sql import AreaField, DistanceField, GeomField, GeoQuery, GeoWhereNode
|
||||||
from django.contrib.gis.geometry import Geometry
|
from django.contrib.gis.geometry.backend import Geometry
|
||||||
from django.contrib.gis.measure import Area, Distance
|
from django.contrib.gis.measure import Area, Distance
|
||||||
|
|
||||||
class GeoQuerySet(QuerySet):
|
class GeoQuerySet(QuerySet):
|
||||||
@ -542,6 +542,7 @@ class GeoQuerySet(QuerySet):
|
|||||||
# units of the geometry field.
|
# units of the geometry field.
|
||||||
connection = connections[self.db]
|
connection = connections[self.db]
|
||||||
geodetic = geo_field.geodetic(connection)
|
geodetic = geo_field.geodetic(connection)
|
||||||
|
geography = geo_field.geography
|
||||||
|
|
||||||
if geodetic:
|
if geodetic:
|
||||||
dist_att = 'm'
|
dist_att = 'm'
|
||||||
@ -569,7 +570,8 @@ class GeoQuerySet(QuerySet):
|
|||||||
# keyword or when 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 (backend.postgis and geodetic and length):
|
if spheroid or (backend.postgis and geodetic and
|
||||||
|
(not geography) and length):
|
||||||
lookup_params.append('spheroid')
|
lookup_params.append('spheroid')
|
||||||
lookup_params = geo_field.get_prep_value(lookup_params)
|
lookup_params = geo_field.get_prep_value(lookup_params)
|
||||||
params = geo_field.get_db_prep_lookup('distance_lte', lookup_params, connection=connection)
|
params = geo_field.get_db_prep_lookup('distance_lte', lookup_params, connection=connection)
|
||||||
@ -625,7 +627,7 @@ class GeoQuerySet(QuerySet):
|
|||||||
# `transform()` was not used on this GeoQuerySet.
|
# `transform()` was not used on this GeoQuerySet.
|
||||||
procedure_fmt = '%(geo_col)s,%(geom)s'
|
procedure_fmt = '%(geo_col)s,%(geom)s'
|
||||||
|
|
||||||
if geodetic:
|
if not geography and geodetic:
|
||||||
# Spherical distance calculation is needed (because the geographic
|
# Spherical distance calculation is needed (because the geographic
|
||||||
# field is geodetic). However, the PostGIS ST_distance_sphere/spheroid()
|
# field is geodetic). However, the PostGIS ST_distance_sphere/spheroid()
|
||||||
# procedures may only do queries from point columns to point geometries
|
# procedures may only do queries from point columns to point geometries
|
||||||
@ -644,7 +646,7 @@ class GeoQuerySet(QuerySet):
|
|||||||
procedure_args.update({'function' : backend.distance_sphere})
|
procedure_args.update({'function' : backend.distance_sphere})
|
||||||
elif length or perimeter:
|
elif length or perimeter:
|
||||||
procedure_fmt = '%(geo_col)s'
|
procedure_fmt = '%(geo_col)s'
|
||||||
if geodetic and length:
|
if not geography and geodetic and length:
|
||||||
# There's no `length_sphere`, and `length_spheroid` also
|
# There's no `length_sphere`, and `length_spheroid` also
|
||||||
# works on 3D geometries.
|
# works on 3D geometries.
|
||||||
procedure_fmt += ",'%(spheroid)s'"
|
procedure_fmt += ",'%(spheroid)s'"
|
||||||
|
@ -5,7 +5,7 @@ from django.contrib.gis.db.models.fields import GeometryField
|
|||||||
from django.contrib.gis.db.models.sql import aggregates as gis_aggregates
|
from django.contrib.gis.db.models.sql import aggregates as gis_aggregates
|
||||||
from django.contrib.gis.db.models.sql.conversion import AreaField, DistanceField, GeomField
|
from django.contrib.gis.db.models.sql.conversion import AreaField, DistanceField, GeomField
|
||||||
from django.contrib.gis.db.models.sql.where import GeoWhereNode
|
from django.contrib.gis.db.models.sql.where import GeoWhereNode
|
||||||
from django.contrib.gis.geometry import Geometry
|
from django.contrib.gis.geometry.backend import Geometry
|
||||||
from django.contrib.gis.measure import Area, Distance
|
from django.contrib.gis.measure import Area, Distance
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,36 +0,0 @@
|
|||||||
from django.db import connections
|
|
||||||
from django.db.models.sql.subqueries import InsertQuery
|
|
||||||
|
|
||||||
class GeoInsertQuery(InsertQuery):
|
|
||||||
def insert_values(self, insert_values, raw_values=False):
|
|
||||||
"""
|
|
||||||
Set up the insert query from the 'insert_values' dictionary. The
|
|
||||||
dictionary gives the model field names and their target values.
|
|
||||||
|
|
||||||
If 'raw_values' is True, the values in the 'insert_values' dictionary
|
|
||||||
are inserted directly into the query, rather than passed as SQL
|
|
||||||
parameters. This provides a way to insert NULL and DEFAULT keywords
|
|
||||||
into the query, for example.
|
|
||||||
"""
|
|
||||||
placeholders, values = [], []
|
|
||||||
for field, val in insert_values:
|
|
||||||
placeholders.append((field, val))
|
|
||||||
self.columns.append(field.column)
|
|
||||||
|
|
||||||
if not placeholders[-1] == 'NULL':
|
|
||||||
values.append(val)
|
|
||||||
if raw_values:
|
|
||||||
self.values.extend([(None, v) for v in values])
|
|
||||||
else:
|
|
||||||
self.params += tuple(values)
|
|
||||||
self.values.extend(placeholders)
|
|
||||||
|
|
||||||
def insert_query(model, values, return_id=False, raw_values=False, using=None):
|
|
||||||
"""
|
|
||||||
Inserts a new record for the given model. This provides an interface to
|
|
||||||
the InsertQuery class and is how Model.save() is implemented. It is not
|
|
||||||
part of the public API.
|
|
||||||
"""
|
|
||||||
query = GeoInsertQuery(model)
|
|
||||||
query.insert_values(values, raw_values)
|
|
||||||
return query.get_compiler(using=using).execute_sql(return_id)
|
|
@ -44,7 +44,7 @@ class GeoWhereNode(WhereNode):
|
|||||||
lvalue, lookup_type, value_annot, params_or_value = child
|
lvalue, lookup_type, value_annot, params_or_value = child
|
||||||
if isinstance(lvalue, GeoConstraint):
|
if isinstance(lvalue, GeoConstraint):
|
||||||
data, params = lvalue.process(lookup_type, params_or_value, connection)
|
data, params = lvalue.process(lookup_type, params_or_value, connection)
|
||||||
spatial_sql = connection.ops.spatial_lookup_sql(data, lookup_type, params_or_value, lvalue.field)
|
spatial_sql = connection.ops.spatial_lookup_sql(data, lookup_type, params_or_value, lvalue.field, qn)
|
||||||
return spatial_sql, params
|
return spatial_sql, params
|
||||||
else:
|
else:
|
||||||
return super(GeoWhereNode, self).make_atom(child, qn, connection)
|
return super(GeoWhereNode, self).make_atom(child, qn, connection)
|
||||||
@ -52,7 +52,7 @@ class GeoWhereNode(WhereNode):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def _check_geo_field(cls, opts, lookup):
|
def _check_geo_field(cls, opts, lookup):
|
||||||
"""
|
"""
|
||||||
Utility for checking the given lookup with the given model options.
|
Utility for checking the given lookup with the given model options.
|
||||||
The lookup is a string either specifying the geographic field, e.g.
|
The lookup is a string either specifying the geographic field, e.g.
|
||||||
'point, 'the_geom', or a related lookup on a geographic field like
|
'point, 'the_geom', or a related lookup on a geographic field like
|
||||||
'address__point'.
|
'address__point'.
|
||||||
@ -74,7 +74,7 @@ class GeoWhereNode(WhereNode):
|
|||||||
# If the field list is still around, then it means that the
|
# If the field list is still around, then it means that the
|
||||||
# lookup was for a geometry field across a relationship --
|
# lookup was for a geometry field across a relationship --
|
||||||
# thus we keep on getting the related model options and the
|
# thus we keep on getting the related model options and the
|
||||||
# model field associated with the next field in the list
|
# model field associated with the next field in the list
|
||||||
# until there's no more left.
|
# until there's no more left.
|
||||||
while len(field_list):
|
while len(field_list):
|
||||||
opts = geo_fld.rel.to._meta
|
opts = geo_fld.rel.to._meta
|
||||||
|
@ -1,9 +0,0 @@
|
|||||||
from django.conf import settings
|
|
||||||
|
|
||||||
__all__ = ['Geometry', 'GeometryException']
|
|
||||||
|
|
||||||
from django.contrib.gis.geos import GEOSGeometry, GEOSException
|
|
||||||
|
|
||||||
Geometry = GEOSGeometry
|
|
||||||
GeometryException = GEOSException
|
|
||||||
|
|
21
django/contrib/gis/geometry/backend/__init__.py
Normal file
21
django/contrib/gis/geometry/backend/__init__.py
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
from django.conf import settings
|
||||||
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
|
from django.utils.importlib import import_module
|
||||||
|
|
||||||
|
geom_backend = getattr(settings, 'GEOMETRY_BACKEND', 'geos')
|
||||||
|
|
||||||
|
try:
|
||||||
|
module = import_module('.%s' % geom_backend, 'django.contrib.gis.geometry.backend')
|
||||||
|
except ImportError, e:
|
||||||
|
try:
|
||||||
|
module = import_module(geom_backend)
|
||||||
|
except ImportError, e_user:
|
||||||
|
raise ImproperlyConfigured('Could not import user-defined GEOMETRY_BACKEND '
|
||||||
|
'"%s".' % geom_backend)
|
||||||
|
|
||||||
|
try:
|
||||||
|
Geometry = module.Geometry
|
||||||
|
GeometryException = module.GeometryException
|
||||||
|
except AttributeError:
|
||||||
|
raise ImproperlyConfigured('Cannot import Geometry from the "%s" '
|
||||||
|
'geometry backend.' % geom_backend)
|
3
django/contrib/gis/geometry/backend/geos.py
Normal file
3
django/contrib/gis/geometry/backend/geos.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
from django.contrib.gis.geos import \
|
||||||
|
GEOSGeometry as Geometry, \
|
||||||
|
GEOSException as GeometryException
|
@ -1,7 +1,7 @@
|
|||||||
import os, unittest
|
import os, unittest
|
||||||
from django.contrib.gis.geos import *
|
from django.contrib.gis.geos import *
|
||||||
from django.contrib.gis.db.models import Collect, Count, Extent, F, Union
|
from django.contrib.gis.db.models import Collect, Count, Extent, F, Union
|
||||||
from django.contrib.gis.geometry import Geometry
|
from django.contrib.gis.geometry.backend import Geometry
|
||||||
from django.contrib.gis.tests.utils import mysql, oracle, postgis, spatialite, no_mysql, no_oracle, no_spatialite
|
from django.contrib.gis.tests.utils import mysql, oracle, postgis, spatialite, no_mysql, no_oracle, no_spatialite
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from models import City, Location, DirectoryEntry, Parcel, Book, Author
|
from models import City, Location, DirectoryEntry, Parcel, Book, Author
|
||||||
|
Loading…
x
Reference in New Issue
Block a user