gis: The `verbose_name` positional keyword now works for `GeometryField`, thanks springmeyer; removed DOS line endings from Oracle & MySQL spatial backends, thanks cramm.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@8224 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Justin Bronn 2008-08-06 21:40:00 +00:00
parent 34a3bd5225
commit b00e82fe2c
3 changed files with 219 additions and 216 deletions

View File

@ -1,59 +1,59 @@
""" """
This module contains the spatial lookup types, and the `get_geo_where_clause` This module contains the spatial lookup types, and the `get_geo_where_clause`
routine for MySQL. routine for MySQL.
Please note that MySQL only supports bounding box queries, also Please note that MySQL only supports bounding box queries, also
known as MBRs (Minimum Bounding Rectangles). Moreover, spatial known as MBRs (Minimum Bounding Rectangles). Moreover, spatial
indices may only be used on MyISAM tables -- if you need indices may only be used on MyISAM tables -- if you need
transactions, take a look at PostGIS. transactions, take a look at PostGIS.
""" """
from django.db import connection from django.db import connection
qn = connection.ops.quote_name qn = connection.ops.quote_name
# To ease implementation, WKT is passed to/from MySQL. # To ease implementation, WKT is passed to/from MySQL.
GEOM_FROM_TEXT = 'GeomFromText' GEOM_FROM_TEXT = 'GeomFromText'
GEOM_FROM_WKB = 'GeomFromWKB' GEOM_FROM_WKB = 'GeomFromWKB'
GEOM_SELECT = 'AsText(%s)' GEOM_SELECT = 'AsText(%s)'
# WARNING: MySQL is NOT compliant w/the OpenGIS specification and # WARNING: MySQL is NOT compliant w/the OpenGIS specification and
# _every_ one of these lookup types is on the _bounding box_ only. # _every_ one of these lookup types is on the _bounding box_ only.
MYSQL_GIS_FUNCTIONS = { MYSQL_GIS_FUNCTIONS = {
'bbcontains' : 'MBRContains', # For consistency w/PostGIS API 'bbcontains' : 'MBRContains', # For consistency w/PostGIS API
'bboverlaps' : 'MBROverlaps', # .. .. 'bboverlaps' : 'MBROverlaps', # .. ..
'contained' : 'MBRWithin', # .. .. 'contained' : 'MBRWithin', # .. ..
'contains' : 'MBRContains', 'contains' : 'MBRContains',
'disjoint' : 'MBRDisjoint', 'disjoint' : 'MBRDisjoint',
'equals' : 'MBREqual', 'equals' : 'MBREqual',
'exact' : 'MBREqual', 'exact' : 'MBREqual',
'intersects' : 'MBRIntersects', 'intersects' : 'MBRIntersects',
'overlaps' : 'MBROverlaps', 'overlaps' : 'MBROverlaps',
'same_as' : 'MBREqual', 'same_as' : 'MBREqual',
'touches' : 'MBRTouches', 'touches' : 'MBRTouches',
'within' : 'MBRWithin', 'within' : 'MBRWithin',
} }
# This lookup type does not require a mapping. # This lookup type does not require a mapping.
MISC_TERMS = ['isnull'] MISC_TERMS = ['isnull']
# Assacceptable lookup types for Oracle spatial. # Assacceptable lookup types for Oracle spatial.
MYSQL_GIS_TERMS = MYSQL_GIS_FUNCTIONS.keys() MYSQL_GIS_TERMS = MYSQL_GIS_FUNCTIONS.keys()
MYSQL_GIS_TERMS += MISC_TERMS MYSQL_GIS_TERMS += MISC_TERMS
MYSQL_GIS_TERMS = dict((term, None) for term in MYSQL_GIS_TERMS) # Making dictionary MYSQL_GIS_TERMS = dict((term, None) for term in MYSQL_GIS_TERMS) # Making dictionary
def get_geo_where_clause(table_alias, name, lookup_type, geo_annot): def get_geo_where_clause(table_alias, name, lookup_type, geo_annot):
"Returns the SQL WHERE clause for use in MySQL spatial SQL construction." "Returns the SQL WHERE clause for use in MySQL spatial SQL construction."
# Getting the quoted field as `geo_col`. # Getting the quoted field as `geo_col`.
geo_col = '%s.%s' % (qn(table_alias), qn(name)) geo_col = '%s.%s' % (qn(table_alias), qn(name))
# See if a MySQL Geometry function matches the lookup type next # See if a MySQL Geometry function matches the lookup type next
lookup_info = MYSQL_GIS_FUNCTIONS.get(lookup_type, False) lookup_info = MYSQL_GIS_FUNCTIONS.get(lookup_type, False)
if lookup_info: if lookup_info:
return "%s(%s, %%s)" % (lookup_info, geo_col) return "%s(%s, %%s)" % (lookup_info, geo_col)
# Handling 'isnull' lookup type # Handling 'isnull' lookup type
# TODO: Is this needed because MySQL cannot handle NULL # TODO: Is this needed because MySQL cannot handle NULL
# geometries in its spatial indices. # geometries in its spatial indices.
if lookup_type == 'isnull': if lookup_type == 'isnull':
return "%s IS %sNULL" % (geo_col, (not geo_annot.value and 'NOT ' or '')) return "%s IS %sNULL" % (geo_col, (not geo_annot.value and 'NOT ' or ''))
raise TypeError("Got invalid lookup_type: %s" % repr(lookup_type)) raise TypeError("Got invalid lookup_type: %s" % repr(lookup_type))

