1
0
mirror of https://github.com/django/django.git synced 2025-07-06 18:59:13 +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:
Alex Gaynor 2009-12-16 02:33:33 +00:00
parent 11c00d632d
commit 05b4d2f67b
18 changed files with 428 additions and 192 deletions

View File

@ -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.')

View File

@ -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))

View File

@ -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)

View File

@ -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

View File

@ -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):
@ -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 ''))

View File

@ -16,6 +16,12 @@ 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
if f.geography:
# Geogrophy columns are created normally.
pass
else:
# Geometry columns are created by `AddGeometryColumn`
# stored procedure.
output.append(style.SQL_KEYWORD('SELECT ') + output.append(style.SQL_KEYWORD('SELECT ') +
style.SQL_TABLE('AddGeometryColumn') + '(' + style.SQL_TABLE('AddGeometryColumn') + '(' +
style.SQL_TABLE(gqn(db_table)) + ', ' + style.SQL_TABLE(gqn(db_table)) + ', ' +
@ -34,14 +40,19 @@ class PostGISCreation(DatabaseCreation):
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):

View File

@ -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:
if hasattr(settings, 'POSTGIS_VERSION'):
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() vtup = self.postgis_version_tuple()
version = vtup[1:] 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

View File

@ -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 ''))

View File

@ -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):
@ -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'

View File

@ -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."
@ -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)

View File

@ -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'"

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -1,9 +0,0 @@
from django.conf import settings
__all__ = ['Geometry', 'GeometryException']
from django.contrib.gis.geos import GEOSGeometry, GEOSException
Geometry = GEOSGeometry
GeometryException = GEOSException

View 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)

View File

@ -0,0 +1,3 @@
from django.contrib.gis.geos import \
GEOSGeometry as Geometry, \
GEOSException as GeometryException

View File

@ -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