1
0
mirror of https://github.com/django/django.git synced 2025-07-04 17:59:13 +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:
Justin Bronn 2007-10-14 20:13:11 +00:00
parent b6c8bba5b6
commit c049672a74
15 changed files with 219 additions and 144 deletions

View File

@ -11,32 +11,41 @@
the backend. the backend.
(4) The `parse_lookup` function, used for spatial SQL construction by (4) The `parse_lookup` function, used for spatial SQL construction by
the GeoQuerySet. the GeoQuerySet.
(5) The `create_spatial_db`, `geo_quotename`, and `get_geo_where_clause` (5) The `create_spatial_db`, and `get_geo_where_clause`
routines (needed by `parse_lookup`. routines (needed by `parse_lookup`).
Currently only PostGIS is supported, but someday backends will be added for Currently only PostGIS is supported, but someday backends will be added for
additional spatial databases (e.g., Oracle, DB2). additional spatial databases (e.g., Oracle, DB2).
""" """
from types import StringType, UnicodeType
from django.conf import settings from django.conf import settings
from django.db import connection from django.db import connection
from django.db.models.query import field_choices, find_field, get_where_clause, \ from django.db.models.query import field_choices, find_field, get_where_clause, \
FieldFound, LOOKUP_SEPARATOR, QUERY_TERMS FieldFound, LOOKUP_SEPARATOR, QUERY_TERMS
from django.utils.datastructures import SortedDict from django.utils.datastructures import SortedDict
from django.contrib.gis.geos import GEOSGeometry
# These routines default to False # These routines (needed by GeoManager), default to False.
ASGML, ASKML, UNION = (False, False, False) ASGML, ASKML, TRANSFORM, UNION= (False, False, False, False)
if settings.DATABASE_ENGINE == 'postgresql_psycopg2': if settings.DATABASE_ENGINE == 'postgresql_psycopg2':
# PostGIS is the spatial database, getting the rquired modules, # PostGIS is the spatial database, getting the rquired modules,
# renaming as necessary. # renaming as necessary.
from django.contrib.gis.db.backend.postgis import \ from django.contrib.gis.db.backend.postgis import \
PostGISField as GeoBackendField, POSTGIS_TERMS as GIS_TERMS, \ PostGISField as GeoBackendField, POSTGIS_TERMS as GIS_TERMS, \
PostGISProxy as GeometryProxy, \ create_spatial_db, get_geo_where_clause, gqn, \
create_spatial_db, geo_quotename, get_geo_where_clause, \ ASGML, ASKML, GEOM_SELECT, TRANSFORM, UNION
ASGML, ASKML, UNION
else: else:
raise NotImplementedError('No Geographic Backend exists for %s' % settings.DATABASE_NAME) 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 #### #### 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
# counterparts to support constructing SQL for geographic queries. # 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 # If the field is a geometry field, then the WHERE clause will need to be obtained
# with the get_geo_where_clause() # with the get_geo_where_clause()
if hasattr(field, '_geom'): 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. # need more than argument.
multiple_args = isinstance(value, tuple) multiple_args = isinstance(value, tuple)
# Getting the geographic where clause. # Getting the preparation SQL object from the field.
gwc = get_geo_where_clause(lookup_type, current_table + '.', column, value)
# Getting the geographic parameters from the field.
if multiple_args: if multiple_args:
geo_params = field.get_db_prep_lookup(lookup_type, value[0]) geo_prep = field.get_db_prep_lookup(lookup_type, value[0])
else: else:
geo_params = field.get_db_prep_lookup(lookup_type, value) geo_prep = field.get_db_prep_lookup(lookup_type, value)
# If a dictionary was passed back from the field modify the where clause. # Getting the adapted geometry from the field.
param_dict = isinstance(geo_params, dict) gwc = get_geo_where_clause(lookup_type, current_table + '.', column, value)
if param_dict:
subst_list = geo_params['where'] # A GeoFieldSQL object is returned by `get_db_prep_lookup` --
if multiple_args: subst_list += map(geo_quotename, value[1:]) # getting the substitution list and the geographic parameters.
geo_params = geo_params['params'] subst_list = geo_prep.where
gwc = gwc % tuple(subst_list) if multiple_args: subst_list += map(geo_quotename, value[1:])
elif multiple_args: gwc = gwc % tuple(subst_list)
# Modify the where clause if we have multiple arguments -- the
# first substitution will be for another placeholder (for the # Finally, appending onto the WHERE clause, and extending with
# geometry) since it is already apart of geo_params. # the additional parameters.
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.
where.append(gwc) where.append(gwc)
params.extend(geo_params) params.extend(geo_prep.params)
else: else:
where.append(get_where_clause(lookup_type, current_table + '.', column, value, db_type)) where.append(get_where_clause(lookup_type, current_table + '.', column, value, db_type))
params.extend(field.get_db_prep_lookup(lookup_type, value)) params.extend(field.get_db_prep_lookup(lookup_type, value))

View File

@ -1,24 +1,10 @@
""" """
The PostGIS spatial database backend module. 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.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 from django.contrib.gis.db.backend.postgis.proxy import PostGISProxy
from django.contrib.gis.db.backend.postgis.query import \
# Functions used by GeoManager methods, and not via lookup types. get_geo_where_clause, GEOM_FUNC_PREFIX, POSTGIS_TERMS, \
if MAJOR_VERSION == 1: MAJOR_VERSION, MINOR_VERSION1, MINOR_VERSION2, \
if MINOR_VERSION1 == 3: ASKML, ASGML, GEOM_FROM_TEXT, UNION, TRANSFORM, GEOM_SELECT
ASKML = 'ST_AsKML'
ASGML = 'ST_AsGML'
UNION = 'ST_Union'
elif MINOR_VERSION1 == 2 and MINOR_VERSION2 >= 1:
ASKML = 'AsKML'
ASGML = 'AsGML'
UNION = 'GeomUnion'

View 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)