View File

@ -1,154 +1,154 @@
""" """
This module contains the spatial lookup types, and the `get_geo_where_clause` This module contains the spatial lookup types, and the `get_geo_where_clause`
routine for Oracle Spatial. routine for Oracle Spatial.
Please note that WKT support is broken on the XE version, and thus Please note that WKT support is broken on the XE version, and thus
this backend will not work on such platforms. Specifically, XE lacks this backend will not work on such platforms. Specifically, XE lacks
support for an internal JVM, and Java libraries are required to use support for an internal JVM, and Java libraries are required to use
the WKT constructors. the WKT constructors.
""" """
import re import re
from decimal import Decimal from decimal import Decimal
from django.db import connection from django.db import connection
from django.contrib.gis.db.backend.util import SpatialFunction from django.contrib.gis.db.backend.util import SpatialFunction
from django.contrib.gis.measure import Distance from django.contrib.gis.measure import Distance
qn = connection.ops.quote_name qn = connection.ops.quote_name
# The GML, distance, transform, and union procedures. # The GML, distance, transform, and union procedures.
AREA = 'SDO_GEOM.SDO_AREA' AREA = 'SDO_GEOM.SDO_AREA'
ASGML = 'SDO_UTIL.TO_GMLGEOMETRY' ASGML = 'SDO_UTIL.TO_GMLGEOMETRY'
CENTROID = 'SDO_GEOM.SDO_CENTROID' CENTROID = 'SDO_GEOM.SDO_CENTROID'
DIFFERENCE = 'SDO_GEOM.SDO_DIFFERENCE' DIFFERENCE = 'SDO_GEOM.SDO_DIFFERENCE'
DISTANCE = 'SDO_GEOM.SDO_DISTANCE' DISTANCE = 'SDO_GEOM.SDO_DISTANCE'
EXTENT = 'SDO_AGGR_MBR' EXTENT = 'SDO_AGGR_MBR'
INTERSECTION = 'SDO_GEOM.SDO_INTERSECTION' INTERSECTION = 'SDO_GEOM.SDO_INTERSECTION'
LENGTH = 'SDO_GEOM.SDO_LENGTH' LENGTH = 'SDO_GEOM.SDO_LENGTH'
NUM_GEOM = 'SDO_UTIL.GETNUMELEM' NUM_GEOM = 'SDO_UTIL.GETNUMELEM'
NUM_POINTS = 'SDO_UTIL.GETNUMVERTICES' NUM_POINTS = 'SDO_UTIL.GETNUMVERTICES'
POINT_ON_SURFACE = 'SDO_GEOM.SDO_POINTONSURFACE' POINT_ON_SURFACE = 'SDO_GEOM.SDO_POINTONSURFACE'
SYM_DIFFERENCE = 'SDO_GEOM.SDO_XOR' SYM_DIFFERENCE = 'SDO_GEOM.SDO_XOR'
TRANSFORM = 'SDO_CS.TRANSFORM' TRANSFORM = 'SDO_CS.TRANSFORM'
UNION = 'SDO_GEOM.SDO_UNION' UNION = 'SDO_GEOM.SDO_UNION'
UNIONAGG = 'SDO_AGGR_UNION' UNIONAGG = 'SDO_AGGR_UNION'
# We want to get SDO Geometries as WKT because it is much easier to # We want to get SDO Geometries as WKT because it is much easier to
# instantiate GEOS proxies from WKT than SDO_GEOMETRY(...) strings. # instantiate GEOS proxies from WKT than SDO_GEOMETRY(...) strings.
# However, this adversely affects performance (i.e., Java is called # However, this adversely affects performance (i.e., Java is called
# to convert to WKT on every query). If someone wishes to write a # to convert to WKT on every query). If someone wishes to write a
# SDO_GEOMETRY(...) parser in Python, let me know =) # SDO_GEOMETRY(...) parser in Python, let me know =)
GEOM_SELECT = 'SDO_UTIL.TO_WKTGEOMETRY(%s)' GEOM_SELECT = 'SDO_UTIL.TO_WKTGEOMETRY(%s)'
#### Classes used in constructing Oracle spatial SQL #### #### Classes used in constructing Oracle spatial SQL ####
class SDOOperation(SpatialFunction): class SDOOperation(SpatialFunction):
"Base class for SDO* Oracle operations." "Base class for SDO* Oracle operations."
def __init__(self, func, **kwargs): def __init__(self, func, **kwargs):
kwargs.setdefault('operator', '=') kwargs.setdefault('operator', '=')
kwargs.setdefault('result', 'TRUE') kwargs.setdefault('result', 'TRUE')
kwargs.setdefault('end_subst', ") %s '%s'") kwargs.setdefault('end_subst', ") %s '%s'")
super(SDOOperation, self).__init__(func, **kwargs) super(SDOOperation, self).__init__(func, **kwargs)
class SDODistance(SpatialFunction): class SDODistance(SpatialFunction):
"Class for Distance queries." "Class for Distance queries."
def __init__(self, op, tolerance=0.05): def __init__(self, op, tolerance=0.05):
super(SDODistance, self).__init__(DISTANCE, end_subst=', %s) %%s %%s' % tolerance, super(SDODistance, self).__init__(DISTANCE, end_subst=', %s) %%s %%s' % tolerance,
operator=op, result='%%s') operator=op, result='%%s')
class SDOGeomRelate(SpatialFunction): class SDOGeomRelate(SpatialFunction):
"Class for using SDO_GEOM.RELATE." "Class for using SDO_GEOM.RELATE."
def __init__(self, mask, tolerance=0.05): def __init__(self, mask, tolerance=0.05):
# SDO_GEOM.RELATE(...) has a peculiar argument order: column, mask, geom, tolerance. # SDO_GEOM.RELATE(...) has a peculiar argument order: column, mask, geom, tolerance.
# Moreover, the runction result is the mask (e.g., 'DISJOINT' instead of 'TRUE'). # Moreover, the runction result is the mask (e.g., 'DISJOINT' instead of 'TRUE').
end_subst = "%s%s) %s '%s'" % (', %%s, ', tolerance, '=', mask) end_subst = "%s%s) %s '%s'" % (', %%s, ', tolerance, '=', mask)
beg_subst = "%%s(%%s, '%s'" % mask beg_subst = "%%s(%%s, '%s'" % mask
super(SDOGeomRelate, self).__init__('SDO_GEOM.RELATE', beg_subst=beg_subst, end_subst=end_subst) super(SDOGeomRelate, self).__init__('SDO_GEOM.RELATE', beg_subst=beg_subst, end_subst=end_subst)
class SDORelate(SpatialFunction): class SDORelate(SpatialFunction):
"Class for using SDO_RELATE." "Class for using SDO_RELATE."
masks = 'TOUCH|OVERLAPBDYDISJOINT|OVERLAPBDYINTERSECT|EQUAL|INSIDE|COVEREDBY|CONTAINS|COVERS|ANYINTERACT|ON' masks = 'TOUCH|OVERLAPBDYDISJOINT|OVERLAPBDYINTERSECT|EQUAL|INSIDE|COVEREDBY|CONTAINS|COVERS|ANYINTERACT|ON'
mask_regex = re.compile(r'^(%s)(\+(%s))*$' % (masks, masks), re.I) mask_regex = re.compile(r'^(%s)(\+(%s))*$' % (masks, masks), re.I)
def __init__(self, mask): def __init__(self, mask):
func = 'SDO_RELATE' func = 'SDO_RELATE'
if not self.mask_regex.match(mask): if not self.mask_regex.match(mask):
raise ValueError('Invalid %s mask: "%s"' % (func, mask)) raise ValueError('Invalid %s mask: "%s"' % (func, mask))
super(SDORelate, self).__init__(func, end_subst=", 'mask=%s') = 'TRUE'" % mask) super(SDORelate, self).__init__(func, end_subst=", 'mask=%s') = 'TRUE'" % mask)
#### Lookup type mapping dictionaries of Oracle spatial operations #### #### Lookup type mapping dictionaries of Oracle spatial operations ####
# Valid distance types and substitutions # Valid distance types and substitutions
dtypes = (Decimal, Distance, float, int, long) dtypes = (Decimal, Distance, float, int, long)
DISTANCE_FUNCTIONS = { DISTANCE_FUNCTIONS = {
'distance_gt' : (SDODistance('>'), dtypes), 'distance_gt' : (SDODistance('>'), dtypes),
'distance_gte' : (SDODistance('>='), dtypes), 'distance_gte' : (SDODistance('>='), dtypes),
'distance_lt' : (SDODistance('<'), dtypes), 'distance_lt' : (SDODistance('<'), dtypes),
'distance_lte' : (SDODistance('<='), dtypes), 'distance_lte' : (SDODistance('<='), dtypes),
'dwithin' : (SDOOperation('SDO_WITHIN_DISTANCE', 'dwithin' : (SDOOperation('SDO_WITHIN_DISTANCE',
beg_subst="%s(%s, %%s, 'distance=%%s'"), dtypes), beg_subst="%s(%s, %%s, 'distance=%%s'"), dtypes),
} }
ORACLE_GEOMETRY_FUNCTIONS = { ORACLE_GEOMETRY_FUNCTIONS = {
'contains' : SDOOperation('SDO_CONTAINS'), 'contains' : SDOOperation('SDO_CONTAINS'),
'coveredby' : SDOOperation('SDO_COVEREDBY'), 'coveredby' : SDOOperation('SDO_COVEREDBY'),
'covers' : SDOOperation('SDO_COVERS'), 'covers' : SDOOperation('SDO_COVERS'),
'disjoint' : SDOGeomRelate('DISJOINT'), 'disjoint' : SDOGeomRelate('DISJOINT'),
'intersects' : SDOOperation('SDO_OVERLAPBDYINTERSECT'), # TODO: Is this really the same as ST_Intersects()? 'intersects' : SDOOperation('SDO_OVERLAPBDYINTERSECT'), # TODO: Is this really the same as ST_Intersects()?
'equals' : SDOOperation('SDO_EQUAL'), 'equals' : SDOOperation('SDO_EQUAL'),
'exact' : SDOOperation('SDO_EQUAL'), 'exact' : SDOOperation('SDO_EQUAL'),
'overlaps' : SDOOperation('SDO_OVERLAPS'), 'overlaps' : SDOOperation('SDO_OVERLAPS'),
'same_as' : SDOOperation('SDO_EQUAL'), 'same_as' : SDOOperation('SDO_EQUAL'),
'relate' : (SDORelate, basestring), # Oracle uses a different syntax, e.g., 'mask=inside+touch' 'relate' : (SDORelate, basestring), # Oracle uses a different syntax, e.g., 'mask=inside+touch'
'touches' : SDOOperation('SDO_TOUCH'), 'touches' : SDOOperation('SDO_TOUCH'),
'within' : SDOOperation('SDO_INSIDE'), 'within' : SDOOperation('SDO_INSIDE'),
} }
ORACLE_GEOMETRY_FUNCTIONS.update(DISTANCE_FUNCTIONS) ORACLE_GEOMETRY_FUNCTIONS.update(DISTANCE_FUNCTIONS)
# This lookup type does not require a mapping. # This lookup type does not require a mapping.
MISC_TERMS = ['isnull'] MISC_TERMS = ['isnull']
# Acceptable lookup types for Oracle spatial. # Acceptable lookup types for Oracle spatial.
ORACLE_SPATIAL_TERMS = ORACLE_GEOMETRY_FUNCTIONS.keys() ORACLE_SPATIAL_TERMS = ORACLE_GEOMETRY_FUNCTIONS.keys()
ORACLE_SPATIAL_TERMS += MISC_TERMS ORACLE_SPATIAL_TERMS += MISC_TERMS
ORACLE_SPATIAL_TERMS = dict((term, None) for term in ORACLE_SPATIAL_TERMS) # Making dictionary for fast lookups ORACLE_SPATIAL_TERMS = dict((term, None) for term in ORACLE_SPATIAL_TERMS) # Making dictionary for fast lookups
#### The `get_geo_where_clause` function for Oracle #### #### The `get_geo_where_clause` function for Oracle ####
def get_geo_where_clause(table_alias, name, lookup_type, geo_annot): def get_geo_where_clause(table_alias, name, lookup_type, geo_annot):
"Returns the SQL WHERE clause for use in Oracle spatial SQL construction." "Returns the SQL WHERE clause for use in Oracle spatial SQL construction."
# Getting the quoted table name as `geo_col`. # Getting the quoted table name as `geo_col`.
geo_col = '%s.%s' % (qn(table_alias), qn(name)) geo_col = '%s.%s' % (qn(table_alias), qn(name))
# See if a Oracle Geometry function matches the lookup type next # See if a Oracle Geometry function matches the lookup type next
lookup_info = ORACLE_GEOMETRY_FUNCTIONS.get(lookup_type, False) lookup_info = ORACLE_GEOMETRY_FUNCTIONS.get(lookup_type, False)
if lookup_info: if lookup_info:
# Lookup types that are tuples take tuple arguments, e.g., 'relate' and # Lookup types that are tuples take tuple arguments, e.g., 'relate' and
# 'dwithin' lookup types. # 'dwithin' lookup types.
if isinstance(lookup_info, tuple): if isinstance(lookup_info, tuple):
# First element of tuple is lookup type, second element is the type # First element of tuple is lookup type, second element is the type
# of the expected argument (e.g., str, float) # of the expected argument (e.g., str, float)
sdo_op, arg_type = lookup_info sdo_op, arg_type = lookup_info
# Ensuring that a tuple _value_ was passed in from the user # Ensuring that a tuple _value_ was passed in from the user
if not isinstance(geo_annot.value, tuple): if not isinstance(geo_annot.value, tuple):
raise TypeError('Tuple required for `%s` lookup type.' % lookup_type) raise TypeError('Tuple required for `%s` lookup type.' % lookup_type)
if len(geo_annot.value) != 2: if len(geo_annot.value) != 2:
raise ValueError('2-element tuple required for %s lookup type.' % lookup_type) raise ValueError('2-element tuple required for %s lookup type.' % lookup_type)
# Ensuring the argument type matches what we expect. # Ensuring the argument type matches what we expect.
if not isinstance(geo_annot.value[1], arg_type): if not isinstance(geo_annot.value[1], arg_type):
raise TypeError('Argument type should be %s, got %s instead.' % (arg_type, type(geo_annot.value[1]))) raise TypeError('Argument type should be %s, got %s instead.' % (arg_type, type(geo_annot.value[1])))
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(geo_annot.value[1]).as_sql(geo_col) return sdo_op(geo_annot.value[1]).as_sql(geo_col)
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) return sdo_op.as_sql(geo_col)
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) return lookup_info.as_sql(geo_col)
elif lookup_type == 'isnull': elif lookup_type == 'isnull':
# Handling 'isnull' lookup type # Handling 'isnull' lookup type
return "%s IS %sNULL" % (geo_col, (not geo_annot.value and 'NOT ' or '')) return "%s IS %sNULL" % (geo_col, (not geo_annot.value and 'NOT ' or ''))
raise TypeError("Got invalid lookup_type: %s" % repr(lookup_type)) raise TypeError("Got invalid lookup_type: %s" % repr(lookup_type))

