mirror of
https://github.com/django/django.git
synced 2025-07-04 09:49: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
|
||||
from django.conf import settings
|
||||
@ -14,8 +15,9 @@ class BaseSpatialOperations(object):
|
||||
distance_functions = {}
|
||||
geometry_functions = {}
|
||||
geometry_operators = {}
|
||||
geography_operators = {}
|
||||
geography_functions = {}
|
||||
gis_terms = {}
|
||||
limited_where = {}
|
||||
|
||||
# Quick booleans for the type of this spatial backend, and
|
||||
# an attribute for the spatial database version tuple (if applicable)
|
||||
@ -28,6 +30,9 @@ class BaseSpatialOperations(object):
|
||||
# How the geometry column should be selected.
|
||||
select = None
|
||||
|
||||
# Does the spatial database have a geography type?
|
||||
geography = False
|
||||
|
||||
area = False
|
||||
centroid = False
|
||||
difference = False
|
||||
@ -37,11 +42,13 @@ class BaseSpatialOperations(object):
|
||||
envelope = False
|
||||
force_rhr = False
|
||||
mem_size = False
|
||||
bounding_circle = False
|
||||
num_geom = False
|
||||
num_points = False
|
||||
perimeter = False
|
||||
perimeter3d = False
|
||||
point_on_surface = False
|
||||
polygonize = False
|
||||
scale = False
|
||||
snap_to_grid = False
|
||||
sym_difference = False
|
||||
@ -67,11 +74,6 @@ class BaseSpatialOperations(object):
|
||||
from_text = 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
|
||||
# for the spatial backend.
|
||||
def convert_extent(self, box):
|
||||
@ -83,6 +85,37 @@ class BaseSpatialOperations(object):
|
||||
def convert_geom(self, geom_val, geom_field):
|
||||
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):
|
||||
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']])
|
||||
|
||||
def geo_db_type(self, f):
|
||||
return f.geom_type
|
||||
|
||||
def get_geom_placeholder(self, value, srid):
|
||||
"""
|
||||
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
|
||||
return placeholder
|
||||
|
||||
def spatial_lookup_sql(self, lvalue, lookup_type, value, field):
|
||||
qn = self.quote_name
|
||||
def spatial_lookup_sql(self, lvalue, lookup_type, value, field, qn):
|
||||
alias, col, db_type = lvalue
|
||||
|
||||
geo_col = '%s.%s' % (qn(alias), qn(col))
|
||||
|
@ -7,4 +7,4 @@ class DatabaseWrapper(OracleDatabaseWrapper):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(DatabaseWrapper, self).__init__(*args, **kwargs)
|
||||
self.creation = OracleCreation(self)
|
||||
self.ops = OracleOperations()
|
||||
self.ops = OracleOperations(self)
|
||||
|
@ -7,7 +7,29 @@ class GeoSQLCompiler(BaseGeoSQLCompiler, SQLCompiler):
|
||||
pass
|
||||
|
||||
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):
|
||||
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.oracle.adapter import OracleSpatialAdapter
|
||||
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
|
||||
|
||||
class SDOOperation(SpatialFunction):
|
||||
@ -91,7 +91,7 @@ class OracleOperations(DatabaseOperations, BaseSpatialOperations):
|
||||
sym_difference = 'SDO_GEOM.SDO_XOR'
|
||||
transform = 'SDO_CS.TRANSFORM'
|
||||
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
|
||||
# instantiate GEOS proxies from WKT than SDO_GEOMETRY(...) strings.
|
||||
@ -128,6 +128,10 @@ class OracleOperations(DatabaseOperations, BaseSpatialOperations):
|
||||
gis_terms += geometry_functions.keys()
|
||||
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):
|
||||
if clob:
|
||||
# Generally, Oracle returns a polygon for the extent -- however,
|
||||
@ -156,7 +160,40 @@ class OracleOperations(DatabaseOperations, BaseSpatialOperations):
|
||||
else:
|
||||
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
|
||||
SRID of the field. Specifically, this routine will substitute in the
|
||||
@ -165,26 +202,25 @@ class OracleOperations(DatabaseOperations, BaseSpatialOperations):
|
||||
if value is None:
|
||||
return 'NULL'
|
||||
|
||||
def transform_value(value, srid):
|
||||
return value.srid != srid
|
||||
def transform_value(val, srid):
|
||||
return val.srid != srid
|
||||
|
||||
if hasattr(value, 'expression'):
|
||||
if transform_value(value, srid):
|
||||
placeholder = '%s(%%s, %s)' % (self.transform, srid)
|
||||
if transform_value(value, f.srid):
|
||||
placeholder = '%s(%%s, %s)' % (self.transform, f.srid)
|
||||
else:
|
||||
placeholder = '%s'
|
||||
# No geometry value used for F expression, substitue in
|
||||
# the column name instead.
|
||||
return placeholder % '%s.%s' % tuple(map(self.quote_name, value.cols[value.expression]))
|
||||
else:
|
||||
if transform_value(value, srid):
|
||||
return '%s(SDO_GEOMETRY(%%s, %s), %s)' % (self.transform, value.srid, srid)
|
||||
if transform_value(value, f.srid):
|
||||
return '%s(SDO_GEOMETRY(%%s, %s), %s)' % (self.transform, value.srid, f.srid)
|
||||
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."
|
||||
qn = self.quote_name
|
||||
alias, col, db_type = lvalue
|
||||
|
||||
# Getting the quoted table name as `geo_col`.
|
||||
@ -214,15 +250,15 @@ class OracleOperations(DatabaseOperations, BaseSpatialOperations):
|
||||
if lookup_type == 'relate':
|
||||
# The SDORelate class handles construction for these queries,
|
||||
# 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:
|
||||
# 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:
|
||||
# Lookup info is a SDOOperation instance, whose `as_sql` method returns
|
||||
# the SQL necessary for the geometry function call. For example:
|
||||
# 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':
|
||||
# Handling 'isnull' lookup type
|
||||
return "%s IS %sNULL" % (geo_col, (not value and 'NOT ' or ''))
|
||||
|
@ -16,32 +16,43 @@ class PostGISCreation(DatabaseCreation):
|
||||
qn = self.connection.ops.quote_name
|
||||
db_table = model._meta.db_table
|
||||
|
||||
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 f.geography:
|
||||
# Geogrophy columns are created normally.
|
||||
pass
|
||||
else:
|
||||
# Geometry columns are created by `AddGeometryColumn`
|
||||
# stored procedure.
|
||||
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:
|
||||
# Add a NOT NULL constraint to the field
|
||||
output.append(style.SQL_KEYWORD('ALTER TABLE ') +
|
||||
style.SQL_TABLE(qn(db_table)) +
|
||||
style.SQL_KEYWORD(' ALTER ') +
|
||||
style.SQL_FIELD(qn(f.column)) +
|
||||
style.SQL_KEYWORD(' SET NOT NULL') + ';')
|
||||
if not f.null:
|
||||
# Add a NOT NULL constraint to the field
|
||||
output.append(style.SQL_KEYWORD('ALTER TABLE ') +
|
||||
style.SQL_TABLE(qn(db_table)) +
|
||||
style.SQL_KEYWORD(' ALTER ') +
|
||||
style.SQL_FIELD(qn(f.column)) +
|
||||
style.SQL_KEYWORD(' SET NOT NULL') + ';')
|
||||
|
||||
|
||||
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 ') +
|
||||
style.SQL_TABLE(qn('%s_%s_id' % (db_table, f.column))) +
|
||||
style.SQL_KEYWORD(' ON ') +
|
||||
style.SQL_TABLE(qn(db_table)) +
|
||||
style.SQL_KEYWORD(' USING ') +
|
||||
style.SQL_COLTYPE(self.geom_index_type) + ' ( ' +
|
||||
style.SQL_FIELD(qn(f.column)) + ' ' +
|
||||
style.SQL_KEYWORD(self.geom_index_opts) + ' );')
|
||||
style.SQL_FIELD(qn(f.column)) + index_opts + ' );')
|
||||
return output
|
||||
|
||||
def sql_table_creation_suffix(self):
|
||||
|
@ -1,12 +1,15 @@
|
||||
import re
|
||||
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.util import SpatialOperation, SpatialFunction
|
||||
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.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 ####
|
||||
class PostGISOperator(SpatialOperation):
|
||||
@ -68,23 +71,48 @@ class PostGISOperations(DatabaseOperations, BaseSpatialOperations):
|
||||
super(PostGISOperations, self).__init__(connection)
|
||||
|
||||
# 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:
|
||||
vtup = self.postgis_version_tuple()
|
||||
version = vtup[1:]
|
||||
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()
|
||||
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):
|
||||
prefix = 'ST_'
|
||||
else:
|
||||
prefix = ''
|
||||
|
||||
self.geom_func_prefix = prefix
|
||||
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:
|
||||
# TODO: Plain raising right now.
|
||||
# TODO: Raise helpful exceptions as they become known.
|
||||
raise
|
||||
|
||||
# PostGIS-specific operators. The commented descriptions of these
|
||||
# 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
|
||||
# is to the left of B's bounding box.
|
||||
'overlaps_left' : PostGISOperator('&<'),
|
||||
@ -166,19 +194,6 @@ class PostGISOperations(DatabaseOperations, BaseSpatialOperations):
|
||||
# Adding the distance functions to the geometries lookup.
|
||||
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
|
||||
# in versions 1.3+.
|
||||
if version < (1, 3, 0):
|
||||
@ -194,7 +209,40 @@ class PostGISOperations(DatabaseOperations, BaseSpatialOperations):
|
||||
else:
|
||||
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.bounding_circle = BOUNDINGCIRCLE
|
||||
self.centroid = prefix + 'Centroid'
|
||||
self.collect = prefix + 'Collect'
|
||||
self.difference = prefix + 'Difference'
|
||||
@ -212,13 +260,14 @@ class PostGISOperations(DatabaseOperations, BaseSpatialOperations):
|
||||
self.length = prefix + 'Length'
|
||||
self.length3d = prefix + 'Length3D'
|
||||
self.length_spheroid = prefix + 'length_spheroid'
|
||||
self.makeline = prefix + 'MakeLine'
|
||||
self.makeline = MAKELINE
|
||||
self.mem_size = prefix + 'mem_size'
|
||||
self.num_geom = prefix + 'NumGeometries'
|
||||
self.num_points =prefix + 'npoints'
|
||||
self.perimeter = prefix + 'Perimeter'
|
||||
self.perimeter3d = prefix + 'Perimeter3D'
|
||||
self.point_on_surface = prefix + 'PointOnSurface'
|
||||
self.polygonize = prefix + 'Polygonize'
|
||||
self.scale = prefix + 'Scale'
|
||||
self.snap_to_grid = prefix + 'SnapToGrid'
|
||||
self.svg = prefix + 'AsSVG'
|
||||
@ -237,16 +286,22 @@ class PostGISOperations(DatabaseOperations, BaseSpatialOperations):
|
||||
return agg_name in self.valid_aggregates
|
||||
|
||||
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(',')
|
||||
xmin, ymin = map(float, ll.split())
|
||||
xmax, ymax = map(float, ur.split())
|
||||
return (xmin, ymin, xmax, ymax)
|
||||
|
||||
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(',')
|
||||
xmin, ymin, zmin = map(float, ll.split())
|
||||
xmax, ymax, zmax = map(float, ur.split())
|
||||
@ -261,17 +316,78 @@ class PostGISOperations(DatabaseOperations, BaseSpatialOperations):
|
||||
else:
|
||||
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
|
||||
SRID of the field. Specifically, this routine will substitute in the
|
||||
ST_Transform() function call.
|
||||
"""
|
||||
if value is None or value.srid == srid:
|
||||
if value is None or value.srid == f.srid:
|
||||
placeholder = '%s'
|
||||
else:
|
||||
# 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 this is an F expression, then we don't really want
|
||||
@ -290,7 +406,7 @@ class PostGISOperations(DatabaseOperations, BaseSpatialOperations):
|
||||
cursor.execute('SELECT %s()' % func)
|
||||
row = cursor.fetchone()
|
||||
except:
|
||||
# TODO: raise helpful exception here.
|
||||
# Responsibility of callers to perform error handling.
|
||||
raise
|
||||
finally:
|
||||
cursor.close()
|
||||
@ -334,32 +450,42 @@ class PostGISOperations(DatabaseOperations, BaseSpatialOperations):
|
||||
|
||||
return (version, major, minor1, minor2)
|
||||
|
||||
def num_params(self, lookup_type, val):
|
||||
def exactly_two(val): return val == 2
|
||||
def two_to_three(val): return val >= 2 and val <=3
|
||||
def num_params(self, lookup_type, num_param):
|
||||
"""
|
||||
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
|
||||
lookup_type != 'dwithin'):
|
||||
return two_to_three(val)
|
||||
return two_to_three(num_param)
|
||||
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
|
||||
(alias, col, db_type), the lookup type string, lookup value, and
|
||||
the geometry field.
|
||||
"""
|
||||
qn = self.quote_name
|
||||
alias, col, db_type = lvalue
|
||||
|
||||
# Getting the quoted geometry column.
|
||||
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.
|
||||
op = self.spatial_operators[lookup_type]
|
||||
return op.as_sql(geo_col, self.get_geom_placeholder(value, field.srid))
|
||||
op = self.geometry_operators[lookup_type]
|
||||
return op.as_sql(geo_col, self.get_geom_placeholder(field, value))
|
||||
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.
|
||||
tmp = self.geometry_functions[lookup_type]
|
||||
|
||||
@ -392,7 +518,7 @@ class PostGISOperations(DatabaseOperations, BaseSpatialOperations):
|
||||
if lookup_type == 'relate':
|
||||
op = op(self.geom_func_prefix, value[1])
|
||||
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.
|
||||
if field.geom_type != 'POINT':
|
||||
raise ValueError('PostGIS spherical operations are only valid on PointFields.')
|
||||
@ -412,7 +538,7 @@ class PostGISOperations(DatabaseOperations, BaseSpatialOperations):
|
||||
geom = value
|
||||
|
||||
# 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':
|
||||
# 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.util import SpatialOperation, SpatialFunction
|
||||
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.core.exceptions import ImproperlyConfigured
|
||||
from django.db.backends.sqlite3.base import DatabaseOperations
|
||||
@ -119,11 +119,17 @@ class SpatiaLiteOperations(DatabaseOperations, BaseSpatialOperations):
|
||||
try:
|
||||
vtup = self.spatialite_version_tuple()
|
||||
version = vtup[1:]
|
||||
self.spatial_version = version
|
||||
if version < (2, 3, 1):
|
||||
raise Exception('GeoDjango only supports SpatiaLite versions 2.3.1+')
|
||||
except Exception, e:
|
||||
raise ImproperlyConfigured('GeoDjango only supports SpatiaLite versions '
|
||||
'2.3.1 and above')
|
||||
self.spatial_version = version
|
||||
except ImproperlyConfigured:
|
||||
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.
|
||||
gis_terms = ['isnull']
|
||||
@ -147,7 +153,36 @@ class SpatiaLiteOperations(DatabaseOperations, BaseSpatialOperations):
|
||||
else:
|
||||
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
|
||||
SRID of the field. Specifically, this routine will substitute in the
|
||||
@ -156,19 +191,19 @@ class SpatiaLiteOperations(DatabaseOperations, BaseSpatialOperations):
|
||||
def transform_value(value, srid):
|
||||
return not (value is None or value.srid == srid)
|
||||
if hasattr(value, 'expression'):
|
||||
if transform_value(value, srid):
|
||||
placeholder = '%s(%%s, %s)' % (self.transform, srid)
|
||||
if transform_value(value, f.srid):
|
||||
placeholder = '%s(%%s, %s)' % (self.transform, f.srid)
|
||||
else:
|
||||
placeholder = '%s'
|
||||
# No geometry value used for F expression, substitue in
|
||||
# the column name instead.
|
||||
return placeholder % '%s.%s' % tuple(map(self.quote_name, value.cols[value.expression]))
|
||||
else:
|
||||
if transform_value(value, srid):
|
||||
if transform_value(value, f.srid):
|
||||
# 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:
|
||||
return '%s(%%s,%s)' % (self.from_text, srid)
|
||||
return '%s(%%s,%s)' % (self.from_text, f.srid)
|
||||
|
||||
def _get_spatialite_func(self, func):
|
||||
"""
|
||||
@ -229,13 +264,12 @@ class SpatiaLiteOperations(DatabaseOperations, BaseSpatialOperations):
|
||||
sql_function = getattr(self, agg_name)
|
||||
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
|
||||
[a tuple of (alias, column, db_type)], lookup type, lookup
|
||||
value, and the model field.
|
||||
"""
|
||||
qn = self.quote_name
|
||||
alias, col, db_type = lvalue
|
||||
|
||||
# Getting the quoted field as `geo_col`.
|
||||
@ -278,7 +312,7 @@ class SpatiaLiteOperations(DatabaseOperations, BaseSpatialOperations):
|
||||
op = tmp
|
||||
geom = value
|
||||
# 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':
|
||||
# Handling 'isnull' lookup type
|
||||
return "%s IS %sNULL" % (geo_col, (not value and 'NOT ' or ''))
|
||||
|
@ -1,7 +1,7 @@
|
||||
from django.db.models.fields import Field
|
||||
from django.contrib.gis import forms
|
||||
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.db.models.sql.expressions import SQLEvaluator
|
||||
|
||||
@ -40,8 +40,8 @@ def get_srid_info(srid, connection):
|
||||
|
||||
return _srid_cache[name][srid]
|
||||
|
||||
class GeometryField(SpatialBackend.Field):
|
||||
"""The base GIS field -- maps to the OpenGIS Specification Geometry type."""
|
||||
class GeometryField(Field):
|
||||
"The base GIS field -- maps to the OpenGIS Specification Geometry type."
|
||||
|
||||
# The OpenGIS Geometry name.
|
||||
geom_type = 'GEOMETRY'
|
||||
@ -50,7 +50,7 @@ class GeometryField(SpatialBackend.Field):
|
||||
geodetic_units = ('Decimal Degree', 'degree')
|
||||
|
||||
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
|
||||
as keyword arguments:
|
||||
@ -67,8 +67,14 @@ class GeometryField(SpatialBackend.Field):
|
||||
dim:
|
||||
The number of dimensions for this geometry. Defaults to 2.
|
||||
|
||||
Oracle-specific keywords:
|
||||
extent, tolerance.
|
||||
extent:
|
||||
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.
|
||||
@ -85,6 +91,9 @@ class GeometryField(SpatialBackend.Field):
|
||||
# first parameter, so this works like normal fields.
|
||||
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
|
||||
# `USER_SDO_GEOM_METADATA`
|
||||
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
|
||||
|
||||
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
|
||||
`D(km=1)` was passed in and the units of the field were in meters,
|
||||
then 1000 would be returned.
|
||||
"""
|
||||
# Getting the distance parameter and any options.
|
||||
if len(dist_val) == 1:
|
||||
dist, option = dist_val[0], None
|
||||
else:
|
||||
dist, option = dist_val
|
||||
return connection.ops.get_distance(self, value, lookup_type)
|
||||
|
||||
if isinstance(dist, Distance):
|
||||
if self.geodetic(connection):
|
||||
@ -149,7 +154,7 @@ class GeometryField(SpatialBackend.Field):
|
||||
|
||||
if connection.ops.oracle and lookup_type == 'dwithin':
|
||||
dist_param = 'distance=%s' % dist_param
|
||||
|
||||
|
||||
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
|
||||
# accuracy of `ST_distance_spheroid` is needed than the spheroid
|
||||
@ -179,11 +184,11 @@ class GeometryField(SpatialBackend.Field):
|
||||
# from the given string input.
|
||||
if isinstance(geom, Geometry):
|
||||
pass
|
||||
elif isinstance(geom, basestring):
|
||||
elif isinstance(geom, basestring) or hasattr(geom, '__geo_interface__'):
|
||||
try:
|
||||
geom = Geometry(geom)
|
||||
except GeometryException:
|
||||
raise ValueError('Could not create geometry from lookup value: %s' % str(value))
|
||||
raise ValueError('Could not create geometry from lookup value.')
|
||||
else:
|
||||
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))
|
||||
|
||||
def db_type(self, connection):
|
||||
if (connection.ops.postgis or
|
||||
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
|
||||
return connection.ops.geo_db_type(self)
|
||||
|
||||
def formfield(self, **kwargs):
|
||||
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):
|
||||
"""
|
||||
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:
|
||||
# special case for isnull lookup
|
||||
@ -254,8 +253,6 @@ class GeometryField(SpatialBackend.Field):
|
||||
if lookup_type in connection.ops.distance_functions:
|
||||
# Getting the distance parameter in the units of the field.
|
||||
params += self.get_distance(value[1:], lookup_type, connection)
|
||||
elif lookup_type in connection.ops.limited_where:
|
||||
pass
|
||||
else:
|
||||
params += value[1:]
|
||||
elif isinstance(value, SQLEvaluator):
|
||||
@ -281,33 +278,31 @@ class GeometryField(SpatialBackend.Field):
|
||||
return connection.ops.Adapter(self.get_prep_value(value))
|
||||
|
||||
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
|
||||
class PointField(GeometryField):
|
||||
"""Point"""
|
||||
geom_type = 'POINT'
|
||||
|
||||
class LineStringField(GeometryField):
|
||||
"""Line string"""
|
||||
geom_type = 'LINESTRING'
|
||||
|
||||
class PolygonField(GeometryField):
|
||||
"""Polygon"""
|
||||
geom_type = 'POLYGON'
|
||||
|
||||
class MultiPointField(GeometryField):
|
||||
"""Multi-point"""
|
||||
geom_type = 'MULTIPOINT'
|
||||
|
||||
class MultiLineStringField(GeometryField):
|
||||
"""Multi-line string"""
|
||||
geom_type = 'MULTILINESTRING'
|
||||
|
||||
class MultiPolygonField(GeometryField):
|
||||
"""Multi polygon"""
|
||||
geom_type = 'MULTIPOLYGON'
|
||||
|
||||
class GeometryCollectionField(GeometryField):
|
||||
"""Geometry collection"""
|
||||
geom_type = 'GEOMETRYCOLLECTION'
|
||||
|
||||
|
@ -1,6 +1,5 @@
|
||||
from django.db.models.manager import Manager
|
||||
from django.contrib.gis.db.models.query import GeoQuerySet
|
||||
from django.contrib.gis.db.models.sql.subqueries import insert_query
|
||||
|
||||
class GeoManager(Manager):
|
||||
"Overrides Manager to return Geographic QuerySets."
|
||||
@ -54,7 +53,7 @@ class GeoManager(Manager):
|
||||
|
||||
def make_line(self, *args, **kwargs):
|
||||
return self.get_query_set().make_line(*args, **kwargs)
|
||||
|
||||
|
||||
def mem_size(self, *args, **kwargs):
|
||||
return self.get_query_set().mem_size(*args, **kwargs)
|
||||
|
||||
@ -93,6 +92,3 @@ class GeoManager(Manager):
|
||||
|
||||
def unionagg(self, *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.fields import get_srid_info, GeometryField, PointField
|
||||
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
|
||||
|
||||
class GeoQuerySet(QuerySet):
|
||||
@ -542,6 +542,7 @@ class GeoQuerySet(QuerySet):
|
||||
# units of the geometry field.
|
||||
connection = connections[self.db]
|
||||
geodetic = geo_field.geodetic(connection)
|
||||
geography = geo_field.geography
|
||||
|
||||
if geodetic:
|
||||
dist_att = 'm'
|
||||
@ -569,7 +570,8 @@ class GeoQuerySet(QuerySet):
|
||||
# keyword or when calculating the length of geodetic field, make
|
||||
# sure the 'spheroid' distance setting string is passed in so we
|
||||
# get the correct spatial stored procedure.
|
||||
if spheroid or (backend.postgis and geodetic and length):
|
||||
if spheroid or (backend.postgis and geodetic and
|
||||
(not geography) and length):
|
||||
lookup_params.append('spheroid')
|
||||
lookup_params = geo_field.get_prep_value(lookup_params)
|
||||
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.
|
||||
procedure_fmt = '%(geo_col)s,%(geom)s'
|
||||
|
||||
if geodetic:
|
||||
if not geography and geodetic:
|
||||
# Spherical distance calculation is needed (because the geographic
|
||||
# field is geodetic). However, the PostGIS ST_distance_sphere/spheroid()
|
||||
# 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})
|
||||
elif length or perimeter:
|
||||
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
|
||||
# works on 3D geometries.
|
||||
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.conversion import AreaField, DistanceField, GeomField
|
||||
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
|
||||
|
||||
|
||||
|
@ -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
|
||||
if isinstance(lvalue, GeoConstraint):
|
||||
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
|
||||
else:
|
||||
return super(GeoWhereNode, self).make_atom(child, qn, connection)
|
||||
@ -52,7 +52,7 @@ class GeoWhereNode(WhereNode):
|
||||
@classmethod
|
||||
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.
|
||||
'point, 'the_geom', or a related lookup on a geographic field like
|
||||
'address__point'.
|
||||
@ -74,7 +74,7 @@ class GeoWhereNode(WhereNode):
|
||||
# If the field list is still around, then it means that the
|
||||
# lookup was for a geometry field across a relationship --
|
||||
# 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.
|
||||
while len(field_list):
|
||||
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
|
||||
from django.contrib.gis.geos import *
|
||||
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.conf import settings
|
||||
from models import City, Location, DirectoryEntry, Parcel, Book, Author
|
||||
|
Loading…
x
Reference in New Issue
Block a user