View File

@ -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.db.models.fields import Field # Django base Field class
from django.contrib.gis.geos import GEOSGeometry, GEOSException from django.contrib.gis.geos import GEOSGeometry, GEOSException
from django.contrib.gis.db.backend.postgis.query import POSTGIS_TERMS, geo_quotename as quotename from django.contrib.gis.db.backend.util import GeoFieldSQL
from types import StringType 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): class PostGISField(Field):
def _add_geom(self, style, db_table): def _add_geom(self, style, db_table):
@ -10,23 +20,23 @@ class PostGISField(Field):
AddGeometryColumn(...) PostGIS (and OGC standard) stored procedure. AddGeometryColumn(...) PostGIS (and OGC standard) stored procedure.
Takes the style object (provides syntax highlighting) and the Takes the style object (provides syntax highlighting) and the
database table as parameters. database table as parameters.
""" """
sql = style.SQL_KEYWORD('SELECT ') + \ sql = style.SQL_KEYWORD('SELECT ') + \
style.SQL_TABLE('AddGeometryColumn') + '(' + \ style.SQL_TABLE('AddGeometryColumn') + '(' + \
style.SQL_TABLE(quotename(db_table)) + ', ' + \ style.SQL_TABLE(gqn(db_table)) + ', ' + \
style.SQL_FIELD(quotename(self.column)) + ', ' + \ style.SQL_FIELD(gqn(self.column)) + ', ' + \
style.SQL_FIELD(str(self._srid)) + ', ' + \ style.SQL_FIELD(str(self._srid)) + ', ' + \
style.SQL_COLTYPE(quotename(self._geom)) + ', ' + \ style.SQL_COLTYPE(gqn(self._geom)) + ', ' + \
style.SQL_KEYWORD(str(self._dim)) + ');' style.SQL_KEYWORD(str(self._dim)) + ');'
if not self.null: if not self.null:
# Add a NOT NULL constraint to the field # Add a NOT NULL constraint to the field
sql += '\n' + \ sql += '\n' + \
style.SQL_KEYWORD('ALTER TABLE ') + \ 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_KEYWORD(' ALTER ') + \
style.SQL_FIELD(quotename(self.column, dbl=True)) + \ style.SQL_FIELD(qn(self.column)) + \
style.SQL_KEYWORD(' SET NOT NULL') + ';' style.SQL_KEYWORD(' SET NOT NULL') + ';'
return sql return sql
@ -34,12 +44,12 @@ class PostGISField(Field):
index_type='GIST', index_opts='GIST_GEOMETRY_OPS'): index_type='GIST', index_opts='GIST_GEOMETRY_OPS'):
"Creates a GiST index for this geometry field." "Creates a GiST index for this geometry field."
sql = style.SQL_KEYWORD('CREATE INDEX ') + \ 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_KEYWORD(' ON ') + \
style.SQL_TABLE(quotename(db_table, dbl=True)) + \ style.SQL_TABLE(qn(db_table)) + \
style.SQL_KEYWORD(' USING ') + \ style.SQL_KEYWORD(' USING ') + \
style.SQL_COLTYPE(index_type) + ' ( ' + \ style.SQL_COLTYPE(index_type) + ' ( ' + \
style.SQL_FIELD(quotename(self.column, dbl=True)) + ' ' + \ style.SQL_FIELD(qn(self.column)) + ' ' + \
style.SQL_KEYWORD(index_opts) + ' );' style.SQL_KEYWORD(index_opts) + ' );'
return sql return sql
@ -64,8 +74,8 @@ class PostGISField(Field):
"Drops the geometry column." "Drops the geometry column."
sql = style.SQL_KEYWORD('SELECT ') + \ sql = style.SQL_KEYWORD('SELECT ') + \
style.SQL_KEYWORD('DropGeometryColumn') + '(' + \ style.SQL_KEYWORD('DropGeometryColumn') + '(' + \
style.SQL_TABLE(quotename(db_table)) + ', ' + \ style.SQL_TABLE(gqn(db_table)) + ', ' + \
style.SQL_FIELD(quotename(self.column)) + ');' style.SQL_FIELD(gqn(self.column)) + ');'
return sql return sql
def db_type(self): def db_type(self):
@ -81,26 +91,37 @@ class PostGISField(Field):
GEOS Geometries for the value. GEOS Geometries for the value.
""" """
if lookup_type in POSTGIS_TERMS: if lookup_type in POSTGIS_TERMS:
if lookup_type == 'isnull': return [value] # special case for NULL geometries. # special case for isnull lookup
if not bool(value): return [None] # If invalid value passed in. 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): if isinstance(value, GEOSGeometry):
# GEOSGeometry instance passed in. pass
if value.srid != self._srid: elif isinstance(value, (StringType, UnicodeType)):
# Returning a dictionary instructs the parse_lookup() to add try:
# what's in the 'where' key to the where parameters, since we value = GEOSGeometry(value)
# need to transform the geometry in the query. except GEOSException:
return {'where' : ["ST_Transform(%s,%s)"], raise TypeError("Could not create geometry from lookup value: %s" % str(value))
'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: else:
raise TypeError("Invalid type (%s) used for field lookup value." % str(type(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:
return GeoFieldSQL(['%s'], [adapt])
else: else:
raise TypeError("Field has invalid lookup: %s" % lookup_type) raise TypeError("Field has invalid lookup: %s" % lookup_type)
@ -108,24 +129,25 @@ class PostGISField(Field):
"Prepares the value for saving in the database." "Prepares the value for saving in the database."
if not bool(value): return None if not bool(value): return None
if isinstance(value, GEOSGeometry): if isinstance(value, GEOSGeometry):
return value return PostGISAdaptor(value, value.srid)
else: else:
raise TypeError('Geometry Proxy should only return GEOSGeometry objects.') raise TypeError('Geometry Proxy should only return GEOSGeometry objects.')
def get_internal_type(self): 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' return 'NoField'
def get_placeholder(self, value): def get_placeholder(self, value):
""" """
Provides a proper substitution value for Geometries that are not in the Provides a proper substitution value for Geometries that are not in the
SRID of the field. Specifically, this routine will substitute in the SRID of the field. Specifically, this routine will substitute in the
ST_Transform() function call. ST_Transform() function call.
""" """
if isinstance(value, GEOSGeometry) and value.srid != self._srid: if isinstance(value, GEOSGeometry) and value.srid != self._srid:
# Adding Transform() to the SQL placeholder. # Adding Transform() to the SQL placeholder.
return 'ST_Transform(%%s, %s)' % self._srid return '%s(%%s, %s)' % (TRANSFORM, self._srid)
else: else:
return '%s' return '%s'

View File

@ -1,11 +1,11 @@
""" """
This module contains the spatial lookup types, and the get_geo_where_clause() This module contains the spatial lookup types, and the get_geo_where_clause()
routine for PostGIS. routine for PostGIS.
""" """
from django.db import connection from django.db import connection
from django.contrib.gis.db.backend.postgis.management import postgis_version_tuple from django.contrib.gis.db.backend.postgis.management import postgis_version_tuple
from types import StringType, UnicodeType from types import StringType, UnicodeType
quote_name = connection.ops.quote_name qn = connection.ops.quote_name
# Getting the PostGIS version information # Getting the PostGIS version information
POSTGIS_VERSION, MAJOR_VERSION, MINOR_VERSION1, MINOR_VERSION2 = postgis_version_tuple() 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): def get_geo_where_clause(lookup_type, table_prefix, field_name, value):
"Returns the SQL WHERE clause for use in PostGIS SQL construction." "Returns the SQL WHERE clause for use in PostGIS SQL construction."
if table_prefix.endswith('.'): if table_prefix.endswith('.'):
table_prefix = quote_name(table_prefix[:-1])+'.' table_prefix = qn(table_prefix[:-1])+'.'
field_name = quote_name(field_name) field_name = qn(field_name)
# See if a PostGIS operator matches the lookup type first # 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]) 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 # 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] lookup_info = POSTGIS_GEOMETRY_FUNCTIONS[lookup_type]
except KeyError:
pass
else:
# Lookup types that are tuples take tuple arguments, e.g., 'relate' and # Lookup types that are tuples take tuple arguments, e.g., 'relate' and
# 'dwithin' lookup types. # 'dwithin' lookup types.
if isinstance(lookup_info, tuple): 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 # Ensuring that a tuple _value_ was passed in from the user
if not isinstance(value, tuple) or len(value) != 2: 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. # Ensuring the argument type matches what we expect.
if not isinstance(value[1], arg_type): 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) return "%s(%s%s, %%s, %%s)" % (func, table_prefix, field_name)
else: else:
# Returning the SQL necessary for the geometry function call. For example: # 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) return '%s(%s%s, %%s)' % (lookup_info, table_prefix, field_name)
# Handling 'isnull' lookup type # 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)) raise TypeError("Got invalid lookup_type: %s" % repr(lookup_type))
def geo_quotename(value, dbl=False): # Functions that we define manually.
"Returns the quotation used for PostGIS on a given value (uses single quotes by default)." if MAJOR_VERSION == 1:
if isinstance(value, (StringType, UnicodeType)): if MINOR_VERSION1 == 3:
if dbl: return '"%s"' % value # PostGIS versions 1.3.x
else: return "'%s'" % value ASKML = 'ST_AsKML'
else: ASGML = 'ST_AsGML'
return str(value) 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

View 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

View File

@ -7,7 +7,7 @@ from django.contrib.gis.db.models.manager import GeoManager
# The GeoQ object # The GeoQ object
from django.contrib.gis.db.models.query import GeoQ 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 \ from django.contrib.gis.db.models.fields import \
GeometryField, PointField, LineStringField, PolygonField, \ GeometryField, PointField, LineStringField, PolygonField, \
MultiPointField, MultiLineStringField, MultiPolygonField, \ MultiPointField, MultiLineStringField, MultiPolygonField, \

View File

@ -1,5 +1,6 @@
from django.conf import settings 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.oldforms import WKTField
from django.contrib.gis.geos import GEOSGeometry from django.contrib.gis.geos import GEOSGeometry

View File

@ -8,7 +8,7 @@
from types import NoneType, StringType, UnicodeType from types import NoneType, StringType, UnicodeType
class PostGISProxy(object): class GeometryProxy(object):
def __init__(self, klass, field): def __init__(self, klass, field):
""" """
Proxy initializes on the given Geometry class (not an instance) and Proxy initializes on the given Geometry class (not an instance) and

View File

@ -6,7 +6,7 @@ from django.db.models.fields import FieldDoesNotExist
from django.utils.datastructures import SortedDict from django.utils.datastructures import SortedDict
from django.contrib.gis.db.models.fields import GeometryField from django.contrib.gis.db.models.fields import GeometryField
# parse_lookup depends on the spatial database backend. # 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 from django.contrib.gis.geos import GEOSGeometry
class GeoQ(Q): class GeoQ(Q):
@ -29,6 +29,11 @@ class GeoQuerySet(QuerySet):
# For replacement fields in the SELECT. # For replacement fields in the SELECT.
self._custom_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): def _filter_or_exclude(self, mapper, *args, **kwargs):
# mapper is a callable used to transform Q objects, # mapper is a callable used to transform Q objects,
# or None for identity transform # or None for identity transform
@ -57,10 +62,21 @@ class GeoQuerySet(QuerySet):
# GeoQuerySet. Specifically, this allows operations to be done on fields # GeoQuerySet. Specifically, this allows operations to be done on fields
# in the SELECT, overriding their values -- this is different from using # in the SELECT, overriding their values -- this is different from using
# QuerySet.extra(select=foo) because extra() adds an an _additional_ # 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: for f in opts.fields:
if f.column in self._custom_select: select.append(self._custom_select[f.column]) # Getting the selection format string.
else: select.append(self._field_column(f)) 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] tables = [quote_only_if_word(t) for t in self._tables]
joins = SortedDict() joins = SortedDict()
@ -204,13 +220,16 @@ class GeoQuerySet(QuerySet):
# Is the given field name a geographic field? # Is the given field name a geographic field?
field = self.model._meta.get_field(field_name) field = self.model._meta.get_field(field_name)
if not isinstance(field, GeometryField): 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 # Setting the key for the field's column with the custom SELECT SQL to
# override the geometry column returned from the database. # override the geometry column returned from the database.
self._custom_select[field.column] = \ 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)) connection.ops.quote_name(field.column))
return self._clone() return self._clone()
def union(self, field_name): def union(self, field_name):

