mirror of
https://github.com/django/django.git
synced 2025-07-04 09:49:12 +00:00
gis: spatial-backend enhancements:
(1) GEOS no longer has psycopg2-specific routines, functionality now part of PostGIS adaptor in the spatial backend. (2) ST_GeomFromWKB() now used to enhance performance. (3) Moved GeometryProxy back to its original location. (4) Should resolve #5498, but not yet confirmed. (5) Test-sql files are now backend-specific. git-svn-id: http://code.djangoproject.com/svn/django/branches/gis@6508 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
parent
b6c8bba5b6
commit
c049672a74
@ -11,32 +11,41 @@
|
||||
the backend.
|
||||
(4) The `parse_lookup` function, used for spatial SQL construction by
|
||||
the GeoQuerySet.
|
||||
(5) The `create_spatial_db`, `geo_quotename`, and `get_geo_where_clause`
|
||||
routines (needed by `parse_lookup`.
|
||||
(5) The `create_spatial_db`, and `get_geo_where_clause`
|
||||
routines (needed by `parse_lookup`).
|
||||
|
||||
Currently only PostGIS is supported, but someday backends will be added for
|
||||
additional spatial databases (e.g., Oracle, DB2).
|
||||
"""
|
||||
from types import StringType, UnicodeType
|
||||
from django.conf import settings
|
||||
from django.db import connection
|
||||
from django.db.models.query import field_choices, find_field, get_where_clause, \
|
||||
FieldFound, LOOKUP_SEPARATOR, QUERY_TERMS
|
||||
from django.utils.datastructures import SortedDict
|
||||
from django.contrib.gis.geos import GEOSGeometry
|
||||
|
||||
# These routines default to False
|
||||
ASGML, ASKML, UNION = (False, False, False)
|
||||
# These routines (needed by GeoManager), default to False.
|
||||
ASGML, ASKML, TRANSFORM, UNION= (False, False, False, False)
|
||||
|
||||
if settings.DATABASE_ENGINE == 'postgresql_psycopg2':
|
||||
# PostGIS is the spatial database, getting the rquired modules,
|
||||
# renaming as necessary.
|
||||
from django.contrib.gis.db.backend.postgis import \
|
||||
PostGISField as GeoBackendField, POSTGIS_TERMS as GIS_TERMS, \
|
||||
PostGISProxy as GeometryProxy, \
|
||||
create_spatial_db, geo_quotename, get_geo_where_clause, \
|
||||
ASGML, ASKML, UNION
|
||||
create_spatial_db, get_geo_where_clause, gqn, \
|
||||
ASGML, ASKML, GEOM_SELECT, TRANSFORM, UNION
|
||||
else:
|
||||
raise NotImplementedError('No Geographic Backend exists for %s' % settings.DATABASE_NAME)
|
||||
|
||||
def geo_quotename(value):
|
||||
"""
|
||||
Returns the quotation used on a given Geometry value using the geometry
|
||||
quoting from the backend (the `gqn` function).
|
||||
"""
|
||||
if isinstance(value, (StringType, UnicodeType)): return gqn(value)
|
||||
else: return str(value)
|
||||
|
||||
#### query.py overloaded functions ####
|
||||
# parse_lookup() and lookup_inner() are modified from their django/db/models/query.py
|
||||
# counterparts to support constructing SQL for geographic queries.
|
||||
@ -263,38 +272,29 @@ def lookup_inner(path, lookup_type, value, opts, table, column):
|
||||
# If the field is a geometry field, then the WHERE clause will need to be obtained
|
||||
# with the get_geo_where_clause()
|
||||
if hasattr(field, '_geom'):
|
||||
# Do we have multiple arguments, e.g., ST_Relate, ST_DWithin lookup types
|
||||
# Do we have multiple arguments, e.g., `relate`, `dwithin` lookup types
|
||||
# need more than argument.
|
||||
multiple_args = isinstance(value, tuple)
|
||||
|
||||
# Getting the geographic where clause.
|
||||
# Getting the preparation SQL object from the field.
|
||||
if multiple_args:
|
||||
geo_prep = field.get_db_prep_lookup(lookup_type, value[0])
|
||||
else:
|
||||
geo_prep = field.get_db_prep_lookup(lookup_type, value)
|
||||
|
||||
# Getting the adapted geometry from the field.
|
||||
gwc = get_geo_where_clause(lookup_type, current_table + '.', column, value)
|
||||
|
||||
# Getting the geographic parameters from the field.
|
||||
if multiple_args:
|
||||
geo_params = field.get_db_prep_lookup(lookup_type, value[0])
|
||||
else:
|
||||
geo_params = field.get_db_prep_lookup(lookup_type, value)
|
||||
|
||||
# If a dictionary was passed back from the field modify the where clause.
|
||||
param_dict = isinstance(geo_params, dict)
|
||||
if param_dict:
|
||||
subst_list = geo_params['where']
|
||||
# A GeoFieldSQL object is returned by `get_db_prep_lookup` --
|
||||
# getting the substitution list and the geographic parameters.
|
||||
subst_list = geo_prep.where
|
||||
if multiple_args: subst_list += map(geo_quotename, value[1:])
|
||||
geo_params = geo_params['params']
|
||||
gwc = gwc % tuple(subst_list)
|
||||
elif multiple_args:
|
||||
# Modify the where clause if we have multiple arguments -- the
|
||||
# first substitution will be for another placeholder (for the
|
||||
# geometry) since it is already apart of geo_params.
|
||||
subst_list = ['%s']
|
||||
subst_list += map(geo_quotename, value[1:])
|
||||
gwc = gwc % tuple(subst_list)
|
||||
|
||||
# Finally, appending onto the WHERE clause, and extending with any
|
||||
# additional parameters.
|
||||
# Finally, appending onto the WHERE clause, and extending with
|
||||
# the additional parameters.
|
||||
where.append(gwc)
|
||||
params.extend(geo_params)
|
||||
params.extend(geo_prep.params)
|
||||
else:
|
||||
where.append(get_where_clause(lookup_type, current_table + '.', column, value, db_type))
|
||||
params.extend(field.get_db_prep_lookup(lookup_type, value))
|
||||
|
@ -1,24 +1,10 @@
|
||||
"""
|
||||
The PostGIS spatial database backend module.
|
||||
"""
|
||||
from django.contrib.gis.db.backend.postgis.query import \
|
||||
get_geo_where_clause, geo_quotename, \
|
||||
GEOM_FUNC_PREFIX, POSTGIS_TERMS, \
|
||||
MAJOR_VERSION, MINOR_VERSION1, MINOR_VERSION2
|
||||
from django.contrib.gis.db.backend.postgis.creation import create_spatial_db
|
||||
from django.contrib.gis.db.backend.postgis.field import PostGISField
|
||||
from django.contrib.gis.db.backend.postgis.field import PostGISField, gqn
|
||||
from django.contrib.gis.db.backend.postgis.proxy import PostGISProxy
|
||||
|
||||
# Functions used by GeoManager methods, and not via lookup types.
|
||||
if MAJOR_VERSION == 1:
|
||||
if MINOR_VERSION1 == 3:
|
||||
ASKML = 'ST_AsKML'
|
||||
ASGML = 'ST_AsGML'
|
||||
UNION = 'ST_Union'
|
||||
elif MINOR_VERSION1 == 2 and MINOR_VERSION2 >= 1:
|
||||
ASKML = 'AsKML'
|
||||
ASGML = 'AsGML'
|
||||
UNION = 'GeomUnion'
|
||||
|
||||
|
||||
|
||||
from django.contrib.gis.db.backend.postgis.query import \
|
||||
get_geo_where_clause, GEOM_FUNC_PREFIX, POSTGIS_TERMS, \
|
||||
MAJOR_VERSION, MINOR_VERSION1, MINOR_VERSION2, \
|
||||
ASKML, ASGML, GEOM_FROM_TEXT, UNION, TRANSFORM, GEOM_SELECT
|
||||
|
29
django/contrib/gis/db/backend/postgis/adaptor.py
Normal file
29
django/contrib/gis/db/backend/postgis/adaptor.py
Normal file
@ -0,0 +1,29 @@
|
||||
"""
|
||||
This object provides quoting for GEOS geometries into PostgreSQL/PostGIS.
|
||||
"""
|
||||
|
||||
from django.contrib.gis.db.backend.postgis.query import GEOM_FROM_WKB
|
||||
from psycopg2 import Binary
|
||||
from psycopg2.extensions import ISQLQuote
|
||||
|
||||
class PostGISAdaptor(object):
|
||||
def __init__(self, geom, srid):
|
||||
"Initializes on the geometry and the SRID."
|
||||
# Getting the WKB and the SRID
|
||||
self.wkb = geom.wkb
|
||||
self.srid = srid
|
||||
|
||||
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 __str__(self):
|
||||
return self.getquoted()
|
||||
|
||||
def getquoted(self):
|
||||
"Returns a properly quoted string for use in PostgreSQL/PostGIS."
|
||||
# Want to use WKB, so wrap with psycopg2 Binary() to quote properly.
|
||||
return "%s(%s, %s)" % (GEOM_FROM_WKB, Binary(self.wkb), self.srid or -1)
|
@ -1,7 +1,17 @@
|
||||
from types import StringType, UnicodeType
|
||||
from django.db import connection
|
||||
from django.db.models.fields import Field # Django base Field class
|
||||
from django.contrib.gis.geos import GEOSGeometry, GEOSException
|
||||
from django.contrib.gis.db.backend.postgis.query import POSTGIS_TERMS, geo_quotename as quotename
|
||||
from types import StringType
|
||||
from django.contrib.gis.db.backend.util import GeoFieldSQL
|
||||
from django.contrib.gis.db.backend.postgis.adaptor import PostGISAdaptor
|
||||
from django.contrib.gis.db.backend.postgis.query import POSTGIS_TERMS, TRANSFORM
|
||||
from psycopg2 import Binary
|
||||
|
||||
# Quotename & geographic quotename, respectively
|
||||
qn = connection.ops.quote_name
|
||||
def gqn(value):
|
||||
if isinstance(value, UnicodeType): value = value.encode('ascii')
|
||||
return "'%s'" % value
|
||||
|
||||
class PostGISField(Field):
|
||||
def _add_geom(self, style, db_table):
|
||||
@ -14,19 +24,19 @@ class PostGISField(Field):
|
||||
"""
|
||||
sql = style.SQL_KEYWORD('SELECT ') + \
|
||||
style.SQL_TABLE('AddGeometryColumn') + '(' + \
|
||||
style.SQL_TABLE(quotename(db_table)) + ', ' + \
|
||||
style.SQL_FIELD(quotename(self.column)) + ', ' + \
|
||||
style.SQL_TABLE(gqn(db_table)) + ', ' + \
|
||||
style.SQL_FIELD(gqn(self.column)) + ', ' + \
|
||||
style.SQL_FIELD(str(self._srid)) + ', ' + \
|
||||
style.SQL_COLTYPE(quotename(self._geom)) + ', ' + \
|
||||
style.SQL_COLTYPE(gqn(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_TABLE(qn(db_table)) + \
|
||||
style.SQL_KEYWORD(' ALTER ') + \
|
||||
style.SQL_FIELD(quotename(self.column, dbl=True)) + \
|
||||
style.SQL_FIELD(qn(self.column)) + \
|
||||
style.SQL_KEYWORD(' SET NOT NULL') + ';'
|
||||
return sql
|
||||
|
||||
@ -34,12 +44,12 @@ class PostGISField(Field):
|
||||
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_TABLE(qn('%s_%s_id' % (db_table, self.column))) + \
|
||||
style.SQL_KEYWORD(' ON ') + \
|
||||
style.SQL_TABLE(quotename(db_table, dbl=True)) + \
|
||||
style.SQL_TABLE(qn(db_table)) + \
|
||||
style.SQL_KEYWORD(' USING ') + \
|
||||
style.SQL_COLTYPE(index_type) + ' ( ' + \
|
||||
style.SQL_FIELD(quotename(self.column, dbl=True)) + ' ' + \
|
||||
style.SQL_FIELD(qn(self.column)) + ' ' + \
|
||||
style.SQL_KEYWORD(index_opts) + ' );'
|
||||
return sql
|
||||
|
||||
@ -64,8 +74,8 @@ class PostGISField(Field):
|
||||
"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)) + ');'
|
||||
style.SQL_TABLE(gqn(db_table)) + ', ' + \
|
||||
style.SQL_FIELD(gqn(self.column)) + ');'
|
||||
return sql
|
||||
|
||||
def db_type(self):
|
||||
@ -81,26 +91,37 @@ class PostGISField(Field):
|
||||
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.
|
||||
# special case for isnull lookup
|
||||
if lookup_type == 'isnull':
|
||||
return GeoFieldSQL([], [value])
|
||||
|
||||
# When the input is not a GEOS geometry, attempt to construct one
|
||||
# from the given string input.
|
||||
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' : ["ST_Transform(%s,%s)"],
|
||||
'params' : [value, self._srid]
|
||||
}
|
||||
pass
|
||||
elif isinstance(value, (StringType, UnicodeType)):
|
||||
try:
|
||||
value = GEOSGeometry(value)
|
||||
except GEOSException:
|
||||
raise TypeError("Could not create geometry from lookup value: %s" % str(value))
|
||||
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)]
|
||||
raise TypeError('Cannot use parameter of %s type as lookup parameter.' % type(value))
|
||||
|
||||
# Getting the SRID of the geometry, or defaulting to that of the field if
|
||||
# it is None.
|
||||
if value.srid is None: srid = self._srid
|
||||
else: srid = value.srid
|
||||
|
||||
# The adaptor will be used by psycopg2 for quoting the WKB.
|
||||
adapt = PostGISAdaptor(value, srid)
|
||||
|
||||
if srid != self._srid:
|
||||
# Adding the necessary string substitutions and parameters
|
||||
# to perform a geometry transformation.
|
||||
return GeoFieldSQL(['%s(%%s,%%s)' % TRANSFORM],
|
||||
[adapt, self._srid])
|
||||
else:
|
||||
raise TypeError("Invalid type (%s) used for field lookup value." % str(type(value)))
|
||||
return GeoFieldSQL(['%s'], [adapt])
|
||||
else:
|
||||
raise TypeError("Field has invalid lookup: %s" % lookup_type)
|
||||
|
||||
@ -108,13 +129,14 @@ class PostGISField(Field):
|
||||
"Prepares the value for saving in the database."
|
||||
if not bool(value): return None
|
||||
if isinstance(value, GEOSGeometry):
|
||||
return value
|
||||
return PostGISAdaptor(value, value.srid)
|
||||
else:
|
||||
raise TypeError('Geometry Proxy should only return GEOSGeometry objects.')
|
||||
|
||||
def get_internal_type(self):
|
||||
"""
|
||||
Returns NoField because a stored procedure is used by PostGIS to create the
|
||||
Returns NoField because a stored procedure is used by PostGIS to create
|
||||
the Geometry Fields.
|
||||
"""
|
||||
return 'NoField'
|
||||
|
||||
@ -126,6 +148,6 @@ class PostGISField(Field):
|
||||
"""
|
||||
if isinstance(value, GEOSGeometry) and value.srid != self._srid:
|
||||
# Adding Transform() to the SQL placeholder.
|
||||
return 'ST_Transform(%%s, %s)' % self._srid
|
||||
return '%s(%%s, %s)' % (TRANSFORM, self._srid)
|
||||
else:
|
||||
return '%s'
|
||||
|
@ -5,7 +5,7 @@
|
||||
from django.db import connection
|
||||
from django.contrib.gis.db.backend.postgis.management import postgis_version_tuple
|
||||
from types import StringType, UnicodeType
|
||||
quote_name = connection.ops.quote_name
|
||||
qn = connection.ops.quote_name
|
||||
|
||||
# Getting the PostGIS version information
|
||||
POSTGIS_VERSION, MAJOR_VERSION, MINOR_VERSION1, MINOR_VERSION2 = postgis_version_tuple()
|
||||
@ -121,21 +121,16 @@ def get_geom_func(lookup_type):
|
||||
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 = quote_name(table_prefix[:-1])+'.'
|
||||
field_name = quote_name(field_name)
|
||||
table_prefix = qn(table_prefix[:-1])+'.'
|
||||
field_name = qn(field_name)
|
||||
|
||||
# See if a PostGIS operator matches the lookup type first
|
||||
try:
|
||||
if lookup_type in POSTGIS_OPERATORS:
|
||||
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:
|
||||
if lookup_type in POSTGIS_GEOMETRY_FUNCTIONS:
|
||||
lookup_info = POSTGIS_GEOMETRY_FUNCTIONS[lookup_type]
|
||||
except KeyError:
|
||||
pass
|
||||
else:
|
||||
# Lookup types that are tuples take tuple arguments, e.g., 'relate' and
|
||||
# 'dwithin' lookup types.
|
||||
if isinstance(lookup_info, tuple):
|
||||
@ -145,7 +140,7 @@ def get_geo_where_clause(lookup_type, table_prefix, field_name, value):
|
||||
|
||||
# Ensuring that a tuple _value_ was passed in from the user
|
||||
if not isinstance(value, tuple) or len(value) != 2:
|
||||
raise TypeError('2-element tuple required for %s lookup type.' % lookup_type)
|
||||
raise TypeError('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):
|
||||
@ -154,7 +149,7 @@ def get_geo_where_clause(lookup_type, table_prefix, field_name, value):
|
||||
return "%s(%s%s, %%s, %%s)" % (func, table_prefix, field_name)
|
||||
else:
|
||||
# Returning the SQL necessary for the geometry function call. For example:
|
||||
# ST_Contains("geoapp_country"."poly", ST_GeomFromText(..))
|
||||
# ST_Contains("geoapp_country"."poly", ST_GeomFromWKB(..))
|
||||
return '%s(%s%s, %%s)' % (lookup_info, table_prefix, field_name)
|
||||
|
||||
# Handling 'isnull' lookup type
|
||||
@ -163,10 +158,35 @@ def get_geo_where_clause(lookup_type, table_prefix, field_name, value):
|
||||
|
||||
raise TypeError("Got invalid lookup_type: %s" % repr(lookup_type))
|
||||
|
||||
def geo_quotename(value, dbl=False):
|
||||
"Returns the quotation used for PostGIS on a given value (uses single quotes by default)."
|
||||
if isinstance(value, (StringType, UnicodeType)):
|
||||
if dbl: return '"%s"' % value
|
||||
else: return "'%s'" % value
|
||||
else:
|
||||
return str(value)
|
||||
# Functions that we define manually.
|
||||
if MAJOR_VERSION == 1:
|
||||
if MINOR_VERSION1 == 3:
|
||||
# PostGIS versions 1.3.x
|
||||
ASKML = 'ST_AsKML'
|
||||
ASGML = 'ST_AsGML'
|
||||
GEOM_FROM_TEXT = 'ST_GeomFromText'
|
||||
GEOM_FROM_WKB = 'ST_GeomFromWKB'
|
||||
UNION = 'ST_Union'
|
||||
TRANSFORM = 'ST_Transform'
|
||||
elif MINOR_VERSION1 == 2 and MINOR_VERSION2 >= 1:
|
||||
# PostGIS versions 1.2.x
|
||||
ASKML = 'AsKML'
|
||||
ASGML = 'AsGML'
|
||||
GEOM_FROM_TEXT = 'GeomFromText'
|
||||
GEOM_FROM_WKB = 'GeomFromWKB'
|
||||
UNION = 'GeomUnion'
|
||||
TRANSFORM = 'Transform'
|
||||
elif MINOR_VERSION1 == 1 and MINOR_VERSION2 >= 0:
|
||||
# PostGIS versions 1.1.x
|
||||
ASKML = False
|
||||
ASGML = 'AsGML'
|
||||
GEOM_FROM_TEXT = 'GeomFromText'
|
||||
GEOM_FROM_WKB = 'GeomFromWKB'
|
||||
TRANSFORM = 'Transform'
|
||||
UNION = 'GeomUnion'
|
||||
|
||||
# Custom selection not needed for PostGIS since GEOS geometries may be
|
||||
# instantiated directly from the HEXEWKB returned by default. If
|
||||
# WKT is needed for some reason in the future, this value may be changed,
|
||||
# 'AsText(%s)'
|
||||
GEOM_SELECT = None
|
||||
|
8
django/contrib/gis/db/backend/util.py
Normal file
8
django/contrib/gis/db/backend/util.py
Normal file
@ -0,0 +1,8 @@
|
||||
class GeoFieldSQL(object):
|
||||
"""
|
||||
Container for passing values to `parse_lookup` from the various
|
||||
backend geometry fields.
|
||||
"""
|
||||
def __init__(self, where=[], params=[]):
|
||||
self.where = where
|
||||
self.params = params
|
@ -7,7 +7,7 @@ from django.contrib.gis.db.models.manager import GeoManager
|
||||
# The GeoQ object
|
||||
from django.contrib.gis.db.models.query import GeoQ
|
||||
|
||||
# The various PostGIS/OpenGIS enabled fields.
|
||||
# The geographic-enabled fields.
|
||||
from django.contrib.gis.db.models.fields import \
|
||||
GeometryField, PointField, LineStringField, PolygonField, \
|
||||
MultiPointField, MultiLineStringField, MultiPolygonField, \
|
||||
|
@ -1,5 +1,6 @@
|
||||
from django.conf import settings
|
||||
from django.contrib.gis.db.backend import GeoBackendField, GeometryProxy # these depend on the spatial database backend.
|
||||
from django.contrib.gis.db.backend import GeoBackendField # these depend on the spatial database backend.
|
||||
from django.contrib.gis.db.models.proxy import GeometryProxy
|
||||
from django.contrib.gis.oldforms import WKTField
|
||||
from django.contrib.gis.geos import GEOSGeometry
|
||||
|
||||
|
@ -8,7 +8,7 @@
|
||||
|
||||
from types import NoneType, StringType, UnicodeType
|
||||
|
||||
class PostGISProxy(object):
|
||||
class GeometryProxy(object):
|
||||
def __init__(self, klass, field):
|
||||
"""
|
||||
Proxy initializes on the given Geometry class (not an instance) and
|
@ -6,7 +6,7 @@ from django.db.models.fields import FieldDoesNotExist
|
||||
from django.utils.datastructures import SortedDict
|
||||
from django.contrib.gis.db.models.fields import GeometryField
|
||||
# parse_lookup depends on the spatial database backend.
|
||||
from django.contrib.gis.db.backend import parse_lookup, ASGML, ASKML, UNION
|
||||
from django.contrib.gis.db.backend import parse_lookup, ASGML, ASKML, GEOM_SELECT, TRANSFORM, UNION
|
||||
from django.contrib.gis.geos import GEOSGeometry
|
||||
|
||||
class GeoQ(Q):
|
||||
@ -29,6 +29,11 @@ class GeoQuerySet(QuerySet):
|
||||
# For replacement fields in the SELECT.
|
||||
self._custom_select = {}
|
||||
|
||||
# If GEOM_SELECT is defined in the backend, then it will be used
|
||||
# for the selection format of the geometry column.
|
||||
if GEOM_SELECT: self._geo_fmt = GEOM_SELECT
|
||||
else: self._geo_fmt = '%s'
|
||||
|
||||
def _filter_or_exclude(self, mapper, *args, **kwargs):
|
||||
# mapper is a callable used to transform Q objects,
|
||||
# or None for identity transform
|
||||
@ -57,10 +62,21 @@ class GeoQuerySet(QuerySet):
|
||||
# GeoQuerySet. Specifically, this allows operations to be done on fields
|
||||
# in the SELECT, overriding their values -- this is different from using
|
||||
# QuerySet.extra(select=foo) because extra() adds an an _additional_
|
||||
# field to be selected. Used in returning transformed geometries.
|
||||
# field to be selected. Used in returning transformed geometries, and
|
||||
# handling the selection of native database geometry formats.
|
||||
for f in opts.fields:
|
||||
if f.column in self._custom_select: select.append(self._custom_select[f.column])
|
||||
else: select.append(self._field_column(f))
|
||||
# Getting the selection format string.
|
||||
if hasattr(f, '_geom'): sel_fmt = self._geo_fmt
|
||||
else: sel_fmt = '%s'
|
||||
|
||||
# Getting the field selection substitution string
|
||||
if f.column in self._custom_select:
|
||||
fld_sel = self._custom_select[f.column]
|
||||
else:
|
||||
fld_sel = self._field_column(f)
|
||||
|
||||
# Appending the selection
|
||||
select.append(sel_fmt % fld_sel)
|
||||
|
||||
tables = [quote_only_if_word(t) for t in self._tables]
|
||||
joins = SortedDict()
|
||||
@ -204,12 +220,15 @@ class GeoQuerySet(QuerySet):
|
||||
# Is the given field name a geographic field?
|
||||
field = self.model._meta.get_field(field_name)
|
||||
if not isinstance(field, GeometryField):
|
||||
raise TypeError('ST_Transform() only available for GeometryFields')
|
||||
raise TypeError('%s() only available for GeometryFields' % TRANSFORM)
|
||||
|
||||
# If there's already custom select SQL.
|
||||
col = self._custom_select.get(field.column, self._field_column(field))
|
||||
|
||||
# Setting the key for the field's column with the custom SELECT SQL to
|
||||
# override the geometry column returned from the database.
|
||||
self._custom_select[field.column] = \
|
||||
'(ST_Transform(%s, %s)) AS %s' % (self._field_column(field), srid,
|
||||
'(%s(%s, %s)) AS %s' % (TRANSFORM, col, srid,
|
||||
connection.ops.quote_name(field.column))
|
||||
return self._clone()
|
||||
|
||||
|
@ -12,7 +12,7 @@ from types import StringType, UnicodeType, IntType, FloatType, BufferType
|
||||
import re
|
||||
from django.contrib.gis.geos.coordseq import GEOSCoordSeq, create_cs
|
||||
from django.contrib.gis.geos.error import GEOSException, GEOSGeometryIndexError
|
||||
from django.contrib.gis.geos.libgeos import lgeos, HAS_NUMPY, ISQLQuote
|
||||
from django.contrib.gis.geos.libgeos import lgeos, HAS_NUMPY
|
||||
from django.contrib.gis.geos.pointer import GEOSPointer, NULL_GEOM
|
||||
|
||||
# Trying to import GDAL libraries, if available. Have to place in
|
||||
@ -48,13 +48,14 @@ class GEOSGeometry(object):
|
||||
The `srid` keyword is used to specify the Source Reference Identifier
|
||||
(SRID) number for this Geometry. If not set, the SRID will be None.
|
||||
"""
|
||||
|
||||
from_hex = False
|
||||
if isinstance(geo_input, UnicodeType):
|
||||
# Encoding to ASCII, WKT or HEXEWKB doesn't need any more.
|
||||
geo_input = geo_input.encode('ascii')
|
||||
if isinstance(geo_input, StringType):
|
||||
if hex_regex.match(geo_input):
|
||||
# If the regex matches, the geometry is in HEX form.
|
||||
from_hex = True
|
||||
sz = c_size_t(len(geo_input))
|
||||
buf = create_string_buffer(geo_input)
|
||||
g = lgeos.GEOSGeomFromHEX_buf(buf, sz)
|
||||
@ -62,7 +63,7 @@ class GEOSGeometry(object):
|
||||
# Otherwise, the geometry is in WKT form.
|
||||
g = lgeos.GEOSGeomFromWKT(c_char_p(geo_input))
|
||||
else:
|
||||
raise GEOSException, 'given string input "%s" unrecognized as WKT or HEXEWKB.' % geo_input
|
||||
raise GEOSException('String or unicode input unrecognized as WKT or HEXEWKB.')
|
||||
elif isinstance(geo_input, (IntType, GEOSPointer)):
|
||||
# When the input is either a memory address (an integer), or a
|
||||
# GEOSPointer object.
|
||||
@ -85,6 +86,10 @@ class GEOSGeometry(object):
|
||||
# Setting the SRID, if given.
|
||||
if srid and isinstance(srid, int): self.srid = srid
|
||||
|
||||
# Exported HEX from other GEOS geometries will have -1 SRID --
|
||||
# set here to 0, when the SRID is not explicitly given.
|
||||
if not srid and from_hex: self.srid = 0
|
||||
|
||||
# Setting the class type (e.g., 'Point', 'Polygon', etc.)
|
||||
self.__class__ = GEOS_CLASSES[self.geom_type]
|
||||
|
||||
@ -207,19 +212,6 @@ class GEOSGeometry(object):
|
||||
self.__class__ = GEOS_CLASSES[gtype]
|
||||
if isinstance(self, (Polygon, GeometryCollection)): self._populate()
|
||||
|
||||
#### Psycopg2 database adaptor routines ####
|
||||
def __conform__(self, proto):
|
||||
# Does the given protocol conform to what Psycopg2 expects?
|
||||
if proto == ISQLQuote:
|
||||
return self
|
||||
else:
|
||||
raise GEOSException, 'Error implementing psycopg2 protocol. Is psycopg2 installed?'
|
||||
|
||||
def getquoted(self):
|
||||
"Returns a properly quoted string for use in PostgreSQL/PostGIS."
|
||||
# Using ST_GeomFromText(), corresponds to SQL/MM ISO standard.
|
||||
return "ST_GeomFromText('%s', %s)" % (self.wkt, self.srid or -1)
|
||||
|
||||
#### Coordinate Sequence Routines ####
|
||||
@property
|
||||
def has_cs(self):
|
||||
@ -425,7 +417,11 @@ class GEOSGeometry(object):
|
||||
|
||||
@property
|
||||
def hex(self):
|
||||
"Returns the HEXEWKB of the Geometry."
|
||||
"""
|
||||
Returns the HEX of the Geometry -- please note that the SRID is not
|
||||
included in this representation, because the GEOS C library uses
|
||||
-1 by default, even if the SRID is set.
|
||||
"""
|
||||
sz = c_size_t()
|
||||
h = lgeos.GEOSGeomToHEX_buf(self._ptr(), byref(sz))
|
||||
return string_at(h, sz.value)
|
||||
|
@ -18,12 +18,6 @@ try:
|
||||
except ImportError:
|
||||
HAS_NUMPY = False
|
||||
|
||||
# Is psycopg2 available?
|
||||
try:
|
||||
from psycopg2.extensions import ISQLQuote
|
||||
except (ImportError, EnvironmentError):
|
||||
ISQLQuote = None
|
||||
|
||||
# Setting the appropriate name for the GEOS-C library, depending on which
|
||||
# OS and POSIX platform we're running.
|
||||
if os.name == 'nt':
|
||||
|
Loading…
x
Reference in New Issue
Block a user