--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/web/lib/django/contrib/gis/db/backends/spatialite/operations.py Tue May 25 02:43:45 2010 +0200
@@ -0,0 +1,343 @@
+import re
+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.backend import Geometry
+from django.contrib.gis.measure import Distance
+from django.core.exceptions import ImproperlyConfigured
+from django.db.backends.sqlite3.base import DatabaseOperations
+from django.db.utils import DatabaseError
+
+class SpatiaLiteOperator(SpatialOperation):
+ "For SpatiaLite operators (e.g. `&&`, `~`)."
+ def __init__(self, operator):
+ super(SpatiaLiteOperator, self).__init__(operator=operator)
+
+class SpatiaLiteFunction(SpatialFunction):
+ "For SpatiaLite function calls."
+ def __init__(self, function, **kwargs):
+ super(SpatiaLiteFunction, self).__init__(function, **kwargs)
+
+class SpatiaLiteFunctionParam(SpatiaLiteFunction):
+ "For SpatiaLite functions that take another parameter."
+ sql_template = '%(function)s(%(geo_col)s, %(geometry)s, %%s)'
+
+class SpatiaLiteDistance(SpatiaLiteFunction):
+ "For SpatiaLite distance operations."
+ dist_func = 'Distance'
+ sql_template = '%(function)s(%(geo_col)s, %(geometry)s) %(operator)s %%s'
+
+ def __init__(self, operator):
+ super(SpatiaLiteDistance, self).__init__(self.dist_func,
+ operator=operator)
+
+class SpatiaLiteRelate(SpatiaLiteFunctionParam):
+ "For SpatiaLite Relate(<geom>, <pattern>) calls."
+ pattern_regex = re.compile(r'^[012TF\*]{9}$')
+ def __init__(self, pattern):
+ if not self.pattern_regex.match(pattern):
+ raise ValueError('Invalid intersection matrix pattern "%s".' % pattern)
+ super(SpatiaLiteRelate, self).__init__('Relate')
+
+# Valid distance types and substitutions
+dtypes = (Decimal, Distance, float, int, long)
+def get_dist_ops(operator):
+ "Returns operations for regular distances; spherical distances are not currently supported."
+ return (SpatiaLiteDistance(operator),)
+
+class SpatiaLiteOperations(DatabaseOperations, BaseSpatialOperations):
+ compiler_module = 'django.contrib.gis.db.models.sql.compiler'
+ name = 'spatialite'
+ spatialite = True
+ version_regex = re.compile(r'^(?P<major>\d)\.(?P<minor1>\d)\.(?P<minor2>\d+)')
+ valid_aggregates = dict([(k, None) for k in ('Extent', 'Union')])
+
+ Adapter = SpatiaLiteAdapter
+ Adaptor = Adapter # Backwards-compatibility alias.
+
+ area = 'Area'
+ centroid = 'Centroid'
+ contained = 'MbrWithin'
+ difference = 'Difference'
+ distance = 'Distance'
+ envelope = 'Envelope'
+ intersection = 'Intersection'
+ length = 'GLength' # OpenGis defines Length, but this conflicts with an SQLite reserved keyword
+ num_geom = 'NumGeometries'
+ num_points = 'NumPoints'
+ point_on_surface = 'PointOnSurface'
+ scale = 'ScaleCoords'
+ svg = 'AsSVG'
+ sym_difference = 'SymDifference'
+ transform = 'Transform'
+ translate = 'ShiftCoords'
+ union = 'GUnion' # OpenGis defines Union, but this conflicts with an SQLite reserved keyword
+ unionagg = 'GUnion'
+
+ from_text = 'GeomFromText'
+ from_wkb = 'GeomFromWKB'
+ select = 'AsText(%s)'
+
+ geometry_functions = {
+ 'equals' : SpatiaLiteFunction('Equals'),
+ 'disjoint' : SpatiaLiteFunction('Disjoint'),
+ 'touches' : SpatiaLiteFunction('Touches'),
+ 'crosses' : SpatiaLiteFunction('Crosses'),
+ 'within' : SpatiaLiteFunction('Within'),
+ 'overlaps' : SpatiaLiteFunction('Overlaps'),
+ 'contains' : SpatiaLiteFunction('Contains'),
+ 'intersects' : SpatiaLiteFunction('Intersects'),
+ 'relate' : (SpatiaLiteRelate, basestring),
+ # Retruns true if B's bounding box completely contains A's bounding box.
+ 'contained' : SpatiaLiteFunction('MbrWithin'),
+ # Returns true if A's bounding box completely contains B's bounding box.
+ 'bbcontains' : SpatiaLiteFunction('MbrContains'),
+ # Returns true if A's bounding box overlaps B's bounding box.
+ 'bboverlaps' : SpatiaLiteFunction('MbrOverlaps'),
+ # These are implemented here as synonyms for Equals
+ 'same_as' : SpatiaLiteFunction('Equals'),
+ 'exact' : SpatiaLiteFunction('Equals'),
+ }
+
+ distance_functions = {
+ 'distance_gt' : (get_dist_ops('>'), dtypes),
+ 'distance_gte' : (get_dist_ops('>='), dtypes),
+ 'distance_lt' : (get_dist_ops('<'), dtypes),
+ 'distance_lte' : (get_dist_ops('<='), dtypes),
+ }
+ geometry_functions.update(distance_functions)
+
+ def __init__(self, connection):
+ super(DatabaseOperations, self).__init__()
+ self.connection = connection
+
+ # Determine the version of the SpatiaLite library.
+ try:
+ vtup = self.spatialite_version_tuple()
+ version = vtup[1:]
+ if version < (2, 3, 0):
+ raise ImproperlyConfigured('GeoDjango only supports SpatiaLite versions '
+ '2.3.0 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']
+ gis_terms += self.geometry_functions.keys()
+ self.gis_terms = dict([(term, None) for term in gis_terms])
+
+ def check_aggregate_support(self, aggregate):
+ """
+ Checks if the given aggregate name is supported (that is, if it's
+ in `self.valid_aggregates`).
+ """
+ agg_name = aggregate.__class__.__name__
+ return agg_name in self.valid_aggregates
+
+ def convert_geom(self, wkt, geo_field):
+ """
+ Converts geometry WKT returned from a SpatiaLite aggregate.
+ """
+ if wkt:
+ return Geometry(wkt, geo_field.srid)
+ else:
+ return None
+
+ 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
+ Transform() and GeomFromText() function call(s).
+ """
+ def transform_value(value, srid):
+ return not (value is None or value.srid == srid)
+ if hasattr(value, 'expression'):
+ 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, f.srid):
+ # Adding Transform() to the SQL placeholder.
+ return '%s(%s(%%s,%s), %s)' % (self.transform, self.from_text, value.srid, f.srid)
+ else:
+ return '%s(%%s,%s)' % (self.from_text, f.srid)
+
+ def _get_spatialite_func(self, func):
+ """
+ Helper routine for calling SpatiaLite functions and returning
+ their result.
+ """
+ cursor = self.connection._cursor()
+ try:
+ try:
+ cursor.execute('SELECT %s' % func)
+ row = cursor.fetchone()
+ except:
+ # Responsibility of caller to perform error handling.
+ raise
+ finally:
+ cursor.close()
+ return row[0]
+
+ def geos_version(self):
+ "Returns the version of GEOS used by SpatiaLite as a string."
+ return self._get_spatialite_func('geos_version()')
+
+ def proj4_version(self):
+ "Returns the version of the PROJ.4 library used by SpatiaLite."
+ return self._get_spatialite_func('proj4_version()')
+
+ def spatialite_version(self):
+ "Returns the SpatiaLite library version as a string."
+ return self._get_spatialite_func('spatialite_version()')
+
+ def spatialite_version_tuple(self):
+ """
+ Returns the SpatiaLite version as a tuple (version string, major,
+ minor, subminor).
+ """
+ # Getting the SpatiaLite version.
+ try:
+ version = self.spatialite_version()
+ except DatabaseError:
+ # The `spatialite_version` function first appeared in version 2.3.1
+ # of SpatiaLite, so doing a fallback test for 2.3.0 (which is
+ # used by popular Debian/Ubuntu packages).
+ version = None
+ try:
+ tmp = self._get_spatialite_func("X(GeomFromText('POINT(1 1)'))")
+ if tmp == 1.0: version = '2.3.0'
+ except DatabaseError:
+ pass
+ # If no version string defined, then just re-raise the original
+ # exception.
+ if version is None: raise
+
+ m = self.version_regex.match(version)
+ if m:
+ major = int(m.group('major'))
+ minor1 = int(m.group('minor1'))
+ minor2 = int(m.group('minor2'))
+ else:
+ raise Exception('Could not parse SpatiaLite version string: %s' % version)
+
+ return (version, major, minor1, minor2)
+
+ def spatial_aggregate_sql(self, agg):
+ """
+ Returns the spatial aggregate SQL template and function for the
+ given Aggregate instance.
+ """
+ agg_name = agg.__class__.__name__
+ if not self.check_aggregate_support(agg):
+ raise NotImplementedError('%s spatial aggregate is not implmented for this backend.' % agg_name)
+ agg_name = agg_name.lower()
+ if agg_name == 'union': agg_name += 'agg'
+ sql_template = self.select % '%(function)s(%(field)s)'
+ sql_function = getattr(self, agg_name)
+ return sql_template, sql_function
+
+ 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, the model field, and the quoting function.
+ """
+ alias, col, db_type = lvalue
+
+ # Getting the quoted field as `geo_col`.
+ geo_col = '%s.%s' % (qn(alias), qn(col))
+
+ if lookup_type in self.geometry_functions:
+ # See if a SpatiaLite geometry function matches the lookup type.
+ tmp = self.geometry_functions[lookup_type]
+
+ # Lookup types that are tuples take tuple arguments, e.g., 'relate' and
+ # distance lookups.
+ if isinstance(tmp, tuple):
+ # First element of tuple is the SpatiaLiteOperation instance, and the
+ # second element is either the type or a tuple of acceptable types
+ # that may passed in as further parameters for the lookup type.
+ op, arg_type = tmp
+
+ # Ensuring that a tuple _value_ was passed in from the user
+ if not isinstance(value, (tuple, list)):
+ raise ValueError('Tuple required for `%s` lookup type.' % lookup_type)
+
+ # Geometry is first element of lookup tuple.
+ geom = value[0]
+
+ # Number of valid tuple parameters depends on the lookup type.
+ if len(value) != 2:
+ raise ValueError('Incorrect number of parameters given for `%s` lookup type.' % lookup_type)
+
+ # Ensuring the argument type matches what we expect.
+ if not isinstance(value[1], arg_type):
+ raise ValueError('Argument type should be %s, got %s instead.' % (arg_type, type(value[1])))
+
+ # For lookup type `relate`, the op instance is not yet created (has
+ # to be instantiated here to check the pattern parameter).
+ if lookup_type == 'relate':
+ op = op(value[1])
+ elif lookup_type in self.distance_functions:
+ op = op[0]
+ else:
+ op = tmp
+ geom = value
+ # Calling the `as_sql` function on the operation instance.
+ 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 ''))
+
+ raise TypeError("Got invalid lookup_type: %s" % repr(lookup_type))
+
+ # Routines for getting the OGC-compliant models.
+ def geometry_columns(self):
+ from django.contrib.gis.db.backends.spatialite.models import GeometryColumns
+ return GeometryColumns
+
+ def spatial_ref_sys(self):
+ from django.contrib.gis.db.backends.spatialite.models import SpatialRefSys
+ return SpatialRefSys