View File

@ -1,5 +1,4 @@
from django.contrib.gis import forms from django.contrib.gis import forms
from django.db import connection
# Getting the SpatialBackend container and the geographic quoting method. # Getting the SpatialBackend container and the geographic quoting method.
from django.contrib.gis.db.backend import SpatialBackend, gqn from django.contrib.gis.db.backend import SpatialBackend, gqn
# GeometryProxy, GEOS, Distance, and oldforms imports. # GeometryProxy, GEOS, Distance, and oldforms imports.
@ -21,7 +20,7 @@ class GeometryField(SpatialBackend.Field):
# Geodetic units. # Geodetic units.
geodetic_units = ('Decimal Degree', 'degree') geodetic_units = ('Decimal Degree', 'degree')
def __init__(self, srid=4326, spatial_index=True, dim=2, **kwargs): def __init__(self, verbose_name=None, srid=4326, spatial_index=True, dim=2, **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:
@ -49,7 +48,11 @@ class GeometryField(SpatialBackend.Field):
# Setting the dimension of the geometry field. # Setting the dimension of the geometry field.
self._dim = dim self._dim = dim
# Setting the verbose_name keyword argument with the positional
# first parameter, so this works like normal fields.
kwargs['verbose_name'] = verbose_name
super(GeometryField, self).__init__(**kwargs) # Calling the parent initializtion function super(GeometryField, self).__init__(**kwargs) # Calling the parent initializtion function
### Routines specific to GeometryField ### ### Routines specific to GeometryField ###