diff options
author | ttt | 2017-05-13 00:29:47 +0530 |
---|---|---|
committer | ttt | 2017-05-13 00:29:47 +0530 |
commit | abf599be33b383a6a5baf9493093b2126a622ac8 (patch) | |
tree | 4c5ab6e0d935d5e65fabcf0258e4a00dd20a5afa /lib/python2.7/site-packages/django/contrib/gis/db | |
download | SBHS-2018-Rpi-abf599be33b383a6a5baf9493093b2126a622ac8.tar.gz SBHS-2018-Rpi-abf599be33b383a6a5baf9493093b2126a622ac8.tar.bz2 SBHS-2018-Rpi-abf599be33b383a6a5baf9493093b2126a622ac8.zip |
added all server files
Diffstat (limited to 'lib/python2.7/site-packages/django/contrib/gis/db')
46 files changed, 4562 insertions, 0 deletions
diff --git a/lib/python2.7/site-packages/django/contrib/gis/db/__init__.py b/lib/python2.7/site-packages/django/contrib/gis/db/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/lib/python2.7/site-packages/django/contrib/gis/db/__init__.py diff --git a/lib/python2.7/site-packages/django/contrib/gis/db/backends/__init__.py b/lib/python2.7/site-packages/django/contrib/gis/db/backends/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/lib/python2.7/site-packages/django/contrib/gis/db/backends/__init__.py diff --git a/lib/python2.7/site-packages/django/contrib/gis/db/backends/adapter.py b/lib/python2.7/site-packages/django/contrib/gis/db/backends/adapter.py new file mode 100644 index 0000000..ca77124 --- /dev/null +++ b/lib/python2.7/site-packages/django/contrib/gis/db/backends/adapter.py @@ -0,0 +1,19 @@ +class WKTAdapter(object): + """ + This provides an adaptor for Geometries sent to the + MySQL and Oracle database backends. + """ + def __init__(self, geom): + self.wkt = geom.wkt + self.srid = geom.srid + + def __eq__(self, other): + if not isinstance(other, WKTAdapter): + return False + return self.wkt == other.wkt and self.srid == other.srid + + def __str__(self): + return self.wkt + + def prepare_database_save(self, unused): + return self diff --git a/lib/python2.7/site-packages/django/contrib/gis/db/backends/base.py b/lib/python2.7/site-packages/django/contrib/gis/db/backends/base.py new file mode 100644 index 0000000..7db7ce5 --- /dev/null +++ b/lib/python2.7/site-packages/django/contrib/gis/db/backends/base.py @@ -0,0 +1,349 @@ +""" +Base/mixin classes for the spatial backend database operations and the +`SpatialRefSys` model the backend. +""" +import re + +from django.contrib.gis import gdal +from django.utils import six +from django.utils.encoding import python_2_unicode_compatible + + +class BaseSpatialOperations(object): + """ + This module holds the base `BaseSpatialBackend` object, which is + instantiated by each spatial database backend with the features + it has. + """ + distance_functions = {} + geometry_functions = {} + geometry_operators = {} + geography_operators = {} + geography_functions = {} + gis_terms = set() + truncate_params = {} + + # Quick booleans for the type of this spatial backend, and + # an attribute for the spatial database version tuple (if applicable) + postgis = False + spatialite = False + mysql = False + oracle = False + spatial_version = None + + # How the geometry column should be selected. + select = None + + # Does the spatial database have a geometry or geography type? + geography = False + geometry = False + + area = False + centroid = False + difference = False + distance = False + distance_sphere = False + distance_spheroid = False + 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 + reverse = False + scale = False + snap_to_grid = False + sym_difference = False + transform = False + translate = False + union = False + + # Aggregates + collect = False + extent = False + extent3d = False + make_line = False + unionagg = False + + # Serialization + geohash = False + geojson = False + gml = False + kml = False + svg = False + + # Constructors + from_text = False + from_wkb = False + + # Default conversion functions for aggregates; will be overridden if implemented + # for the spatial backend. + def convert_extent(self, box): + raise NotImplementedError('Aggregate extent not implemented for this spatial backend.') + + def convert_extent3d(self, box): + raise NotImplementedError('Aggregate 3D extent not implemented for this spatial backend.') + + 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): + 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 + + def get_expression_column(self, evaluator): + """ + Helper method to return the quoted column string from the evaluator + for its expression. + """ + for expr, col_tup in evaluator.cols: + if expr is evaluator.expression: + return '%s.%s' % tuple(map(self.quote_name, col_tup)) + raise Exception("Could not find the column for the expression.") + + # Spatial SQL Construction + def spatial_aggregate_sql(self, agg): + raise NotImplementedError('Aggregate support not implemented for this spatial backend.') + + def spatial_lookup_sql(self, lvalue, lookup_type, value, field): + raise NotImplementedError + + # Routines for getting the OGC-compliant models. + def geometry_columns(self): + raise NotImplementedError + + def spatial_ref_sys(self): + raise NotImplementedError + +@python_2_unicode_compatible +class SpatialRefSysMixin(object): + """ + The SpatialRefSysMixin is a class used by the database-dependent + SpatialRefSys objects to reduce redundnant code. + """ + # For pulling out the spheroid from the spatial reference string. This + # regular expression is used only if the user does not have GDAL installed. + # TODO: Flattening not used in all ellipsoids, could also be a minor axis, + # or 'b' parameter. + spheroid_regex = re.compile(r'.+SPHEROID\[\"(?P<name>.+)\",(?P<major>\d+(\.\d+)?),(?P<flattening>\d{3}\.\d+),') + + # For pulling out the units on platforms w/o GDAL installed. + # TODO: Figure out how to pull out angular units of projected coordinate system and + # fix for LOCAL_CS types. GDAL should be highly recommended for performing + # distance queries. + units_regex = re.compile(r'.+UNIT ?\["(?P<unit_name>[\w \'\(\)]+)", ?(?P<unit>[\d\.]+)(,AUTHORITY\["(?P<unit_auth_name>[\w \'\(\)]+)","(?P<unit_auth_val>\d+)"\])?\]([\w ]+)?(,AUTHORITY\["(?P<auth_name>[\w \'\(\)]+)","(?P<auth_val>\d+)"\])?\]$') + + @property + def srs(self): + """ + Returns a GDAL SpatialReference object, if GDAL is installed. + """ + if gdal.HAS_GDAL: + # TODO: Is caching really necessary here? Is complexity worth it? + if hasattr(self, '_srs'): + # Returning a clone of the cached SpatialReference object. + return self._srs.clone() + else: + # Attempting to cache a SpatialReference object. + + # Trying to get from WKT first. + try: + self._srs = gdal.SpatialReference(self.wkt) + return self.srs + except Exception as msg: + pass + + try: + self._srs = gdal.SpatialReference(self.proj4text) + return self.srs + except Exception as msg: + pass + + raise Exception('Could not get OSR SpatialReference from WKT: %s\nError:\n%s' % (self.wkt, msg)) + else: + raise Exception('GDAL is not installed.') + + @property + def ellipsoid(self): + """ + Returns a tuple of the ellipsoid parameters: + (semimajor axis, semiminor axis, and inverse flattening). + """ + if gdal.HAS_GDAL: + return self.srs.ellipsoid + else: + m = self.spheroid_regex.match(self.wkt) + if m: return (float(m.group('major')), float(m.group('flattening'))) + else: return None + + @property + def name(self): + "Returns the projection name." + return self.srs.name + + @property + def spheroid(self): + "Returns the spheroid name for this spatial reference." + return self.srs['spheroid'] + + @property + def datum(self): + "Returns the datum for this spatial reference." + return self.srs['datum'] + + @property + def projected(self): + "Is this Spatial Reference projected?" + if gdal.HAS_GDAL: + return self.srs.projected + else: + return self.wkt.startswith('PROJCS') + + @property + def local(self): + "Is this Spatial Reference local?" + if gdal.HAS_GDAL: + return self.srs.local + else: + return self.wkt.startswith('LOCAL_CS') + + @property + def geographic(self): + "Is this Spatial Reference geographic?" + if gdal.HAS_GDAL: + return self.srs.geographic + else: + return self.wkt.startswith('GEOGCS') + + @property + def linear_name(self): + "Returns the linear units name." + if gdal.HAS_GDAL: + return self.srs.linear_name + elif self.geographic: + return None + else: + m = self.units_regex.match(self.wkt) + return m.group('unit_name') + + @property + def linear_units(self): + "Returns the linear units." + if gdal.HAS_GDAL: + return self.srs.linear_units + elif self.geographic: + return None + else: + m = self.units_regex.match(self.wkt) + return m.group('unit') + + @property + def angular_name(self): + "Returns the name of the angular units." + if gdal.HAS_GDAL: + return self.srs.angular_name + elif self.projected: + return None + else: + m = self.units_regex.match(self.wkt) + return m.group('unit_name') + + @property + def angular_units(self): + "Returns the angular units." + if gdal.HAS_GDAL: + return self.srs.angular_units + elif self.projected: + return None + else: + m = self.units_regex.match(self.wkt) + return m.group('unit') + + @property + def units(self): + "Returns a tuple of the units and the name." + if self.projected or self.local: + return (self.linear_units, self.linear_name) + elif self.geographic: + return (self.angular_units, self.angular_name) + else: + return (None, None) + + @classmethod + def get_units(cls, wkt): + """ + Class method used by GeometryField on initialization to + retrive the units on the given WKT, without having to use + any of the database fields. + """ + if gdal.HAS_GDAL: + return gdal.SpatialReference(wkt).units + else: + m = cls.units_regex.match(wkt) + return m.group('unit'), m.group('unit_name') + + @classmethod + def get_spheroid(cls, wkt, string=True): + """ + Class method used by GeometryField on initialization to + retrieve the `SPHEROID[..]` parameters from the given WKT. + """ + if gdal.HAS_GDAL: + srs = gdal.SpatialReference(wkt) + sphere_params = srs.ellipsoid + sphere_name = srs['spheroid'] + else: + m = cls.spheroid_regex.match(wkt) + if m: + sphere_params = (float(m.group('major')), float(m.group('flattening'))) + sphere_name = m.group('name') + else: + return None + + if not string: + return sphere_name, sphere_params + else: + # `string` parameter used to place in format acceptable by PostGIS + if len(sphere_params) == 3: + radius, flattening = sphere_params[0], sphere_params[2] + else: + radius, flattening = sphere_params + return 'SPHEROID["%s",%s,%s]' % (sphere_name, radius, flattening) + + def __str__(self): + """ + Returns the string representation. If GDAL is installed, + it will be 'pretty' OGC WKT. + """ + try: + return six.text_type(self.srs) + except: + return six.text_type(self.wkt) diff --git a/lib/python2.7/site-packages/django/contrib/gis/db/backends/mysql/__init__.py b/lib/python2.7/site-packages/django/contrib/gis/db/backends/mysql/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/lib/python2.7/site-packages/django/contrib/gis/db/backends/mysql/__init__.py diff --git a/lib/python2.7/site-packages/django/contrib/gis/db/backends/mysql/base.py b/lib/python2.7/site-packages/django/contrib/gis/db/backends/mysql/base.py new file mode 100644 index 0000000..7d94458 --- /dev/null +++ b/lib/python2.7/site-packages/django/contrib/gis/db/backends/mysql/base.py @@ -0,0 +1,13 @@ +from django.db.backends.mysql.base import * +from django.db.backends.mysql.base import DatabaseWrapper as MySQLDatabaseWrapper +from django.contrib.gis.db.backends.mysql.creation import MySQLCreation +from django.contrib.gis.db.backends.mysql.introspection import MySQLIntrospection +from django.contrib.gis.db.backends.mysql.operations import MySQLOperations + +class DatabaseWrapper(MySQLDatabaseWrapper): + + def __init__(self, *args, **kwargs): + super(DatabaseWrapper, self).__init__(*args, **kwargs) + self.creation = MySQLCreation(self) + self.ops = MySQLOperations(self) + self.introspection = MySQLIntrospection(self) diff --git a/lib/python2.7/site-packages/django/contrib/gis/db/backends/mysql/compiler.py b/lib/python2.7/site-packages/django/contrib/gis/db/backends/mysql/compiler.py new file mode 100644 index 0000000..f4654ef --- /dev/null +++ b/lib/python2.7/site-packages/django/contrib/gis/db/backends/mysql/compiler.py @@ -0,0 +1,35 @@ +from django.contrib.gis.db.models.sql.compiler import GeoSQLCompiler as BaseGeoSQLCompiler +from django.db.backends.mysql import compiler + +SQLCompiler = compiler.SQLCompiler + +class GeoSQLCompiler(BaseGeoSQLCompiler, SQLCompiler): + def resolve_columns(self, row, fields=()): + """ + Integrate the cases handled both by the base GeoSQLCompiler and the + main MySQL compiler (converting 0/1 to True/False for boolean fields). + + Refs #15169. + + """ + row = BaseGeoSQLCompiler.resolve_columns(self, row, fields) + return SQLCompiler.resolve_columns(self, row, fields) + + +class SQLInsertCompiler(compiler.SQLInsertCompiler, GeoSQLCompiler): + pass + +class SQLDeleteCompiler(compiler.SQLDeleteCompiler, GeoSQLCompiler): + pass + +class SQLUpdateCompiler(compiler.SQLUpdateCompiler, GeoSQLCompiler): + pass + +class SQLAggregateCompiler(compiler.SQLAggregateCompiler, GeoSQLCompiler): + pass + +class SQLDateCompiler(compiler.SQLDateCompiler, GeoSQLCompiler): + pass + +class SQLDateTimeCompiler(compiler.SQLDateTimeCompiler, GeoSQLCompiler): + pass diff --git a/lib/python2.7/site-packages/django/contrib/gis/db/backends/mysql/creation.py b/lib/python2.7/site-packages/django/contrib/gis/db/backends/mysql/creation.py new file mode 100644 index 0000000..dda77ea --- /dev/null +++ b/lib/python2.7/site-packages/django/contrib/gis/db/backends/mysql/creation.py @@ -0,0 +1,18 @@ +from django.db.backends.mysql.creation import DatabaseCreation + +class MySQLCreation(DatabaseCreation): + + def sql_indexes_for_field(self, model, f, style): + from django.contrib.gis.db.models.fields import GeometryField + output = super(MySQLCreation, self).sql_indexes_for_field(model, f, style) + + if isinstance(f, GeometryField) and f.spatial_index: + qn = self.connection.ops.quote_name + db_table = model._meta.db_table + idx_name = '%s_%s_id' % (db_table, f.column) + output.append(style.SQL_KEYWORD('CREATE SPATIAL INDEX ') + + style.SQL_TABLE(qn(idx_name)) + + style.SQL_KEYWORD(' ON ') + + style.SQL_TABLE(qn(db_table)) + '(' + + style.SQL_FIELD(qn(f.column)) + ');') + return output diff --git a/lib/python2.7/site-packages/django/contrib/gis/db/backends/mysql/introspection.py b/lib/python2.7/site-packages/django/contrib/gis/db/backends/mysql/introspection.py new file mode 100644 index 0000000..59d0f62 --- /dev/null +++ b/lib/python2.7/site-packages/django/contrib/gis/db/backends/mysql/introspection.py @@ -0,0 +1,32 @@ +from MySQLdb.constants import FIELD_TYPE + +from django.contrib.gis.gdal import OGRGeomType +from django.db.backends.mysql.introspection import DatabaseIntrospection + +class MySQLIntrospection(DatabaseIntrospection): + # Updating the data_types_reverse dictionary with the appropriate + # type for Geometry fields. + data_types_reverse = DatabaseIntrospection.data_types_reverse.copy() + data_types_reverse[FIELD_TYPE.GEOMETRY] = 'GeometryField' + + def get_geometry_type(self, table_name, geo_col): + cursor = self.connection.cursor() + try: + # In order to get the specific geometry type of the field, + # we introspect on the table definition using `DESCRIBE`. + cursor.execute('DESCRIBE %s' % + self.connection.ops.quote_name(table_name)) + # Increment over description info until we get to the geometry + # column. + for column, typ, null, key, default, extra in cursor.fetchall(): + if column == geo_col: + # Using OGRGeomType to convert from OGC name to Django field. + # MySQL does not support 3D or SRIDs, so the field params + # are empty. + field_type = OGRGeomType(typ).django + field_params = {} + break + finally: + cursor.close() + + return field_type, field_params diff --git a/lib/python2.7/site-packages/django/contrib/gis/db/backends/mysql/operations.py b/lib/python2.7/site-packages/django/contrib/gis/db/backends/mysql/operations.py new file mode 100644 index 0000000..26cec74 --- /dev/null +++ b/lib/python2.7/site-packages/django/contrib/gis/db/backends/mysql/operations.py @@ -0,0 +1,67 @@ +from django.db.backends.mysql.base import DatabaseOperations + +from django.contrib.gis.db.backends.adapter import WKTAdapter +from django.contrib.gis.db.backends.base import BaseSpatialOperations + + +class MySQLOperations(DatabaseOperations, BaseSpatialOperations): + + compiler_module = 'django.contrib.gis.db.backends.mysql.compiler' + mysql = True + name = 'mysql' + select = 'AsText(%s)' + from_wkb = 'GeomFromWKB' + from_text = 'GeomFromText' + + Adapter = WKTAdapter + Adaptor = Adapter # Backwards-compatibility alias. + + geometry_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', + } + + gis_terms = set(geometry_functions) | set(['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 + MySQL does not support spatial transformations, there is no need to + modify the placeholder based on the contents of the given value. + """ + if hasattr(value, 'expression'): + placeholder = self.get_expression_column(value) + else: + placeholder = '%s(%%s)' % self.from_text + return placeholder + + def spatial_lookup_sql(self, lvalue, lookup_type, value, field, qn): + alias, col, db_type = lvalue + + geo_col = '%s.%s' % (qn(alias), qn(col)) + + lookup_info = self.geometry_functions.get(lookup_type, False) + if lookup_info: + sql = "%s(%s, %s)" % (lookup_info, geo_col, + self.get_geom_placeholder(value, field.srid)) + return sql, [] + + # TODO: Is this really necessary? MySQL can't handle NULL geometries + # in its spatial indexes anyways. + if lookup_type == 'isnull': + return "%s IS %sNULL" % (geo_col, ('' if value else 'NOT ')), [] + + raise TypeError("Got invalid lookup_type: %s" % repr(lookup_type)) diff --git a/lib/python2.7/site-packages/django/contrib/gis/db/backends/oracle/__init__.py b/lib/python2.7/site-packages/django/contrib/gis/db/backends/oracle/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/lib/python2.7/site-packages/django/contrib/gis/db/backends/oracle/__init__.py diff --git a/lib/python2.7/site-packages/django/contrib/gis/db/backends/oracle/adapter.py b/lib/python2.7/site-packages/django/contrib/gis/db/backends/oracle/adapter.py new file mode 100644 index 0000000..ea340d9 --- /dev/null +++ b/lib/python2.7/site-packages/django/contrib/gis/db/backends/oracle/adapter.py @@ -0,0 +1,5 @@ +from cx_Oracle import CLOB +from django.contrib.gis.db.backends.adapter import WKTAdapter + +class OracleSpatialAdapter(WKTAdapter): + input_size = CLOB diff --git a/lib/python2.7/site-packages/django/contrib/gis/db/backends/oracle/base.py b/lib/python2.7/site-packages/django/contrib/gis/db/backends/oracle/base.py new file mode 100644 index 0000000..398b3d3 --- /dev/null +++ b/lib/python2.7/site-packages/django/contrib/gis/db/backends/oracle/base.py @@ -0,0 +1,12 @@ +from django.db.backends.oracle.base import * +from django.db.backends.oracle.base import DatabaseWrapper as OracleDatabaseWrapper +from django.contrib.gis.db.backends.oracle.creation import OracleCreation +from django.contrib.gis.db.backends.oracle.introspection import OracleIntrospection +from django.contrib.gis.db.backends.oracle.operations import OracleOperations + +class DatabaseWrapper(OracleDatabaseWrapper): + def __init__(self, *args, **kwargs): + super(DatabaseWrapper, self).__init__(*args, **kwargs) + self.ops = OracleOperations(self) + self.creation = OracleCreation(self) + self.introspection = OracleIntrospection(self) diff --git a/lib/python2.7/site-packages/django/contrib/gis/db/backends/oracle/compiler.py b/lib/python2.7/site-packages/django/contrib/gis/db/backends/oracle/compiler.py new file mode 100644 index 0000000..d00af7f --- /dev/null +++ b/lib/python2.7/site-packages/django/contrib/gis/db/backends/oracle/compiler.py @@ -0,0 +1,25 @@ +from django.contrib.gis.db.models.sql.compiler import GeoSQLCompiler as BaseGeoSQLCompiler +from django.db.backends.oracle import compiler + +SQLCompiler = compiler.SQLCompiler + +class GeoSQLCompiler(BaseGeoSQLCompiler, SQLCompiler): + pass + +class SQLInsertCompiler(compiler.SQLInsertCompiler, GeoSQLCompiler): + pass + +class SQLDeleteCompiler(compiler.SQLDeleteCompiler, GeoSQLCompiler): + pass + +class SQLUpdateCompiler(compiler.SQLUpdateCompiler, GeoSQLCompiler): + pass + +class SQLAggregateCompiler(compiler.SQLAggregateCompiler, GeoSQLCompiler): + pass + +class SQLDateCompiler(compiler.SQLDateCompiler, GeoSQLCompiler): + pass + +class SQLDateTimeCompiler(compiler.SQLDateTimeCompiler, GeoSQLCompiler): + pass diff --git a/lib/python2.7/site-packages/django/contrib/gis/db/backends/oracle/creation.py b/lib/python2.7/site-packages/django/contrib/gis/db/backends/oracle/creation.py new file mode 100644 index 0000000..043da91 --- /dev/null +++ b/lib/python2.7/site-packages/django/contrib/gis/db/backends/oracle/creation.py @@ -0,0 +1,42 @@ +from django.db.backends.oracle.creation import DatabaseCreation +from django.db.backends.util import truncate_name + +class OracleCreation(DatabaseCreation): + + def sql_indexes_for_field(self, model, f, style): + "Return any spatial index creation SQL for the field." + from django.contrib.gis.db.models.fields import GeometryField + + output = super(OracleCreation, self).sql_indexes_for_field(model, f, style) + + if isinstance(f, GeometryField): + gqn = self.connection.ops.geo_quote_name + qn = self.connection.ops.quote_name + db_table = model._meta.db_table + + output.append(style.SQL_KEYWORD('INSERT INTO ') + + style.SQL_TABLE('USER_SDO_GEOM_METADATA') + + ' (%s, %s, %s, %s)\n ' % tuple(map(qn, ['TABLE_NAME', 'COLUMN_NAME', 'DIMINFO', 'SRID'])) + + style.SQL_KEYWORD(' VALUES ') + '(\n ' + + style.SQL_TABLE(gqn(db_table)) + ',\n ' + + style.SQL_FIELD(gqn(f.column)) + ',\n ' + + style.SQL_KEYWORD("MDSYS.SDO_DIM_ARRAY") + '(\n ' + + style.SQL_KEYWORD("MDSYS.SDO_DIM_ELEMENT") + + ("('LONG', %s, %s, %s),\n " % (f._extent[0], f._extent[2], f._tolerance)) + + style.SQL_KEYWORD("MDSYS.SDO_DIM_ELEMENT") + + ("('LAT', %s, %s, %s)\n ),\n" % (f._extent[1], f._extent[3], f._tolerance)) + + ' %s\n );' % f.srid) + + if f.spatial_index: + # Getting the index name, Oracle doesn't allow object + # names > 30 characters. + idx_name = truncate_name('%s_%s_id' % (db_table, f.column), 30) + + output.append(style.SQL_KEYWORD('CREATE INDEX ') + + style.SQL_TABLE(qn(idx_name)) + + style.SQL_KEYWORD(' ON ') + + style.SQL_TABLE(qn(db_table)) + '(' + + style.SQL_FIELD(qn(f.column)) + ') ' + + style.SQL_KEYWORD('INDEXTYPE IS ') + + style.SQL_TABLE('MDSYS.SPATIAL_INDEX') + ';') + return output diff --git a/lib/python2.7/site-packages/django/contrib/gis/db/backends/oracle/introspection.py b/lib/python2.7/site-packages/django/contrib/gis/db/backends/oracle/introspection.py new file mode 100644 index 0000000..d6c8f45 --- /dev/null +++ b/lib/python2.7/site-packages/django/contrib/gis/db/backends/oracle/introspection.py @@ -0,0 +1,44 @@ +import cx_Oracle +import sys +from django.db.backends.oracle.introspection import DatabaseIntrospection +from django.utils import six + +class OracleIntrospection(DatabaseIntrospection): + # Associating any OBJECTVAR instances with GeometryField. Of course, + # this won't work right on Oracle objects that aren't MDSYS.SDO_GEOMETRY, + # but it is the only object type supported within Django anyways. + data_types_reverse = DatabaseIntrospection.data_types_reverse.copy() + data_types_reverse[cx_Oracle.OBJECT] = 'GeometryField' + + def get_geometry_type(self, table_name, geo_col): + cursor = self.connection.cursor() + try: + # Querying USER_SDO_GEOM_METADATA to get the SRID and dimension information. + try: + cursor.execute('SELECT "DIMINFO", "SRID" FROM "USER_SDO_GEOM_METADATA" WHERE "TABLE_NAME"=%s AND "COLUMN_NAME"=%s', + (table_name.upper(), geo_col.upper())) + row = cursor.fetchone() + except Exception as msg: + new_msg = ( + 'Could not find entry in USER_SDO_GEOM_METADATA ' + 'corresponding to "%s"."%s"\n' + 'Error message: %s.') % (table_name, geo_col, msg) + six.reraise(Exception, Exception(new_msg), sys.exc_info()[2]) + + # TODO: Research way to find a more specific geometry field type for + # the column's contents. + field_type = 'GeometryField' + + # Getting the field parameters. + field_params = {} + dim, srid = row + if srid != 4326: + field_params['srid'] = srid + # Length of object array ( SDO_DIM_ARRAY ) is number of dimensions. + dim = len(dim) + if dim != 2: + field_params['dim'] = dim + finally: + cursor.close() + + return field_type, field_params diff --git a/lib/python2.7/site-packages/django/contrib/gis/db/backends/oracle/models.py b/lib/python2.7/site-packages/django/contrib/gis/db/backends/oracle/models.py new file mode 100644 index 0000000..b7deb3a --- /dev/null +++ b/lib/python2.7/site-packages/django/contrib/gis/db/backends/oracle/models.py @@ -0,0 +1,66 @@ +""" + The GeometryColumns and SpatialRefSys models for the Oracle spatial + backend. + + It should be noted that Oracle Spatial does not have database tables + named according to the OGC standard, so the closest analogs are used. + For example, the `USER_SDO_GEOM_METADATA` is used for the GeometryColumns + model and the `SDO_COORD_REF_SYS` is used for the SpatialRefSys model. +""" +from django.contrib.gis.db import models +from django.contrib.gis.db.backends.base import SpatialRefSysMixin +from django.utils.encoding import python_2_unicode_compatible + +@python_2_unicode_compatible +class GeometryColumns(models.Model): + "Maps to the Oracle USER_SDO_GEOM_METADATA table." + table_name = models.CharField(max_length=32) + column_name = models.CharField(max_length=1024) + srid = models.IntegerField(primary_key=True) + # TODO: Add support for `diminfo` column (type MDSYS.SDO_DIM_ARRAY). + class Meta: + db_table = 'USER_SDO_GEOM_METADATA' + managed = False + + @classmethod + def table_name_col(cls): + """ + Returns the name of the metadata column used to store the + the feature table name. + """ + return 'table_name' + + @classmethod + def geom_col_name(cls): + """ + Returns the name of the metadata column used to store the + the feature geometry column. + """ + return 'column_name' + + def __str__(self): + return '%s - %s (SRID: %s)' % (self.table_name, self.column_name, self.srid) + +class SpatialRefSys(models.Model, SpatialRefSysMixin): + "Maps to the Oracle MDSYS.CS_SRS table." + cs_name = models.CharField(max_length=68) + srid = models.IntegerField(primary_key=True) + auth_srid = models.IntegerField() + auth_name = models.CharField(max_length=256) + wktext = models.CharField(max_length=2046) + # Optional geometry representing the bounds of this coordinate + # system. By default, all are NULL in the table. + cs_bounds = models.PolygonField(null=True) + objects = models.GeoManager() + + class Meta: + db_table = 'CS_SRS' + managed = False + + @property + def wkt(self): + return self.wktext + + @classmethod + def wkt_col(cls): + return 'wktext' diff --git a/lib/python2.7/site-packages/django/contrib/gis/db/backends/oracle/operations.py b/lib/python2.7/site-packages/django/contrib/gis/db/backends/oracle/operations.py new file mode 100644 index 0000000..84217c3 --- /dev/null +++ b/lib/python2.7/site-packages/django/contrib/gis/db/backends/oracle/operations.py @@ -0,0 +1,304 @@ +""" + 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.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.backend import Geometry +from django.contrib.gis.measure import Distance +from django.utils import six + + +class SDOOperation(SpatialFunction): + "Base class for SDO* Oracle operations." + sql_template = "%(function)s(%(geo_col)s, %(geometry)s) %(operator)s '%(result)s'" + + def __init__(self, func, **kwargs): + kwargs.setdefault('operator', '=') + kwargs.setdefault('result', 'TRUE') + super(SDOOperation, self).__init__(func, **kwargs) + +class SDODistance(SpatialFunction): + "Class for Distance queries." + sql_template = ('%(function)s(%(geo_col)s, %(geometry)s, %(tolerance)s) ' + '%(operator)s %(result)s') + dist_func = 'SDO_GEOM.SDO_DISTANCE' + + def __init__(self, op, tolerance=0.05): + super(SDODistance, self).__init__(self.dist_func, + tolerance=tolerance, + operator=op, result='%s') + +class SDODWithin(SpatialFunction): + dwithin_func = 'SDO_WITHIN_DISTANCE' + sql_template = "%(function)s(%(geo_col)s, %(geometry)s, %%s) = 'TRUE'" + + def __init__(self): + super(SDODWithin, self).__init__(self.dwithin_func) + +class SDOGeomRelate(SpatialFunction): + "Class for using SDO_GEOM.RELATE." + relate_func = 'SDO_GEOM.RELATE' + sql_template = ("%(function)s(%(geo_col)s, '%(mask)s', %(geometry)s, " + "%(tolerance)s) %(operator)s '%(mask)s'") + + 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'). + super(SDOGeomRelate, self).__init__(self.relate_func, operator='=', + mask=mask, tolerance=tolerance) + +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) + sql_template = "%(function)s(%(geo_col)s, %(geometry)s, 'mask=%(mask)s') = 'TRUE'" + relate_func = 'SDO_RELATE' + + def __init__(self, mask): + if not self.mask_regex.match(mask): + raise ValueError('Invalid %s mask: "%s"' % (self.relate_func, mask)) + super(SDORelate, self).__init__(self.relate_func, mask=mask) + +# Valid distance types and substitutions +dtypes = (Decimal, Distance, float) + six.integer_types + +class OracleOperations(DatabaseOperations, BaseSpatialOperations): + compiler_module = "django.contrib.gis.db.backends.oracle.compiler" + + name = 'oracle' + oracle = True + valid_aggregates = dict([(a, None) for a in ('Union', 'Extent')]) + + Adapter = OracleSpatialAdapter + Adaptor = Adapter # Backwards-compatibility alias. + + area = 'SDO_GEOM.SDO_AREA' + gml = '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' + perimeter = length + point_on_surface = 'SDO_GEOM.SDO_POINTONSURFACE' + reverse = 'SDO_UTIL.REVERSE_LINESTRING' + 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 =) + select = 'SDO_UTIL.TO_WKTGEOMETRY(%s)' + + distance_functions = { + 'distance_gt' : (SDODistance('>'), dtypes), + 'distance_gte' : (SDODistance('>='), dtypes), + 'distance_lt' : (SDODistance('<'), dtypes), + 'distance_lte' : (SDODistance('<='), dtypes), + 'dwithin' : (SDODWithin(), dtypes), + } + + 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, six.string_types), # Oracle uses a different syntax, e.g., 'mask=inside+touch' + 'touches' : SDOOperation('SDO_TOUCH'), + 'within' : SDOOperation('SDO_INSIDE'), + } + geometry_functions.update(distance_functions) + + gis_terms = set(['isnull']) + gis_terms.update(geometry_functions) + + truncate_params = {'relate' : None} + + def convert_extent(self, clob): + if clob: + # Generally, Oracle returns a polygon for the extent -- however, + # it can return a single point if there's only one Point in the + # table. + ext_geom = Geometry(clob.read()) + gtype = str(ext_geom.geom_type) + if gtype == 'Polygon': + # Construct the 4-tuple from the coordinates in the polygon. + shell = ext_geom.shell + ll, ur = shell[0][:2], shell[2][:2] + elif gtype == 'Point': + ll = ext_geom.coords[:2] + ur = ll + else: + raise Exception('Unexpected geometry type returned for extent: %s' % gtype) + xmin, ymin = ll + xmax, ymax = ur + return (xmin, ymin, xmax, ymax) + else: + return None + + def convert_geom(self, clob, geo_field): + if clob: + return Geometry(clob.read(), geo_field.srid) + else: + return None + + 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 + SDO_CS.TRANSFORM() function call. + """ + if value is None: + return 'NULL' + + def transform_value(val, srid): + return val.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 % self.get_expression_column(value) + else: + 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)' % f.srid + + def spatial_lookup_sql(self, lvalue, lookup_type, value, field, qn): + "Returns the SQL WHERE clause for use in Oracle spatial SQL construction." + alias, col, db_type = lvalue + + # Getting the quoted table name as `geo_col`. + geo_col = '%s.%s' % (qn(alias), qn(col)) + + # See if a Oracle Geometry function matches the lookup type next + lookup_info = self.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 + geom = value[0] + + # Ensuring that a tuple _value_ was passed in from the user + if not isinstance(value, tuple): + raise ValueError('Tuple required for `%s` lookup type.' % lookup_type) + if len(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(value[1], arg_type): + raise ValueError('Argument type should be %s, got %s instead.' % (arg_type, type(value[1]))) + + 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(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(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(field, value)) + elif lookup_type == 'isnull': + # Handling 'isnull' lookup type + return "%s IS %sNULL" % (geo_col, ('' if value else 'NOT ')), [] + + raise TypeError("Got invalid lookup_type: %s" % repr(lookup_type)) + + def spatial_aggregate_sql(self, agg): + """ + Returns the spatial aggregate SQL template and function for the + given Aggregate instance. + """ + agg_name = agg.__class__.__name__.lower() + if agg_name == 'union': + agg_name += 'agg' + if agg.is_extent: + sql_template = '%(function)s(%(field)s)' + else: + sql_template = '%(function)s(SDOAGGRTYPE(%(field)s,%(tolerance)s))' + sql_function = getattr(self, agg_name) + return self.select % sql_template, sql_function + + # Routines for getting the OGC-compliant models. + def geometry_columns(self): + from django.contrib.gis.db.backends.oracle.models import GeometryColumns + return GeometryColumns + + def spatial_ref_sys(self): + from django.contrib.gis.db.backends.oracle.models import SpatialRefSys + return SpatialRefSys + + def modify_insert_params(self, placeholders, params): + """Drop out insert parameters for NULL placeholder. Needed for Oracle Spatial + backend due to #10888 + """ + # This code doesn't work for bulk insert cases. + assert len(placeholders) == 1 + return [[param for pholder, param + in six.moves.zip(placeholders[0], params[0]) if pholder != 'NULL'], ] diff --git a/lib/python2.7/site-packages/django/contrib/gis/db/backends/postgis/__init__.py b/lib/python2.7/site-packages/django/contrib/gis/db/backends/postgis/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/lib/python2.7/site-packages/django/contrib/gis/db/backends/postgis/__init__.py diff --git a/lib/python2.7/site-packages/django/contrib/gis/db/backends/postgis/adapter.py b/lib/python2.7/site-packages/django/contrib/gis/db/backends/postgis/adapter.py new file mode 100644 index 0000000..8bb514d --- /dev/null +++ b/lib/python2.7/site-packages/django/contrib/gis/db/backends/postgis/adapter.py @@ -0,0 +1,46 @@ +""" + This object provides quoting for GEOS geometries into PostgreSQL/PostGIS. +""" +from __future__ import unicode_literals + +from psycopg2 import Binary +from psycopg2.extensions import ISQLQuote + +class PostGISAdapter(object): + def __init__(self, geom): + "Initializes on the geometry." + # Getting the WKB (in string form, to allow easy pickling of + # the adaptor) and the SRID from the geometry. + self.ewkb = bytes(geom.ewkb) + self.srid = geom.srid + self._adapter = Binary(self.ewkb) + + def __conform__(self, proto): + # Does the given protocol conform to what Psycopg2 expects? + if proto == ISQLQuote: + return self + else: + raise Exception('Error implementing psycopg2 protocol. Is psycopg2 installed?') + + def __eq__(self, other): + if not isinstance(other, PostGISAdapter): + return False + return (self.ewkb == other.ewkb) and (self.srid == other.srid) + + def __str__(self): + return self.getquoted() + + def prepare(self, conn): + """ + This method allows escaping the binary in the style required by the + server's `standard_conforming_string` setting. + """ + self._adapter.prepare(conn) + + def getquoted(self): + "Returns a properly quoted string for use in PostgreSQL/PostGIS." + # psycopg will figure out whether to use E'\\000' or '\000' + return str('ST_GeomFromEWKB(%s)' % self._adapter.getquoted().decode()) + + def prepare_database_save(self, unused): + return self diff --git a/lib/python2.7/site-packages/django/contrib/gis/db/backends/postgis/base.py b/lib/python2.7/site-packages/django/contrib/gis/db/backends/postgis/base.py new file mode 100644 index 0000000..634a7d5 --- /dev/null +++ b/lib/python2.7/site-packages/django/contrib/gis/db/backends/postgis/base.py @@ -0,0 +1,12 @@ +from django.db.backends.postgresql_psycopg2.base import * +from django.db.backends.postgresql_psycopg2.base import DatabaseWrapper as Psycopg2DatabaseWrapper +from django.contrib.gis.db.backends.postgis.creation import PostGISCreation +from django.contrib.gis.db.backends.postgis.introspection import PostGISIntrospection +from django.contrib.gis.db.backends.postgis.operations import PostGISOperations + +class DatabaseWrapper(Psycopg2DatabaseWrapper): + def __init__(self, *args, **kwargs): + super(DatabaseWrapper, self).__init__(*args, **kwargs) + self.creation = PostGISCreation(self) + self.ops = PostGISOperations(self) + self.introspection = PostGISIntrospection(self) diff --git a/lib/python2.7/site-packages/django/contrib/gis/db/backends/postgis/creation.py b/lib/python2.7/site-packages/django/contrib/gis/db/backends/postgis/creation.py new file mode 100644 index 0000000..4f64ecc --- /dev/null +++ b/lib/python2.7/site-packages/django/contrib/gis/db/backends/postgis/creation.py @@ -0,0 +1,95 @@ +from django.conf import settings +from django.db.backends.postgresql_psycopg2.creation import DatabaseCreation +from django.utils.functional import cached_property + + +class PostGISCreation(DatabaseCreation): + geom_index_type = 'GIST' + geom_index_ops = 'GIST_GEOMETRY_OPS' + geom_index_ops_nd = 'GIST_GEOMETRY_OPS_ND' + + @cached_property + def template_postgis(self): + template_postgis = getattr(settings, 'POSTGIS_TEMPLATE', 'template_postgis') + cursor = self.connection.cursor() + cursor.execute('SELECT 1 FROM pg_database WHERE datname = %s LIMIT 1;', (template_postgis,)) + if cursor.fetchone(): + return template_postgis + return None + + def sql_indexes_for_field(self, model, f, style): + "Return any spatial index creation SQL for the field." + from django.contrib.gis.db.models.fields import GeometryField + + output = super(PostGISCreation, self).sql_indexes_for_field(model, f, style) + + if isinstance(f, GeometryField): + gqn = self.connection.ops.geo_quote_name + qn = self.connection.ops.quote_name + db_table = model._meta.db_table + + if f.geography or self.connection.ops.geometry: + # Geography and Geometry (PostGIS 2.0+) 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 f.spatial_index: + # Spatial indexes created the same way for both Geometry and + # Geography columns. + # PostGIS 2.0 does not support GIST_GEOMETRY_OPS. So, on 1.5 + # we use GIST_GEOMETRY_OPS, on 2.0 we use either "nd" ops + # which are fast on multidimensional cases, or just plain + # gist index for the 2d case. + if f.geography: + index_ops = '' + elif self.connection.ops.geometry: + if f.dim > 2: + index_ops = ' ' + style.SQL_KEYWORD(self.geom_index_ops_nd) + else: + index_ops = '' + else: + index_ops = ' ' + style.SQL_KEYWORD(self.geom_index_ops) + 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)) + index_ops + ' );') + return output + + def sql_table_creation_suffix(self): + if self.template_postgis is not None: + return ' TEMPLATE %s' % ( + self.connection.ops.quote_name(self.template_postgis),) + return '' + + def _create_test_db(self, verbosity, autoclobber): + test_database_name = super(PostGISCreation, self)._create_test_db(verbosity, autoclobber) + if self.template_postgis is None: + # Connect to the test database in order to create the postgis extension + self.connection.close() + self.connection.settings_dict["NAME"] = test_database_name + cursor = self.connection.cursor() + cursor.execute("CREATE EXTENSION postgis") + cursor.connection.commit() + + return test_database_name diff --git a/lib/python2.7/site-packages/django/contrib/gis/db/backends/postgis/introspection.py b/lib/python2.7/site-packages/django/contrib/gis/db/backends/postgis/introspection.py new file mode 100644 index 0000000..7df09d0 --- /dev/null +++ b/lib/python2.7/site-packages/django/contrib/gis/db/backends/postgis/introspection.py @@ -0,0 +1,103 @@ +from django.db.backends.postgresql_psycopg2.introspection import DatabaseIntrospection +from django.contrib.gis.gdal import OGRGeomType + +class GeoIntrospectionError(Exception): + pass + +class PostGISIntrospection(DatabaseIntrospection): + # Reverse dictionary for PostGIS geometry types not populated until + # introspection is actually performed. + postgis_types_reverse = {} + + ignored_tables = DatabaseIntrospection.ignored_tables + [ + 'geography_columns', + 'geometry_columns', + 'raster_columns', + 'spatial_ref_sys', + 'raster_overviews', + ] + + def get_postgis_types(self): + """ + Returns a dictionary with keys that are the PostgreSQL object + identification integers for the PostGIS geometry and/or + geography types (if supported). + """ + cursor = self.connection.cursor() + # The OID integers associated with the geometry type may + # be different across versions; hence, this is why we have + # to query the PostgreSQL pg_type table corresponding to the + # PostGIS custom data types. + oid_sql = 'SELECT "oid" FROM "pg_type" WHERE "typname" = %s' + try: + cursor.execute(oid_sql, ('geometry',)) + GEOM_TYPE = cursor.fetchone()[0] + postgis_types = { GEOM_TYPE : 'GeometryField' } + if self.connection.ops.geography: + cursor.execute(oid_sql, ('geography',)) + GEOG_TYPE = cursor.fetchone()[0] + # The value for the geography type is actually a tuple + # to pass in the `geography=True` keyword to the field + # definition. + postgis_types[GEOG_TYPE] = ('GeometryField', {'geography' : True}) + finally: + cursor.close() + + return postgis_types + + def get_field_type(self, data_type, description): + if not self.postgis_types_reverse: + # If the PostGIS types reverse dictionary is not populated, do so + # now. In order to prevent unnecessary requests upon connection + # intialization, the `data_types_reverse` dictionary is not updated + # with the PostGIS custom types until introspection is actually + # performed -- in other words, when this function is called. + self.postgis_types_reverse = self.get_postgis_types() + self.data_types_reverse.update(self.postgis_types_reverse) + return super(PostGISIntrospection, self).get_field_type(data_type, description) + + def get_geometry_type(self, table_name, geo_col): + """ + The geometry type OID used by PostGIS does not indicate the particular + type of field that a geometry column is (e.g., whether it's a + PointField or a PolygonField). Thus, this routine queries the PostGIS + metadata tables to determine the geometry type, + """ + cursor = self.connection.cursor() + try: + try: + # First seeing if this geometry column is in the `geometry_columns` + cursor.execute('SELECT "coord_dimension", "srid", "type" ' + 'FROM "geometry_columns" ' + 'WHERE "f_table_name"=%s AND "f_geometry_column"=%s', + (table_name, geo_col)) + row = cursor.fetchone() + if not row: raise GeoIntrospectionError + except GeoIntrospectionError: + if self.connection.ops.geography: + cursor.execute('SELECT "coord_dimension", "srid", "type" ' + 'FROM "geography_columns" ' + 'WHERE "f_table_name"=%s AND "f_geography_column"=%s', + (table_name, geo_col)) + row = cursor.fetchone() + + if not row: + raise Exception('Could not find a geometry or geography column for "%s"."%s"' % + (table_name, geo_col)) + + # OGRGeomType does not require GDAL and makes it easy to convert + # from OGC geom type name to Django field. + field_type = OGRGeomType(row[2]).django + + # Getting any GeometryField keyword arguments that are not the default. + dim = row[0] + srid = row[1] + field_params = {} + if srid != 4326: + field_params['srid'] = srid + if dim != 2: + field_params['dim'] = dim + finally: + cursor.close() + + return field_type, field_params diff --git a/lib/python2.7/site-packages/django/contrib/gis/db/backends/postgis/models.py b/lib/python2.7/site-packages/django/contrib/gis/db/backends/postgis/models.py new file mode 100644 index 0000000..e805259 --- /dev/null +++ b/lib/python2.7/site-packages/django/contrib/gis/db/backends/postgis/models.py @@ -0,0 +1,68 @@ +""" + The GeometryColumns and SpatialRefSys models for the PostGIS backend. +""" +from django.db import models +from django.contrib.gis.db.backends.base import SpatialRefSysMixin +from django.utils.encoding import python_2_unicode_compatible + +@python_2_unicode_compatible +class GeometryColumns(models.Model): + """ + The 'geometry_columns' table from the PostGIS. See the PostGIS + documentation at Ch. 4.2.2. + """ + f_table_catalog = models.CharField(max_length=256) + f_table_schema = models.CharField(max_length=256) + f_table_name = models.CharField(max_length=256) + f_geometry_column = models.CharField(max_length=256) + coord_dimension = models.IntegerField() + srid = models.IntegerField(primary_key=True) + type = models.CharField(max_length=30) + + class Meta: + db_table = 'geometry_columns' + managed = False + + @classmethod + def table_name_col(cls): + """ + Returns the name of the metadata column used to store the + the feature table name. + """ + return 'f_table_name' + + @classmethod + def geom_col_name(cls): + """ + Returns the name of the metadata column used to store the + the feature geometry column. + """ + return 'f_geometry_column' + + def __str__(self): + return "%s.%s - %dD %s field (SRID: %d)" % \ + (self.f_table_name, self.f_geometry_column, + self.coord_dimension, self.type, self.srid) + +class SpatialRefSys(models.Model, SpatialRefSysMixin): + """ + The 'spatial_ref_sys' table from PostGIS. See the PostGIS + documentaiton at Ch. 4.2.1. + """ + srid = models.IntegerField(primary_key=True) + auth_name = models.CharField(max_length=256) + auth_srid = models.IntegerField() + srtext = models.CharField(max_length=2048) + proj4text = models.CharField(max_length=2048) + + class Meta: + db_table = 'spatial_ref_sys' + managed = False + + @property + def wkt(self): + return self.srtext + + @classmethod + def wkt_col(cls): + return 'srtext' diff --git a/lib/python2.7/site-packages/django/contrib/gis/db/backends/postgis/operations.py b/lib/python2.7/site-packages/django/contrib/gis/db/backends/postgis/operations.py new file mode 100644 index 0000000..84dbda3 --- /dev/null +++ b/lib/python2.7/site-packages/django/contrib/gis/db/backends/postgis/operations.py @@ -0,0 +1,569 @@ +import re +from decimal import Decimal + +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.backend import Geometry +from django.contrib.gis.measure import Distance +from django.core.exceptions import ImproperlyConfigured +from django.db.backends.postgresql_psycopg2.base import DatabaseOperations +from django.db.utils import DatabaseError +from django.utils import six +from django.utils.functional import cached_property + +from .models import GeometryColumns, SpatialRefSys + + +#### Classes used in constructing PostGIS spatial SQL #### +class PostGISOperator(SpatialOperation): + "For PostGIS operators (e.g. `&&`, `~`)." + def __init__(self, operator): + super(PostGISOperator, self).__init__(operator=operator) + +class PostGISFunction(SpatialFunction): + "For PostGIS function calls (e.g., `ST_Contains(table, geom)`)." + def __init__(self, prefix, function, **kwargs): + super(PostGISFunction, self).__init__(prefix + function, **kwargs) + +class PostGISFunctionParam(PostGISFunction): + "For PostGIS functions that take another parameter (e.g. DWithin, Relate)." + sql_template = '%(function)s(%(geo_col)s, %(geometry)s, %%s)' + +class PostGISDistance(PostGISFunction): + "For PostGIS distance operations." + dist_func = 'Distance' + sql_template = '%(function)s(%(geo_col)s, %(geometry)s) %(operator)s %%s' + + def __init__(self, prefix, operator): + super(PostGISDistance, self).__init__(prefix, self.dist_func, + operator=operator) + +class PostGISSpheroidDistance(PostGISFunction): + "For PostGIS spherical distance operations (using the spheroid)." + dist_func = 'distance_spheroid' + sql_template = '%(function)s(%(geo_col)s, %(geometry)s, %%s) %(operator)s %%s' + def __init__(self, prefix, operator): + # An extra parameter in `end_subst` is needed for the spheroid string. + super(PostGISSpheroidDistance, self).__init__(prefix, self.dist_func, + operator=operator) + +class PostGISSphereDistance(PostGISDistance): + "For PostGIS spherical distance operations." + dist_func = 'distance_sphere' + +class PostGISRelate(PostGISFunctionParam): + "For PostGIS Relate(<geom>, <pattern>) calls." + pattern_regex = re.compile(r'^[012TF\*]{9}$') + def __init__(self, prefix, pattern): + if not self.pattern_regex.match(pattern): + raise ValueError('Invalid intersection matrix pattern "%s".' % pattern) + super(PostGISRelate, self).__init__(prefix, 'Relate') + + +class PostGISOperations(DatabaseOperations, BaseSpatialOperations): + compiler_module = 'django.contrib.gis.db.models.sql.compiler' + name = 'postgis' + postgis = True + geom_func_prefix = 'ST_' + version_regex = re.compile(r'^(?P<major>\d)\.(?P<minor1>\d)\.(?P<minor2>\d+)') + valid_aggregates = dict([(k, None) for k in + ('Collect', 'Extent', 'Extent3D', 'MakeLine', 'Union')]) + + Adapter = PostGISAdapter + Adaptor = Adapter # Backwards-compatibility alias. + + def __init__(self, connection): + super(PostGISOperations, self).__init__(connection) + + prefix = self.geom_func_prefix + # PostGIS-specific operators. The commented descriptions of these + # operators come from Section 7.6 of the PostGIS 1.4 documentation. + 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('&<'), + # The "&>" operator returns true if A's bounding box overlaps or + # is to the right of B's bounding box. + 'overlaps_right' : PostGISOperator('&>'), + # The "<<" operator returns true if A's bounding box is strictly + # to the left of B's bounding box. + 'left' : PostGISOperator('<<'), + # The ">>" operator returns true if A's bounding box is strictly + # to the right of B's bounding box. + 'right' : PostGISOperator('>>'), + # The "&<|" operator returns true if A's bounding box overlaps or + # is below B's bounding box. + 'overlaps_below' : PostGISOperator('&<|'), + # The "|&>" operator returns true if A's bounding box overlaps or + # is above B's bounding box. + 'overlaps_above' : PostGISOperator('|&>'), + # The "<<|" operator returns true if A's bounding box is strictly + # below B's bounding box. + 'strictly_below' : PostGISOperator('<<|'), + # The "|>>" operator returns true if A's bounding box is strictly + # above B's bounding box. + 'strictly_above' : PostGISOperator('|>>'), + # The "~=" operator is the "same as" operator. It tests actual + # geometric equality of two features. So if A and B are the same feature, + # vertex-by-vertex, the operator returns true. + 'same_as' : PostGISOperator('~='), + 'exact' : PostGISOperator('~='), + # The "@" operator returns true if A's bounding box is completely contained + # by B's bounding box. + 'contained' : PostGISOperator('@'), + # The "~" operator returns true if A's bounding box completely contains + # by B's bounding box. + 'bbcontains' : PostGISOperator('~'), + # The "&&" operator returns true if A's bounding box overlaps + # B's bounding box. + 'bboverlaps' : PostGISOperator('&&'), + } + + self.geometry_functions = { + 'equals' : PostGISFunction(prefix, 'Equals'), + 'disjoint' : PostGISFunction(prefix, 'Disjoint'), + 'touches' : PostGISFunction(prefix, 'Touches'), + 'crosses' : PostGISFunction(prefix, 'Crosses'), + 'within' : PostGISFunction(prefix, 'Within'), + 'overlaps' : PostGISFunction(prefix, 'Overlaps'), + 'contains' : PostGISFunction(prefix, 'Contains'), + 'intersects' : PostGISFunction(prefix, 'Intersects'), + 'relate' : (PostGISRelate, six.string_types), + 'coveredby' : PostGISFunction(prefix, 'CoveredBy'), + 'covers' : PostGISFunction(prefix, 'Covers'), + } + + # Valid distance types and substitutions + dtypes = (Decimal, Distance, float) + six.integer_types + def get_dist_ops(operator): + "Returns operations for both regular and spherical distances." + return {'cartesian' : PostGISDistance(prefix, operator), + 'sphere' : PostGISSphereDistance(prefix, operator), + 'spheroid' : PostGISSpheroidDistance(prefix, operator), + } + self.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), + 'dwithin' : (PostGISFunctionParam(prefix, 'DWithin'), dtypes) + } + + # Adding the distance functions to the geometries lookup. + self.geometry_functions.update(self.distance_functions) + + # Only PostGIS versions 1.3.4+ have GeoJSON serialization support. + if self.spatial_version < (1, 3, 4): + GEOJSON = False + else: + GEOJSON = prefix + 'AsGeoJson' + + # ST_ContainsProperly ST_MakeLine, and ST_GeoHash added in 1.4. + if self.spatial_version >= (1, 4, 0): + GEOHASH = 'ST_GeoHash' + BOUNDINGCIRCLE = 'ST_MinimumBoundingCircle' + self.geometry_functions['contains_properly'] = PostGISFunction(prefix, 'ContainsProperly') + else: + GEOHASH, BOUNDINGCIRCLE = False, False + + # Geography type support added in 1.5. + if self.spatial_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('&&'), + } + + # Native geometry type support added in PostGIS 2.0. + if self.spatial_version >= (2, 0, 0): + self.geometry = True + + # Creating a dictionary lookup of all GIS terms for PostGIS. + self.gis_terms = set(['isnull']) + self.gis_terms.update(self.geometry_operators) + self.gis_terms.update(self.geometry_functions) + + self.area = prefix + 'Area' + self.bounding_circle = BOUNDINGCIRCLE + self.centroid = prefix + 'Centroid' + self.collect = prefix + 'Collect' + self.difference = prefix + 'Difference' + self.distance = prefix + 'Distance' + self.distance_sphere = prefix + 'distance_sphere' + self.distance_spheroid = prefix + 'distance_spheroid' + self.envelope = prefix + 'Envelope' + self.extent = prefix + 'Extent' + self.force_rhr = prefix + 'ForceRHR' + self.geohash = GEOHASH + self.geojson = GEOJSON + self.gml = prefix + 'AsGML' + self.intersection = prefix + 'Intersection' + self.kml = prefix + 'AsKML' + self.length = prefix + 'Length' + self.length_spheroid = prefix + 'length_spheroid' + self.makeline = prefix + 'MakeLine' + self.mem_size = prefix + 'mem_size' + self.num_geom = prefix + 'NumGeometries' + self.num_points = prefix + 'npoints' + self.perimeter = prefix + 'Perimeter' + self.point_on_surface = prefix + 'PointOnSurface' + self.polygonize = prefix + 'Polygonize' + self.reverse = prefix + 'Reverse' + self.scale = prefix + 'Scale' + self.snap_to_grid = prefix + 'SnapToGrid' + self.svg = prefix + 'AsSVG' + self.sym_difference = prefix + 'SymDifference' + self.transform = prefix + 'Transform' + self.translate = prefix + 'Translate' + self.union = prefix + 'Union' + self.unionagg = prefix + 'Union' + + if self.spatial_version >= (2, 0, 0): + self.extent3d = prefix + '3DExtent' + self.length3d = prefix + '3DLength' + self.perimeter3d = prefix + '3DPerimeter' + else: + self.extent3d = prefix + 'Extent3D' + self.length3d = prefix + 'Length3D' + self.perimeter3d = prefix + 'Perimeter3D' + + @cached_property + def spatial_version(self): + """Determine the version of the PostGIS library.""" + # Trying to get the PostGIS version because the function + # 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. + if hasattr(settings, 'POSTGIS_VERSION'): + version = settings.POSTGIS_VERSION + else: + try: + vtup = self.postgis_version_tuple() + except DatabaseError: + 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'] + ) + version = vtup[1:] + return version + + 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_extent(self, box): + """ + 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): + """ + 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()) + return (xmin, ymin, zmin, xmax, ymax, zmax) + + def convert_geom(self, hex, geo_field): + """ + Converts the geometry returned from PostGIS aggretates. + """ + if hex: + return Geometry(hex) + else: + return None + + 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) + elif self.geometry: + # Postgis 2.0 supports type-based geometries. + # TODO: Support 'M' extension. + if f.dim == 3: + geom_type = f.geom_type + 'Z' + else: + geom_type = f.geom_type + return 'geometry(%s,%d)' % (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 == f.srid: + placeholder = '%s' + else: + # Adding Transform() to the SQL placeholder. + placeholder = '%s(%%s, %s)' % (self.transform, f.srid) + + if hasattr(value, 'expression'): + # If this is an F expression, then we don't really want + # a placeholder and instead substitute in the column + # of the expression. + placeholder = placeholder % self.get_expression_column(value) + + return placeholder + + def _get_postgis_func(self, func): + """ + Helper routine for calling PostGIS functions and returning their result. + """ + # Close out the connection. See #9437. + with self.connection.temporary_connection() as cursor: + cursor.execute('SELECT %s()' % func) + return cursor.fetchone()[0] + + def postgis_geos_version(self): + "Returns the version of the GEOS library used with PostGIS." + return self._get_postgis_func('postgis_geos_version') + + def postgis_lib_version(self): + "Returns the version number of the PostGIS library used with PostgreSQL." + return self._get_postgis_func('postgis_lib_version') + + def postgis_proj_version(self): + "Returns the version of the PROJ.4 library used with PostGIS." + return self._get_postgis_func('postgis_proj_version') + + def postgis_version(self): + "Returns PostGIS version number and compile-time options." + return self._get_postgis_func('postgis_version') + + def postgis_full_version(self): + "Returns PostGIS version number and compile-time options." + return self._get_postgis_func('postgis_full_version') + + def postgis_version_tuple(self): + """ + Returns the PostGIS version as a tuple (version string, major, + minor, subminor). + """ + # Getting the PostGIS version + version = self.postgis_lib_version() + 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 PostGIS version string: %s' % version) + + return (version, major, minor1, minor2) + + def proj_version_tuple(self): + """ + Return the version of PROJ.4 used by PostGIS as a tuple of the + major, minor, and subminor release numbers. + """ + proj_regex = re.compile(r'(\d+)\.(\d+)\.(\d+)') + proj_ver_str = self.postgis_proj_version() + m = proj_regex.search(proj_ver_str) + if m: + return tuple(map(int, [m.group(1), m.group(2), m.group(3)])) + else: + raise Exception('Could not determine PROJ.4 version from PostGIS.') + + 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(num_param) + else: + return exactly_two(num_param) + + 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. + """ + alias, col, db_type = lvalue + + # Getting the quoted geometry column. + geo_col = '%s.%s' % (qn(alias), qn(col)) + + 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.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] + + # Lookup types that are tuples take tuple arguments, e.g., 'relate' and + # distance lookups. + if isinstance(tmp, tuple): + # First element of tuple is the PostGISOperation 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. + nparams = len(value) + if not self.num_params(lookup_type, nparams): + 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(self.geom_func_prefix, value[1]) + elif lookup_type in self.distance_functions and lookup_type != 'dwithin': + if not field.geography and field.geodetic(self.connection): + # Geodetic distances are only available from Points to + # PointFields on PostGIS 1.4 and below. + if not self.connection.ops.geography: + if field.geom_type != 'POINT': + raise ValueError('PostGIS spherical operations are only valid on PointFields.') + + if str(geom.geom_type) != 'Point': + raise ValueError('PostGIS geometry distance parameter is required to be of type Point.') + + # Setting up the geodetic operation appropriately. + if nparams == 3 and value[2] == 'spheroid': + op = op['spheroid'] + else: + op = op['sphere'] + else: + op = op['cartesian'] + 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, ('' if value else 'NOT ')), [] + + raise TypeError("Got invalid lookup_type: %s" % repr(lookup_type)) + + 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 = '%(function)s(%(field)s)' + sql_function = getattr(self, agg_name) + return sql_template, sql_function + + # Routines for getting the OGC-compliant models. + def geometry_columns(self): + return GeometryColumns + + def spatial_ref_sys(self): + return SpatialRefSys diff --git a/lib/python2.7/site-packages/django/contrib/gis/db/backends/spatialite/__init__.py b/lib/python2.7/site-packages/django/contrib/gis/db/backends/spatialite/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/lib/python2.7/site-packages/django/contrib/gis/db/backends/spatialite/__init__.py diff --git a/lib/python2.7/site-packages/django/contrib/gis/db/backends/spatialite/adapter.py b/lib/python2.7/site-packages/django/contrib/gis/db/backends/spatialite/adapter.py new file mode 100644 index 0000000..d8fefba --- /dev/null +++ b/lib/python2.7/site-packages/django/contrib/gis/db/backends/spatialite/adapter.py @@ -0,0 +1,8 @@ +from django.db.backends.sqlite3.base import Database +from django.contrib.gis.db.backends.adapter import WKTAdapter + +class SpatiaLiteAdapter(WKTAdapter): + "SQLite adaptor for geometry objects." + def __conform__(self, protocol): + if protocol is Database.PrepareProtocol: + return str(self) diff --git a/lib/python2.7/site-packages/django/contrib/gis/db/backends/spatialite/base.py b/lib/python2.7/site-packages/django/contrib/gis/db/backends/spatialite/base.py new file mode 100644 index 0000000..7b49d71 --- /dev/null +++ b/lib/python2.7/site-packages/django/contrib/gis/db/backends/spatialite/base.py @@ -0,0 +1,60 @@ +import sys +from ctypes.util import find_library +from django.conf import settings + +from django.core.exceptions import ImproperlyConfigured +from django.db.backends.sqlite3.base import (Database, + DatabaseWrapper as SQLiteDatabaseWrapper, SQLiteCursorWrapper) +from django.contrib.gis.db.backends.spatialite.client import SpatiaLiteClient +from django.contrib.gis.db.backends.spatialite.creation import SpatiaLiteCreation +from django.contrib.gis.db.backends.spatialite.introspection import SpatiaLiteIntrospection +from django.contrib.gis.db.backends.spatialite.operations import SpatiaLiteOperations +from django.utils import six + +class DatabaseWrapper(SQLiteDatabaseWrapper): + def __init__(self, *args, **kwargs): + # Before we get too far, make sure pysqlite 2.5+ is installed. + if Database.version_info < (2, 5, 0): + raise ImproperlyConfigured('Only versions of pysqlite 2.5+ are ' + 'compatible with SpatiaLite and GeoDjango.') + + # Trying to find the location of the SpatiaLite library. + # Here we are figuring out the path to the SpatiaLite library + # (`libspatialite`). If it's not in the system library path (e.g., it + # cannot be found by `ctypes.util.find_library`), then it may be set + # manually in the settings via the `SPATIALITE_LIBRARY_PATH` setting. + self.spatialite_lib = getattr(settings, 'SPATIALITE_LIBRARY_PATH', + find_library('spatialite')) + if not self.spatialite_lib: + raise ImproperlyConfigured('Unable to locate the SpatiaLite library. ' + 'Make sure it is in your library path, or set ' + 'SPATIALITE_LIBRARY_PATH in your settings.' + ) + super(DatabaseWrapper, self).__init__(*args, **kwargs) + self.ops = SpatiaLiteOperations(self) + self.client = SpatiaLiteClient(self) + self.creation = SpatiaLiteCreation(self) + self.introspection = SpatiaLiteIntrospection(self) + + def get_new_connection(self, conn_params): + conn = super(DatabaseWrapper, self).get_new_connection(conn_params) + # Enabling extension loading on the SQLite connection. + try: + conn.enable_load_extension(True) + except AttributeError: + raise ImproperlyConfigured( + 'The pysqlite library does not support C extension loading. ' + 'Both SQLite and pysqlite must be configured to allow ' + 'the loading of extensions to use SpatiaLite.') + # Loading the SpatiaLite library extension on the connection, and returning + # the created cursor. + cur = conn.cursor(factory=SQLiteCursorWrapper) + try: + cur.execute("SELECT load_extension(%s)", (self.spatialite_lib,)) + except Exception as msg: + new_msg = ( + 'Unable to load the SpatiaLite library extension ' + '"%s" because: %s') % (self.spatialite_lib, msg) + six.reraise(ImproperlyConfigured, ImproperlyConfigured(new_msg), sys.exc_info()[2]) + cur.close() + return conn diff --git a/lib/python2.7/site-packages/django/contrib/gis/db/backends/spatialite/client.py b/lib/python2.7/site-packages/django/contrib/gis/db/backends/spatialite/client.py new file mode 100644 index 0000000..536065a --- /dev/null +++ b/lib/python2.7/site-packages/django/contrib/gis/db/backends/spatialite/client.py @@ -0,0 +1,5 @@ +from django.db.backends.sqlite3.client import DatabaseClient + +class SpatiaLiteClient(DatabaseClient): + executable_name = 'spatialite' + diff --git a/lib/python2.7/site-packages/django/contrib/gis/db/backends/spatialite/creation.py b/lib/python2.7/site-packages/django/contrib/gis/db/backends/spatialite/creation.py new file mode 100644 index 0000000..d0a5f82 --- /dev/null +++ b/lib/python2.7/site-packages/django/contrib/gis/db/backends/spatialite/creation.py @@ -0,0 +1,129 @@ +import os +from django.conf import settings +from django.core.cache import get_cache +from django.core.cache.backends.db import BaseDatabaseCache +from django.core.exceptions import ImproperlyConfigured +from django.db.backends.sqlite3.creation import DatabaseCreation + +class SpatiaLiteCreation(DatabaseCreation): + + def create_test_db(self, verbosity=1, autoclobber=False): + """ + Creates a test database, prompting the user for confirmation if the + database already exists. Returns the name of the test database created. + + This method is overloaded to load up the SpatiaLite initialization + SQL prior to calling the `syncdb` command. + """ + # Don't import django.core.management if it isn't needed. + from django.core.management import call_command + + test_database_name = self._get_test_db_name() + + if verbosity >= 1: + test_db_repr = '' + if verbosity >= 2: + test_db_repr = " ('%s')" % test_database_name + print("Creating test database for alias '%s'%s..." % (self.connection.alias, test_db_repr)) + + self._create_test_db(verbosity, autoclobber) + + self.connection.close() + self.connection.settings_dict["NAME"] = test_database_name + + # Need to load the SpatiaLite initialization SQL before running `syncdb`. + self.load_spatialite_sql() + + # Report syncdb messages at one level lower than that requested. + # This ensures we don't get flooded with messages during testing + # (unless you really ask to be flooded) + call_command('syncdb', + verbosity=max(verbosity - 1, 0), + interactive=False, + database=self.connection.alias, + load_initial_data=False) + + # We need to then do a flush to ensure that any data installed by + # custom SQL has been removed. The only test data should come from + # test fixtures, or autogenerated from post_syncdb triggers. + # This has the side effect of loading initial data (which was + # intentionally skipped in the syncdb). + call_command('flush', + verbosity=max(verbosity - 1, 0), + interactive=False, + database=self.connection.alias) + + from django.core.cache import get_cache + from django.core.cache.backends.db import BaseDatabaseCache + for cache_alias in settings.CACHES: + cache = get_cache(cache_alias) + if isinstance(cache, BaseDatabaseCache): + call_command('createcachetable', cache._table, database=self.connection.alias) + + # Get a cursor (even though we don't need one yet). This has + # the side effect of initializing the test database. + cursor = self.connection.cursor() + + return test_database_name + + def sql_indexes_for_field(self, model, f, style): + "Return any spatial index creation SQL for the field." + from django.contrib.gis.db.models.fields import GeometryField + + output = super(SpatiaLiteCreation, self).sql_indexes_for_field(model, f, style) + + if isinstance(f, GeometryField): + gqn = self.connection.ops.geo_quote_name + 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)) + ', ' + + style.SQL_KEYWORD(str(int(not f.null))) + + ');') + + if f.spatial_index: + output.append(style.SQL_KEYWORD('SELECT ') + + style.SQL_TABLE('CreateSpatialIndex') + '(' + + style.SQL_TABLE(gqn(db_table)) + ', ' + + style.SQL_FIELD(gqn(f.column)) + ');') + + return output + + def load_spatialite_sql(self): + """ + This routine loads up the SpatiaLite SQL file. + """ + if self.connection.ops.spatial_version[:2] >= (2, 4): + # Spatialite >= 2.4 -- No need to load any SQL file, calling + # InitSpatialMetaData() transparently creates the spatial metadata + # tables + cur = self.connection._cursor() + cur.execute("SELECT InitSpatialMetaData()") + else: + # Spatialite < 2.4 -- Load the initial SQL + + # Getting the location of the SpatiaLite SQL file, and confirming + # it exists. + spatialite_sql = self.spatialite_init_file() + if not os.path.isfile(spatialite_sql): + raise ImproperlyConfigured('Could not find the required SpatiaLite initialization ' + 'SQL file (necessary for testing): %s' % spatialite_sql) + + # Opening up the SpatiaLite SQL initialization file and executing + # as a script. + with open(spatialite_sql, 'r') as sql_fh: + cur = self.connection._cursor() + cur.executescript(sql_fh.read()) + + def spatialite_init_file(self): + # SPATIALITE_SQL may be placed in settings to tell GeoDjango + # to use a specific path to the SpatiaLite initilization SQL. + return getattr(settings, 'SPATIALITE_SQL', + 'init_spatialite-%s.%s.sql' % + self.connection.ops.spatial_version[:2]) diff --git a/lib/python2.7/site-packages/django/contrib/gis/db/backends/spatialite/introspection.py b/lib/python2.7/site-packages/django/contrib/gis/db/backends/spatialite/introspection.py new file mode 100644 index 0000000..4f12ade --- /dev/null +++ b/lib/python2.7/site-packages/django/contrib/gis/db/backends/spatialite/introspection.py @@ -0,0 +1,52 @@ +from django.contrib.gis.gdal import OGRGeomType +from django.db.backends.sqlite3.introspection import DatabaseIntrospection, FlexibleFieldLookupDict +from django.utils import six + +class GeoFlexibleFieldLookupDict(FlexibleFieldLookupDict): + """ + Sublcass that includes updates the `base_data_types_reverse` dict + for geometry field types. + """ + base_data_types_reverse = FlexibleFieldLookupDict.base_data_types_reverse.copy() + base_data_types_reverse.update( + {'point' : 'GeometryField', + 'linestring' : 'GeometryField', + 'polygon' : 'GeometryField', + 'multipoint' : 'GeometryField', + 'multilinestring' : 'GeometryField', + 'multipolygon' : 'GeometryField', + 'geometrycollection' : 'GeometryField', + }) + +class SpatiaLiteIntrospection(DatabaseIntrospection): + data_types_reverse = GeoFlexibleFieldLookupDict() + + def get_geometry_type(self, table_name, geo_col): + cursor = self.connection.cursor() + try: + # Querying the `geometry_columns` table to get additional metadata. + cursor.execute('SELECT "coord_dimension", "srid", "type" ' + 'FROM "geometry_columns" ' + 'WHERE "f_table_name"=%s AND "f_geometry_column"=%s', + (table_name, geo_col)) + row = cursor.fetchone() + if not row: + raise Exception('Could not find a geometry column for "%s"."%s"' % + (table_name, geo_col)) + + # OGRGeomType does not require GDAL and makes it easy to convert + # from OGC geom type name to Django field. + field_type = OGRGeomType(row[2]).django + + # Getting any GeometryField keyword arguments that are not the default. + dim = row[0] + srid = row[1] + field_params = {} + if srid != 4326: + field_params['srid'] = srid + if isinstance(dim, six.string_types) and 'Z' in dim: + field_params['dim'] = 3 + finally: + cursor.close() + + return field_type, field_params diff --git a/lib/python2.7/site-packages/django/contrib/gis/db/backends/spatialite/models.py b/lib/python2.7/site-packages/django/contrib/gis/db/backends/spatialite/models.py new file mode 100644 index 0000000..b281f0b --- /dev/null +++ b/lib/python2.7/site-packages/django/contrib/gis/db/backends/spatialite/models.py @@ -0,0 +1,62 @@ +""" + The GeometryColumns and SpatialRefSys models for the SpatiaLite backend. +""" +from django.db import models +from django.contrib.gis.db.backends.base import SpatialRefSysMixin +from django.utils.encoding import python_2_unicode_compatible + +@python_2_unicode_compatible +class GeometryColumns(models.Model): + """ + The 'geometry_columns' table from SpatiaLite. + """ + f_table_name = models.CharField(max_length=256) + f_geometry_column = models.CharField(max_length=256) + type = models.CharField(max_length=30) + coord_dimension = models.IntegerField() + srid = models.IntegerField(primary_key=True) + spatial_index_enabled = models.IntegerField() + + class Meta: + db_table = 'geometry_columns' + managed = False + + @classmethod + def table_name_col(cls): + """ + Returns the name of the metadata column used to store the + the feature table name. + """ + return 'f_table_name' + + @classmethod + def geom_col_name(cls): + """ + Returns the name of the metadata column used to store the + the feature geometry column. + """ + return 'f_geometry_column' + + def __str__(self): + return "%s.%s - %dD %s field (SRID: %d)" % \ + (self.f_table_name, self.f_geometry_column, + self.coord_dimension, self.type, self.srid) + +class SpatialRefSys(models.Model, SpatialRefSysMixin): + """ + The 'spatial_ref_sys' table from SpatiaLite. + """ + srid = models.IntegerField(primary_key=True) + auth_name = models.CharField(max_length=256) + auth_srid = models.IntegerField() + ref_sys_name = models.CharField(max_length=256) + proj4text = models.CharField(max_length=2048) + + @property + def wkt(self): + from django.contrib.gis.gdal import SpatialReference + return SpatialReference(self.proj4text).wkt + + class Meta: + db_table = 'spatial_ref_sys' + managed = False diff --git a/lib/python2.7/site-packages/django/contrib/gis/db/backends/spatialite/operations.py b/lib/python2.7/site-packages/django/contrib/gis/db/backends/spatialite/operations.py new file mode 100644 index 0000000..4281caf --- /dev/null +++ b/lib/python2.7/site-packages/django/contrib/gis/db/backends/spatialite/operations.py @@ -0,0 +1,373 @@ +import re +import sys +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 +from django.utils import six +from django.utils.functional import cached_property + + +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) + six.integer_types +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, six.string_types), + # Returns 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__(connection) + + # Creating the GIS terms dictionary. + self.gis_terms = set(['isnull']) + self.gis_terms.update(self.geometry_functions) + + @cached_property + def spatial_version(self): + """Determine the version of the SpatiaLite library.""" + try: + version = self.spatialite_version_tuple()[1:] + except Exception as msg: + new_msg = ( + '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) + six.reraise(ImproperlyConfigured, ImproperlyConfigured(new_msg), sys.exc_info()[2]) + if version < (2, 3, 0): + raise ImproperlyConfigured('GeoDjango only supports SpatiaLite versions ' + '2.3.0 and above') + return version + + @property + def _version_greater_2_4_0_rc4(self): + if self.spatial_version >= (2, 4, 1): + return True + elif self.spatial_version < (2, 4, 0): + return False + else: + # Spatialite 2.4.0-RC4 added AsGML and AsKML, however both + # RC2 (shipped in popular Debian/Ubuntu packages) and RC4 + # report version as '2.4.0', so we fall back to feature detection + try: + self._get_spatialite_func("AsGML(GeomFromText('POINT(1 1)'))") + except DatabaseError: + return False + return True + + @cached_property + def gml(self): + return 'AsGML' if self._version_greater_2_4_0_rc4 else None + + @cached_property + def kml(self): + return 'AsKML' if self._version_greater_2_4_0_rc4 else None + + @cached_property + def geojson(self): + return 'AsGeoJSON' if self.spatial_version >= (3, 0, 0) else None + + 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 % self.get_expression_column(value) + 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, ('' if value else 'NOT ')), [] + + 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 diff --git a/lib/python2.7/site-packages/django/contrib/gis/db/backends/util.py b/lib/python2.7/site-packages/django/contrib/gis/db/backends/util.py new file mode 100644 index 0000000..2612810 --- /dev/null +++ b/lib/python2.7/site-packages/django/contrib/gis/db/backends/util.py @@ -0,0 +1,44 @@ +""" +A collection of utility routines and classes used by the spatial +backends. +""" + +class SpatialOperation(object): + """ + Base class for generating spatial SQL. + """ + sql_template = '%(geo_col)s %(operator)s %(geometry)s' + + def __init__(self, function='', operator='', result='', **kwargs): + self.function = function + self.operator = operator + self.result = result + self.extra = kwargs + + def as_sql(self, geo_col, geometry='%s'): + return self.sql_template % self.params(geo_col, geometry), [] + + def params(self, geo_col, geometry): + params = {'function' : self.function, + 'geo_col' : geo_col, + 'geometry' : geometry, + 'operator' : self.operator, + 'result' : self.result, + } + params.update(self.extra) + return params + +class SpatialFunction(SpatialOperation): + """ + Base class for generating spatial SQL related to a function. + """ + sql_template = '%(function)s(%(geo_col)s, %(geometry)s)' + + def __init__(self, func, result='', operator='', **kwargs): + # Getting the function prefix. + default = {'function' : func, + 'operator' : operator, + 'result' : result + } + kwargs.update(default) + super(SpatialFunction, self).__init__(**kwargs) diff --git a/lib/python2.7/site-packages/django/contrib/gis/db/models/__init__.py b/lib/python2.7/site-packages/django/contrib/gis/db/models/__init__.py new file mode 100644 index 0000000..e36aa36 --- /dev/null +++ b/lib/python2.7/site-packages/django/contrib/gis/db/models/__init__.py @@ -0,0 +1,14 @@ +# Want to get everything from the 'normal' models package. +from django.db.models import * + +# Geographic aggregate functions +from django.contrib.gis.db.models.aggregates import * + +# The GeoManager +from django.contrib.gis.db.models.manager import GeoManager + +# The geographic-enabled fields. +from django.contrib.gis.db.models.fields import ( + GeometryField, PointField, LineStringField, PolygonField, + MultiPointField, MultiLineStringField, MultiPolygonField, + GeometryCollectionField) diff --git a/lib/python2.7/site-packages/django/contrib/gis/db/models/aggregates.py b/lib/python2.7/site-packages/django/contrib/gis/db/models/aggregates.py new file mode 100644 index 0000000..d0fc6d3 --- /dev/null +++ b/lib/python2.7/site-packages/django/contrib/gis/db/models/aggregates.py @@ -0,0 +1,16 @@ +from django.db.models import Aggregate + +class Collect(Aggregate): + name = 'Collect' + +class Extent(Aggregate): + name = 'Extent' + +class Extent3D(Aggregate): + name = 'Extent3D' + +class MakeLine(Aggregate): + name = 'MakeLine' + +class Union(Aggregate): + name = 'Union' diff --git a/lib/python2.7/site-packages/django/contrib/gis/db/models/fields.py b/lib/python2.7/site-packages/django/contrib/gis/db/models/fields.py new file mode 100644 index 0000000..2e221b7 --- /dev/null +++ b/lib/python2.7/site-packages/django/contrib/gis/db/models/fields.py @@ -0,0 +1,305 @@ +from django.db.models.fields import Field +from django.db.models.sql.expressions import SQLEvaluator +from django.utils.translation import ugettext_lazy as _ +from django.contrib.gis import forms +from django.contrib.gis.db.models.proxy import GeometryProxy +from django.contrib.gis.geometry.backend import Geometry, GeometryException +from django.utils import six + +# Local cache of the spatial_ref_sys table, which holds SRID data for each +# spatial database alias. This cache exists so that the database isn't queried +# for SRID info each time a distance query is constructed. +_srid_cache = {} + +def get_srid_info(srid, connection): + """ + Returns the units, unit name, and spheroid WKT associated with the + given SRID from the `spatial_ref_sys` (or equivalent) spatial database + table for the given database connection. These results are cached. + """ + global _srid_cache + + try: + # The SpatialRefSys model for the spatial backend. + SpatialRefSys = connection.ops.spatial_ref_sys() + except NotImplementedError: + # No `spatial_ref_sys` table in spatial backend (e.g., MySQL). + return None, None, None + + if not connection.alias in _srid_cache: + # Initialize SRID dictionary for database if it doesn't exist. + _srid_cache[connection.alias] = {} + + if not srid in _srid_cache[connection.alias]: + # Use `SpatialRefSys` model to query for spatial reference info. + sr = SpatialRefSys.objects.using(connection.alias).get(srid=srid) + units, units_name = sr.units + spheroid = SpatialRefSys.get_spheroid(sr.wkt) + _srid_cache[connection.alias][srid] = (units, units_name, spheroid) + + return _srid_cache[connection.alias][srid] + +class GeometryField(Field): + "The base GIS field -- maps to the OpenGIS Specification Geometry type." + + # The OpenGIS Geometry name. + geom_type = 'GEOMETRY' + form_class = forms.GeometryField + + # Geodetic units. + geodetic_units = ('Decimal Degree', 'degree') + + description = _("The base GIS field -- maps to the OpenGIS Specification Geometry type.") + + def __init__(self, verbose_name=None, srid=4326, spatial_index=True, dim=2, + geography=False, **kwargs): + """ + The initialization function for geometry fields. Takes the following + as keyword arguments: + + srid: + The spatial reference system identifier, an OGC standard. + Defaults to 4326 (WGS84). + + spatial_index: + Indicates whether to create a spatial index. Defaults to True. + Set this instead of 'db_index' for geographic fields since index + creation is different for geometry columns. + + dim: + The number of dimensions for this geometry. Defaults to 2. + + 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. + self.spatial_index = spatial_index + + # Setting the SRID and getting the units. Unit information must be + # easily available in the field instance for distance queries. + self.srid = srid + + # 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 + + # Is this a geography rather than a geometry column? + self.geography = geography + + # Oracle-specific private attributes for creating the entry in + # `USER_SDO_GEOM_METADATA` + self._extent = kwargs.pop('extent', (-180.0, -90.0, 180.0, 90.0)) + self._tolerance = kwargs.pop('tolerance', 0.05) + + super(GeometryField, self).__init__(**kwargs) + + # The following functions are used to get the units, their name, and + # the spheroid corresponding to the SRID of the GeometryField. + def _get_srid_info(self, connection): + # Get attributes from `get_srid_info`. + self._units, self._units_name, self._spheroid = get_srid_info(self.srid, connection) + + def spheroid(self, connection): + if not hasattr(self, '_spheroid'): + self._get_srid_info(connection) + return self._spheroid + + def units(self, connection): + if not hasattr(self, '_units'): + self._get_srid_info(connection) + return self._units + + def units_name(self, connection): + if not hasattr(self, '_units_name'): + self._get_srid_info(connection) + return self._units_name + + ### Routines specific to GeometryField ### + def geodetic(self, connection): + """ + Returns true if this field's SRID corresponds with a coordinate + system that uses non-projected units (e.g., latitude/longitude). + """ + return self.units_name(connection) in self.geodetic_units + + 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. + """ + return connection.ops.get_distance(self, value, lookup_type) + + def get_prep_value(self, value): + """ + Spatial lookup values are either a parameter that is (or may be + converted to) a geometry, or a sequence of lookup values that + begins with a geometry. This routine will setup the geometry + value properly, and preserve any other lookup parameters before + returning to the caller. + """ + if isinstance(value, SQLEvaluator): + return value + elif isinstance(value, (tuple, list)): + geom = value[0] + seq_value = True + else: + geom = value + seq_value = False + + # When the input is not a GEOS geometry, attempt to construct one + # from the given string input. + if isinstance(geom, Geometry): + pass + elif isinstance(geom, (bytes, six.string_types)) or hasattr(geom, '__geo_interface__'): + try: + geom = Geometry(geom) + except GeometryException: + raise ValueError('Could not create geometry from lookup value.') + else: + raise ValueError('Cannot use object with type %s for a geometry lookup parameter.' % type(geom).__name__) + + # Assigning the SRID value. + geom.srid = self.get_srid(geom) + + if seq_value: + lookup_val = [geom] + lookup_val.extend(value[1:]) + return tuple(lookup_val) + else: + return geom + + def get_srid(self, geom): + """ + Returns the default SRID for the given geometry, taking into account + the SRID set for the field. For example, if the input geometry + has no SRID, then that of the field will be returned. + """ + gsrid = geom.srid # SRID of given geometry. + if gsrid is None or self.srid == -1 or (gsrid == -1 and self.srid != -1): + return self.srid + else: + return gsrid + + ### Routines overloaded from Field ### + def contribute_to_class(self, cls, name): + super(GeometryField, self).contribute_to_class(cls, name) + + # Setup for lazy-instantiated Geometry object. + setattr(cls, self.attname, GeometryProxy(Geometry, self)) + + def db_type(self, connection): + return connection.ops.geo_db_type(self) + + def formfield(self, **kwargs): + defaults = {'form_class' : self.form_class, + 'geom_type' : self.geom_type, + 'srid' : self.srid, + } + defaults.update(kwargs) + if (self.dim > 2 and not 'widget' in kwargs and + not getattr(defaults['form_class'].widget, 'supports_3d', False)): + defaults['widget'] = forms.Textarea + return super(GeometryField, self).formfield(**defaults) + + def get_db_prep_lookup(self, lookup_type, value, connection, prepared=False): + """ + 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 + if lookup_type == 'isnull': + return [] + + # Populating the parameters list, and wrapping the Geometry + # with the Adapter of the spatial backend. + if isinstance(value, (tuple, list)): + params = [connection.ops.Adapter(value[0])] + 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.truncate_params: + # Lookup is one where SQL parameters aren't needed from the + # given lookup value. + pass + else: + params += value[1:] + elif isinstance(value, SQLEvaluator): + params = [] + else: + params = [connection.ops.Adapter(value)] + + return params + else: + raise ValueError('%s is not a valid spatial lookup for %s.' % + (lookup_type, self.__class__.__name__)) + + def get_prep_lookup(self, lookup_type, value): + if lookup_type == 'isnull': + return bool(value) + else: + return self.get_prep_value(value) + + def get_db_prep_save(self, value, connection): + "Prepares the value for saving in the database." + if value is None: + return None + else: + return connection.ops.Adapter(self.get_prep_value(value)) + + def get_placeholder(self, value, connection): + """ + 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): + geom_type = 'POINT' + form_class = forms.PointField + description = _("Point") + +class LineStringField(GeometryField): + geom_type = 'LINESTRING' + form_class = forms.LineStringField + description = _("Line string") + +class PolygonField(GeometryField): + geom_type = 'POLYGON' + form_class = forms.PolygonField + description = _("Polygon") + +class MultiPointField(GeometryField): + geom_type = 'MULTIPOINT' + form_class = forms.MultiPointField + description = _("Multi-point") + +class MultiLineStringField(GeometryField): + geom_type = 'MULTILINESTRING' + form_class = forms.MultiLineStringField + description = _("Multi-line string") + +class MultiPolygonField(GeometryField): + geom_type = 'MULTIPOLYGON' + form_class = forms.MultiPolygonField + description = _("Multi polygon") + +class GeometryCollectionField(GeometryField): + geom_type = 'GEOMETRYCOLLECTION' + form_class = forms.GeometryCollectionField + description = _("Geometry collection") diff --git a/lib/python2.7/site-packages/django/contrib/gis/db/models/manager.py b/lib/python2.7/site-packages/django/contrib/gis/db/models/manager.py new file mode 100644 index 0000000..aa57e3a --- /dev/null +++ b/lib/python2.7/site-packages/django/contrib/gis/db/models/manager.py @@ -0,0 +1,103 @@ +from django.db.models.manager import Manager +from django.contrib.gis.db.models.query import GeoQuerySet + +class GeoManager(Manager): + "Overrides Manager to return Geographic QuerySets." + + # This manager should be used for queries on related fields + # so that geometry columns on Oracle and MySQL are selected + # properly. + use_for_related_fields = True + + def get_queryset(self): + return GeoQuerySet(self.model, using=self._db) + + def area(self, *args, **kwargs): + return self.get_queryset().area(*args, **kwargs) + + def centroid(self, *args, **kwargs): + return self.get_queryset().centroid(*args, **kwargs) + + def collect(self, *args, **kwargs): + return self.get_queryset().collect(*args, **kwargs) + + def difference(self, *args, **kwargs): + return self.get_queryset().difference(*args, **kwargs) + + def distance(self, *args, **kwargs): + return self.get_queryset().distance(*args, **kwargs) + + def envelope(self, *args, **kwargs): + return self.get_queryset().envelope(*args, **kwargs) + + def extent(self, *args, **kwargs): + return self.get_queryset().extent(*args, **kwargs) + + def extent3d(self, *args, **kwargs): + return self.get_queryset().extent3d(*args, **kwargs) + + def force_rhr(self, *args, **kwargs): + return self.get_queryset().force_rhr(*args, **kwargs) + + def geohash(self, *args, **kwargs): + return self.get_queryset().geohash(*args, **kwargs) + + def geojson(self, *args, **kwargs): + return self.get_queryset().geojson(*args, **kwargs) + + def gml(self, *args, **kwargs): + return self.get_queryset().gml(*args, **kwargs) + + def intersection(self, *args, **kwargs): + return self.get_queryset().intersection(*args, **kwargs) + + def kml(self, *args, **kwargs): + return self.get_queryset().kml(*args, **kwargs) + + def length(self, *args, **kwargs): + return self.get_queryset().length(*args, **kwargs) + + def make_line(self, *args, **kwargs): + return self.get_queryset().make_line(*args, **kwargs) + + def mem_size(self, *args, **kwargs): + return self.get_queryset().mem_size(*args, **kwargs) + + def num_geom(self, *args, **kwargs): + return self.get_queryset().num_geom(*args, **kwargs) + + def num_points(self, *args, **kwargs): + return self.get_queryset().num_points(*args, **kwargs) + + def perimeter(self, *args, **kwargs): + return self.get_queryset().perimeter(*args, **kwargs) + + def point_on_surface(self, *args, **kwargs): + return self.get_queryset().point_on_surface(*args, **kwargs) + + def reverse_geom(self, *args, **kwargs): + return self.get_queryset().reverse_geom(*args, **kwargs) + + def scale(self, *args, **kwargs): + return self.get_queryset().scale(*args, **kwargs) + + def snap_to_grid(self, *args, **kwargs): + return self.get_queryset().snap_to_grid(*args, **kwargs) + + def svg(self, *args, **kwargs): + return self.get_queryset().svg(*args, **kwargs) + + def sym_difference(self, *args, **kwargs): + return self.get_queryset().sym_difference(*args, **kwargs) + + def transform(self, *args, **kwargs): + return self.get_queryset().transform(*args, **kwargs) + + def translate(self, *args, **kwargs): + return self.get_queryset().translate(*args, **kwargs) + + def union(self, *args, **kwargs): + return self.get_queryset().union(*args, **kwargs) + + def unionagg(self, *args, **kwargs): + return self.get_queryset().unionagg(*args, **kwargs) diff --git a/lib/python2.7/site-packages/django/contrib/gis/db/models/proxy.py b/lib/python2.7/site-packages/django/contrib/gis/db/models/proxy.py new file mode 100644 index 0000000..1fdc503 --- /dev/null +++ b/lib/python2.7/site-packages/django/contrib/gis/db/models/proxy.py @@ -0,0 +1,66 @@ +""" +The GeometryProxy object, allows for lazy-geometries. The proxy uses +Python descriptors for instantiating and setting Geometry objects +corresponding to geographic model fields. + +Thanks to Robert Coup for providing this functionality (see #4322). +""" +from django.contrib.gis import memoryview +from django.utils import six + +class GeometryProxy(object): + def __init__(self, klass, field): + """ + Proxy initializes on the given Geometry class (not an instance) and + the GeometryField. + """ + self._field = field + self._klass = klass + + def __get__(self, obj, type=None): + """ + This accessor retrieves the geometry, initializing it using the geometry + class specified during initialization and the HEXEWKB value of the field. + Currently, only GEOS or OGR geometries are supported. + """ + if obj is None: + # Accessed on a class, not an instance + return self + + # Getting the value of the field. + geom_value = obj.__dict__[self._field.attname] + + if isinstance(geom_value, self._klass): + geom = geom_value + elif (geom_value is None) or (geom_value==''): + geom = None + else: + # Otherwise, a Geometry object is built using the field's contents, + # and the model's corresponding attribute is set. + geom = self._klass(geom_value) + setattr(obj, self._field.attname, geom) + return geom + + def __set__(self, obj, value): + """ + This accessor sets the proxied geometry with the geometry class + specified during initialization. Values of None, HEXEWKB, or WKT may + be used to set the geometry as well. + """ + # The OGC Geometry type of the field. + gtype = self._field.geom_type + + # The geometry type must match that of the field -- unless the + # general GeometryField is used. + if isinstance(value, self._klass) and (str(value.geom_type).upper() == gtype or gtype == 'GEOMETRY'): + # Assigning the SRID to the geometry. + if value.srid is None: value.srid = self._field.srid + elif value is None or isinstance(value, six.string_types + (memoryview,)): + # Set with None, WKT, HEX, or WKB + pass + else: + raise TypeError('cannot set %s GeometryProxy with value of type: %s' % (obj.__class__.__name__, type(value))) + + # Setting the objects dictionary with the value, and returning. + obj.__dict__[self._field.attname] = value + return value diff --git a/lib/python2.7/site-packages/django/contrib/gis/db/models/query.py b/lib/python2.7/site-packages/django/contrib/gis/db/models/query.py new file mode 100644 index 0000000..c89912b --- /dev/null +++ b/lib/python2.7/site-packages/django/contrib/gis/db/models/query.py @@ -0,0 +1,784 @@ +from django.db import connections +from django.db.models.query import QuerySet, ValuesQuerySet, ValuesListQuerySet + +from django.contrib.gis import memoryview +from django.contrib.gis.db.models import aggregates +from django.contrib.gis.db.models.fields import get_srid_info, PointField, LineStringField +from django.contrib.gis.db.models.sql import AreaField, DistanceField, GeomField, GeoQuery +from django.contrib.gis.geometry.backend import Geometry +from django.contrib.gis.measure import Area, Distance + +from django.utils import six + + +class GeoQuerySet(QuerySet): + "The Geographic QuerySet." + + ### Methods overloaded from QuerySet ### + def __init__(self, model=None, query=None, using=None): + super(GeoQuerySet, self).__init__(model=model, query=query, using=using) + self.query = query or GeoQuery(self.model) + + def values(self, *fields): + return self._clone(klass=GeoValuesQuerySet, setup=True, _fields=fields) + + def values_list(self, *fields, **kwargs): + flat = kwargs.pop('flat', False) + if kwargs: + raise TypeError('Unexpected keyword arguments to values_list: %s' + % (list(kwargs),)) + if flat and len(fields) > 1: + raise TypeError("'flat' is not valid when values_list is called with more than one field.") + return self._clone(klass=GeoValuesListQuerySet, setup=True, flat=flat, + _fields=fields) + + ### GeoQuerySet Methods ### + def area(self, tolerance=0.05, **kwargs): + """ + Returns the area of the geographic field in an `area` attribute on + each element of this GeoQuerySet. + """ + # Peforming setup here rather than in `_spatial_attribute` so that + # we can get the units for `AreaField`. + procedure_args, geo_field = self._spatial_setup('area', field_name=kwargs.get('field_name', None)) + s = {'procedure_args' : procedure_args, + 'geo_field' : geo_field, + 'setup' : False, + } + connection = connections[self.db] + backend = connection.ops + if backend.oracle: + s['procedure_fmt'] = '%(geo_col)s,%(tolerance)s' + s['procedure_args']['tolerance'] = tolerance + s['select_field'] = AreaField('sq_m') # Oracle returns area in units of meters. + elif backend.postgis or backend.spatialite: + if backend.geography: + # Geography fields support area calculation, returns square meters. + s['select_field'] = AreaField('sq_m') + elif not geo_field.geodetic(connection): + # Getting the area units of the geographic field. + s['select_field'] = AreaField(Area.unit_attname(geo_field.units_name(connection))) + else: + # TODO: Do we want to support raw number areas for geodetic fields? + raise Exception('Area on geodetic coordinate systems not supported.') + return self._spatial_attribute('area', s, **kwargs) + + def centroid(self, **kwargs): + """ + Returns the centroid of the geographic field in a `centroid` + attribute on each element of this GeoQuerySet. + """ + return self._geom_attribute('centroid', **kwargs) + + def collect(self, **kwargs): + """ + Performs an aggregate collect operation on the given geometry field. + This is analagous to a union operation, but much faster because + boundaries are not dissolved. + """ + return self._spatial_aggregate(aggregates.Collect, **kwargs) + + def difference(self, geom, **kwargs): + """ + Returns the spatial difference of the geographic field in a `difference` + attribute on each element of this GeoQuerySet. + """ + return self._geomset_attribute('difference', geom, **kwargs) + + def distance(self, geom, **kwargs): + """ + Returns the distance from the given geographic field name to the + given geometry in a `distance` attribute on each element of the + GeoQuerySet. + + Keyword Arguments: + `spheroid` => If the geometry field is geodetic and PostGIS is + the spatial database, then the more accurate + spheroid calculation will be used instead of the + quicker sphere calculation. + + `tolerance` => Used only for Oracle. The tolerance is + in meters -- a default of 5 centimeters (0.05) + is used. + """ + return self._distance_attribute('distance', geom, **kwargs) + + def envelope(self, **kwargs): + """ + Returns a Geometry representing the bounding box of the + Geometry field in an `envelope` attribute on each element of + the GeoQuerySet. + """ + return self._geom_attribute('envelope', **kwargs) + + def extent(self, **kwargs): + """ + Returns the extent (aggregate) of the features in the GeoQuerySet. The + extent will be returned as a 4-tuple, consisting of (xmin, ymin, xmax, ymax). + """ + return self._spatial_aggregate(aggregates.Extent, **kwargs) + + def extent3d(self, **kwargs): + """ + Returns the aggregate extent, in 3D, of the features in the + GeoQuerySet. It is returned as a 6-tuple, comprising: + (xmin, ymin, zmin, xmax, ymax, zmax). + """ + return self._spatial_aggregate(aggregates.Extent3D, **kwargs) + + def force_rhr(self, **kwargs): + """ + Returns a modified version of the Polygon/MultiPolygon in which + all of the vertices follow the Right-Hand-Rule. By default, + this is attached as the `force_rhr` attribute on each element + of the GeoQuerySet. + """ + return self._geom_attribute('force_rhr', **kwargs) + + def geojson(self, precision=8, crs=False, bbox=False, **kwargs): + """ + Returns a GeoJSON representation of the geomtry field in a `geojson` + attribute on each element of the GeoQuerySet. + + The `crs` and `bbox` keywords may be set to True if the users wants + the coordinate reference system and the bounding box to be included + in the GeoJSON representation of the geometry. + """ + backend = connections[self.db].ops + if not backend.geojson: + raise NotImplementedError('Only PostGIS 1.3.4+ and SpatiaLite 3.0+ ' + 'support GeoJSON serialization.') + + if not isinstance(precision, six.integer_types): + raise TypeError('Precision keyword must be set with an integer.') + + # Setting the options flag -- which depends on which version of + # PostGIS we're using. SpatiaLite only uses the first group of options. + if backend.spatial_version >= (1, 4, 0): + options = 0 + if crs and bbox: options = 3 + elif bbox: options = 1 + elif crs: options = 2 + else: + options = 0 + if crs and bbox: options = 3 + elif crs: options = 1 + elif bbox: options = 2 + s = {'desc' : 'GeoJSON', + 'procedure_args' : {'precision' : precision, 'options' : options}, + 'procedure_fmt' : '%(geo_col)s,%(precision)s,%(options)s', + } + return self._spatial_attribute('geojson', s, **kwargs) + + def geohash(self, precision=20, **kwargs): + """ + Returns a GeoHash representation of the given field in a `geohash` + attribute on each element of the GeoQuerySet. + + The `precision` keyword may be used to custom the number of + _characters_ used in the output GeoHash, the default is 20. + """ + s = {'desc' : 'GeoHash', + 'procedure_args': {'precision': precision}, + 'procedure_fmt': '%(geo_col)s,%(precision)s', + } + return self._spatial_attribute('geohash', s, **kwargs) + + def gml(self, precision=8, version=2, **kwargs): + """ + Returns GML representation of the given field in a `gml` attribute + on each element of the GeoQuerySet. + """ + backend = connections[self.db].ops + s = {'desc' : 'GML', 'procedure_args' : {'precision' : precision}} + if backend.postgis: + # PostGIS AsGML() aggregate function parameter order depends on the + # version -- uggh. + if backend.spatial_version > (1, 3, 1): + s['procedure_fmt'] = '%(version)s,%(geo_col)s,%(precision)s' + else: + s['procedure_fmt'] = '%(geo_col)s,%(precision)s,%(version)s' + s['procedure_args'] = {'precision' : precision, 'version' : version} + + return self._spatial_attribute('gml', s, **kwargs) + + def intersection(self, geom, **kwargs): + """ + Returns the spatial intersection of the Geometry field in + an `intersection` attribute on each element of this + GeoQuerySet. + """ + return self._geomset_attribute('intersection', geom, **kwargs) + + def kml(self, **kwargs): + """ + Returns KML representation of the geometry field in a `kml` + attribute on each element of this GeoQuerySet. + """ + s = {'desc' : 'KML', + 'procedure_fmt' : '%(geo_col)s,%(precision)s', + 'procedure_args' : {'precision' : kwargs.pop('precision', 8)}, + } + return self._spatial_attribute('kml', s, **kwargs) + + def length(self, **kwargs): + """ + Returns the length of the geometry field as a `Distance` object + stored in a `length` attribute on each element of this GeoQuerySet. + """ + return self._distance_attribute('length', None, **kwargs) + + def make_line(self, **kwargs): + """ + Creates a linestring from all of the PointField geometries in the + this GeoQuerySet and returns it. This is a spatial aggregate + method, and thus returns a geometry rather than a GeoQuerySet. + """ + return self._spatial_aggregate(aggregates.MakeLine, geo_field_type=PointField, **kwargs) + + def mem_size(self, **kwargs): + """ + Returns the memory size (number of bytes) that the geometry field takes + in a `mem_size` attribute on each element of this GeoQuerySet. + """ + return self._spatial_attribute('mem_size', {}, **kwargs) + + def num_geom(self, **kwargs): + """ + Returns the number of geometries if the field is a + GeometryCollection or Multi* Field in a `num_geom` + attribute on each element of this GeoQuerySet; otherwise + the sets with None. + """ + return self._spatial_attribute('num_geom', {}, **kwargs) + + def num_points(self, **kwargs): + """ + Returns the number of points in the first linestring in the + Geometry field in a `num_points` attribute on each element of + this GeoQuerySet; otherwise sets with None. + """ + return self._spatial_attribute('num_points', {}, **kwargs) + + def perimeter(self, **kwargs): + """ + Returns the perimeter of the geometry field as a `Distance` object + stored in a `perimeter` attribute on each element of this GeoQuerySet. + """ + return self._distance_attribute('perimeter', None, **kwargs) + + def point_on_surface(self, **kwargs): + """ + Returns a Point geometry guaranteed to lie on the surface of the + Geometry field in a `point_on_surface` attribute on each element + of this GeoQuerySet; otherwise sets with None. + """ + return self._geom_attribute('point_on_surface', **kwargs) + + def reverse_geom(self, **kwargs): + """ + Reverses the coordinate order of the geometry, and attaches as a + `reverse` attribute on each element of this GeoQuerySet. + """ + s = {'select_field' : GeomField(),} + kwargs.setdefault('model_att', 'reverse_geom') + if connections[self.db].ops.oracle: + s['geo_field_type'] = LineStringField + return self._spatial_attribute('reverse', s, **kwargs) + + def scale(self, x, y, z=0.0, **kwargs): + """ + Scales the geometry to a new size by multiplying the ordinates + with the given x,y,z scale factors. + """ + if connections[self.db].ops.spatialite: + if z != 0.0: + raise NotImplementedError('SpatiaLite does not support 3D scaling.') + s = {'procedure_fmt' : '%(geo_col)s,%(x)s,%(y)s', + 'procedure_args' : {'x' : x, 'y' : y}, + 'select_field' : GeomField(), + } + else: + s = {'procedure_fmt' : '%(geo_col)s,%(x)s,%(y)s,%(z)s', + 'procedure_args' : {'x' : x, 'y' : y, 'z' : z}, + 'select_field' : GeomField(), + } + return self._spatial_attribute('scale', s, **kwargs) + + def snap_to_grid(self, *args, **kwargs): + """ + Snap all points of the input geometry to the grid. How the + geometry is snapped to the grid depends on how many arguments + were given: + - 1 argument : A single size to snap both the X and Y grids to. + - 2 arguments: X and Y sizes to snap the grid to. + - 4 arguments: X, Y sizes and the X, Y origins. + """ + if False in [isinstance(arg, (float,) + six.integer_types) for arg in args]: + raise TypeError('Size argument(s) for the grid must be a float or integer values.') + + nargs = len(args) + if nargs == 1: + size = args[0] + procedure_fmt = '%(geo_col)s,%(size)s' + procedure_args = {'size' : size} + elif nargs == 2: + xsize, ysize = args + procedure_fmt = '%(geo_col)s,%(xsize)s,%(ysize)s' + procedure_args = {'xsize' : xsize, 'ysize' : ysize} + elif nargs == 4: + xsize, ysize, xorigin, yorigin = args + procedure_fmt = '%(geo_col)s,%(xorigin)s,%(yorigin)s,%(xsize)s,%(ysize)s' + procedure_args = {'xsize' : xsize, 'ysize' : ysize, + 'xorigin' : xorigin, 'yorigin' : yorigin} + else: + raise ValueError('Must provide 1, 2, or 4 arguments to `snap_to_grid`.') + + s = {'procedure_fmt' : procedure_fmt, + 'procedure_args' : procedure_args, + 'select_field' : GeomField(), + } + + return self._spatial_attribute('snap_to_grid', s, **kwargs) + + def svg(self, relative=False, precision=8, **kwargs): + """ + Returns SVG representation of the geographic field in a `svg` + attribute on each element of this GeoQuerySet. + + Keyword Arguments: + `relative` => If set to True, this will evaluate the path in + terms of relative moves (rather than absolute). + + `precision` => May be used to set the maximum number of decimal + digits used in output (defaults to 8). + """ + relative = int(bool(relative)) + if not isinstance(precision, six.integer_types): + raise TypeError('SVG precision keyword argument must be an integer.') + s = {'desc' : 'SVG', + 'procedure_fmt' : '%(geo_col)s,%(rel)s,%(precision)s', + 'procedure_args' : {'rel' : relative, + 'precision' : precision, + } + } + return self._spatial_attribute('svg', s, **kwargs) + + def sym_difference(self, geom, **kwargs): + """ + Returns the symmetric difference of the geographic field in a + `sym_difference` attribute on each element of this GeoQuerySet. + """ + return self._geomset_attribute('sym_difference', geom, **kwargs) + + def translate(self, x, y, z=0.0, **kwargs): + """ + Translates the geometry to a new location using the given numeric + parameters as offsets. + """ + if connections[self.db].ops.spatialite: + if z != 0.0: + raise NotImplementedError('SpatiaLite does not support 3D translation.') + s = {'procedure_fmt' : '%(geo_col)s,%(x)s,%(y)s', + 'procedure_args' : {'x' : x, 'y' : y}, + 'select_field' : GeomField(), + } + else: + s = {'procedure_fmt' : '%(geo_col)s,%(x)s,%(y)s,%(z)s', + 'procedure_args' : {'x' : x, 'y' : y, 'z' : z}, + 'select_field' : GeomField(), + } + return self._spatial_attribute('translate', s, **kwargs) + + def transform(self, srid=4326, **kwargs): + """ + Transforms the given geometry field to the given SRID. If no SRID is + provided, the transformation will default to using 4326 (WGS84). + """ + if not isinstance(srid, six.integer_types): + raise TypeError('An integer SRID must be provided.') + field_name = kwargs.get('field_name', None) + tmp, geo_field = self._spatial_setup('transform', field_name=field_name) + + # Getting the selection SQL for the given geographic field. + field_col = self._geocol_select(geo_field, field_name) + + # Why cascading substitutions? Because spatial backends like + # Oracle and MySQL already require a function call to convert to text, thus + # when there's also a transformation we need to cascade the substitutions. + # For example, 'SDO_UTIL.TO_WKTGEOMETRY(SDO_CS.TRANSFORM( ... )' + geo_col = self.query.custom_select.get(geo_field, field_col) + + # Setting the key for the field's column with the custom SELECT SQL to + # override the geometry column returned from the database. + custom_sel = '%s(%s, %s)' % (connections[self.db].ops.transform, geo_col, srid) + # TODO: Should we have this as an alias? + # custom_sel = '(%s(%s, %s)) AS %s' % (SpatialBackend.transform, geo_col, srid, qn(geo_field.name)) + self.query.transformed_srid = srid # So other GeoQuerySet methods + self.query.custom_select[geo_field] = custom_sel + return self._clone() + + def union(self, geom, **kwargs): + """ + Returns the union of the geographic field with the given + Geometry in a `union` attribute on each element of this GeoQuerySet. + """ + return self._geomset_attribute('union', geom, **kwargs) + + def unionagg(self, **kwargs): + """ + Performs an aggregate union on the given geometry field. Returns + None if the GeoQuerySet is empty. The `tolerance` keyword is for + Oracle backends only. + """ + return self._spatial_aggregate(aggregates.Union, **kwargs) + + ### Private API -- Abstracted DRY routines. ### + def _spatial_setup(self, att, desc=None, field_name=None, geo_field_type=None): + """ + Performs set up for executing the spatial function. + """ + # Does the spatial backend support this? + connection = connections[self.db] + func = getattr(connection.ops, att, False) + if desc is None: desc = att + if not func: + raise NotImplementedError('%s stored procedure not available on ' + 'the %s backend.' % + (desc, connection.ops.name)) + + # Initializing the procedure arguments. + procedure_args = {'function' : func} + + # Is there a geographic field in the model to perform this + # operation on? + geo_field = self.query._geo_field(field_name) + if not geo_field: + raise TypeError('%s output only available on GeometryFields.' % func) + + # If the `geo_field_type` keyword was used, then enforce that + # type limitation. + if not geo_field_type is None and not isinstance(geo_field, geo_field_type): + raise TypeError('"%s" stored procedures may only be called on %ss.' % (func, geo_field_type.__name__)) + + # Setting the procedure args. + procedure_args['geo_col'] = self._geocol_select(geo_field, field_name) + + return procedure_args, geo_field + + def _spatial_aggregate(self, aggregate, field_name=None, + geo_field_type=None, tolerance=0.05): + """ + DRY routine for calling aggregate spatial stored procedures and + returning their result to the caller of the function. + """ + # Getting the field the geographic aggregate will be called on. + geo_field = self.query._geo_field(field_name) + if not geo_field: + raise TypeError('%s aggregate only available on GeometryFields.' % aggregate.name) + + # Checking if there are any geo field type limitations on this + # aggregate (e.g. ST_Makeline only operates on PointFields). + if not geo_field_type is None and not isinstance(geo_field, geo_field_type): + raise TypeError('%s aggregate may only be called on %ss.' % (aggregate.name, geo_field_type.__name__)) + + # Getting the string expression of the field name, as this is the + # argument taken by `Aggregate` objects. + agg_col = field_name or geo_field.name + + # Adding any keyword parameters for the Aggregate object. Oracle backends + # in particular need an additional `tolerance` parameter. + agg_kwargs = {} + if connections[self.db].ops.oracle: agg_kwargs['tolerance'] = tolerance + + # Calling the QuerySet.aggregate, and returning only the value of the aggregate. + return self.aggregate(geoagg=aggregate(agg_col, **agg_kwargs))['geoagg'] + + def _spatial_attribute(self, att, settings, field_name=None, model_att=None): + """ + DRY routine for calling a spatial stored procedure on a geometry column + and attaching its output as an attribute of the model. + + Arguments: + att: + The name of the spatial attribute that holds the spatial + SQL function to call. + + settings: + Dictonary of internal settings to customize for the spatial procedure. + + Public Keyword Arguments: + + field_name: + The name of the geographic field to call the spatial + function on. May also be a lookup to a geometry field + as part of a foreign key relation. + + model_att: + The name of the model attribute to attach the output of + the spatial function to. + """ + # Default settings. + settings.setdefault('desc', None) + settings.setdefault('geom_args', ()) + settings.setdefault('geom_field', None) + settings.setdefault('procedure_args', {}) + settings.setdefault('procedure_fmt', '%(geo_col)s') + settings.setdefault('select_params', []) + + connection = connections[self.db] + backend = connection.ops + + # Performing setup for the spatial column, unless told not to. + if settings.get('setup', True): + default_args, geo_field = self._spatial_setup(att, desc=settings['desc'], field_name=field_name, + geo_field_type=settings.get('geo_field_type', None)) + for k, v in six.iteritems(default_args): settings['procedure_args'].setdefault(k, v) + else: + geo_field = settings['geo_field'] + + # The attribute to attach to the model. + if not isinstance(model_att, six.string_types): model_att = att + + # Special handling for any argument that is a geometry. + for name in settings['geom_args']: + # Using the field's get_placeholder() routine to get any needed + # transformation SQL. + geom = geo_field.get_prep_value(settings['procedure_args'][name]) + params = geo_field.get_db_prep_lookup('contains', geom, connection=connection) + geom_placeholder = geo_field.get_placeholder(geom, connection) + + # Replacing the procedure format with that of any needed + # transformation SQL. + old_fmt = '%%(%s)s' % name + new_fmt = geom_placeholder % '%%s' + settings['procedure_fmt'] = settings['procedure_fmt'].replace(old_fmt, new_fmt) + settings['select_params'].extend(params) + + # Getting the format for the stored procedure. + fmt = '%%(function)s(%s)' % settings['procedure_fmt'] + + # If the result of this function needs to be converted. + if settings.get('select_field', False): + sel_fld = settings['select_field'] + if isinstance(sel_fld, GeomField) and backend.select: + self.query.custom_select[model_att] = backend.select + if connection.ops.oracle: + sel_fld.empty_strings_allowed = False + self.query.extra_select_fields[model_att] = sel_fld + + # Finally, setting the extra selection attribute with + # the format string expanded with the stored procedure + # arguments. + return self.extra(select={model_att : fmt % settings['procedure_args']}, + select_params=settings['select_params']) + + def _distance_attribute(self, func, geom=None, tolerance=0.05, spheroid=False, **kwargs): + """ + DRY routine for GeoQuerySet distance attribute routines. + """ + # Setting up the distance procedure arguments. + procedure_args, geo_field = self._spatial_setup(func, field_name=kwargs.get('field_name', None)) + + # If geodetic defaulting distance attribute to meters (Oracle and + # PostGIS spherical distances return meters). Otherwise, use the + # units of the geometry field. + connection = connections[self.db] + geodetic = geo_field.geodetic(connection) + geography = geo_field.geography + + if geodetic: + dist_att = 'm' + else: + dist_att = Distance.unit_attname(geo_field.units_name(connection)) + + # Shortcut booleans for what distance function we're using and + # whether the geometry field is 3D. + distance = func == 'distance' + length = func == 'length' + perimeter = func == 'perimeter' + if not (distance or length or perimeter): + raise ValueError('Unknown distance function: %s' % func) + geom_3d = geo_field.dim == 3 + + # The field's get_db_prep_lookup() is used to get any + # extra distance parameters. Here we set up the + # parameters that will be passed in to field's function. + lookup_params = [geom or 'POINT (0 0)', 0] + + # Getting the spatial backend operations. + backend = connection.ops + + # If the spheroid calculation is desired, either by the `spheroid` + # 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 + (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) + + # The `geom_args` flag is set to true if a geometry parameter was + # passed in. + geom_args = bool(geom) + + if backend.oracle: + if distance: + procedure_fmt = '%(geo_col)s,%(geom)s,%(tolerance)s' + elif length or perimeter: + procedure_fmt = '%(geo_col)s,%(tolerance)s' + procedure_args['tolerance'] = tolerance + else: + # Getting whether this field is in units of degrees since the field may have + # been transformed via the `transform` GeoQuerySet method. + if self.query.transformed_srid: + u, unit_name, s = get_srid_info(self.query.transformed_srid, connection) + geodetic = unit_name in geo_field.geodetic_units + + if backend.spatialite and geodetic: + raise ValueError('SQLite does not support linear distance calculations on geodetic coordinate systems.') + + if distance: + if self.query.transformed_srid: + # Setting the `geom_args` flag to false because we want to handle + # transformation SQL here, rather than the way done by default + # (which will transform to the original SRID of the field rather + # than to what was transformed to). + geom_args = False + procedure_fmt = '%s(%%(geo_col)s, %s)' % (backend.transform, self.query.transformed_srid) + if geom.srid is None or geom.srid == self.query.transformed_srid: + # If the geom parameter srid is None, it is assumed the coordinates + # are in the transformed units. A placeholder is used for the + # geometry parameter. `GeomFromText` constructor is also needed + # to wrap geom placeholder for SpatiaLite. + if backend.spatialite: + procedure_fmt += ', %s(%%%%s, %s)' % (backend.from_text, self.query.transformed_srid) + else: + procedure_fmt += ', %%s' + else: + # We need to transform the geom to the srid specified in `transform()`, + # so wrapping the geometry placeholder in transformation SQL. + # SpatiaLite also needs geometry placeholder wrapped in `GeomFromText` + # constructor. + if backend.spatialite: + procedure_fmt += ', %s(%s(%%%%s, %s), %s)' % (backend.transform, backend.from_text, + geom.srid, self.query.transformed_srid) + else: + procedure_fmt += ', %s(%%%%s, %s)' % (backend.transform, self.query.transformed_srid) + else: + # `transform()` was not used on this GeoQuerySet. + procedure_fmt = '%(geo_col)s,%(geom)s' + + 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 + # some error checking is required. + if not backend.geography: + if not isinstance(geo_field, PointField): + raise ValueError('Spherical distance calculation only supported on PointFields.') + if not str(Geometry(memoryview(params[0].ewkb)).geom_type) == 'Point': + raise ValueError('Spherical distance calculation only supported with Point Geometry parameters') + # The `function` procedure argument needs to be set differently for + # geodetic distance calculations. + if spheroid: + # Call to distance_spheroid() requires spheroid param as well. + procedure_fmt += ",'%(spheroid)s'" + procedure_args.update({'function' : backend.distance_spheroid, 'spheroid' : params[1]}) + else: + procedure_args.update({'function' : backend.distance_sphere}) + elif length or perimeter: + procedure_fmt = '%(geo_col)s' + if not geography and geodetic and length: + # There's no `length_sphere`, and `length_spheroid` also + # works on 3D geometries. + procedure_fmt += ",'%(spheroid)s'" + procedure_args.update({'function' : backend.length_spheroid, 'spheroid' : params[1]}) + elif geom_3d and backend.postgis: + # Use 3D variants of perimeter and length routines on PostGIS. + if perimeter: + procedure_args.update({'function' : backend.perimeter3d}) + elif length: + procedure_args.update({'function' : backend.length3d}) + + # Setting up the settings for `_spatial_attribute`. + s = {'select_field' : DistanceField(dist_att), + 'setup' : False, + 'geo_field' : geo_field, + 'procedure_args' : procedure_args, + 'procedure_fmt' : procedure_fmt, + } + if geom_args: + s['geom_args'] = ('geom',) + s['procedure_args']['geom'] = geom + elif geom: + # The geometry is passed in as a parameter because we handled + # transformation conditions in this routine. + s['select_params'] = [backend.Adapter(geom)] + return self._spatial_attribute(func, s, **kwargs) + + def _geom_attribute(self, func, tolerance=0.05, **kwargs): + """ + DRY routine for setting up a GeoQuerySet method that attaches a + Geometry attribute (e.g., `centroid`, `point_on_surface`). + """ + s = {'select_field' : GeomField(),} + if connections[self.db].ops.oracle: + s['procedure_fmt'] = '%(geo_col)s,%(tolerance)s' + s['procedure_args'] = {'tolerance' : tolerance} + return self._spatial_attribute(func, s, **kwargs) + + def _geomset_attribute(self, func, geom, tolerance=0.05, **kwargs): + """ + DRY routine for setting up a GeoQuerySet method that attaches a + Geometry attribute and takes a Geoemtry parameter. This is used + for geometry set-like operations (e.g., intersection, difference, + union, sym_difference). + """ + s = {'geom_args' : ('geom',), + 'select_field' : GeomField(), + 'procedure_fmt' : '%(geo_col)s,%(geom)s', + 'procedure_args' : {'geom' : geom}, + } + if connections[self.db].ops.oracle: + s['procedure_fmt'] += ',%(tolerance)s' + s['procedure_args']['tolerance'] = tolerance + return self._spatial_attribute(func, s, **kwargs) + + def _geocol_select(self, geo_field, field_name): + """ + Helper routine for constructing the SQL to select the geographic + column. Takes into account if the geographic field is in a + ForeignKey relation to the current model. + """ + opts = self.model._meta + if not geo_field in opts.fields: + # Is this operation going to be on a related geographic field? + # If so, it'll have to be added to the select related information + # (e.g., if 'location__point' was given as the field name). + self.query.add_select_related([field_name]) + compiler = self.query.get_compiler(self.db) + compiler.pre_sql_setup() + for (rel_table, rel_col), field in self.query.related_select_cols: + if field == geo_field: + return compiler._field_column(geo_field, rel_table) + raise ValueError("%r not in self.query.related_select_cols" % geo_field) + elif not geo_field in opts.local_fields: + # This geographic field is inherited from another model, so we have to + # use the db table for the _parent_ model instead. + tmp_fld, parent_model, direct, m2m = opts.get_field_by_name(geo_field.name) + return self.query.get_compiler(self.db)._field_column(geo_field, parent_model._meta.db_table) + else: + return self.query.get_compiler(self.db)._field_column(geo_field) + +class GeoValuesQuerySet(ValuesQuerySet): + def __init__(self, *args, **kwargs): + super(GeoValuesQuerySet, self).__init__(*args, **kwargs) + # This flag tells `resolve_columns` to run the values through + # `convert_values`. This ensures that Geometry objects instead + # of string values are returned with `values()` or `values_list()`. + self.query.geo_values = True + +class GeoValuesListQuerySet(GeoValuesQuerySet, ValuesListQuerySet): + pass diff --git a/lib/python2.7/site-packages/django/contrib/gis/db/models/sql/__init__.py b/lib/python2.7/site-packages/django/contrib/gis/db/models/sql/__init__.py new file mode 100644 index 0000000..38d9507 --- /dev/null +++ b/lib/python2.7/site-packages/django/contrib/gis/db/models/sql/__init__.py @@ -0,0 +1,3 @@ +from django.contrib.gis.db.models.sql.conversion import AreaField, DistanceField, GeomField +from django.contrib.gis.db.models.sql.query import GeoQuery +from django.contrib.gis.db.models.sql.where import GeoWhereNode diff --git a/lib/python2.7/site-packages/django/contrib/gis/db/models/sql/aggregates.py b/lib/python2.7/site-packages/django/contrib/gis/db/models/sql/aggregates.py new file mode 100644 index 0000000..ae848c0 --- /dev/null +++ b/lib/python2.7/site-packages/django/contrib/gis/db/models/sql/aggregates.py @@ -0,0 +1,62 @@ +from django.db.models.sql.aggregates import * +from django.contrib.gis.db.models.fields import GeometryField + +class GeoAggregate(Aggregate): + # Default SQL template for spatial aggregates. + sql_template = '%(function)s(%(field)s)' + + # Conversion class, if necessary. + conversion_class = None + + # Flags for indicating the type of the aggregate. + is_extent = False + + def __init__(self, col, source=None, is_summary=False, tolerance=0.05, **extra): + super(GeoAggregate, self).__init__(col, source, is_summary, **extra) + + # Required by some Oracle aggregates. + self.tolerance = tolerance + + # Can't use geographic aggregates on non-geometry fields. + if not isinstance(self.source, GeometryField): + raise ValueError('Geospatial aggregates only allowed on geometry fields.') + + def as_sql(self, qn, connection): + "Return the aggregate, rendered as SQL with parameters." + + if connection.ops.oracle: + self.extra['tolerance'] = self.tolerance + + params = [] + + if hasattr(self.col, 'as_sql'): + field_name, params = self.col.as_sql(qn, connection) + elif isinstance(self.col, (list, tuple)): + field_name = '.'.join([qn(c) for c in self.col]) + else: + field_name = self.col + + sql_template, sql_function = connection.ops.spatial_aggregate_sql(self) + + substitutions = { + 'function': sql_function, + 'field': field_name + } + substitutions.update(self.extra) + + return sql_template % substitutions, params + +class Collect(GeoAggregate): + pass + +class Extent(GeoAggregate): + is_extent = '2D' + +class Extent3D(GeoAggregate): + is_extent = '3D' + +class MakeLine(GeoAggregate): + pass + +class Union(GeoAggregate): + pass diff --git a/lib/python2.7/site-packages/django/contrib/gis/db/models/sql/compiler.py b/lib/python2.7/site-packages/django/contrib/gis/db/models/sql/compiler.py new file mode 100644 index 0000000..3fc9c17 --- /dev/null +++ b/lib/python2.7/site-packages/django/contrib/gis/db/models/sql/compiler.py @@ -0,0 +1,313 @@ +import datetime + +from django.conf import settings +from django.db.backends.util import truncate_name, typecast_date, typecast_timestamp +from django.db.models.sql import compiler +from django.db.models.sql.constants import MULTI +from django.utils import six +from django.utils.six.moves import zip, zip_longest +from django.utils import timezone + +SQLCompiler = compiler.SQLCompiler + +class GeoSQLCompiler(compiler.SQLCompiler): + + def get_columns(self, with_aliases=False): + """ + Return the list of columns to use in the select statement. If no + columns have been specified, returns all columns relating to fields in + the model. + + If 'with_aliases' is true, any column names that are duplicated + (without the table names) are given unique aliases. This is needed in + some cases to avoid ambiguitity with nested queries. + + This routine is overridden from Query to handle customized selection of + geometry columns. + """ + qn = self.quote_name_unless_alias + qn2 = self.connection.ops.quote_name + result = ['(%s) AS %s' % (self.get_extra_select_format(alias) % col[0], qn2(alias)) + for alias, col in six.iteritems(self.query.extra_select)] + params = [] + aliases = set(self.query.extra_select.keys()) + if with_aliases: + col_aliases = aliases.copy() + else: + col_aliases = set() + if self.query.select: + only_load = self.deferred_to_columns() + # This loop customized for GeoQuery. + for col, field in self.query.select: + if isinstance(col, (list, tuple)): + alias, column = col + table = self.query.alias_map[alias].table_name + if table in only_load and column not in only_load[table]: + continue + r = self.get_field_select(field, alias, column) + if with_aliases: + if col[1] in col_aliases: + c_alias = 'Col%d' % len(col_aliases) + result.append('%s AS %s' % (r, c_alias)) + aliases.add(c_alias) + col_aliases.add(c_alias) + else: + result.append('%s AS %s' % (r, qn2(col[1]))) + aliases.add(r) + col_aliases.add(col[1]) + else: + result.append(r) + aliases.add(r) + col_aliases.add(col[1]) + else: + col_sql, col_params = col.as_sql(qn, self.connection) + result.append(col_sql) + params.extend(col_params) + + if hasattr(col, 'alias'): + aliases.add(col.alias) + col_aliases.add(col.alias) + + elif self.query.default_cols: + cols, new_aliases = self.get_default_columns(with_aliases, + col_aliases) + result.extend(cols) + aliases.update(new_aliases) + + max_name_length = self.connection.ops.max_name_length() + for alias, aggregate in self.query.aggregate_select.items(): + agg_sql, agg_params = aggregate.as_sql(qn, self.connection) + if alias is None: + result.append(agg_sql) + else: + result.append('%s AS %s' % (agg_sql, qn(truncate_name(alias, max_name_length)))) + params.extend(agg_params) + + # This loop customized for GeoQuery. + for (table, col), field in self.query.related_select_cols: + r = self.get_field_select(field, table, col) + if with_aliases and col in col_aliases: + c_alias = 'Col%d' % len(col_aliases) + result.append('%s AS %s' % (r, c_alias)) + aliases.add(c_alias) + col_aliases.add(c_alias) + else: + result.append(r) + aliases.add(r) + col_aliases.add(col) + + self._select_aliases = aliases + return result, params + + def get_default_columns(self, with_aliases=False, col_aliases=None, + start_alias=None, opts=None, as_pairs=False, from_parent=None): + """ + Computes the default columns for selecting every field in the base + model. Will sometimes be called to pull in related models (e.g. via + select_related), in which case "opts" and "start_alias" will be given + to provide a starting point for the traversal. + + Returns a list of strings, quoted appropriately for use in SQL + directly, as well as a set of aliases used in the select statement (if + 'as_pairs' is True, returns a list of (alias, col_name) pairs instead + of strings as the first component and None as the second component). + + This routine is overridden from Query to handle customized selection of + geometry columns. + """ + result = [] + if opts is None: + opts = self.query.get_meta() + aliases = set() + only_load = self.deferred_to_columns() + seen = self.query.included_inherited_models.copy() + if start_alias: + seen[None] = start_alias + for field, model in opts.get_concrete_fields_with_model(): + if from_parent and model is not None and issubclass(from_parent, model): + # Avoid loading data for already loaded parents. + continue + alias = self.query.join_parent_model(opts, model, start_alias, seen) + table = self.query.alias_map[alias].table_name + if table in only_load and field.column not in only_load[table]: + continue + if as_pairs: + result.append((alias, field)) + aliases.add(alias) + continue + # This part of the function is customized for GeoQuery. We + # see if there was any custom selection specified in the + # dictionary, and set up the selection format appropriately. + field_sel = self.get_field_select(field, alias) + if with_aliases and field.column in col_aliases: + c_alias = 'Col%d' % len(col_aliases) + result.append('%s AS %s' % (field_sel, c_alias)) + col_aliases.add(c_alias) + aliases.add(c_alias) + else: + r = field_sel + result.append(r) + aliases.add(r) + if with_aliases: + col_aliases.add(field.column) + return result, aliases + + def resolve_columns(self, row, fields=()): + """ + This routine is necessary so that distances and geometries returned + from extra selection SQL get resolved appropriately into Python + objects. + """ + values = [] + aliases = list(self.query.extra_select) + + # Have to set a starting row number offset that is used for + # determining the correct starting row index -- needed for + # doing pagination with Oracle. + rn_offset = 0 + if self.connection.ops.oracle: + if self.query.high_mark is not None or self.query.low_mark: rn_offset = 1 + index_start = rn_offset + len(aliases) + + # Converting any extra selection values (e.g., geometries and + # distance objects added by GeoQuerySet methods). + values = [self.query.convert_values(v, + self.query.extra_select_fields.get(a, None), + self.connection) + for v, a in zip(row[rn_offset:index_start], aliases)] + if self.connection.ops.oracle or getattr(self.query, 'geo_values', False): + # We resolve the rest of the columns if we're on Oracle or if + # the `geo_values` attribute is defined. + for value, field in zip_longest(row[index_start:], fields): + values.append(self.query.convert_values(value, field, self.connection)) + else: + values.extend(row[index_start:]) + return tuple(values) + + #### Routines unique to GeoQuery #### + def get_extra_select_format(self, alias): + sel_fmt = '%s' + if hasattr(self.query, 'custom_select') and alias in self.query.custom_select: + sel_fmt = sel_fmt % self.query.custom_select[alias] + return sel_fmt + + def get_field_select(self, field, alias=None, column=None): + """ + Returns the SELECT SQL string for the given field. Figures out + if any custom selection SQL is needed for the column The `alias` + keyword may be used to manually specify the database table where + the column exists, if not in the model associated with this + `GeoQuery`. Similarly, `column` may be used to specify the exact + column name, rather than using the `column` attribute on `field`. + """ + sel_fmt = self.get_select_format(field) + if field in self.query.custom_select: + field_sel = sel_fmt % self.query.custom_select[field] + else: + field_sel = sel_fmt % self._field_column(field, alias, column) + return field_sel + + def get_select_format(self, fld): + """ + Returns the selection format string, depending on the requirements + of the spatial backend. For example, Oracle and MySQL require custom + selection formats in order to retrieve geometries in OGC WKT. For all + other fields a simple '%s' format string is returned. + """ + if self.connection.ops.select and hasattr(fld, 'geom_type'): + # This allows operations to be done on fields in the SELECT, + # overriding their values -- used by the Oracle and MySQL + # spatial backends to get database values as WKT, and by the + # `transform` method. + sel_fmt = self.connection.ops.select + + # Because WKT doesn't contain spatial reference information, + # the SRID is prefixed to the returned WKT to ensure that the + # transformed geometries have an SRID different than that of the + # field -- this is only used by `transform` for Oracle and + # SpatiaLite backends. + if self.query.transformed_srid and ( self.connection.ops.oracle or + self.connection.ops.spatialite ): + sel_fmt = "'SRID=%d;'||%s" % (self.query.transformed_srid, sel_fmt) + else: + sel_fmt = '%s' + return sel_fmt + + # Private API utilities, subject to change. + def _field_column(self, field, table_alias=None, column=None): + """ + Helper function that returns the database column for the given field. + The table and column are returned (quoted) in the proper format, e.g., + `"geoapp_city"."point"`. If `table_alias` is not specified, the + database table associated with the model of this `GeoQuery` will be + used. If `column` is specified, it will be used instead of the value + in `field.column`. + """ + if table_alias is None: table_alias = self.query.get_meta().db_table + return "%s.%s" % (self.quote_name_unless_alias(table_alias), + self.connection.ops.quote_name(column or field.column)) + +class SQLInsertCompiler(compiler.SQLInsertCompiler, GeoSQLCompiler): + pass + +class SQLDeleteCompiler(compiler.SQLDeleteCompiler, GeoSQLCompiler): + pass + +class SQLUpdateCompiler(compiler.SQLUpdateCompiler, GeoSQLCompiler): + pass + +class SQLAggregateCompiler(compiler.SQLAggregateCompiler, GeoSQLCompiler): + pass + +class SQLDateCompiler(compiler.SQLDateCompiler, GeoSQLCompiler): + """ + This is overridden for GeoDjango to properly cast date columns, since + `GeoQuery.resolve_columns` is used for spatial values. + See #14648, #16757. + """ + def results_iter(self): + if self.connection.ops.oracle: + from django.db.models.fields import DateTimeField + fields = [DateTimeField()] + else: + needs_string_cast = self.connection.features.needs_datetime_string_cast + + offset = len(self.query.extra_select) + for rows in self.execute_sql(MULTI): + for row in rows: + date = row[offset] + if self.connection.ops.oracle: + date = self.resolve_columns(row, fields)[offset] + elif needs_string_cast: + date = typecast_date(str(date)) + if isinstance(date, datetime.datetime): + date = date.date() + yield date + +class SQLDateTimeCompiler(compiler.SQLDateTimeCompiler, GeoSQLCompiler): + """ + This is overridden for GeoDjango to properly cast date columns, since + `GeoQuery.resolve_columns` is used for spatial values. + See #14648, #16757. + """ + def results_iter(self): + if self.connection.ops.oracle: + from django.db.models.fields import DateTimeField + fields = [DateTimeField()] + else: + needs_string_cast = self.connection.features.needs_datetime_string_cast + + offset = len(self.query.extra_select) + for rows in self.execute_sql(MULTI): + for row in rows: + datetime = row[offset] + if self.connection.ops.oracle: + datetime = self.resolve_columns(row, fields)[offset] + elif needs_string_cast: + datetime = typecast_timestamp(str(datetime)) + # Datetimes are artifically returned in UTC on databases that + # don't support time zone. Restore the zone used in the query. + if settings.USE_TZ: + datetime = datetime.replace(tzinfo=None) + datetime = timezone.make_aware(datetime, self.query.tzinfo) + yield datetime diff --git a/lib/python2.7/site-packages/django/contrib/gis/db/models/sql/conversion.py b/lib/python2.7/site-packages/django/contrib/gis/db/models/sql/conversion.py new file mode 100644 index 0000000..160b623 --- /dev/null +++ b/lib/python2.7/site-packages/django/contrib/gis/db/models/sql/conversion.py @@ -0,0 +1,27 @@ +""" +This module holds simple classes used by GeoQuery.convert_values +to convert geospatial values from the database. +""" + +class BaseField(object): + empty_strings_allowed = True + def get_internal_type(self): + "Overloaded method so OracleQuery.convert_values doesn't balk." + return None + +class AreaField(BaseField): + "Wrapper for Area values." + def __init__(self, area_att): + self.area_att = area_att + +class DistanceField(BaseField): + "Wrapper for Distance values." + def __init__(self, distance_att): + self.distance_att = distance_att + +class GeomField(BaseField): + """ + Wrapper for Geometry values. It is a lightweight alternative to + using GeometryField (which requires an SQL query upon instantiation). + """ + pass diff --git a/lib/python2.7/site-packages/django/contrib/gis/db/models/sql/query.py b/lib/python2.7/site-packages/django/contrib/gis/db/models/sql/query.py new file mode 100644 index 0000000..5877f29 --- /dev/null +++ b/lib/python2.7/site-packages/django/contrib/gis/db/models/sql/query.py @@ -0,0 +1,121 @@ +from django.db import connections +from django.db.models.query import sql + +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.backend import Geometry +from django.contrib.gis.measure import Area, Distance + + +ALL_TERMS = set([ + 'bbcontains', 'bboverlaps', 'contained', 'contains', + 'contains_properly', 'coveredby', 'covers', 'crosses', 'disjoint', + 'distance_gt', 'distance_gte', 'distance_lt', 'distance_lte', + 'dwithin', 'equals', 'exact', + 'intersects', 'overlaps', 'relate', 'same_as', 'touches', 'within', + 'left', 'right', 'overlaps_left', 'overlaps_right', + 'overlaps_above', 'overlaps_below', + 'strictly_above', 'strictly_below' + ]) +ALL_TERMS.update(sql.constants.QUERY_TERMS) + +class GeoQuery(sql.Query): + """ + A single spatial SQL query. + """ + # Overridding the valid query terms. + query_terms = ALL_TERMS + aggregates_module = gis_aggregates + + compiler = 'GeoSQLCompiler' + + #### Methods overridden from the base Query class #### + def __init__(self, model, where=GeoWhereNode): + super(GeoQuery, self).__init__(model, where) + # The following attributes are customized for the GeoQuerySet. + # The GeoWhereNode and SpatialBackend classes contain backend-specific + # routines and functions. + self.custom_select = {} + self.transformed_srid = None + self.extra_select_fields = {} + + def clone(self, *args, **kwargs): + obj = super(GeoQuery, self).clone(*args, **kwargs) + # Customized selection dictionary and transformed srid flag have + # to also be added to obj. + obj.custom_select = self.custom_select.copy() + obj.transformed_srid = self.transformed_srid + obj.extra_select_fields = self.extra_select_fields.copy() + return obj + + def convert_values(self, value, field, connection): + """ + Using the same routines that Oracle does we can convert our + extra selection objects into Geometry and Distance objects. + TODO: Make converted objects 'lazy' for less overhead. + """ + if connection.ops.oracle: + # Running through Oracle's first. + value = super(GeoQuery, self).convert_values(value, field or GeomField(), connection) + + if value is None: + # Output from spatial function is NULL (e.g., called + # function on a geometry field with NULL value). + pass + elif isinstance(field, DistanceField): + # Using the field's distance attribute, can instantiate + # `Distance` with the right context. + value = Distance(**{field.distance_att : value}) + elif isinstance(field, AreaField): + value = Area(**{field.area_att : value}) + elif isinstance(field, (GeomField, GeometryField)) and value: + value = Geometry(value) + elif field is not None: + return super(GeoQuery, self).convert_values(value, field, connection) + return value + + def get_aggregation(self, using): + # Remove any aggregates marked for reduction from the subquery + # and move them to the outer AggregateQuery. + connection = connections[using] + for alias, aggregate in self.aggregate_select.items(): + if isinstance(aggregate, gis_aggregates.GeoAggregate): + if not getattr(aggregate, 'is_extent', False) or connection.ops.oracle: + self.extra_select_fields[alias] = GeomField() + return super(GeoQuery, self).get_aggregation(using) + + def resolve_aggregate(self, value, aggregate, connection): + """ + Overridden from GeoQuery's normalize to handle the conversion of + GeoAggregate objects. + """ + if isinstance(aggregate, self.aggregates_module.GeoAggregate): + if aggregate.is_extent: + if aggregate.is_extent == '3D': + return connection.ops.convert_extent3d(value) + else: + return connection.ops.convert_extent(value) + else: + return connection.ops.convert_geom(value, aggregate.source) + else: + return super(GeoQuery, self).resolve_aggregate(value, aggregate, connection) + + # Private API utilities, subject to change. + def _geo_field(self, field_name=None): + """ + Returns the first Geometry field encountered; or specified via the + `field_name` keyword. The `field_name` may be a string specifying + the geometry field on this GeoQuery's model, or a lookup string + to a geometry field via a ForeignKey relation. + """ + if field_name is None: + # Incrementing until the first geographic field is found. + for fld in self.model._meta.fields: + if isinstance(fld, GeometryField): return fld + return False + else: + # Otherwise, check by the given field name -- which may be + # a lookup to a _related_ geographic field. + return GeoWhereNode._check_geo_field(self.model._meta, field_name) diff --git a/lib/python2.7/site-packages/django/contrib/gis/db/models/sql/where.py b/lib/python2.7/site-packages/django/contrib/gis/db/models/sql/where.py new file mode 100644 index 0000000..c29533b --- /dev/null +++ b/lib/python2.7/site-packages/django/contrib/gis/db/models/sql/where.py @@ -0,0 +1,91 @@ +from django.db.models.constants import LOOKUP_SEP +from django.db.models.fields import FieldDoesNotExist +from django.db.models.sql.expressions import SQLEvaluator +from django.db.models.sql.where import Constraint, WhereNode +from django.contrib.gis.db.models.fields import GeometryField + +class GeoConstraint(Constraint): + """ + This subclass overrides `process` to better handle geographic SQL + construction. + """ + def __init__(self, init_constraint): + self.alias = init_constraint.alias + self.col = init_constraint.col + self.field = init_constraint.field + + def process(self, lookup_type, value, connection): + if isinstance(value, SQLEvaluator): + # Make sure the F Expression destination field exists, and + # set an `srid` attribute with the same as that of the + # destination. + geo_fld = GeoWhereNode._check_geo_field(value.opts, value.expression.name) + if not geo_fld: + raise ValueError('No geographic field found in expression.') + value.srid = geo_fld.srid + db_type = self.field.db_type(connection=connection) + params = self.field.get_db_prep_lookup(lookup_type, value, connection=connection) + return (self.alias, self.col, db_type), params + +class GeoWhereNode(WhereNode): + """ + Used to represent the SQL where-clause for spatial databases -- + these are tied to the GeoQuery class that created it. + """ + + def _prepare_data(self, data): + if isinstance(data, (list, tuple)): + obj, lookup_type, value = data + if ( isinstance(obj, Constraint) and + isinstance(obj.field, GeometryField) ): + data = (GeoConstraint(obj), lookup_type, value) + return super(GeoWhereNode, self)._prepare_data(data) + + def make_atom(self, child, qn, connection): + 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, spatial_params = connection.ops.spatial_lookup_sql( + data, lookup_type, params_or_value, lvalue.field, qn) + return spatial_sql, spatial_params + params + else: + return super(GeoWhereNode, self).make_atom(child, qn, connection) + + @classmethod + def _check_geo_field(cls, opts, lookup): + """ + 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'. + + If a GeometryField exists according to the given lookup on the model + options, it will be returned. Otherwise returns None. + """ + # This takes into account the situation where the lookup is a + # lookup to a related geographic field, e.g., 'address__point'. + field_list = lookup.split(LOOKUP_SEP) + + # Reversing so list operates like a queue of related lookups, + # and popping the top lookup. + field_list.reverse() + fld_name = field_list.pop() + + try: + geo_fld = opts.get_field(fld_name) + # 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 + # until there's no more left. + while len(field_list): + opts = geo_fld.rel.to._meta + geo_fld = opts.get_field(field_list.pop()) + except (FieldDoesNotExist, AttributeError): + return False + + # Finally, make sure we got a Geographic field and return. + if isinstance(geo_fld, GeometryField): + return geo_fld + else: + return False |