mirror of
https://github.com/django/django.git
synced 2025-07-04 17:59:13 +00:00
gis: created backend module, that will (in the future) allow for support of different spatial databases; improved postgis-specific code in preparation for PostGIS 1.2.2 (and beyond).
git-svn-id: http://code.djangoproject.com/svn/django/branches/gis@5776 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
parent
7a98b6128e
commit
ffcc38ebe8
@ -1,92 +1,29 @@
|
|||||||
# This module is meant to re-define the helper routines used by the
|
"""
|
||||||
# django.db.models.query objects to be customized for PostGIS.
|
This module provides the backend for spatial SQL construction with Django.
|
||||||
|
|
||||||
|
Specifically, this module will import the correct routines and modules
|
||||||
|
needed for GeoDjango
|
||||||
|
|
||||||
|
(1) GeoBackEndField, a base class needed for GeometryField.
|
||||||
|
(2) The parse_lookup() function, used for spatial SQL construction by
|
||||||
|
the GeoQuerySet.
|
||||||
|
|
||||||
|
Currently only PostGIS is supported, but someday backends will be aded for
|
||||||
|
additional spatial databases.
|
||||||
|
"""
|
||||||
|
from django.conf import settings
|
||||||
from django.db import backend
|
from django.db import backend
|
||||||
from django.db.models.query import LOOKUP_SEPARATOR, field_choices, find_field, FieldFound, QUERY_TERMS, get_where_clause
|
from django.db.models.query import LOOKUP_SEPARATOR, field_choices, find_field, FieldFound, QUERY_TERMS, get_where_clause
|
||||||
from django.utils.datastructures import SortedDict
|
from django.utils.datastructures import SortedDict
|
||||||
|
|
||||||
# PostGIS-specific operators. The commented descriptions of these
|
if settings.DATABASE_ENGINE == 'postgresql_psycopg2':
|
||||||
# operators come from Section 6.2.2 of the official PostGIS documentation.
|
# PostGIS is the spatial database, getting the rquired modules, renaming as necessary.
|
||||||
POSTGIS_OPERATORS = {
|
from postgis import \
|
||||||
# The "&<" operator returns true if A's bounding box overlaps or is to the left of B's bounding box.
|
PostGISField as GeoBackendField, \
|
||||||
'overlaps_left' : '&< %s',
|
POSTGIS_TERMS as GIS_TERMS, \
|
||||||
# The "&>" operator returns true if A's bounding box overlaps or is to the right of B's bounding box.
|
create_spatial_db, get_geo_where_clause
|
||||||
'overlaps_right' : '&> %s',
|
else:
|
||||||
# The "<<" operator returns true if A's bounding box is strictly to the left of B's bounding box.
|
raise NotImplementedError, 'No Geographic Backend exists for %s' % settings.DATABASE_NAME
|
||||||
'left' : '<< %s',
|
|
||||||
# The ">>" operator returns true if A's bounding box is strictly to the right of B's bounding box.
|
|
||||||
'right' : '>> %s',
|
|
||||||
# The "&<|" operator returns true if A's bounding box overlaps or is below B's bounding box.
|
|
||||||
'overlaps_below' : '&<| %s',
|
|
||||||
# The "|&>" operator returns true if A's bounding box overlaps or is above B's bounding box.
|
|
||||||
'overlaps_above' : '|&> %s',
|
|
||||||
# The "<<|" operator returns true if A's bounding box is strictly below B's bounding box.
|
|
||||||
'strictly_below' : '<<| %s',
|
|
||||||
# The "|>>" operator returns true if A's bounding box is strictly above B's bounding box.
|
|
||||||
'strictly_above' : '|>> %s',
|
|
||||||
# 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' : '~= %s',
|
|
||||||
'exact' : '~= %s',
|
|
||||||
# The "@" operator returns true if A's bounding box is completely contained by B's bounding box.
|
|
||||||
'contained' : '@ %s',
|
|
||||||
# The "~" operator returns true if A's bounding box completely contains B's bounding box.
|
|
||||||
'bbcontains' : '~ %s',
|
|
||||||
# The "&&" operator is the "overlaps" operator. If A's bounding boux overlaps B's bounding box the
|
|
||||||
# operator returns true.
|
|
||||||
'bboverlaps' : '&& %s',
|
|
||||||
}
|
|
||||||
|
|
||||||
# PostGIS Geometry Functions -- most of these use GEOS.
|
|
||||||
POSTGIS_GEOMETRY_FUNCTIONS = {
|
|
||||||
#'distance' : 'Distance', -- doesn't work right now.
|
|
||||||
'equals' : 'Equals',
|
|
||||||
'disjoint' : 'Disjoint',
|
|
||||||
'touches' : 'Touches',
|
|
||||||
'crosses' : 'Crosses',
|
|
||||||
'within' : 'Within',
|
|
||||||
'overlaps' : 'Overlaps',
|
|
||||||
'contains' : 'Contains',
|
|
||||||
'intersects' : 'Intersects',
|
|
||||||
'relate' : 'Relate',
|
|
||||||
}
|
|
||||||
|
|
||||||
# Any other lookup types that do not require a mapping.
|
|
||||||
MISC_TERMS = ['isnull']
|
|
||||||
|
|
||||||
# The quotation used for postgis (uses single quotes).
|
|
||||||
def quotename(value, dbl=False):
|
|
||||||
if dbl: return '"%s"' % value
|
|
||||||
else: return "'%s'" % value
|
|
||||||
|
|
||||||
# These are the PostGIS-customized QUERY_TERMS, combines both the operators
|
|
||||||
# and the geometry functions.
|
|
||||||
POSTGIS_TERMS = list(POSTGIS_OPERATORS.keys()) # Getting the operators first
|
|
||||||
POSTGIS_TERMS += list(POSTGIS_GEOMETRY_FUNCTIONS.keys()) # Adding on the Geometry Functions
|
|
||||||
POSTGIS_TERMS += MISC_TERMS # Adding any other miscellaneous terms (e.g., 'isnull')
|
|
||||||
POSTGIS_TERMS = tuple(POSTGIS_TERMS) # Making immutable
|
|
||||||
|
|
||||||
def get_geo_where_clause(lookup_type, table_prefix, field_name, value):
|
|
||||||
if table_prefix.endswith('.'):
|
|
||||||
table_prefix = backend.quote_name(table_prefix[:-1])+'.'
|
|
||||||
field_name = backend.quote_name(field_name)
|
|
||||||
|
|
||||||
# See if a PostGIS operator matches the lookup type first
|
|
||||||
try:
|
|
||||||
return '%s%s %s' % (table_prefix, field_name, (POSTGIS_OPERATORS[lookup_type] % '%s'))
|
|
||||||
except KeyError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
# See if a PostGIS Geometry function matches the lookup type next
|
|
||||||
try:
|
|
||||||
return '%s(%s%s, %%s)' % (POSTGIS_GEOMETRY_FUNCTIONS[lookup_type], table_prefix, field_name)
|
|
||||||
except KeyError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
# For any other lookup type
|
|
||||||
if lookup_type == 'isnull':
|
|
||||||
return "%s%s IS %sNULL" % (table_prefix, field_name, (not value and 'NOT ' or ''))
|
|
||||||
|
|
||||||
raise TypeError, "Got invalid lookup_type: %s" % repr(lookup_type)
|
|
||||||
|
|
||||||
#### query.py overloaded functions ####
|
#### query.py overloaded functions ####
|
||||||
# parse_lookup() and lookup_inner() are modified from their django/db/models/query.py
|
# parse_lookup() and lookup_inner() are modified from their django/db/models/query.py
|
||||||
@ -130,7 +67,7 @@ def parse_lookup(kwarg_items, opts):
|
|||||||
if lookup_type == 'pk':
|
if lookup_type == 'pk':
|
||||||
lookup_type = 'exact'
|
lookup_type = 'exact'
|
||||||
path.append(None)
|
path.append(None)
|
||||||
elif len(path) == 0 or not ((lookup_type in QUERY_TERMS) or (lookup_type in POSTGIS_TERMS)):
|
elif len(path) == 0 or not ((lookup_type in QUERY_TERMS) or (lookup_type in GIS_TERMS)):
|
||||||
path.append(lookup_type)
|
path.append(lookup_type)
|
||||||
lookup_type = 'exact'
|
lookup_type = 'exact'
|
||||||
|
|
7
django/contrib/gis/db/backend/postgis/__init__.py
Normal file
7
django/contrib/gis/db/backend/postgis/__init__.py
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
"""
|
||||||
|
The PostGIS spatial database backend module.
|
||||||
|
"""
|
||||||
|
from query import get_geo_where_clause, GEOM_FUNC_PREFIX, POSTGIS_TERMS
|
||||||
|
from creation import create_spatial_db
|
||||||
|
from field import PostGISField
|
||||||
|
|
107
django/contrib/gis/db/backend/postgis/field.py
Normal file
107
django/contrib/gis/db/backend/postgis/field.py
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
from django.db.models.fields import Field # Django base Field class
|
||||||
|
from django.contrib.gis.geos import GEOSGeometry, GEOSException
|
||||||
|
from types import StringType
|
||||||
|
from query import POSTGIS_TERMS, quotename
|
||||||
|
|
||||||
|
class PostGISField(Field):
|
||||||
|
def _add_geom(self, style, db_table):
|
||||||
|
"""Constructs the addition of the geometry to the table using the
|
||||||
|
AddGeometryColumn(...) PostGIS (and OGC standard) stored procedure.
|
||||||
|
|
||||||
|
Takes the style object (provides syntax highlighting) and the
|
||||||
|
database table as parameters.
|
||||||
|
"""
|
||||||
|
sql = style.SQL_KEYWORD('SELECT ') + \
|
||||||
|
style.SQL_TABLE('AddGeometryColumn') + '(' + \
|
||||||
|
style.SQL_TABLE(quotename(db_table)) + ', ' + \
|
||||||
|
style.SQL_FIELD(quotename(self.column)) + ', ' + \
|
||||||
|
style.SQL_FIELD(str(self._srid)) + ', ' + \
|
||||||
|
style.SQL_COLTYPE(quotename(self._geom)) + ', ' + \
|
||||||
|
style.SQL_KEYWORD(str(self._dim)) + ');'
|
||||||
|
|
||||||
|
if not self.null:
|
||||||
|
# Add a NOT NULL constraint to the field
|
||||||
|
sql += '\n' + \
|
||||||
|
style.SQL_KEYWORD('ALTER TABLE ') + \
|
||||||
|
style.SQL_TABLE(quotename(db_table, dbl=True)) + \
|
||||||
|
style.SQL_KEYWORD(' ALTER ') + \
|
||||||
|
style.SQL_FIELD(quotename(self.column, dbl=True)) + \
|
||||||
|
style.SQL_KEYWORD(' SET NOT NULL') + ';'
|
||||||
|
return sql
|
||||||
|
|
||||||
|
def _geom_index(self, style, db_table,
|
||||||
|
index_type='GIST', index_opts='GIST_GEOMETRY_OPS'):
|
||||||
|
"Creates a GiST index for this geometry field."
|
||||||
|
sql = style.SQL_KEYWORD('CREATE INDEX ') + \
|
||||||
|
style.SQL_TABLE(quotename('%s_%s_id' % (db_table, self.column), dbl=True)) + \
|
||||||
|
style.SQL_KEYWORD(' ON ') + \
|
||||||
|
style.SQL_TABLE(quotename(db_table, dbl=True)) + \
|
||||||
|
style.SQL_KEYWORD(' USING ') + \
|
||||||
|
style.SQL_COLTYPE(index_type) + ' ( ' + \
|
||||||
|
style.SQL_FIELD(quotename(self.column, dbl=True)) + ' ' + \
|
||||||
|
style.SQL_KEYWORD(index_opts) + ' );'
|
||||||
|
return sql
|
||||||
|
|
||||||
|
def _post_create_sql(self, style, db_table):
|
||||||
|
"""Returns SQL that will be executed after the model has been
|
||||||
|
created. Geometry columns must be added after creation with the
|
||||||
|
PostGIS AddGeometryColumn() function."""
|
||||||
|
|
||||||
|
# Getting the AddGeometryColumn() SQL necessary to create a PostGIS
|
||||||
|
# geometry field.
|
||||||
|
post_sql = self._add_geom(style, db_table)
|
||||||
|
|
||||||
|
# If the user wants to index this data, then get the indexing SQL as well.
|
||||||
|
if self._index:
|
||||||
|
return '%s\n%s' % (post_sql, self._geom_index(style, db_table))
|
||||||
|
else:
|
||||||
|
return post_sql
|
||||||
|
|
||||||
|
def _post_delete_sql(self, style, db_table):
|
||||||
|
"Drops the geometry column."
|
||||||
|
sql = style.SQL_KEYWORD('SELECT ') + \
|
||||||
|
style.SQL_KEYWORD('DropGeometryColumn') + '(' + \
|
||||||
|
style.SQL_TABLE(quotename(db_table)) + ', ' + \
|
||||||
|
style.SQL_FIELD(quotename(self.column)) + ');'
|
||||||
|
return sql
|
||||||
|
|
||||||
|
def get_db_prep_lookup(self, lookup_type, value):
|
||||||
|
"Returns field's value prepared for database lookup, accepts WKT and GEOS Geometries for the value."
|
||||||
|
if lookup_type in POSTGIS_TERMS:
|
||||||
|
if lookup_type == 'isnull': return [value] # special case for NULL geometries.
|
||||||
|
if not bool(value): return [None] # If invalid value passed in.
|
||||||
|
if isinstance(value, GEOSGeometry):
|
||||||
|
# GEOSGeometry instance passed in.
|
||||||
|
if value.srid != self._srid:
|
||||||
|
# Returning a dictionary instructs the parse_lookup() to add what's in the 'where' key
|
||||||
|
# to the where parameters, since we need to transform the geometry in the query.
|
||||||
|
return {'where' : "Transform(%s,%s)",
|
||||||
|
'params' : [value, self._srid]
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
# Just return the GEOSGeometry, it has its own psycopg2 adaptor.
|
||||||
|
return [value]
|
||||||
|
elif isinstance(value, StringType):
|
||||||
|
# String instance passed in, assuming WKT.
|
||||||
|
# TODO: Any validation needed here to prevent SQL injection?
|
||||||
|
return ["SRID=%d;%s" % (self._srid, value)]
|
||||||
|
else:
|
||||||
|
raise TypeError("Invalid type (%s) used for field lookup value." % str(type(value)))
|
||||||
|
else:
|
||||||
|
raise TypeError("Field has invalid lookup: %s" % lookup_type)
|
||||||
|
|
||||||
|
def get_db_prep_save(self, value):
|
||||||
|
"Prepares the value for saving in the database."
|
||||||
|
if not bool(value): return None
|
||||||
|
if isinstance(value, GEOSGeometry):
|
||||||
|
return value
|
||||||
|
else:
|
||||||
|
return ("SRID=%d;%s" % (self._srid, wkt))
|
||||||
|
|
||||||
|
def get_placeholder(self, value):
|
||||||
|
"Provides a proper substitution value for "
|
||||||
|
if isinstance(value, GEOSGeometry) and value.srid != self._srid:
|
||||||
|
# Adding Transform() to the SQL placeholder.
|
||||||
|
return 'Transform(%%s, %s)' % self._srid
|
||||||
|
else:
|
||||||
|
return '%s'
|
36
django/contrib/gis/db/backend/postgis/management.py
Normal file
36
django/contrib/gis/db/backend/postgis/management.py
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
"""
|
||||||
|
This utility module is for obtaining information about the PostGIS installation.
|
||||||
|
|
||||||
|
See PostGIS docs at Ch. 6.2.1 for more information on these functions.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def _get_postgis_func(func):
|
||||||
|
"Helper routine for calling PostGIS functions and returning their result."
|
||||||
|
from django.db import connection
|
||||||
|
cursor = connection.cursor()
|
||||||
|
cursor.execute('SELECT %s()' % func)
|
||||||
|
row = cursor.fetchone()
|
||||||
|
cursor.close()
|
||||||
|
return row[0]
|
||||||
|
|
||||||
|
def postgis_geos_version():
|
||||||
|
"Returns the version of the GEOS library used with PostGIS."
|
||||||
|
return _get_postgis_func('postgis_geos_version')
|
||||||
|
|
||||||
|
def postgis_lib_version():
|
||||||
|
"Returns the version number of the PostGIS library used with PostgreSQL."
|
||||||
|
return _get_postgis_func('postgis_lib_version')
|
||||||
|
|
||||||
|
def postgis_proj_version():
|
||||||
|
"Returns the version of the PROJ.4 library used with PostGIS."
|
||||||
|
return _get_postgis_func('postgis_proj_version')
|
||||||
|
|
||||||
|
def postgis_version():
|
||||||
|
"Returns PostGIS version number and compile-time options."
|
||||||
|
return _get_postgis_func('postgis_version')
|
||||||
|
|
||||||
|
def postgis_full_version():
|
||||||
|
"Returns PostGIS version number and compile-time options."
|
||||||
|
return _get_postgis_func('postgis_full_version')
|
||||||
|
|
||||||
|
|
127
django/contrib/gis/db/backend/postgis/query.py
Normal file
127
django/contrib/gis/db/backend/postgis/query.py
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
"""
|
||||||
|
This module contains the spatial lookup types, and the get_geo_where_clause()
|
||||||
|
routine for PostGIS.
|
||||||
|
"""
|
||||||
|
from django.db import backend
|
||||||
|
from management import postgis_lib_version
|
||||||
|
|
||||||
|
# Getting the PostGIS version
|
||||||
|
POSTGIS_VERSION = postgis_lib_version()
|
||||||
|
MAJOR_VERSION, MINOR_VERSION1, MINOR_VERSION2 = map(int, POSTGIS_VERSION.split('.'))
|
||||||
|
|
||||||
|
# The supported PostGIS versions.
|
||||||
|
# TODO: Confirm tests with PostGIS versions 1.1.x -- should work. Versions <= 1.0.x didn't use GEOS C API.
|
||||||
|
if MAJOR_VERSION != 1 or MINOR_VERSION1 <= 1:
|
||||||
|
raise Exception, 'PostGIS version %s not supported.' % POSTGIS_VERSION
|
||||||
|
|
||||||
|
# PostGIS-specific operators. The commented descriptions of these
|
||||||
|
# operators come from Section 6.2.2 of the official PostGIS documentation.
|
||||||
|
POSTGIS_OPERATORS = {
|
||||||
|
# The "&<" operator returns true if A's bounding box overlaps or is to the left of B's bounding box.
|
||||||
|
'overlaps_left' : '&<',
|
||||||
|
# The "&>" operator returns true if A's bounding box overlaps or is to the right of B's bounding box.
|
||||||
|
'overlaps_right' : '&>',
|
||||||
|
# The "<<" operator returns true if A's bounding box is strictly to the left of B's bounding box.
|
||||||
|
'left' : '<<',
|
||||||
|
# The ">>" operator returns true if A's bounding box is strictly to the right of B's bounding box.
|
||||||
|
'right' : '>>',
|
||||||
|
# The "&<|" operator returns true if A's bounding box overlaps or is below B's bounding box.
|
||||||
|
'overlaps_below' : '&<|',
|
||||||
|
# The "|&>" operator returns true if A's bounding box overlaps or is above B's bounding box.
|
||||||
|
'overlaps_above' : '|&>',
|
||||||
|
# The "<<|" operator returns true if A's bounding box is strictly below B's bounding box.
|
||||||
|
'strictly_below' : '<<|',
|
||||||
|
# The "|>>" operator returns true if A's bounding box is strictly above B's bounding box.
|
||||||
|
'strictly_above' : '|>>',
|
||||||
|
# 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' : '~=',
|
||||||
|
'exact' : '~=',
|
||||||
|
# The "@" operator returns true if A's bounding box is completely contained by B's bounding box.
|
||||||
|
'contained' : '@',
|
||||||
|
# The "~" operator returns true if A's bounding box completely contains B's bounding box.
|
||||||
|
'bbcontains' : '~',
|
||||||
|
# The "&&" operator is the "overlaps" operator. If A's bounding boux overlaps B's bounding box the
|
||||||
|
# operator returns true.
|
||||||
|
'bboverlaps' : '&&',
|
||||||
|
}
|
||||||
|
|
||||||
|
# PostGIS Geometry Relationship Functions -- most of these use GEOS.
|
||||||
|
#
|
||||||
|
# For PostGIS >= 1.2.2 these routines will do a bounding box query first before calling
|
||||||
|
# the more expensive GEOS routines (called 'inline index magic').
|
||||||
|
#
|
||||||
|
POSTGIS_GEOMETRY_FUNCTIONS = {
|
||||||
|
'equals' : '%sEquals',
|
||||||
|
'disjoint' : '%sDisjoint',
|
||||||
|
'touches' : '%sTouches',
|
||||||
|
'crosses' : '%sCrosses',
|
||||||
|
'within' : '%sWithin',
|
||||||
|
'overlaps' : '%sOverlaps',
|
||||||
|
'contains' : '%sContains',
|
||||||
|
'intersects' : '%sIntersects',
|
||||||
|
'relate' : ('%sRelate', str),
|
||||||
|
}
|
||||||
|
|
||||||
|
# Versions of PostGIS >= 1.2.2 changed their naming convention to be 'SQL-MM-centric'.
|
||||||
|
# Practically, this means that 'ST_' is appended to geometry function names.
|
||||||
|
if MINOR_VERSION1 >= 2 and MINOR_VERSION2 >= 2:
|
||||||
|
# The ST_DWithin, ST_CoveredBy, and ST_Covers routines become available in 1.2.2.
|
||||||
|
POSTGIS_GEOMETRY_FUNCTIONS.update(
|
||||||
|
{'dwithin' : ('%sDWithin', float),
|
||||||
|
'coveredby' : '%sCoveredBy',
|
||||||
|
'covers' : '%sCovers',
|
||||||
|
}
|
||||||
|
)
|
||||||
|
GEOM_FUNC_PREFIX = 'ST_'
|
||||||
|
else:
|
||||||
|
GEOM_FUNC_PREFIX = ''
|
||||||
|
|
||||||
|
# Updating with the geometry function prefix.
|
||||||
|
for k, v in POSTGIS_GEOMETRY_FUNCTIONS.items():
|
||||||
|
if isinstance(v, tuple):
|
||||||
|
v = list(v)
|
||||||
|
v[0] = v[0] % GEOM_FUNC_PREFIX
|
||||||
|
v = tuple(v)
|
||||||
|
else:
|
||||||
|
v = v % GEOM_FUNC_PREFIX
|
||||||
|
POSTGIS_GEOMETRY_FUNCTIONS[k] = v
|
||||||
|
|
||||||
|
# Any other lookup types that do not require a mapping.
|
||||||
|
MISC_TERMS = ['isnull']
|
||||||
|
|
||||||
|
# The quotation used for postgis (uses single quotes).
|
||||||
|
def quotename(value, dbl=False):
|
||||||
|
if dbl: return '"%s"' % value
|
||||||
|
else: return "'%s'" % value
|
||||||
|
|
||||||
|
# These are the PostGIS-customized QUERY_TERMS -- a list of the lookup types
|
||||||
|
# allowed for geographic queries.
|
||||||
|
POSTGIS_TERMS = list(POSTGIS_OPERATORS.keys()) # Getting the operators first
|
||||||
|
POSTGIS_TERMS += list(POSTGIS_GEOMETRY_FUNCTIONS.keys()) # Adding on the Geometry Functions
|
||||||
|
POSTGIS_TERMS += MISC_TERMS # Adding any other miscellaneous terms (e.g., 'isnull')
|
||||||
|
POSTGIS_TERMS = tuple(POSTGIS_TERMS) # Making immutable
|
||||||
|
|
||||||
|
def get_geo_where_clause(lookup_type, table_prefix, field_name, value):
|
||||||
|
"Returns the SQL WHERE clause for use in PostGIS SQL construction."
|
||||||
|
if table_prefix.endswith('.'):
|
||||||
|
table_prefix = backend.quote_name(table_prefix[:-1])+'.'
|
||||||
|
field_name = backend.quote_name(field_name)
|
||||||
|
|
||||||
|
# See if a PostGIS operator matches the lookup type first
|
||||||
|
try:
|
||||||
|
return '%s%s %s %%s' % (table_prefix, field_name, POSTGIS_OPERATORS[lookup_type])
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# See if a PostGIS Geometry function matches the lookup type next
|
||||||
|
try:
|
||||||
|
return '%s(%s%s, %%s)' % (POSTGIS_GEOMETRY_FUNCTIONS[lookup_type], table_prefix, field_name)
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Handling 'isnull' lookup type
|
||||||
|
if lookup_type == 'isnull':
|
||||||
|
return "%s%s IS %sNULL" % (table_prefix, field_name, (not value and 'NOT ' or ''))
|
||||||
|
|
||||||
|
raise TypeError, "Got invalid lookup_type: %s" % repr(lookup_type)
|
@ -1,15 +1,10 @@
|
|||||||
# The Django base Field class.
|
from django.contrib.gis.db.backend import GeoBackendField # depends on the spatial database backend.
|
||||||
from django.db.models.fields import Field
|
|
||||||
from django.contrib.gis.db.models.proxy import GeometryProxy
|
from django.contrib.gis.db.models.proxy import GeometryProxy
|
||||||
from django.contrib.gis.db.models.postgis import POSTGIS_TERMS, quotename
|
|
||||||
from django.contrib.gis.oldforms import WKTField
|
from django.contrib.gis.oldforms import WKTField
|
||||||
from django.utils.functional import curry
|
from django.utils.functional import curry
|
||||||
from django.contrib.gis.geos import GEOSGeometry, GEOSException
|
|
||||||
from types import StringType
|
|
||||||
|
|
||||||
#TODO: Flesh out widgets.
|
#TODO: Flesh out widgets.
|
||||||
|
class GeometryField(GeoBackendField):
|
||||||
class GeometryField(Field):
|
|
||||||
"The base GIS field -- maps to the OpenGIS Specification Geometry type."
|
"The base GIS field -- maps to the OpenGIS Specification Geometry type."
|
||||||
|
|
||||||
# The OpenGIS Geometry name.
|
# The OpenGIS Geometry name.
|
||||||
@ -33,67 +28,6 @@ class GeometryField(Field):
|
|||||||
self._dim = dim
|
self._dim = dim
|
||||||
super(GeometryField, self).__init__(**kwargs) # Calling the parent initializtion function
|
super(GeometryField, self).__init__(**kwargs) # Calling the parent initializtion function
|
||||||
|
|
||||||
def _add_geom(self, style, db_table):
|
|
||||||
"""Constructs the addition of the geometry to the table using the
|
|
||||||
AddGeometryColumn(...) PostGIS (and OGC standard) stored procedure.
|
|
||||||
|
|
||||||
Takes the style object (provides syntax highlighting) and the
|
|
||||||
database table as parameters.
|
|
||||||
"""
|
|
||||||
sql = style.SQL_KEYWORD('SELECT ') + \
|
|
||||||
style.SQL_TABLE('AddGeometryColumn') + '(' + \
|
|
||||||
style.SQL_TABLE(quotename(db_table)) + ', ' + \
|
|
||||||
style.SQL_FIELD(quotename(self.column)) + ', ' + \
|
|
||||||
style.SQL_FIELD(str(self._srid)) + ', ' + \
|
|
||||||
style.SQL_COLTYPE(quotename(self._geom)) + ', ' + \
|
|
||||||
style.SQL_KEYWORD(str(self._dim)) + ');'
|
|
||||||
|
|
||||||
if not self.null:
|
|
||||||
# Add a NOT NULL constraint to the field
|
|
||||||
sql += '\n' + \
|
|
||||||
style.SQL_KEYWORD('ALTER TABLE ') + \
|
|
||||||
style.SQL_TABLE(quotename(db_table, dbl=True)) + \
|
|
||||||
style.SQL_KEYWORD(' ALTER ') + \
|
|
||||||
style.SQL_FIELD(quotename(self.column, dbl=True)) + \
|
|
||||||
style.SQL_KEYWORD(' SET NOT NULL') + ';'
|
|
||||||
return sql
|
|
||||||
|
|
||||||
def _geom_index(self, style, db_table,
|
|
||||||
index_type='GIST', index_opts='GIST_GEOMETRY_OPS'):
|
|
||||||
"Creates a GiST index for this geometry field."
|
|
||||||
sql = style.SQL_KEYWORD('CREATE INDEX ') + \
|
|
||||||
style.SQL_TABLE(quotename('%s_%s_id' % (db_table, self.column), dbl=True)) + \
|
|
||||||
style.SQL_KEYWORD(' ON ') + \
|
|
||||||
style.SQL_TABLE(quotename(db_table, dbl=True)) + \
|
|
||||||
style.SQL_KEYWORD(' USING ') + \
|
|
||||||
style.SQL_COLTYPE(index_type) + ' ( ' + \
|
|
||||||
style.SQL_FIELD(quotename(self.column, dbl=True)) + ' ' + \
|
|
||||||
style.SQL_KEYWORD(index_opts) + ' );'
|
|
||||||
return sql
|
|
||||||
|
|
||||||
def _post_create_sql(self, style, db_table):
|
|
||||||
"""Returns SQL that will be executed after the model has been
|
|
||||||
created. Geometry columns must be added after creation with the
|
|
||||||
PostGIS AddGeometryColumn() function."""
|
|
||||||
|
|
||||||
# Getting the AddGeometryColumn() SQL necessary to create a PostGIS
|
|
||||||
# geometry field.
|
|
||||||
post_sql = self._add_geom(style, db_table)
|
|
||||||
|
|
||||||
# If the user wants to index this data, then get the indexing SQL as well.
|
|
||||||
if self._index:
|
|
||||||
return '%s\n%s' % (post_sql, self._geom_index(style, db_table))
|
|
||||||
else:
|
|
||||||
return post_sql
|
|
||||||
|
|
||||||
def _post_delete_sql(self, style, db_table):
|
|
||||||
"Drops the geometry column."
|
|
||||||
sql = style.SQL_KEYWORD('SELECT ') + \
|
|
||||||
style.SQL_KEYWORD('DropGeometryColumn') + '(' + \
|
|
||||||
style.SQL_TABLE(quotename(db_table)) + ', ' + \
|
|
||||||
style.SQL_FIELD(quotename(self.column)) + ');'
|
|
||||||
return sql
|
|
||||||
|
|
||||||
def contribute_to_class(self, cls, name):
|
def contribute_to_class(self, cls, name):
|
||||||
super(GeometryField, self).contribute_to_class(cls, name)
|
super(GeometryField, self).contribute_to_class(cls, name)
|
||||||
|
|
||||||
@ -112,47 +46,6 @@ class GeometryField(Field):
|
|||||||
def get_internal_type(self):
|
def get_internal_type(self):
|
||||||
return "NoField"
|
return "NoField"
|
||||||
|
|
||||||
def get_db_prep_lookup(self, lookup_type, value):
|
|
||||||
"Returns field's value prepared for database lookup, accepts WKT and GEOS Geometries for the value."
|
|
||||||
if lookup_type in POSTGIS_TERMS:
|
|
||||||
if lookup_type == 'isnull': return [value] # special case for NULL geometries.
|
|
||||||
if not bool(value): return [None] # If invalid value passed in.
|
|
||||||
if isinstance(value, GEOSGeometry):
|
|
||||||
# GEOSGeometry instance passed in.
|
|
||||||
if value.srid != self._srid:
|
|
||||||
# Returning a dictionary instructs the parse_lookup() to add what's in the 'where' key
|
|
||||||
# to the where parameters, since we need to transform the geometry in the query.
|
|
||||||
return {'where' : "Transform(%s,%s)",
|
|
||||||
'params' : [value, self._srid]
|
|
||||||
}
|
|
||||||
else:
|
|
||||||
# Just return the GEOSGeometry, it has its own psycopg2 adaptor.
|
|
||||||
return [value]
|
|
||||||
elif isinstance(value, StringType):
|
|
||||||
# String instance passed in, assuming WKT.
|
|
||||||
# TODO: Any validation needed here to prevent SQL injection?
|
|
||||||
return ["SRID=%d;%s" % (self._srid, value)]
|
|
||||||
else:
|
|
||||||
raise TypeError("Invalid type (%s) used for field lookup value." % str(type(value)))
|
|
||||||
else:
|
|
||||||
raise TypeError("Field has invalid lookup: %s" % lookup_type)
|
|
||||||
|
|
||||||
def get_db_prep_save(self, value):
|
|
||||||
"Prepares the value for saving in the database."
|
|
||||||
if not bool(value): return None
|
|
||||||
if isinstance(value, GEOSGeometry):
|
|
||||||
return value
|
|
||||||
else:
|
|
||||||
return ("SRID=%d;%s" % (self._srid, wkt))
|
|
||||||
|
|
||||||
def get_placeholder(self, value):
|
|
||||||
"Provides a proper substitution value for "
|
|
||||||
if isinstance(value, GEOSGeometry) and value.srid != self._srid:
|
|
||||||
# Adding Transform() to the SQL placeholder.
|
|
||||||
return 'Transform(%%s, %s)' % self._srid
|
|
||||||
else:
|
|
||||||
return '%s'
|
|
||||||
|
|
||||||
def get_manipulator_field_objs(self):
|
def get_manipulator_field_objs(self):
|
||||||
"Using the WKTField (defined above) to be our manipulator."
|
"Using the WKTField (defined above) to be our manipulator."
|
||||||
return [WKTField]
|
return [WKTField]
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
from django.db.models.query import Q, QuerySet
|
from django.db.models.query import Q, QuerySet
|
||||||
from django.db import backend
|
from django.db import backend
|
||||||
from django.contrib.gis.db.models.fields import GeometryField
|
from django.contrib.gis.db.models.fields import GeometryField
|
||||||
from django.contrib.gis.db.models.postgis import parse_lookup
|
from django.contrib.gis.db.backend import parse_lookup # parse_lookup depends on the spatial database backend.
|
||||||
from django.db.models.fields import FieldDoesNotExist
|
from django.db.models.fields import FieldDoesNotExist
|
||||||
import operator
|
import operator
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
from copy import copy
|
from copy import copy
|
||||||
from unittest import TestSuite, TextTestRunner
|
from unittest import TestSuite, TextTestRunner
|
||||||
from django.contrib.gis.utils import create_spatial_db
|
from django.contrib.gis.db.backend import create_spatial_db
|
||||||
from django.db import connection
|
from django.db import connection
|
||||||
from django.test.utils import destroy_test_db
|
from django.test.utils import destroy_test_db
|
||||||
|
|
||||||
|
@ -91,7 +91,7 @@ class GeoModelTest(unittest.TestCase):
|
|||||||
nmi.save()
|
nmi.save()
|
||||||
|
|
||||||
def test005_left_right(self):
|
def test005_left_right(self):
|
||||||
"Testing the left ('<<') right ('>>') operators."
|
"Testing the 'left' and 'right' lookup types."
|
||||||
|
|
||||||
# Left: A << B => true if xmax(A) < xmin(B)
|
# Left: A << B => true if xmax(A) < xmin(B)
|
||||||
# Right: A >> B => true if xmin(A) > xmax(B)
|
# Right: A >> B => true if xmin(A) > xmax(B)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user