View File

@ -12,7 +12,7 @@ from types import StringType, UnicodeType, IntType, FloatType, BufferType
import re import re
from django.contrib.gis.geos.coordseq import GEOSCoordSeq, create_cs from django.contrib.gis.geos.coordseq import GEOSCoordSeq, create_cs
from django.contrib.gis.geos.error import GEOSException, GEOSGeometryIndexError 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 from django.contrib.gis.geos.pointer import GEOSPointer, NULL_GEOM
# Trying to import GDAL libraries, if available. Have to place in # Trying to import GDAL libraries, if available. Have to place in
@ -47,14 +47,15 @@ class GEOSGeometry(object):
The `srid` keyword is used to specify the Source Reference Identifier The `srid` keyword is used to specify the Source Reference Identifier
(SRID) number for this Geometry. If not set, the SRID will be None. (SRID) number for this Geometry. If not set, the SRID will be None.
""" """
from_hex = False
if isinstance(geo_input, UnicodeType): if isinstance(geo_input, UnicodeType):
# Encoding to ASCII, WKT or HEXEWKB doesn't need any more. # Encoding to ASCII, WKT or HEXEWKB doesn't need any more.
geo_input = geo_input.encode('ascii') geo_input = geo_input.encode('ascii')
if isinstance(geo_input, StringType): if isinstance(geo_input, StringType):
if hex_regex.match(geo_input): if hex_regex.match(geo_input):
# If the regex matches, the geometry is in HEX form. # If the regex matches, the geometry is in HEX form.
from_hex = True
sz = c_size_t(len(geo_input)) sz = c_size_t(len(geo_input))
buf = create_string_buffer(geo_input) buf = create_string_buffer(geo_input)
g = lgeos.GEOSGeomFromHEX_buf(buf, sz) g = lgeos.GEOSGeomFromHEX_buf(buf, sz)
@ -62,7 +63,7 @@ class GEOSGeometry(object):
# Otherwise, the geometry is in WKT form. # Otherwise, the geometry is in WKT form.
g = lgeos.GEOSGeomFromWKT(c_char_p(geo_input)) g = lgeos.GEOSGeomFromWKT(c_char_p(geo_input))
else: 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)): elif isinstance(geo_input, (IntType, GEOSPointer)):
# When the input is either a memory address (an integer), or a # When the input is either a memory address (an integer), or a
# GEOSPointer object. # GEOSPointer object.
@ -85,6 +86,10 @@ class GEOSGeometry(object):
# Setting the SRID, if given. # Setting the SRID, if given.
if srid and isinstance(srid, int): self.srid = srid 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.) # Setting the class type (e.g., 'Point', 'Polygon', etc.)
self.__class__ = GEOS_CLASSES[self.geom_type] self.__class__ = GEOS_CLASSES[self.geom_type]
@ -207,19 +212,6 @@ class GEOSGeometry(object):
self.__class__ = GEOS_CLASSES[gtype] self.__class__ = GEOS_CLASSES[gtype]
if isinstance(self, (Polygon, GeometryCollection)): self._populate() 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 #### #### Coordinate Sequence Routines ####
@property @property
def has_cs(self): def has_cs(self):
@ -425,7 +417,11 @@ class GEOSGeometry(object):
@property @property
def hex(self): 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() sz = c_size_t()
h = lgeos.GEOSGeomToHEX_buf(self._ptr(), byref(sz)) h = lgeos.GEOSGeomToHEX_buf(self._ptr(), byref(sz))
return string_at(h, sz.value) return string_at(h, sz.value)

View File

@ -18,12 +18,6 @@ try:
except ImportError: except ImportError:
HAS_NUMPY = False 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 # Setting the appropriate name for the GEOS-C library, depending on which
# OS and POSIX platform we're running. # OS and POSIX platform we're running.
if os.name == 'nt': if os.name == 'nt':