From b00e82fe2c0a907ce983b071ece07d261301db7e Mon Sep 17 00:00:00 2001 From: Justin Bronn Date: Wed, 6 Aug 2008 21:40:00 +0000 Subject: [PATCH] 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 --- django/contrib/gis/db/backend/mysql/query.py | 118 +++---- django/contrib/gis/db/backend/oracle/query.py | 308 +++++++++--------- .../contrib/gis/db/models/fields/__init__.py | 9 +- 3 files changed, 219 insertions(+), 216 deletions(-) diff --git a/django/contrib/gis/db/backend/mysql/query.py b/django/contrib/gis/db/backend/mysql/query.py index 6522235135..2fa984f325 100644 --- a/django/contrib/gis/db/backend/mysql/query.py +++ b/django/contrib/gis/db/backend/mysql/query.py @@ -1,59 +1,59 @@ -""" - This module contains the spatial lookup types, and the `get_geo_where_clause` - routine for MySQL. - - Please note that MySQL only supports bounding box queries, also - known as MBRs (Minimum Bounding Rectangles). Moreover, spatial - indices may only be used on MyISAM tables -- if you need - transactions, take a look at PostGIS. -""" -from django.db import connection -qn = connection.ops.quote_name - -# To ease implementation, WKT is passed to/from MySQL. -GEOM_FROM_TEXT = 'GeomFromText' -GEOM_FROM_WKB = 'GeomFromWKB' -GEOM_SELECT = 'AsText(%s)' - -# WARNING: MySQL is NOT compliant w/the OpenGIS specification and -# _every_ one of these lookup types is on the _bounding box_ only. -MYSQL_GIS_FUNCTIONS = { - 'bbcontains' : 'MBRContains', # For consistency w/PostGIS API - 'bboverlaps' : 'MBROverlaps', # .. .. - 'contained' : 'MBRWithin', # .. .. - 'contains' : 'MBRContains', - 'disjoint' : 'MBRDisjoint', - 'equals' : 'MBREqual', - 'exact' : 'MBREqual', - 'intersects' : 'MBRIntersects', - 'overlaps' : 'MBROverlaps', - 'same_as' : 'MBREqual', - 'touches' : 'MBRTouches', - 'within' : 'MBRWithin', - } - -# This lookup type does not require a mapping. -MISC_TERMS = ['isnull'] - -# Assacceptable lookup types for Oracle spatial. -MYSQL_GIS_TERMS = MYSQL_GIS_FUNCTIONS.keys() -MYSQL_GIS_TERMS += MISC_TERMS -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): - "Returns the SQL WHERE clause for use in MySQL spatial SQL construction." - # Getting the quoted field as `geo_col`. - geo_col = '%s.%s' % (qn(table_alias), qn(name)) - - # See if a MySQL Geometry function matches the lookup type next - lookup_info = MYSQL_GIS_FUNCTIONS.get(lookup_type, False) - if lookup_info: - return "%s(%s, %%s)" % (lookup_info, geo_col) - - # Handling 'isnull' lookup type - # TODO: Is this needed because MySQL cannot handle NULL - # geometries in its spatial indices. - if lookup_type == 'isnull': - return "%s IS %sNULL" % (geo_col, (not geo_annot.value and 'NOT ' or '')) - - raise TypeError("Got invalid lookup_type: %s" % repr(lookup_type)) +""" + This module contains the spatial lookup types, and the `get_geo_where_clause` + routine for MySQL. + + Please note that MySQL only supports bounding box queries, also + known as MBRs (Minimum Bounding Rectangles). Moreover, spatial + indices may only be used on MyISAM tables -- if you need + transactions, take a look at PostGIS. +""" +from django.db import connection +qn = connection.ops.quote_name + +# To ease implementation, WKT is passed to/from MySQL. +GEOM_FROM_TEXT = 'GeomFromText' +GEOM_FROM_WKB = 'GeomFromWKB' +GEOM_SELECT = 'AsText(%s)' + +# WARNING: MySQL is NOT compliant w/the OpenGIS specification and +# _every_ one of these lookup types is on the _bounding box_ only. +MYSQL_GIS_FUNCTIONS = { + 'bbcontains' : 'MBRContains', # For consistency w/PostGIS API + 'bboverlaps' : 'MBROverlaps', # .. .. + 'contained' : 'MBRWithin', # .. .. + 'contains' : 'MBRContains', + 'disjoint' : 'MBRDisjoint', + 'equals' : 'MBREqual', + 'exact' : 'MBREqual', + 'intersects' : 'MBRIntersects', + 'overlaps' : 'MBROverlaps', + 'same_as' : 'MBREqual', + 'touches' : 'MBRTouches', + 'within' : 'MBRWithin', + } + +# This lookup type does not require a mapping. +MISC_TERMS = ['isnull'] + +# Assacceptable lookup types for Oracle spatial. +MYSQL_GIS_TERMS = MYSQL_GIS_FUNCTIONS.keys() +MYSQL_GIS_TERMS += MISC_TERMS +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): + "Returns the SQL WHERE clause for use in MySQL spatial SQL construction." + # Getting the quoted field as `geo_col`. + geo_col = '%s.%s' % (qn(table_alias), qn(name)) + + # See if a MySQL Geometry function matches the lookup type next + lookup_info = MYSQL_GIS_FUNCTIONS.get(lookup_type, False) + if lookup_info: + return "%s(%s, %%s)" % (lookup_info, geo_col) + + # Handling 'isnull' lookup type + # TODO: Is this needed because MySQL cannot handle NULL + # geometries in its spatial indices. + if lookup_type == 'isnull': + return "%s IS %sNULL" % (geo_col, (not geo_annot.value and 'NOT ' or '')) + + raise TypeError("Got invalid lookup_type: %s" % repr(lookup_type)) diff --git a/django/contrib/gis/db/backend/oracle/query.py b/django/contrib/gis/db/backend/oracle/query.py index 35e30d40eb..dcf6f67ae2 100644 --- a/django/contrib/gis/db/backend/oracle/query.py +++ b/django/contrib/gis/db/backend/oracle/query.py @@ -1,154 +1,154 @@ -""" - This module contains the spatial lookup types, and the `get_geo_where_clause` - routine for Oracle Spatial. - - Please note that WKT support is broken on the XE version, and thus - this backend will not work on such platforms. Specifically, XE lacks - support for an internal JVM, and Java libraries are required to use - the WKT constructors. -""" -import re -from decimal import Decimal -from django.db import connection -from django.contrib.gis.db.backend.util import SpatialFunction -from django.contrib.gis.measure import Distance -qn = connection.ops.quote_name - -# The GML, distance, transform, and union procedures. -AREA = 'SDO_GEOM.SDO_AREA' -ASGML = 'SDO_UTIL.TO_GMLGEOMETRY' -CENTROID = 'SDO_GEOM.SDO_CENTROID' -DIFFERENCE = 'SDO_GEOM.SDO_DIFFERENCE' -DISTANCE = 'SDO_GEOM.SDO_DISTANCE' -EXTENT = 'SDO_AGGR_MBR' -INTERSECTION = 'SDO_GEOM.SDO_INTERSECTION' -LENGTH = 'SDO_GEOM.SDO_LENGTH' -NUM_GEOM = 'SDO_UTIL.GETNUMELEM' -NUM_POINTS = 'SDO_UTIL.GETNUMVERTICES' -POINT_ON_SURFACE = 'SDO_GEOM.SDO_POINTONSURFACE' -SYM_DIFFERENCE = 'SDO_GEOM.SDO_XOR' -TRANSFORM = 'SDO_CS.TRANSFORM' -UNION = 'SDO_GEOM.SDO_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. -# However, this adversely affects performance (i.e., Java is called -# to convert to WKT on every query). If someone wishes to write a -# SDO_GEOMETRY(...) parser in Python, let me know =) -GEOM_SELECT = 'SDO_UTIL.TO_WKTGEOMETRY(%s)' - -#### Classes used in constructing Oracle spatial SQL #### -class SDOOperation(SpatialFunction): - "Base class for SDO* Oracle operations." - def __init__(self, func, **kwargs): - kwargs.setdefault('operator', '=') - kwargs.setdefault('result', 'TRUE') - kwargs.setdefault('end_subst', ") %s '%s'") - super(SDOOperation, self).__init__(func, **kwargs) - -class SDODistance(SpatialFunction): - "Class for Distance queries." - def __init__(self, op, tolerance=0.05): - super(SDODistance, self).__init__(DISTANCE, end_subst=', %s) %%s %%s' % tolerance, - operator=op, result='%%s') - -class SDOGeomRelate(SpatialFunction): - "Class for using SDO_GEOM.RELATE." - def __init__(self, mask, tolerance=0.05): - # 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'). - end_subst = "%s%s) %s '%s'" % (', %%s, ', tolerance, '=', mask) - beg_subst = "%%s(%%s, '%s'" % mask - super(SDOGeomRelate, self).__init__('SDO_GEOM.RELATE', beg_subst=beg_subst, end_subst=end_subst) - -class SDORelate(SpatialFunction): - "Class for using SDO_RELATE." - masks = 'TOUCH|OVERLAPBDYDISJOINT|OVERLAPBDYINTERSECT|EQUAL|INSIDE|COVEREDBY|CONTAINS|COVERS|ANYINTERACT|ON' - mask_regex = re.compile(r'^(%s)(\+(%s))*$' % (masks, masks), re.I) - def __init__(self, mask): - func = 'SDO_RELATE' - if not self.mask_regex.match(mask): - raise ValueError('Invalid %s mask: "%s"' % (func, mask)) - super(SDORelate, self).__init__(func, end_subst=", 'mask=%s') = 'TRUE'" % mask) - -#### Lookup type mapping dictionaries of Oracle spatial operations #### - -# Valid distance types and substitutions -dtypes = (Decimal, Distance, float, int, long) -DISTANCE_FUNCTIONS = { - 'distance_gt' : (SDODistance('>'), dtypes), - 'distance_gte' : (SDODistance('>='), dtypes), - 'distance_lt' : (SDODistance('<'), dtypes), - 'distance_lte' : (SDODistance('<='), dtypes), - 'dwithin' : (SDOOperation('SDO_WITHIN_DISTANCE', - beg_subst="%s(%s, %%s, 'distance=%%s'"), dtypes), - } - -ORACLE_GEOMETRY_FUNCTIONS = { - 'contains' : SDOOperation('SDO_CONTAINS'), - 'coveredby' : SDOOperation('SDO_COVEREDBY'), - 'covers' : SDOOperation('SDO_COVERS'), - 'disjoint' : SDOGeomRelate('DISJOINT'), - 'intersects' : SDOOperation('SDO_OVERLAPBDYINTERSECT'), # TODO: Is this really the same as ST_Intersects()? - 'equals' : SDOOperation('SDO_EQUAL'), - 'exact' : SDOOperation('SDO_EQUAL'), - 'overlaps' : SDOOperation('SDO_OVERLAPS'), - 'same_as' : SDOOperation('SDO_EQUAL'), - 'relate' : (SDORelate, basestring), # Oracle uses a different syntax, e.g., 'mask=inside+touch' - 'touches' : SDOOperation('SDO_TOUCH'), - 'within' : SDOOperation('SDO_INSIDE'), - } -ORACLE_GEOMETRY_FUNCTIONS.update(DISTANCE_FUNCTIONS) - -# This lookup type does not require a mapping. -MISC_TERMS = ['isnull'] - -# Acceptable lookup types for Oracle spatial. -ORACLE_SPATIAL_TERMS = ORACLE_GEOMETRY_FUNCTIONS.keys() -ORACLE_SPATIAL_TERMS += MISC_TERMS -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 #### -def get_geo_where_clause(table_alias, name, lookup_type, geo_annot): - "Returns the SQL WHERE clause for use in Oracle spatial SQL construction." - # Getting the quoted table name as `geo_col`. - geo_col = '%s.%s' % (qn(table_alias), qn(name)) - - # See if a Oracle Geometry function matches the lookup type next - lookup_info = ORACLE_GEOMETRY_FUNCTIONS.get(lookup_type, False) - if lookup_info: - # Lookup types that are tuples take tuple arguments, e.g., 'relate' and - # 'dwithin' lookup types. - if isinstance(lookup_info, tuple): - # First element of tuple is lookup type, second element is the type - # of the expected argument (e.g., str, float) - sdo_op, arg_type = lookup_info - - # Ensuring that a tuple _value_ was passed in from the user - if not isinstance(geo_annot.value, tuple): - raise TypeError('Tuple required for `%s` lookup type.' % lookup_type) - if len(geo_annot.value) != 2: - raise ValueError('2-element tuple required for %s lookup type.' % lookup_type) - - # Ensuring the argument type matches what we expect. - 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]))) - - if lookup_type == 'relate': - # The SDORelate class handles construction for these queries, - # and verifies the mask argument. - return sdo_op(geo_annot.value[1]).as_sql(geo_col) - else: - # Otherwise, just call the `as_sql` method on the SDOOperation instance. - return sdo_op.as_sql(geo_col) - 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) - elif lookup_type == 'isnull': - # Handling 'isnull' lookup type - return "%s IS %sNULL" % (geo_col, (not geo_annot.value and 'NOT ' or '')) - - raise TypeError("Got invalid lookup_type: %s" % repr(lookup_type)) +""" + This module contains the spatial lookup types, and the `get_geo_where_clause` + routine for Oracle Spatial. + + Please note that WKT support is broken on the XE version, and thus + this backend will not work on such platforms. Specifically, XE lacks + support for an internal JVM, and Java libraries are required to use + the WKT constructors. +""" +import re +from decimal import Decimal +from django.db import connection +from django.contrib.gis.db.backend.util import SpatialFunction +from django.contrib.gis.measure import Distance +qn = connection.ops.quote_name + +# The GML, distance, transform, and union procedures. +AREA = 'SDO_GEOM.SDO_AREA' +ASGML = 'SDO_UTIL.TO_GMLGEOMETRY' +CENTROID = 'SDO_GEOM.SDO_CENTROID' +DIFFERENCE = 'SDO_GEOM.SDO_DIFFERENCE' +DISTANCE = 'SDO_GEOM.SDO_DISTANCE' +EXTENT = 'SDO_AGGR_MBR' +INTERSECTION = 'SDO_GEOM.SDO_INTERSECTION' +LENGTH = 'SDO_GEOM.SDO_LENGTH' +NUM_GEOM = 'SDO_UTIL.GETNUMELEM' +NUM_POINTS = 'SDO_UTIL.GETNUMVERTICES' +POINT_ON_SURFACE = 'SDO_GEOM.SDO_POINTONSURFACE' +SYM_DIFFERENCE = 'SDO_GEOM.SDO_XOR' +TRANSFORM = 'SDO_CS.TRANSFORM' +UNION = 'SDO_GEOM.SDO_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. +# However, this adversely affects performance (i.e., Java is called +# to convert to WKT on every query). If someone wishes to write a +# SDO_GEOMETRY(...) parser in Python, let me know =) +GEOM_SELECT = 'SDO_UTIL.TO_WKTGEOMETRY(%s)' + +#### Classes used in constructing Oracle spatial SQL #### +class SDOOperation(SpatialFunction): + "Base class for SDO* Oracle operations." + def __init__(self, func, **kwargs): + kwargs.setdefault('operator', '=') + kwargs.setdefault('result', 'TRUE') + kwargs.setdefault('end_subst', ") %s '%s'") + super(SDOOperation, self).__init__(func, **kwargs) + +class SDODistance(SpatialFunction): + "Class for Distance queries." + def __init__(self, op, tolerance=0.05): + super(SDODistance, self).__init__(DISTANCE, end_subst=', %s) %%s %%s' % tolerance, + operator=op, result='%%s') + +class SDOGeomRelate(SpatialFunction): + "Class for using SDO_GEOM.RELATE." + def __init__(self, mask, tolerance=0.05): + # 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'). + end_subst = "%s%s) %s '%s'" % (', %%s, ', tolerance, '=', mask) + beg_subst = "%%s(%%s, '%s'" % mask + super(SDOGeomRelate, self).__init__('SDO_GEOM.RELATE', beg_subst=beg_subst, end_subst=end_subst) + +class SDORelate(SpatialFunction): + "Class for using SDO_RELATE." + masks = 'TOUCH|OVERLAPBDYDISJOINT|OVERLAPBDYINTERSECT|EQUAL|INSIDE|COVEREDBY|CONTAINS|COVERS|ANYINTERACT|ON' + mask_regex = re.compile(r'^(%s)(\+(%s))*$' % (masks, masks), re.I) + def __init__(self, mask): + func = 'SDO_RELATE' + if not self.mask_regex.match(mask): + raise ValueError('Invalid %s mask: "%s"' % (func, mask)) + super(SDORelate, self).__init__(func, end_subst=", 'mask=%s') = 'TRUE'" % mask) + +#### Lookup type mapping dictionaries of Oracle spatial operations #### + +# Valid distance types and substitutions +dtypes = (Decimal, Distance, float, int, long) +DISTANCE_FUNCTIONS = { + 'distance_gt' : (SDODistance('>'), dtypes), + 'distance_gte' : (SDODistance('>='), dtypes), + 'distance_lt' : (SDODistance('<'), dtypes), + 'distance_lte' : (SDODistance('<='), dtypes), + 'dwithin' : (SDOOperation('SDO_WITHIN_DISTANCE', + beg_subst="%s(%s, %%s, 'distance=%%s'"), dtypes), + } + +ORACLE_GEOMETRY_FUNCTIONS = { + 'contains' : SDOOperation('SDO_CONTAINS'), + 'coveredby' : SDOOperation('SDO_COVEREDBY'), + 'covers' : SDOOperation('SDO_COVERS'), + 'disjoint' : SDOGeomRelate('DISJOINT'), + 'intersects' : SDOOperation('SDO_OVERLAPBDYINTERSECT'), # TODO: Is this really the same as ST_Intersects()? + 'equals' : SDOOperation('SDO_EQUAL'), + 'exact' : SDOOperation('SDO_EQUAL'), + 'overlaps' : SDOOperation('SDO_OVERLAPS'), + 'same_as' : SDOOperation('SDO_EQUAL'), + 'relate' : (SDORelate, basestring), # Oracle uses a different syntax, e.g., 'mask=inside+touch' + 'touches' : SDOOperation('SDO_TOUCH'), + 'within' : SDOOperation('SDO_INSIDE'), + } +ORACLE_GEOMETRY_FUNCTIONS.update(DISTANCE_FUNCTIONS) + +# This lookup type does not require a mapping. +MISC_TERMS = ['isnull'] + +# Acceptable lookup types for Oracle spatial. +ORACLE_SPATIAL_TERMS = ORACLE_GEOMETRY_FUNCTIONS.keys() +ORACLE_SPATIAL_TERMS += MISC_TERMS +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 #### +def get_geo_where_clause(table_alias, name, lookup_type, geo_annot): + "Returns the SQL WHERE clause for use in Oracle spatial SQL construction." + # Getting the quoted table name as `geo_col`. + geo_col = '%s.%s' % (qn(table_alias), qn(name)) + + # See if a Oracle Geometry function matches the lookup type next + lookup_info = ORACLE_GEOMETRY_FUNCTIONS.get(lookup_type, False) + if lookup_info: + # Lookup types that are tuples take tuple arguments, e.g., 'relate' and + # 'dwithin' lookup types. + if isinstance(lookup_info, tuple): + # First element of tuple is lookup type, second element is the type + # of the expected argument (e.g., str, float) + sdo_op, arg_type = lookup_info + + # Ensuring that a tuple _value_ was passed in from the user + if not isinstance(geo_annot.value, tuple): + raise TypeError('Tuple required for `%s` lookup type.' % lookup_type) + if len(geo_annot.value) != 2: + raise ValueError('2-element tuple required for %s lookup type.' % lookup_type) + + # Ensuring the argument type matches what we expect. + 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]))) + + if lookup_type == 'relate': + # The SDORelate class handles construction for these queries, + # and verifies the mask argument. + return sdo_op(geo_annot.value[1]).as_sql(geo_col) + else: + # Otherwise, just call the `as_sql` method on the SDOOperation instance. + return sdo_op.as_sql(geo_col) + 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) + elif lookup_type == 'isnull': + # Handling 'isnull' lookup type + return "%s IS %sNULL" % (geo_col, (not geo_annot.value and 'NOT ' or '')) + + raise TypeError("Got invalid lookup_type: %s" % repr(lookup_type)) diff --git a/django/contrib/gis/db/models/fields/__init__.py b/django/contrib/gis/db/models/fields/__init__.py index d32f1b118e..cc4d0a70b5 100644 --- a/django/contrib/gis/db/models/fields/__init__.py +++ b/django/contrib/gis/db/models/fields/__init__.py @@ -1,5 +1,4 @@ from django.contrib.gis import forms -from django.db import connection # Getting the SpatialBackend container and the geographic quoting method. from django.contrib.gis.db.backend import SpatialBackend, gqn # GeometryProxy, GEOS, Distance, and oldforms imports. @@ -21,7 +20,7 @@ class GeometryField(SpatialBackend.Field): # Geodetic units. 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 as keyword arguments: @@ -49,7 +48,11 @@ class GeometryField(SpatialBackend.Field): # Setting the dimension of the geometry field. 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 ### Routines specific to GeometryField ###